This commit is contained in:
Iliyan Angelov
2025-10-13 22:47:06 +03:00
parent 5ad9cbe3a6
commit dfcaebaf8c
12 changed files with 3233 additions and 373 deletions

View File

@@ -3,7 +3,11 @@ import { useState, FormEvent } from 'react';
import { createTicket, CreateTicketData } from '@/lib/api/supportService';
import { useTicketCategories } from '@/lib/hooks/useSupport';
const CreateTicketForm = () => {
interface CreateTicketFormProps {
onOpenStatusCheck?: () => void;
}
const CreateTicketForm = ({ onOpenStatusCheck }: CreateTicketFormProps) => {
const { categories, loading: categoriesLoading } = useTicketCategories();
const [formData, setFormData] = useState<CreateTicketData>({
@@ -21,6 +25,7 @@ const CreateTicketForm = () => {
const [submitError, setSubmitError] = useState<string | null>(null);
const [submitSuccess, setSubmitSuccess] = useState(false);
const [ticketNumber, setTicketNumber] = useState<string>('');
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
@@ -30,10 +35,46 @@ const CreateTicketForm = () => {
...prev,
[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>) => {
e.preventDefault();
if (!validateForm()) {
return;
}
setIsSubmitting(true);
setSubmitError(null);
setSubmitSuccess(false);
@@ -54,6 +95,7 @@ const CreateTicketForm = () => {
company: '',
category: undefined
});
setFieldErrors({});
} catch (error: any) {
console.error('Ticket creation error:', error);
@@ -88,226 +130,334 @@ const CreateTicketForm = () => {
<i className="fa-solid fa-circle-check"></i>
</div>
<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">
We've received your support request and will respond as soon as possible.
Please save your ticket number for future reference.
</p>
<button
className="btn btn-primary"
onClick={() => setSubmitSuccess(false)}
>
Submit Another Ticket
</button>
<div className="success-actions">
<button
className="btn btn-primary"
onClick={() => setSubmitSuccess(false)}
>
<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>
);
}
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 (
<div className="create-ticket-form">
<div className="create-ticket-form enterprise-form">
<div className="row justify-content-center">
<div className="col-12 col-lg-10 col-xl-8">
<div className="form-header">
<div className="col-12">
<div className="form-header-enterprise">
<div className="form-header-icon">
<i className="fa-solid fa-ticket"></i>
</div>
<h2>Submit a Support Ticket</h2>
<p>Fill out the form below and our team will get back to you shortly.</p>
<div style={{
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(59, 130, 246, 0.05) 100%)',
border: '1px solid rgba(59, 130, 246, 0.3)',
borderRadius: '8px',
padding: '12px 16px',
marginTop: '1rem',
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
<p>Fill out the form below and our dedicated support team will get back to you within 24 hours.</p>
<div className="info-banner">
<i className="fa-solid fa-shield-check"></i>
<div>
<strong>Secure & Confidential</strong>
<span>All tickets are encrypted and handled with enterprise-grade security standards.</span>
</div>
</div>
</div>
{submitError && (
<div className="alert alert-danger" role="alert">
<i className="fa-solid fa-triangle-exclamation me-2"></i>
{submitError}
<div className="alert-enterprise alert-error">
<i className="fa-solid fa-triangle-exclamation"></i>
<div>
<strong>Submission Error</strong>
<p>{submitError}</p>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="support-form">
<div className="row g-4">
{/* Personal Information */}
<div className="col-12">
<h4 className="form-section-title">Personal Information</h4>
<form onSubmit={handleSubmit} className="support-form-enterprise">
{/* Personal Information */}
<div className="form-section">
<div className="section-header">
<i className="fa-solid fa-user"></i>
<h3>Personal Information</h3>
</div>
<div className="row g-4">
<div className="col-md-6">
<div className="form-group-enterprise">
<label htmlFor="user_name" className="form-label-enterprise">
<span>Full Name</span>
<span className="required-badge">Required</span>
</label>
<div className="input-with-icon">
<i className="fa-solid fa-user"></i>
<input
type="text"
id="user_name"
name="user_name"
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">
<label htmlFor="user_name">
Full Name <span className="required">*</span>
</label>
<input
type="text"
id="user_name"
name="user_name"
value={formData.user_name}
onChange={handleInputChange}
required
className="form-control"
placeholder="John Doe"
/>
<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 className="col-md-6">
<div className="form-group">
<label htmlFor="user_email">
Email Address <span className="required">*</span>
</label>
<input
type="email"
id="user_email"
name="user_email"
value={formData.user_email}
onChange={handleInputChange}
required
className="form-control"
placeholder="john@company.com"
/>
{/* Ticket Details */}
<div className="form-section">
<div className="section-header">
<i className="fa-solid fa-clipboard-list"></i>
<h3>Ticket Details</h3>
</div>
<div className="row g-4">
<div className="col-md-6">
<div className="form-group-enterprise">
<label htmlFor="ticket_type" className="form-label-enterprise">
<span>Issue Type</span>
<span className="required-badge">Required</span>
</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 className="col-md-6">
<div className="form-group">
<label htmlFor="user_phone">Phone Number</label>
<input
type="tel"
id="user_phone"
name="user_phone"
value={formData.user_phone}
onChange={handleInputChange}
className="form-control"
placeholder="+1 (555) 123-4567"
/>
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label htmlFor="company">Company Name</label>
<input
type="text"
id="company"
name="company"
value={formData.company}
onChange={handleInputChange}
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>
{/* Submit Section */}
<div className="form-submit-section">
<button
type="submit"
className="btn-submit-enterprise"
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<span className="spinner-enterprise"></span>
<span>Submitting Ticket...</span>
</>
) : (
<>
<i className="fa-solid fa-paper-plane"></i>
<span>Submit Ticket</span>
<i className="fa-solid fa-arrow-right"></i>
</>
)}
</button>
<p className="submit-note">
<i className="fa-solid fa-clock"></i>
Average response time: <strong>2-4 hours</strong>
</p>
</div>
</form>
</div>