updates
This commit is contained in:
@@ -15,7 +15,11 @@ const SupportCenterPage = () => {
|
|||||||
<Header />
|
<Header />
|
||||||
<main>
|
<main>
|
||||||
<SupportCenterHero onFeatureClick={setActiveModal} />
|
<SupportCenterHero onFeatureClick={setActiveModal} />
|
||||||
<SupportCenterContent activeModal={activeModal} onClose={() => setActiveModal(null)} />
|
<SupportCenterContent
|
||||||
|
activeModal={activeModal}
|
||||||
|
onClose={() => setActiveModal(null)}
|
||||||
|
onOpenModal={setActiveModal}
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
gnx-react/backend/media/case_studies/clients/logo_dieselor.png
Normal file
BIN
gnx-react/backend/media/case_studies/clients/logo_dieselor.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
@@ -3,7 +3,11 @@ import { useState, FormEvent } from 'react';
|
|||||||
import { createTicket, CreateTicketData } from '@/lib/api/supportService';
|
import { createTicket, CreateTicketData } from '@/lib/api/supportService';
|
||||||
import { useTicketCategories } from '@/lib/hooks/useSupport';
|
import { useTicketCategories } from '@/lib/hooks/useSupport';
|
||||||
|
|
||||||
const CreateTicketForm = () => {
|
interface CreateTicketFormProps {
|
||||||
|
onOpenStatusCheck?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateTicketForm = ({ onOpenStatusCheck }: CreateTicketFormProps) => {
|
||||||
const { categories, loading: categoriesLoading } = useTicketCategories();
|
const { categories, loading: categoriesLoading } = useTicketCategories();
|
||||||
|
|
||||||
const [formData, setFormData] = useState<CreateTicketData>({
|
const [formData, setFormData] = useState<CreateTicketData>({
|
||||||
@@ -21,6 +25,7 @@ const CreateTicketForm = () => {
|
|||||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||||
const [submitSuccess, setSubmitSuccess] = useState(false);
|
const [submitSuccess, setSubmitSuccess] = useState(false);
|
||||||
const [ticketNumber, setTicketNumber] = useState<string>('');
|
const [ticketNumber, setTicketNumber] = useState<string>('');
|
||||||
|
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
|
||||||
|
|
||||||
const handleInputChange = (
|
const handleInputChange = (
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||||
@@ -30,10 +35,46 @@ const CreateTicketForm = () => {
|
|||||||
...prev,
|
...prev,
|
||||||
[name]: name === 'category' ? (value ? parseInt(value) : undefined) : value
|
[name]: name === 'category' ? (value ? parseInt(value) : undefined) : value
|
||||||
}));
|
}));
|
||||||
|
// Clear field error when user starts typing
|
||||||
|
if (fieldErrors[name]) {
|
||||||
|
setFieldErrors(prev => ({ ...prev, [name]: '' }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (!formData.user_name.trim()) {
|
||||||
|
errors.user_name = 'Full name is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.user_email.trim()) {
|
||||||
|
errors.user_email = 'Email address is required';
|
||||||
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.user_email)) {
|
||||||
|
errors.user_email = 'Please enter a valid email address';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.title.trim()) {
|
||||||
|
errors.title = 'Subject is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!formData.description.trim()) {
|
||||||
|
errors.description = 'Description is required';
|
||||||
|
} else if (formData.description.trim().length < 10) {
|
||||||
|
errors.description = 'Please provide a more detailed description (minimum 10 characters)';
|
||||||
|
}
|
||||||
|
|
||||||
|
setFieldErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!validateForm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
setSubmitError(null);
|
setSubmitError(null);
|
||||||
setSubmitSuccess(false);
|
setSubmitSuccess(false);
|
||||||
@@ -54,6 +95,7 @@ const CreateTicketForm = () => {
|
|||||||
company: '',
|
company: '',
|
||||||
category: undefined
|
category: undefined
|
||||||
});
|
});
|
||||||
|
setFieldErrors({});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Ticket creation error:', error);
|
console.error('Ticket creation error:', error);
|
||||||
|
|
||||||
@@ -88,226 +130,334 @@ const CreateTicketForm = () => {
|
|||||||
<i className="fa-solid fa-circle-check"></i>
|
<i className="fa-solid fa-circle-check"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3>Ticket Created Successfully!</h3>
|
<h3>Ticket Created Successfully!</h3>
|
||||||
<p className="ticket-number">Your ticket number: <strong>{ticketNumber}</strong></p>
|
<div className="ticket-number-container">
|
||||||
|
<div className="ticket-number-label">Your Ticket Number</div>
|
||||||
|
<div className="ticket-number-value">{ticketNumber}</div>
|
||||||
|
<button
|
||||||
|
className="btn-copy-ticket"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(ticketNumber);
|
||||||
|
const btn = document.querySelector('.btn-copy-ticket');
|
||||||
|
if (btn) {
|
||||||
|
btn.textContent = 'Copied!';
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.textContent = 'Copy';
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<p className="ticket-info">
|
<p className="ticket-info">
|
||||||
We've received your support request and will respond as soon as possible.
|
We've received your support request and will respond as soon as possible.
|
||||||
Please save your ticket number for future reference.
|
Please save your ticket number for future reference.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<div className="success-actions">
|
||||||
className="btn btn-primary"
|
<button
|
||||||
onClick={() => setSubmitSuccess(false)}
|
className="btn btn-primary"
|
||||||
>
|
onClick={() => setSubmitSuccess(false)}
|
||||||
Submit Another Ticket
|
>
|
||||||
</button>
|
<i className="fa-solid fa-plus me-2"></i>
|
||||||
|
Submit Another Ticket
|
||||||
|
</button>
|
||||||
|
{onOpenStatusCheck && (
|
||||||
|
<button
|
||||||
|
className="btn btn-outline"
|
||||||
|
onClick={onOpenStatusCheck}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-search me-2"></i>
|
||||||
|
Check Ticket Status
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const issueTypeIcons: Record<string, string> = {
|
||||||
|
general: 'fa-info-circle',
|
||||||
|
technical: 'fa-tools',
|
||||||
|
billing: 'fa-credit-card',
|
||||||
|
feature_request: 'fa-lightbulb',
|
||||||
|
bug_report: 'fa-bug',
|
||||||
|
account: 'fa-user-circle'
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="create-ticket-form">
|
<div className="create-ticket-form enterprise-form">
|
||||||
<div className="row justify-content-center">
|
<div className="row justify-content-center">
|
||||||
<div className="col-12 col-lg-10 col-xl-8">
|
<div className="col-12">
|
||||||
<div className="form-header">
|
<div className="form-header-enterprise">
|
||||||
|
<div className="form-header-icon">
|
||||||
|
<i className="fa-solid fa-ticket"></i>
|
||||||
|
</div>
|
||||||
<h2>Submit a Support Ticket</h2>
|
<h2>Submit a Support Ticket</h2>
|
||||||
<p>Fill out the form below and our team will get back to you shortly.</p>
|
<p>Fill out the form below and our dedicated support team will get back to you within 24 hours.</p>
|
||||||
<div style={{
|
<div className="info-banner">
|
||||||
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%)',
|
<i className="fa-solid fa-shield-check"></i>
|
||||||
border: '1px solid rgba(59, 130, 246, 0.3)',
|
<div>
|
||||||
borderRadius: '8px',
|
<strong>Secure & Confidential</strong>
|
||||||
padding: '12px 16px',
|
<span>All tickets are encrypted and handled with enterprise-grade security standards.</span>
|
||||||
marginTop: '1rem',
|
</div>
|
||||||
fontSize: '0.95rem',
|
|
||||||
color: '#1e293b'
|
|
||||||
}}>
|
|
||||||
<strong>ℹ️ Note:</strong> Only registered email addresses can submit tickets.
|
|
||||||
If your email is not registered, please contact support@gnxsoft.com
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{submitError && (
|
{submitError && (
|
||||||
<div className="alert alert-danger" role="alert">
|
<div className="alert-enterprise alert-error">
|
||||||
<i className="fa-solid fa-triangle-exclamation me-2"></i>
|
<i className="fa-solid fa-triangle-exclamation"></i>
|
||||||
{submitError}
|
<div>
|
||||||
|
<strong>Submission Error</strong>
|
||||||
|
<p>{submitError}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="support-form">
|
<form onSubmit={handleSubmit} className="support-form-enterprise">
|
||||||
<div className="row g-4">
|
{/* Personal Information */}
|
||||||
{/* Personal Information */}
|
<div className="form-section">
|
||||||
<div className="col-12">
|
<div className="section-header">
|
||||||
<h4 className="form-section-title">Personal Information</h4>
|
<i className="fa-solid fa-user"></i>
|
||||||
|
<h3>Personal Information</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6">
|
<div className="row g-4">
|
||||||
<div className="form-group">
|
<div className="col-md-6">
|
||||||
<label htmlFor="user_name">
|
<div className="form-group-enterprise">
|
||||||
Full Name <span className="required">*</span>
|
<label htmlFor="user_name" className="form-label-enterprise">
|
||||||
</label>
|
<span>Full Name</span>
|
||||||
<input
|
<span className="required-badge">Required</span>
|
||||||
type="text"
|
</label>
|
||||||
id="user_name"
|
<div className="input-with-icon">
|
||||||
name="user_name"
|
<i className="fa-solid fa-user"></i>
|
||||||
value={formData.user_name}
|
<input
|
||||||
onChange={handleInputChange}
|
type="text"
|
||||||
required
|
id="user_name"
|
||||||
className="form-control"
|
name="user_name"
|
||||||
placeholder="John Doe"
|
value={formData.user_name}
|
||||||
/>
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
className={`form-control-enterprise ${fieldErrors.user_name ? 'error' : ''}`}
|
||||||
|
placeholder="Enter your full name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{fieldErrors.user_name && (
|
||||||
|
<span className="field-error">{fieldErrors.user_name}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="form-group-enterprise">
|
||||||
|
<label htmlFor="user_email" className="form-label-enterprise">
|
||||||
|
<span>Email Address</span>
|
||||||
|
<span className="required-badge">Required</span>
|
||||||
|
</label>
|
||||||
|
<div className="input-with-icon">
|
||||||
|
<i className="fa-solid fa-envelope"></i>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="user_email"
|
||||||
|
name="user_email"
|
||||||
|
value={formData.user_email}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
className={`form-control-enterprise ${fieldErrors.user_email ? 'error' : ''}`}
|
||||||
|
placeholder="your.email@company.com"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{fieldErrors.user_email && (
|
||||||
|
<span className="field-error">{fieldErrors.user_email}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="form-group-enterprise">
|
||||||
|
<label htmlFor="user_phone" className="form-label-enterprise">
|
||||||
|
<span>Phone Number</span>
|
||||||
|
<span className="optional-badge">Optional</span>
|
||||||
|
</label>
|
||||||
|
<div className="input-with-icon">
|
||||||
|
<i className="fa-solid fa-phone"></i>
|
||||||
|
<input
|
||||||
|
type="tel"
|
||||||
|
id="user_phone"
|
||||||
|
name="user_phone"
|
||||||
|
value={formData.user_phone}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="form-control-enterprise"
|
||||||
|
placeholder="+1 (555) 123-4567"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="form-group-enterprise">
|
||||||
|
<label htmlFor="company" className="form-label-enterprise">
|
||||||
|
<span>Company Name</span>
|
||||||
|
<span className="optional-badge">Optional</span>
|
||||||
|
</label>
|
||||||
|
<div className="input-with-icon">
|
||||||
|
<i className="fa-solid fa-building"></i>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="company"
|
||||||
|
name="company"
|
||||||
|
value={formData.company}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="form-control-enterprise"
|
||||||
|
placeholder="Your Company Inc."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6">
|
{/* Ticket Details */}
|
||||||
<div className="form-group">
|
<div className="form-section">
|
||||||
<label htmlFor="user_email">
|
<div className="section-header">
|
||||||
Email Address <span className="required">*</span>
|
<i className="fa-solid fa-clipboard-list"></i>
|
||||||
</label>
|
<h3>Ticket Details</h3>
|
||||||
<input
|
</div>
|
||||||
type="email"
|
|
||||||
id="user_email"
|
<div className="row g-4">
|
||||||
name="user_email"
|
<div className="col-md-6">
|
||||||
value={formData.user_email}
|
<div className="form-group-enterprise">
|
||||||
onChange={handleInputChange}
|
<label htmlFor="ticket_type" className="form-label-enterprise">
|
||||||
required
|
<span>Issue Type</span>
|
||||||
className="form-control"
|
<span className="required-badge">Required</span>
|
||||||
placeholder="john@company.com"
|
</label>
|
||||||
/>
|
<div className="select-with-icon">
|
||||||
|
<i className={`fa-solid ${issueTypeIcons[formData.ticket_type] || 'fa-tag'}`}></i>
|
||||||
|
<select
|
||||||
|
id="ticket_type"
|
||||||
|
name="ticket_type"
|
||||||
|
value={formData.ticket_type}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
className="form-control-enterprise select-enterprise"
|
||||||
|
>
|
||||||
|
<option value="general">General Inquiry</option>
|
||||||
|
<option value="technical">Technical Issue</option>
|
||||||
|
<option value="billing">Billing Question</option>
|
||||||
|
<option value="feature_request">Feature Request</option>
|
||||||
|
<option value="bug_report">Bug Report</option>
|
||||||
|
<option value="account">Account Issue</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-6">
|
||||||
|
<div className="form-group-enterprise">
|
||||||
|
<label htmlFor="category" className="form-label-enterprise">
|
||||||
|
<span>Category</span>
|
||||||
|
<span className="optional-badge">Optional</span>
|
||||||
|
</label>
|
||||||
|
<div className="select-with-icon">
|
||||||
|
<i className="fa-solid fa-folder"></i>
|
||||||
|
<select
|
||||||
|
id="category"
|
||||||
|
name="category"
|
||||||
|
value={formData.category || ''}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
className="form-control-enterprise select-enterprise"
|
||||||
|
disabled={categoriesLoading}
|
||||||
|
>
|
||||||
|
<option value="">Select a category</option>
|
||||||
|
{Array.isArray(categories) && categories.map(cat => (
|
||||||
|
<option key={cat.id} value={cat.id}>
|
||||||
|
{cat.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form-group-enterprise">
|
||||||
|
<label htmlFor="title" className="form-label-enterprise">
|
||||||
|
<span>Subject</span>
|
||||||
|
<span className="required-badge">Required</span>
|
||||||
|
</label>
|
||||||
|
<div className="input-with-icon">
|
||||||
|
<i className="fa-solid fa-heading"></i>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="title"
|
||||||
|
name="title"
|
||||||
|
value={formData.title}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
className={`form-control-enterprise ${fieldErrors.title ? 'error' : ''}`}
|
||||||
|
placeholder="Brief, descriptive subject line"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{fieldErrors.title && (
|
||||||
|
<span className="field-error">{fieldErrors.title}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form-group-enterprise">
|
||||||
|
<label htmlFor="description" className="form-label-enterprise">
|
||||||
|
<span>Description</span>
|
||||||
|
<span className="required-badge">Required</span>
|
||||||
|
<span className="char-count">{formData.description.length} characters</span>
|
||||||
|
</label>
|
||||||
|
<div className="textarea-wrapper">
|
||||||
|
<textarea
|
||||||
|
id="description"
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
className={`form-control-enterprise textarea-enterprise ${fieldErrors.description ? 'error' : ''}`}
|
||||||
|
rows={6}
|
||||||
|
placeholder="Please provide detailed information about your issue, including any error messages, steps to reproduce, or relevant context..."
|
||||||
|
/>
|
||||||
|
<div className="textarea-footer">
|
||||||
|
<i className="fa-solid fa-lightbulb"></i>
|
||||||
|
<span>Tip: More details help us resolve your issue faster</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{fieldErrors.description && (
|
||||||
|
<span className="field-error">{fieldErrors.description}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6">
|
{/* Submit Section */}
|
||||||
<div className="form-group">
|
<div className="form-submit-section">
|
||||||
<label htmlFor="user_phone">Phone Number</label>
|
<button
|
||||||
<input
|
type="submit"
|
||||||
type="tel"
|
className="btn-submit-enterprise"
|
||||||
id="user_phone"
|
disabled={isSubmitting}
|
||||||
name="user_phone"
|
>
|
||||||
value={formData.user_phone}
|
{isSubmitting ? (
|
||||||
onChange={handleInputChange}
|
<>
|
||||||
className="form-control"
|
<span className="spinner-enterprise"></span>
|
||||||
placeholder="+1 (555) 123-4567"
|
<span>Submitting Ticket...</span>
|
||||||
/>
|
</>
|
||||||
</div>
|
) : (
|
||||||
</div>
|
<>
|
||||||
|
<i className="fa-solid fa-paper-plane"></i>
|
||||||
<div className="col-md-6">
|
<span>Submit Ticket</span>
|
||||||
<div className="form-group">
|
<i className="fa-solid fa-arrow-right"></i>
|
||||||
<label htmlFor="company">Company Name</label>
|
</>
|
||||||
<input
|
)}
|
||||||
type="text"
|
</button>
|
||||||
id="company"
|
<p className="submit-note">
|
||||||
name="company"
|
<i className="fa-solid fa-clock"></i>
|
||||||
value={formData.company}
|
Average response time: <strong>2-4 hours</strong>
|
||||||
onChange={handleInputChange}
|
</p>
|
||||||
className="form-control"
|
|
||||||
placeholder="Your Company Inc."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Ticket Details */}
|
|
||||||
<div className="col-12">
|
|
||||||
<h4 className="form-section-title">Ticket Details</h4>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-md-6">
|
|
||||||
<div className="form-group">
|
|
||||||
<label htmlFor="ticket_type">
|
|
||||||
Issue Type <span className="required">*</span>
|
|
||||||
</label>
|
|
||||||
<select
|
|
||||||
id="ticket_type"
|
|
||||||
name="ticket_type"
|
|
||||||
value={formData.ticket_type}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="form-control"
|
|
||||||
>
|
|
||||||
<option value="general">General Inquiry</option>
|
|
||||||
<option value="technical">Technical Issue</option>
|
|
||||||
<option value="billing">Billing Question</option>
|
|
||||||
<option value="feature_request">Feature Request</option>
|
|
||||||
<option value="bug_report">Bug Report</option>
|
|
||||||
<option value="account">Account Issue</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-md-6">
|
|
||||||
<div className="form-group">
|
|
||||||
<label htmlFor="category">Category</label>
|
|
||||||
<select
|
|
||||||
id="category"
|
|
||||||
name="category"
|
|
||||||
value={formData.category || ''}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
className="form-control"
|
|
||||||
disabled={categoriesLoading}
|
|
||||||
>
|
|
||||||
<option value="">Select a category</option>
|
|
||||||
{Array.isArray(categories) && categories.map(cat => (
|
|
||||||
<option key={cat.id} value={cat.id}>
|
|
||||||
{cat.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="form-group">
|
|
||||||
<label htmlFor="title">
|
|
||||||
Subject <span className="required">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="title"
|
|
||||||
name="title"
|
|
||||||
value={formData.title}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="form-control"
|
|
||||||
placeholder="Brief description of your issue"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="form-group">
|
|
||||||
<label htmlFor="description">
|
|
||||||
Description <span className="required">*</span>
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
value={formData.description}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
className="form-control"
|
|
||||||
rows={6}
|
|
||||||
placeholder="Please provide detailed information about your issue..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-primary btn-lg"
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
{isSubmitting ? (
|
|
||||||
<>
|
|
||||||
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
|
||||||
Submitting...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<i className="fa-solid fa-paper-plane me-2"></i>
|
|
||||||
Submit Ticket
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ type ModalType = 'create' | 'knowledge' | 'status' | null;
|
|||||||
interface SupportCenterContentProps {
|
interface SupportCenterContentProps {
|
||||||
activeModal: ModalType;
|
activeModal: ModalType;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
onOpenModal?: (type: ModalType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SupportCenterContent = ({ activeModal, onClose }: SupportCenterContentProps) => {
|
const SupportCenterContent = ({ activeModal, onClose, onOpenModal }: SupportCenterContentProps) => {
|
||||||
// Close modal on escape key
|
// Close modal on escape key
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
@@ -115,7 +116,13 @@ const SupportCenterContent = ({ activeModal, onClose }: SupportCenterContentProp
|
|||||||
|
|
||||||
{/* Modal Body */}
|
{/* Modal Body */}
|
||||||
<div className="support-modal-body" style={{ padding: '40px' }}>
|
<div className="support-modal-body" style={{ padding: '40px' }}>
|
||||||
{activeModal === 'create' && <CreateTicketForm />}
|
{activeModal === 'create' && (
|
||||||
|
<CreateTicketForm onOpenStatusCheck={() => {
|
||||||
|
if (onOpenModal) {
|
||||||
|
onOpenModal('status');
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
)}
|
||||||
{activeModal === 'knowledge' && <KnowledgeBase />}
|
{activeModal === 'knowledge' && <KnowledgeBase />}
|
||||||
{activeModal === 'status' && <TicketStatusCheck />}
|
{activeModal === 'status' && <TicketStatusCheck />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,122 +39,233 @@ const TicketStatusCheck = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRelativeTime = (dateString: string) => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
const now = new Date();
|
||||||
|
const diffMs = now.getTime() - date.getTime();
|
||||||
|
const diffMins = Math.floor(diffMs / 60000);
|
||||||
|
const diffHours = Math.floor(diffMins / 60);
|
||||||
|
const diffDays = Math.floor(diffHours / 24);
|
||||||
|
|
||||||
|
if (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? 's' : ''} ago`;
|
||||||
|
if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;
|
||||||
|
if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;
|
||||||
|
return formatDate(dateString);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusIcon = (statusName: string) => {
|
||||||
|
const status = statusName.toLowerCase();
|
||||||
|
if (status.includes('open') || status.includes('new')) return 'fa-inbox';
|
||||||
|
if (status.includes('progress') || status.includes('working')) return 'fa-spinner';
|
||||||
|
if (status.includes('pending') || status.includes('waiting')) return 'fa-clock';
|
||||||
|
if (status.includes('resolved') || status.includes('closed')) return 'fa-check-circle';
|
||||||
|
return 'fa-ticket';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPriorityIcon = (priorityName: string) => {
|
||||||
|
const priority = priorityName.toLowerCase();
|
||||||
|
if (priority.includes('urgent') || priority.includes('critical')) return 'fa-exclamation-triangle';
|
||||||
|
if (priority.includes('high')) return 'fa-arrow-up';
|
||||||
|
if (priority.includes('medium')) return 'fa-minus';
|
||||||
|
if (priority.includes('low')) return 'fa-arrow-down';
|
||||||
|
return 'fa-flag';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ticket-status-check">
|
<div className="ticket-status-check enterprise-status-check">
|
||||||
<div className="row justify-content-center">
|
<div className="row justify-content-center">
|
||||||
<div className="col-12 col-lg-8">
|
<div className="col-12">
|
||||||
<div className="form-header text-center">
|
<div className="status-header-enterprise">
|
||||||
|
<div className="status-header-icon">
|
||||||
|
<i className="fa-solid fa-magnifying-glass"></i>
|
||||||
|
</div>
|
||||||
<h2>Check Ticket Status</h2>
|
<h2>Check Ticket Status</h2>
|
||||||
<p>Enter your ticket number to view the current status and details of your support request.</p>
|
<p>Track your support request in real-time with instant status updates</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="status-search-form">
|
<form onSubmit={handleSubmit} className="status-search-form-enterprise">
|
||||||
<div className="search-input-group">
|
<div className="search-container">
|
||||||
<input
|
<div className="search-input-wrapper">
|
||||||
type="text"
|
<i className="fa-solid fa-ticket search-icon"></i>
|
||||||
value={ticketNumber}
|
<input
|
||||||
onChange={(e) => setTicketNumber(e.target.value)}
|
type="text"
|
||||||
placeholder="Enter your ticket number (e.g., TKT-20231015-ABCDE)"
|
value={ticketNumber}
|
||||||
required
|
onChange={(e) => setTicketNumber(e.target.value.toUpperCase())}
|
||||||
className="form-control"
|
placeholder="TKT-YYYYMMDD-XXXXX"
|
||||||
/>
|
required
|
||||||
|
className="search-input-enterprise"
|
||||||
|
pattern="TKT-\d{8}-[A-Z0-9]{5}"
|
||||||
|
title="Please enter a valid ticket number (e.g., TKT-20231015-ABCDE)"
|
||||||
|
/>
|
||||||
|
{ticketNumber && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="clear-btn"
|
||||||
|
onClick={() => {
|
||||||
|
setTicketNumber('');
|
||||||
|
setTicket(null);
|
||||||
|
setSearchError(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-times"></i>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-primary"
|
className="btn-search-enterprise"
|
||||||
disabled={isSearching}
|
disabled={isSearching}
|
||||||
>
|
>
|
||||||
{isSearching ? (
|
{isSearching ? (
|
||||||
<>
|
<>
|
||||||
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
<span className="spinner-enterprise"></span>
|
||||||
Searching...
|
<span>Searching...</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<i className="fa-solid fa-search me-2"></i>
|
<i className="fa-solid fa-search"></i>
|
||||||
Check Status
|
<span>Search</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="search-hint">
|
||||||
|
<i className="fa-solid fa-info-circle"></i>
|
||||||
|
Enter your ticket number exactly as it appears in your confirmation email
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{searchError && (
|
{searchError && (
|
||||||
<div className="alert alert-danger mt-4" role="alert">
|
<div className="alert-enterprise alert-error">
|
||||||
<i className="fa-solid fa-triangle-exclamation me-2"></i>
|
<i className="fa-solid fa-exclamation-circle"></i>
|
||||||
{searchError}
|
<div>
|
||||||
|
<strong>Ticket Not Found</strong>
|
||||||
|
<p>{searchError}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{ticket && (
|
{ticket && (
|
||||||
<div className="ticket-details">
|
<div className="ticket-details-enterprise">
|
||||||
<div className="ticket-header">
|
{/* Header Section */}
|
||||||
<div className="ticket-number">
|
<div className="ticket-header-enterprise">
|
||||||
<i className="fa-solid fa-ticket me-2"></i>
|
<div className="ticket-number-section">
|
||||||
{ticket.ticket_number}
|
<span className="ticket-label">Ticket Number</span>
|
||||||
|
<div className="ticket-number-display">
|
||||||
|
<i className="fa-solid fa-ticket"></i>
|
||||||
|
<span>{ticket.ticket_number}</span>
|
||||||
|
<button
|
||||||
|
className="btn-copy-inline"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(ticket.ticket_number);
|
||||||
|
const btn = document.querySelector('.btn-copy-inline');
|
||||||
|
if (btn) {
|
||||||
|
const originalHTML = btn.innerHTML;
|
||||||
|
btn.innerHTML = '<i class="fa-solid fa-check"></i>';
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.innerHTML = originalHTML;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="ticket-status-badge"
|
className="status-badge-enterprise"
|
||||||
style={{ backgroundColor: ticket.status_color }}
|
style={{
|
||||||
|
backgroundColor: ticket.status_color || '#6366f1',
|
||||||
|
boxShadow: `0 0 20px ${ticket.status_color}33`
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{ticket.status_name}
|
<i className={`fa-solid ${getStatusIcon(ticket.status_name)}`}></i>
|
||||||
|
<span>{ticket.status_name}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ticket-info">
|
{/* Info Cards Section */}
|
||||||
<h3>{ticket.title}</h3>
|
<div className="ticket-info-cards">
|
||||||
<div className="ticket-meta">
|
<div className="info-card">
|
||||||
<div className="meta-item">
|
<i className="fa-solid fa-calendar-plus"></i>
|
||||||
<i className="fa-solid fa-calendar me-2"></i>
|
<div>
|
||||||
<strong>Created:</strong> {formatDate(ticket.created_at)}
|
<span className="card-label">Created</span>
|
||||||
|
<span className="card-value">{getRelativeTime(ticket.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="meta-item">
|
</div>
|
||||||
<i className="fa-solid fa-clock me-2"></i>
|
<div className="info-card">
|
||||||
<strong>Last Updated:</strong> {formatDate(ticket.updated_at)}
|
<i className="fa-solid fa-clock"></i>
|
||||||
|
<div>
|
||||||
|
<span className="card-label">Last Updated</span>
|
||||||
|
<span className="card-value">{getRelativeTime(ticket.updated_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
{ticket.priority_name && (
|
</div>
|
||||||
<div className="meta-item">
|
{ticket.priority_name && (
|
||||||
<i className="fa-solid fa-flag me-2"></i>
|
<div className="info-card">
|
||||||
<strong>Priority:</strong>
|
<i className={`fa-solid ${getPriorityIcon(ticket.priority_name)}`}></i>
|
||||||
|
<div>
|
||||||
|
<span className="card-label">Priority</span>
|
||||||
<span
|
<span
|
||||||
className="priority-badge ms-2"
|
className="card-value priority-value"
|
||||||
style={{ backgroundColor: ticket.priority_color }}
|
style={{ color: ticket.priority_color }}
|
||||||
>
|
>
|
||||||
{ticket.priority_name}
|
{ticket.priority_name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
{ticket.category_name && (
|
)}
|
||||||
<div className="meta-item">
|
{ticket.category_name && (
|
||||||
<i className="fa-solid fa-folder me-2"></i>
|
<div className="info-card">
|
||||||
<strong>Category:</strong> {ticket.category_name}
|
<i className="fa-solid fa-folder-open"></i>
|
||||||
|
<div>
|
||||||
|
<span className="card-label">Category</span>
|
||||||
|
<span className="card-value">{ticket.category_name}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div className="ticket-content-section">
|
||||||
|
<div className="content-header">
|
||||||
|
<h3>{ticket.title}</h3>
|
||||||
|
<div className="content-meta">
|
||||||
|
<span><i className="fa-solid fa-user"></i> {ticket.user_name}</span>
|
||||||
|
{ticket.company && (
|
||||||
|
<span><i className="fa-solid fa-building"></i> {ticket.company}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ticket-description">
|
<div className="description-section">
|
||||||
<h4>Description</h4>
|
<h4><i className="fa-solid fa-align-left"></i> Description</h4>
|
||||||
<p>{ticket.description}</p>
|
<p>{ticket.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ticket.messages && ticket.messages.length > 0 && (
|
{/* Messages Section */}
|
||||||
<div className="ticket-messages">
|
{ticket.messages && ticket.messages.filter(msg => !msg.is_internal).length > 0 && (
|
||||||
<h4>Messages ({ticket.messages.length})</h4>
|
<div className="messages-section-enterprise">
|
||||||
<div className="messages-list">
|
<h4>
|
||||||
|
<i className="fa-solid fa-comments"></i>
|
||||||
|
Conversation
|
||||||
|
<span className="count-badge">{ticket.messages.filter(msg => !msg.is_internal).length}</span>
|
||||||
|
</h4>
|
||||||
|
<div className="messages-list-enterprise">
|
||||||
{ticket.messages
|
{ticket.messages
|
||||||
.filter(msg => !msg.is_internal)
|
.filter(msg => !msg.is_internal)
|
||||||
.map((message, index) => (
|
.map((message) => (
|
||||||
<div key={message.id} className="message-item">
|
<div key={message.id} className="message-card-enterprise">
|
||||||
<div className="message-header">
|
<div className="message-avatar">
|
||||||
<div className="message-author">
|
<i className="fa-solid fa-user"></i>
|
||||||
<i className="fa-solid fa-user-circle me-2"></i>
|
|
||||||
{message.author_name || message.author_email}
|
|
||||||
</div>
|
|
||||||
<div className="message-date">
|
|
||||||
{formatDate(message.created_at)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="message-content">
|
<div className="message-content-wrapper">
|
||||||
{message.content}
|
<div className="message-header-enterprise">
|
||||||
|
<span className="message-author">{message.author_name || message.author_email}</span>
|
||||||
|
<span className="message-time">{getRelativeTime(message.created_at)}</span>
|
||||||
|
</div>
|
||||||
|
<div className="message-text">
|
||||||
|
{message.content}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -162,22 +273,22 @@ const TicketStatusCheck = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Activity Timeline */}
|
||||||
{ticket.activities && ticket.activities.length > 0 && (
|
{ticket.activities && ticket.activities.length > 0 && (
|
||||||
<div className="ticket-timeline">
|
<div className="timeline-section-enterprise">
|
||||||
<h4>Activity Timeline</h4>
|
<h4>
|
||||||
<div className="timeline-list">
|
<i className="fa-solid fa-list-check"></i>
|
||||||
|
Activity Timeline
|
||||||
|
</h4>
|
||||||
|
<div className="timeline-items">
|
||||||
{ticket.activities.slice(0, 5).map((activity, index) => (
|
{ticket.activities.slice(0, 5).map((activity, index) => (
|
||||||
<div key={activity.id} className="timeline-item">
|
<div key={activity.id} className="timeline-item-enterprise">
|
||||||
<div className="timeline-icon">
|
<div className="timeline-marker">
|
||||||
<i className="fa-solid fa-circle"></i>
|
<i className="fa-solid fa-circle"></i>
|
||||||
</div>
|
</div>
|
||||||
<div className="timeline-content">
|
<div className="timeline-content-wrapper">
|
||||||
<div className="timeline-description">
|
<div className="timeline-text">{activity.description}</div>
|
||||||
{activity.description}
|
<div className="timeline-time">{getRelativeTime(activity.created_at)}</div>
|
||||||
</div>
|
|
||||||
<div className="timeline-date">
|
|
||||||
{formatDate(activity.created_at)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -185,6 +296,21 @@ const TicketStatusCheck = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Footer Actions */}
|
||||||
|
<div className="ticket-footer-enterprise">
|
||||||
|
<div className="footer-info">
|
||||||
|
<i className="fa-solid fa-shield-check"></i>
|
||||||
|
<span>This ticket is securely tracked and monitored by our support team</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="btn-refresh"
|
||||||
|
onClick={() => handleSubmit({ preventDefault: () => {} } as FormEvent<HTMLFormElement>)}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-rotate"></i>
|
||||||
|
Refresh Status
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ const Footer = () => {
|
|||||||
<Image
|
<Image
|
||||||
src={logoSrc}
|
src={logoSrc}
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
width={160}
|
width={120}
|
||||||
height={120}
|
height={90}
|
||||||
style={{
|
style={{
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
height: 'auto'
|
height: 'auto'
|
||||||
|
|||||||
@@ -800,23 +800,30 @@
|
|||||||
.cta-primary {
|
.cta-primary {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
background: linear-gradient(135deg, #1e40af, #0ea5e9);
|
background: linear-gradient(135deg, #1a365d, #2563eb);
|
||||||
color: white;
|
color: #ffffff !important;
|
||||||
padding: 14px 28px;
|
padding: 16px 32px;
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
box-shadow: 0 6px 24px rgba(30, 64, 175, 0.3);
|
box-shadow: 0 4px 14px rgba(26, 54, 93, 0.25);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
span, i {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 8px 32px rgba(30, 64, 175, 0.4);
|
box-shadow: 0 6px 20px rgba(26, 54, 93, 0.35);
|
||||||
|
background: linear-gradient(135deg, #1e40af, #3b82f6);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -824,7 +831,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
transition: transform 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,25 +843,31 @@
|
|||||||
.cta-secondary {
|
.cta-secondary {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
background: transparent;
|
background: rgba(255, 255, 255, 0.05);
|
||||||
color: white;
|
color: #ffffff !important;
|
||||||
padding: 14px 28px;
|
padding: 16px 32px;
|
||||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
border-radius: 8px;
|
border-radius: 6px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
|
span, i {
|
||||||
|
color: #ffffff !important;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
border-color: rgba(255, 255, 255, 0.4);
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 14px rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -862,7 +875,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
img {
|
img {
|
||||||
filter: brightness(0) invert(1); // Make logo white for dark footer
|
filter: brightness(0) invert(1); // Make logo white for dark footer
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
max-height: 120px;
|
max-height: 90px;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,7 +479,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-logo img {
|
.footer-logo img {
|
||||||
max-height: 100px;
|
max-height: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-badges-left,
|
.security-badges-left,
|
||||||
@@ -511,7 +511,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer-logo img {
|
.footer-logo img {
|
||||||
max-height: 80px;
|
max-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-badges-left,
|
.security-badges-left,
|
||||||
@@ -540,7 +540,7 @@
|
|||||||
@media (max-width: 575.98px) {
|
@media (max-width: 575.98px) {
|
||||||
.footer-logo-section {
|
.footer-logo-section {
|
||||||
.footer-logo img {
|
.footer-logo img {
|
||||||
max-height: 60px;
|
max-height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.security-badges-left,
|
.security-badges-left,
|
||||||
|
|||||||
@@ -71,13 +71,16 @@
|
|||||||
.navbar__dropdown-label {
|
.navbar__dropdown-label {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-right: 25px;
|
padding-right: 25px;
|
||||||
|
padding: 10px 25px 10px 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "\f107";
|
content: "\f107";
|
||||||
font-family: "Font Awesome 6 Free";
|
font-family: "Font Awesome 6 Free";
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 8px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -87,6 +90,17 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--enterprise-gold);
|
color: var(--enterprise-gold);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
color: var(--enterprise-gold);
|
||||||
|
transform: translateY(-50%) rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.navbar__item-active {
|
||||||
|
color: var(--enterprise-gold);
|
||||||
|
background: rgba(212, 175, 55, 0.1);
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
color: var(--enterprise-gold);
|
color: var(--enterprise-gold);
|
||||||
@@ -101,71 +115,103 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .navbar__dropdown-label::after {
|
&:hover .navbar__dropdown-label {
|
||||||
transform: translateY(-50%) rotate(180deg);
|
color: var(--enterprise-gold);
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
transform: translateY(-50%) rotate(180deg);
|
||||||
|
color: var(--enterprise-gold);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar__sub-menu {
|
.navbar__sub-menu {
|
||||||
position: absolute;
|
position: absolute !important;
|
||||||
top: calc(100% + 8px);
|
top: calc(100% + 12px) !important;
|
||||||
left: 50%;
|
left: 50% !important;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%) !important;
|
||||||
background: rgba(0, 0, 0, 0.92);
|
background: #1a1a1a !important;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 20px 60px rgba(0, 0, 0, 0.5),
|
0 25px 70px rgba(0, 0, 0, 0.7),
|
||||||
0 8px 25px rgba(0, 0, 0, 0.3),
|
0 10px 35px rgba(0, 0, 0, 0.5),
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
0 0 0 1px rgba(255, 255, 255, 0.2) !important;
|
||||||
padding: 4px 0;
|
padding: 12px !important;
|
||||||
min-width: 240px;
|
min-width: 280px !important;
|
||||||
|
max-width: 320px;
|
||||||
|
max-height: 500px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
transform: translateX(-50%) translateY(-10px) scale(0.96);
|
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
z-index: 1000;
|
z-index: 9999 !important;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
backdrop-filter: blur(25px) saturate(180%);
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
|
// Custom scrollbar styles
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(212, 175, 55, 0.4);
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(212, 175, 55, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.show {
|
&.show {
|
||||||
opacity: 1;
|
opacity: 1 !important;
|
||||||
visibility: visible;
|
visibility: visible !important;
|
||||||
transform: translateX(-50%) translateY(0) scale(1);
|
transform: translateX(-50%) translateY(0) scale(1) !important;
|
||||||
|
display: block !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -7px;
|
top: -8px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
border-left: 7px solid transparent;
|
border-left: 8px solid transparent;
|
||||||
border-right: 7px solid transparent;
|
border-right: 8px solid transparent;
|
||||||
border-bottom: 7px solid rgba(0, 0, 0, 0.92);
|
border-bottom: 8px solid #1a1a1a;
|
||||||
filter: drop-shadow(0 -2px 6px rgba(0, 0, 0, 0.4));
|
filter: drop-shadow(0 -3px 8px rgba(0, 0, 0, 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 1px 6px;
|
margin: 2px 0;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
display: flex;
|
display: block !important;
|
||||||
align-items: center;
|
padding: 14px 20px !important;
|
||||||
padding: 10px 16px;
|
color: #ffffff !important;
|
||||||
color: rgba(255, 255, 255, 0.85);
|
font-size: 14px !important;
|
||||||
font-size: 13px;
|
font-weight: 500 !important;
|
||||||
font-weight: 500;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
text-decoration: none;
|
text-decoration: none !important;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.3px;
|
||||||
line-height: 1.4;
|
line-height: 1.5;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
@@ -173,30 +219,33 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 2px;
|
width: 3px;
|
||||||
background: linear-gradient(135deg, var(--enterprise-gold), #f4d03f);
|
background: linear-gradient(135deg, var(--enterprise-gold), #f4d03f);
|
||||||
transform: scaleY(0);
|
transform: scaleY(0);
|
||||||
transition: transform 0.25s ease;
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
border-radius: 0 1px 1px 0;
|
border-radius: 0 2px 2px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "→";
|
content: "→";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: 20px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(-3px);
|
transform: translateX(-5px);
|
||||||
transition: all 0.25s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
color: var(--enterprise-gold);
|
color: var(--enterprise-gold);
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
font-size: 10px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--enterprise-gold);
|
color: var(--enterprise-gold);
|
||||||
background: rgba(212, 175, 55, 0.12);
|
background: linear-gradient(135deg, rgba(212, 175, 55, 0.18), rgba(212, 175, 55, 0.08));
|
||||||
transform: translateX(3px);
|
transform: translateX(4px);
|
||||||
box-shadow: 0 2px 8px rgba(212, 175, 55, 0.15);
|
box-shadow:
|
||||||
|
0 4px 12px rgba(212, 175, 55, 0.2),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||||
|
padding-right: 35px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
transform: scaleY(1);
|
transform: scaleY(1);
|
||||||
@@ -210,9 +259,11 @@
|
|||||||
|
|
||||||
&.active-current-sub {
|
&.active-current-sub {
|
||||||
color: var(--enterprise-gold);
|
color: var(--enterprise-gold);
|
||||||
background: rgba(212, 175, 55, 0.18);
|
background: linear-gradient(135deg, rgba(212, 175, 55, 0.22), rgba(212, 175, 55, 0.12));
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
box-shadow: 0 1px 6px rgba(212, 175, 55, 0.25);
|
box-shadow:
|
||||||
|
0 2px 10px rgba(212, 175, 55, 0.3),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
transform: scaleY(1);
|
transform: scaleY(1);
|
||||||
@@ -222,34 +273,34 @@
|
|||||||
|
|
||||||
// Loading and error states
|
// Loading and error states
|
||||||
.text-muted {
|
.text-muted {
|
||||||
color: rgba(255, 255, 255, 0.6);
|
color: rgba(255, 255, 255, 0.65) !important;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
padding: 10px 16px;
|
padding: 14px 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.3px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "⏳";
|
content: "⏳";
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-danger {
|
.text-danger {
|
||||||
color: #ff6b6b;
|
color: #ff6b6b !important;
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
padding: 10px 16px;
|
padding: 14px 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
letter-spacing: 0.2px;
|
letter-spacing: 0.3px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "⚠️";
|
content: "⚠️";
|
||||||
font-size: 10px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,6 +311,17 @@
|
|||||||
to { transform: rotate(360deg); }
|
to { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInSlideUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.navbar__options {
|
.navbar__options {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -350,20 +412,101 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar__item--has-children {
|
||||||
|
.navbar__dropdown-label {
|
||||||
|
&:hover {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.navbar__item-active {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background: rgba(212, 175, 55, 0.12);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .navbar__dropdown-label {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.navbar__sub-menu {
|
.navbar__sub-menu {
|
||||||
background-color: white;
|
background: #ffffff;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
box-shadow:
|
||||||
|
0 25px 70px rgba(0, 0, 0, 0.15),
|
||||||
|
0 10px 35px rgba(0, 0, 0, 0.08),
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||||
|
backdrop-filter: blur(25px) saturate(180%);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
border-bottom-color: white;
|
border-bottom-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom scrollbar for light mode
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(212, 175, 55, 0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(212, 175, 55, 0.7);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
li a {
|
li a {
|
||||||
color: var(--secondary-color);
|
display: block !important;
|
||||||
|
color: #222222 !important;
|
||||||
|
opacity: 1 !important;
|
||||||
|
visibility: visible !important;
|
||||||
|
|
||||||
&:hover {
|
&::before {
|
||||||
|
background: linear-gradient(135deg, var(--primary-color), var(--enterprise-gold));
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
background: linear-gradient(135deg, rgba(212, 175, 55, 0.15), rgba(212, 175, 55, 0.08));
|
||||||
|
box-shadow:
|
||||||
|
0 4px 12px rgba(212, 175, 55, 0.15),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active-current-sub {
|
||||||
|
color: var(--primary-color) !important;
|
||||||
|
background: linear-gradient(135deg, rgba(212, 175, 55, 0.2), rgba(212, 175, 55, 0.1));
|
||||||
|
box-shadow:
|
||||||
|
0 2px 10px rgba(212, 175, 55, 0.2),
|
||||||
|
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading and error states for light mode
|
||||||
|
.text-muted {
|
||||||
|
color: rgba(0, 0, 0, 0.55) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #dc3545 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -936,6 +1079,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Responsive styles for enterprise header
|
// Responsive styles for enterprise header
|
||||||
|
@media (max-width: 1199.98px) {
|
||||||
|
.tp-header {
|
||||||
|
.navbar__sub-menu {
|
||||||
|
min-width: 240px;
|
||||||
|
max-width: 280px;
|
||||||
|
|
||||||
|
li a {
|
||||||
|
padding: 12px 18px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 991.98px) {
|
@media (max-width: 991.98px) {
|
||||||
.tp-header {
|
.tp-header {
|
||||||
.navbar__menu {
|
.navbar__menu {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user