From 514508df89c0046c9666690a76de48d85bfec8cb Mon Sep 17 00:00:00 2001 From: CycroftX Date: Mon, 9 Feb 2026 22:32:51 +0530 Subject: [PATCH] Complete Support Module Implementation & Refinements --- .../users/components/UserMetricsBar.tsx | 4 +- .../components/dialogs/EscalationForm.tsx | 132 ++++++++----- .../users/components/tabs/SupportTab.tsx | 178 +++++++++++++++--- src/lib/actions/support.ts | 80 ++++++++ src/lib/validations/support.ts | 67 +++++++ 5 files changed, 384 insertions(+), 77 deletions(-) create mode 100644 src/lib/actions/support.ts create mode 100644 src/lib/validations/support.ts diff --git a/src/features/users/components/UserMetricsBar.tsx b/src/features/users/components/UserMetricsBar.tsx index 3f91eaf..9ac02a9 100644 --- a/src/features/users/components/UserMetricsBar.tsx +++ b/src/features/users/components/UserMetricsBar.tsx @@ -33,11 +33,11 @@ function MetricCard({ title, value, trend, icon: Icon, iconColor, iconBg }: Metr
{title} -
+
{value} {trend !== undefined && (
= 0 ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700' )}> {trend >= 0 ? ( diff --git a/src/features/users/components/dialogs/EscalationForm.tsx b/src/features/users/components/dialogs/EscalationForm.tsx index b0811cb..1948acd 100644 --- a/src/features/users/components/dialogs/EscalationForm.tsx +++ b/src/features/users/components/dialogs/EscalationForm.tsx @@ -21,20 +21,22 @@ import { SelectValue, } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; -import { Loader2, Ticket, Phone, Clock, AlertTriangle } from 'lucide-react'; -import { createEscalationTicket } from '@/lib/actions/userActions'; +import { Loader2, Ticket, Phone, AlertTriangle } from 'lucide-react'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; +import { createEscalation } from '@/lib/actions/support'; +import { SupportTicketPriority, SupportTicketType } from '@/lib/validations/support'; interface EscalationFormProps { open: boolean; onOpenChange: (open: boolean) => void; userId: string; - userName?: string; // Optional for display + userName?: string; + onSuccess?: (ticket: any) => void; } -const TICKET_TYPES = ['Refund', 'Payment', 'Account access', 'Fraud', 'Event issue', 'Other']; -const PRIORITIES = ['Low', 'Normal', 'High', 'Urgent']; +const PRIORITIES = Object.values(SupportTicketPriority); +const TICKET_TYPES = Object.values(SupportTicketType); export function EscalationForm({ open, @@ -43,8 +45,10 @@ export function EscalationForm({ userName }: EscalationFormProps) { const [isPending, startTransition] = useTransition(); - const [type, setType] = useState(''); - const [priority, setPriority] = useState('Normal'); + + // Form State + const [type, setType] = useState(''); + const [priority, setPriority] = useState('Normal'); const [subject, setSubject] = useState(''); const [description, setDescription] = useState(''); const [callbackRequired, setCallbackRequired] = useState(false); @@ -52,30 +56,47 @@ export function EscalationForm({ const [assigneeId, setAssigneeId] = useState(''); const handleSubmit = () => { - if (!type || !subject || !description) { - toast.error("Please fill in all required fields"); + // Basic Client Validation + if (!subject || !description || !type) { + toast.error("Please fill in all required fields."); + return; + } + + if (priority === 'Critical' && description.length < 20) { + toast.error("Critical tickets require a detailed description (>20 chars)."); + return; + } + + if (callbackRequired && (!callbackPhone || callbackPhone.length < 5)) { + toast.error("Phone number required for callback."); return; } startTransition(async () => { - const result = await createEscalationTicket({ + const result = await createEscalation({ userId, + // Cast to specific enums as we're using string state for UI simplicity type: type as any, priority: priority as any, subject, description, callbackRequired, callbackPhone: callbackRequired ? callbackPhone : undefined, - assigneeId: assigneeId || undefined, - }); + assigneeId: (assigneeId && assigneeId !== 'auto') ? assigneeId : undefined, + }) as any; // Type assertion for mocked return if (result.success) { toast.success(result.message); onOpenChange(false); + // Reset form setSubject(''); setDescription(''); + setCallbackRequired(false); + if (onSuccess && result.ticket) { + onSuccess(result.ticket); + } } else { - toast.error(result.message); + toast.error(result.message || "Failed to create ticket."); } }); }; @@ -84,19 +105,20 @@ export function EscalationForm({ - + Create Escalation Ticket - Raise a new internal support ticket or escalation. + Raise a new internal support ticket or escalation for {userName || 'User'}.
+ {/* Top Row: Type & Priority */}
- +
- - + +
+ {PRIORITIES.map((p) => ( + + ))} +
+

+ {priority === 'Critical' ? 'SLA: 1 Hour (Triggers PagerDuty)' : + priority === 'High' ? 'SLA: 4 Hours' : + priority === 'Medium' || priority === 'Normal' ? 'SLA: 24 Hours' : 'SLA: 48 Hours'} +

+ {/* Subject */}
setSubject(e.target.value)} />
+ {/* Description */}