diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index f0069334..b142e5a6 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -23,6 +23,7 @@ import { ClipboardCheck, Award, Crown, + LifeBuoy, } from "lucide-react" import { useAuth } from "@/lib/hooks/useAuth" @@ -165,6 +166,11 @@ const sidebarItems: SidebarGroupType[] = [ { title: "Support", items: [ + { + title: "Support Tickets", + url: "/admin/support", + icon: LifeBuoy, + }, { title: "Messages", url: "/admin/messages", diff --git a/app/admin/support/[id]/page.tsx b/app/admin/support/[id]/page.tsx new file mode 100644 index 00000000..103d1b4e --- /dev/null +++ b/app/admin/support/[id]/page.tsx @@ -0,0 +1,314 @@ +'use client' + +import React, { useState, useEffect } from 'react' +import { useParams, useRouter } from 'next/navigation' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Skeleton } from '@/components/ui/skeleton' +import { Textarea } from '@/components/ui/textarea' +import { + ArrowLeft, + Mail, + Bug, + Clock, + User, + Calendar, + MessageSquare, + Save +} from 'lucide-react' +import { toast } from 'sonner' +import Link from 'next/link' + +interface SupportTicket { + id: string + user_id: string + type: 'contact' | 'bug' + subject: string + message: string + status: 'open' | 'in_progress' | 'resolved' | 'closed' + created_at: string + updated_at: string + user?: { + id: string + email: string + first_name?: string + last_name?: string + avatar_url?: string + } +} + +export default function TicketDetailPage() { + const params = useParams() + const router = useRouter() + const ticketId = params.id as string + + const [ticket, setTicket] = useState(null) + const [loading, setLoading] = useState(true) + const [updating, setUpdating] = useState(false) + const [notes, setNotes] = useState('') + + useEffect(() => { + fetchTicket() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ticketId]) + + const fetchTicket = async () => { + try { + const response = await fetch(`/api/admin/support/tickets/${ticketId}`) + if (response.ok) { + const data = await response.json() + setTicket(data.ticket) + } else { + toast.error('Failed to load ticket') + router.push('/admin/support') + } + } catch (error) { + console.error('Error fetching ticket:', error) + toast.error('Failed to load ticket') + } finally { + setLoading(false) + } + } + + const updateStatus = async (newStatus: string) => { + setUpdating(true) + try { + const response = await fetch(`/api/admin/support/tickets/${ticketId}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status: newStatus }), + }) + + if (response.ok) { + toast.success('Status updated successfully') + fetchTicket() + } else { + toast.error('Failed to update status') + } + } catch (error) { + console.error('Error updating status:', error) + toast.error('Failed to update status') + } finally { + setUpdating(false) + } + } + + const getStatusColor = (status: string) => { + switch (status) { + case 'open': return 'bg-red-500/10 text-red-400 border-red-500/20' + case 'in_progress': return 'bg-yellow-500/10 text-yellow-400 border-yellow-500/20' + case 'resolved': return 'bg-green-500/10 text-green-400 border-green-500/20' + case 'closed': return 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20' + default: return 'bg-zinc-500/10 text-zinc-400 border-zinc-500/20' + } + } + + if (loading) { + return ( +
+ +
+
+ +
+
+ + +
+
+
+ ) + } + + if (!ticket) { + return ( +
+ + +

Ticket not found

+ +
+
+
+ ) + } + + return ( +
+ {/* Header */} +
+ +
+

Ticket Details

+

ID: {ticket.id}

+
+ + {ticket.status.replace('_', ' ')} + +
+ +
+ {/* Main Content */} +
+ {/* Ticket Info */} + + +
+ {ticket.type === 'bug' ? ( + + ) : ( + + )} + {ticket.subject} +
+ + {ticket.type === 'bug' ? 'Bug Report' : 'Support Request'} + +
+ +
+

{ticket.message}

+
+
+
+ + {/* Internal Notes */} + + + + + Internal Notes + + + Add notes visible only to admins + + + +