Taste_Rider_Web / src /components /SupportTicketComponent.jsx
Harry9233's picture
Upload 20790 files
5c05829 verified
import React, { useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card";
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { useToast } from "@/components/ui/use-toast";
import { supabase } from '@/lib/supabaseClient';
import { MessageSquare, Plus, Send, Clock, CheckCircle, AlertCircle, X, ChevronDown, ChevronUp, RefreshCw } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
const SupportTicketComponent = ({ session }) => {
const { toast } = useToast();
const [tickets, setTickets] = useState([]);
const [loading, setLoading] = useState(true);
const [creating, setCreating] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [expandedTicket, setExpandedTicket] = useState(null);
const [newMessage, setNewMessage] = useState('');
const [newTicket, setNewTicket] = useState({
subject: '',
message: '',
order_id: ''
});
// Mock ticket data - in a real app, this would come from Supabase
const mockTickets = [
{
id: 'TKT-001',
subject: 'Order Delivery Delay',
status: 'Open',
created_at: '2023-11-15T10:30:00',
updated_at: '2023-11-15T14:45:00',
messages: [
{
id: 1,
sender: 'user',
message: 'My order #ORD-001 was supposed to be delivered yesterday but I haven\'t received it yet. Can you please check the status?',
timestamp: '2023-11-15T10:30:00'
},
{
id: 2,
sender: 'support',
message: 'Thank you for reaching out. I apologize for the delay. I\'ve checked your order and there seems to be a slight delay with the courier. Your package is expected to be delivered by tomorrow. We\'ll keep you updated.',
timestamp: '2023-11-15T14:45:00'
}
]
},
{
id: 'TKT-002',
subject: 'Product Quality Issue',
status: 'Closed',
created_at: '2023-10-28T09:15:00',
updated_at: '2023-10-30T11:20:00',
messages: [
{
id: 1,
sender: 'user',
message: 'I received my Garam Masala but the packaging was damaged and some of the product had spilled.',
timestamp: '2023-10-28T09:15:00'
},
{
id: 2,
sender: 'support',
message: 'I\'m sorry to hear about the damaged packaging. We take product quality very seriously. Could you please send a photo of the damaged package?',
timestamp: '2023-10-28T15:30:00'
},
{
id: 3,
sender: 'user',
message: 'I\'ve sent the photos to your email address.',
timestamp: '2023-10-29T10:45:00'
},
{
id: 4,
sender: 'support',
message: 'Thank you for the photos. We\'ve processed a replacement order for you which will be shipped today. You should receive it within 3-5 business days. We\'ve also added a complimentary sample of our new Chat Masala as a token of apology for the inconvenience.',
timestamp: '2023-10-29T14:20:00'
},
{
id: 5,
sender: 'user',
message: 'Thank you for the quick resolution! Looking forward to trying the Chat Masala.',
timestamp: '2023-10-30T09:10:00'
},
{
id: 6,
sender: 'support',
message: 'You\'re welcome! We\'re glad we could resolve this to your satisfaction. Please let us know if you need anything else. Enjoy the Chat Masala!',
timestamp: '2023-10-30T11:20:00'
}
]
}
];
useEffect(() => {
// In a real app, fetch tickets from Supabase
// For now, we'll use mock data
const fetchTickets = async () => {
try {
setLoading(true);
// Simulate API call delay
setTimeout(() => {
setTickets(mockTickets);
setLoading(false);
}, 1000);
// In a real implementation, you would fetch from Supabase:
// const { data, error } = await supabase
// .from('support_tickets')
// .select('*')
// .eq('user_id', session.user.id)
// .order('created_at', { ascending: false });
//
// if (error) throw error;
// setTickets(data || []);
} catch (error) {
console.error('Error fetching tickets:', error);
toast({
title: 'Error',
description: 'Failed to load support tickets',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
if (session) {
fetchTickets();
}
}, [session, toast]);
const handleCreateTicket = async (e) => {
e.preventDefault();
try {
setSubmitting(true);
// Validate form
if (!newTicket.subject || !newTicket.message) {
toast({
title: 'Missing Information',
description: 'Please fill in all required fields',
variant: 'destructive',
});
setSubmitting(false);
return;
}
// In a real app, create ticket in Supabase
// For now, simulate API call and add to local state
setTimeout(() => {
const newTicketObj = {
id: `TKT-${Math.floor(Math.random() * 1000).toString().padStart(3, '0')}`,
subject: newTicket.subject,
status: 'Open',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
messages: [
{
id: 1,
sender: 'user',
message: newTicket.message,
timestamp: new Date().toISOString()
}
]
};
setTickets([newTicketObj, ...tickets]);
setNewTicket({
subject: '',
message: '',
order_id: ''
});
setCreating(false);
setSubmitting(false);
toast({
title: 'Ticket Created',
description: 'Your support ticket has been successfully created',
className: 'toast-success-theme',
});
}, 1500);
// In a real implementation:
// const { data, error } = await supabase
// .from('support_tickets')
// .insert([{
// user_id: session.user.id,
// subject: newTicket.subject,
// status: 'Open',
// order_id: newTicket.order_id || null
// }])
// .select();
//
// if (error) throw error;
//
// // Add initial message
// const { error: messageError } = await supabase
// .from('ticket_messages')
// .insert([{
// ticket_id: data[0].id,
// user_id: session.user.id,
// message: newTicket.message,
// is_from_support: false
// }]);
//
// if (messageError) throw messageError;
} catch (error) {
console.error('Error creating ticket:', error);
toast({
title: 'Error',
description: 'Failed to create support ticket',
variant: 'destructive',
});
} finally {
setSubmitting(false);
}
};
const handleSendMessage = async (ticketId) => {
if (!newMessage.trim()) return;
try {
setSubmitting(true);
// In a real app, add message to Supabase
// For now, update local state
setTimeout(() => {
const updatedTickets = tickets.map(ticket => {
if (ticket.id === ticketId) {
const updatedMessages = [
...ticket.messages,
{
id: ticket.messages.length + 1,
sender: 'user',
message: newMessage,
timestamp: new Date().toISOString()
}
];
return {
...ticket,
messages: updatedMessages,
updated_at: new Date().toISOString()
};
}
return ticket;
});
setTickets(updatedTickets);
setNewMessage('');
setSubmitting(false);
// Simulate support response after 3 seconds
setTimeout(() => {
const updatedTicketsWithResponse = tickets.map(ticket => {
if (ticket.id === ticketId) {
const updatedMessages = [
...ticket.messages,
{
id: ticket.messages.length + 1,
sender: 'user',
message: newMessage,
timestamp: new Date().toISOString()
},
{
id: ticket.messages.length + 2,
sender: 'support',
message: 'Thank you for your message. Our support team will get back to you shortly.',
timestamp: new Date(Date.now() + 5000).toISOString()
}
];
return {
...ticket,
messages: updatedMessages,
updated_at: new Date(Date.now() + 5000).toISOString()
};
}
return ticket;
});
setTickets(updatedTicketsWithResponse);
}, 3000);
}, 1000);
// In a real implementation:
// const { error } = await supabase
// .from('ticket_messages')
// .insert([{
// ticket_id: ticketId,
// user_id: session.user.id,
// message: newMessage,
// is_from_support: false
// }]);
//
// if (error) throw error;
//
// // Update ticket updated_at timestamp
// await supabase
// .from('support_tickets')
// .update({ updated_at: new Date() })
// .eq('id', ticketId);
} catch (error) {
console.error('Error sending message:', error);
toast({
title: 'Error',
description: 'Failed to send message',
variant: 'destructive',
});
} finally {
setSubmitting(false);
}
};
const formatDate = (dateString) => {
const options = { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' };
return new Date(dateString).toLocaleDateString('en-US', options);
};
const getStatusColor = (status) => {
switch (status.toLowerCase()) {
case 'open':
return 'bg-green-100 text-green-800';
case 'closed':
return 'bg-stone-100 text-stone-800';
case 'pending':
return 'bg-amber-100 text-amber-800';
default:
return 'bg-blue-100 text-blue-800';
}
};
const toggleTicket = (ticketId) => {
setExpandedTicket(expandedTicket === ticketId ? null : ticketId);
};
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<h2 className="text-2xl font-bold text-amber-800 flex items-center">
<MessageSquare className="mr-2 h-6 w-6" /> Support Tickets
</h2>
{!creating && (
<Button
onClick={() => setCreating(true)}
className="bg-amber-600 hover:bg-amber-700 text-white"
>
<Plus className="mr-2 h-4 w-4" /> New Ticket
</Button>
)}
</div>
{creating && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<Card className="border-amber-200 bg-amber-50/50">
<CardHeader>
<CardTitle className="text-xl text-amber-800">Create New Support Ticket</CardTitle>
<CardDescription>Please provide details about your issue or question</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleCreateTicket} className="space-y-4">
<div>
<Label htmlFor="subject" className="text-stone-700">Subject *</Label>
<Input
id="subject"
value={newTicket.subject}
onChange={(e) => setNewTicket({...newTicket, subject: e.target.value})}
className="mt-1 bg-white border-amber-200 focus:border-amber-600"
placeholder="Brief description of your issue"
required
/>
</div>
<div>
<Label htmlFor="order_id" className="text-stone-700">Order ID (if applicable)</Label>
<Input
id="order_id"
value={newTicket.order_id}
onChange={(e) => setNewTicket({...newTicket, order_id: e.target.value})}
className="mt-1 bg-white border-amber-200 focus:border-amber-600"
placeholder="e.g. ORD-001"
/>
</div>
<div>
<Label htmlFor="message" className="text-stone-700">Message *</Label>
<Textarea
id="message"
value={newTicket.message}
onChange={(e) => setNewTicket({...newTicket, message: e.target.value})}
className="mt-1 bg-white border-amber-200 focus:border-amber-600"
placeholder="Please describe your issue in detail"
rows={5}
required
/>
</div>
<div className="flex justify-end space-x-2 pt-2">
<Button
type="button"
variant="outline"
onClick={() => {
setCreating(false);
setNewTicket({
subject: '',
message: '',
order_id: ''
});
}}
disabled={submitting}
className="border-amber-200"
>
Cancel
</Button>
<Button
type="submit"
disabled={submitting}
className="bg-amber-600 hover:bg-amber-700 text-white"
>
{submitting ? (
<>
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
className="w-5 h-5 border-2 border-white border-t-transparent rounded-full mr-2"
></motion.div>
Submitting...
</>
) : (
<>
<Send className="mr-2 h-4 w-4" /> Submit Ticket
</>
)}
</Button>
</div>
</form>
</CardContent>
</Card>
</motion.div>
)}
{loading ? (
<div className="text-center py-8">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
className="w-8 h-8 border-4 border-amber-600 border-t-transparent rounded-full mx-auto mb-4"
></motion.div>
<p className="text-stone-600">Loading your support tickets...</p>
</div>
) : tickets.length === 0 ? (
<div className="text-center py-12 bg-amber-50 rounded-lg border border-amber-200">
<MessageSquare className="h-12 w-12 text-amber-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-amber-800 mb-2">No Support Tickets</h3>
<p className="text-stone-600 mb-6">You haven't created any support tickets yet</p>
{!creating && (
<Button
onClick={() => setCreating(true)}
className="bg-amber-600 hover:bg-amber-700 text-white"
>
<Plus className="mr-2 h-4 w-4" /> Create Your First Ticket
</Button>
)}
</div>
) : (
<div className="space-y-4">
{tickets.map((ticket) => (
<Card key={ticket.id} className={`border-l-4 ${ticket.status.toLowerCase() === 'open' ? 'border-l-green-500' : 'border-l-stone-400'}`}>
<CardHeader className="pb-3">
<div className="flex justify-between items-start">
<div>
<CardTitle className="text-lg text-amber-800 flex items-center">
{ticket.subject}
<Badge className={`ml-3 ${getStatusColor(ticket.status)}`}>
{ticket.status}
</Badge>
</CardTitle>
<CardDescription className="mt-1 flex items-center">
<span className="text-xs text-stone-500 flex items-center">
<Clock className="h-3 w-3 mr-1" /> Created: {formatDate(ticket.created_at)}
</span>
<span className="mx-2 text-stone-300">|</span>
<span className="text-xs text-stone-500 flex items-center">
<RefreshCw className="h-3 w-3 mr-1" /> Updated: {formatDate(ticket.updated_at)}
</span>
</CardDescription>
</div>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 text-stone-500"
onClick={() => toggleTicket(ticket.id)}
>
{expandedTicket === ticket.id ? (
<ChevronUp className="h-5 w-5" />
) : (
<ChevronDown className="h-5 w-5" />
)}
</Button>
</div>
</CardHeader>
{expandedTicket === ticket.id && (
<>
<Separator className="mx-6 bg-amber-100" />
<CardContent className="pt-4">
<div className="space-y-4 mb-4">
{ticket.messages.map((msg) => (
<div
key={msg.id}
className={`flex ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-[80%] rounded-lg p-3 ${msg.sender === 'user'
? 'bg-amber-100 text-stone-800'
: 'bg-white border border-amber-200 text-stone-800'}`}
>
<div className="text-sm">{msg.message}</div>
<div className="text-xs text-stone-500 mt-1 flex justify-end items-center">
{formatDate(msg.timestamp)}
{msg.sender === 'support' && (
<CheckCircle className="h-3 w-3 ml-1 text-green-600" />
)}
</div>
</div>
</div>
))}
</div>
{ticket.status.toLowerCase() === 'open' && (
<div className="mt-4">
<div className="flex space-x-2">
<Input
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Type your reply here..."
className="flex-grow bg-white border-amber-200 focus:border-amber-600"
disabled={submitting}
/>
<Button
onClick={() => handleSendMessage(ticket.id)}
disabled={!newMessage.trim() || submitting}
className="bg-amber-600 hover:bg-amber-700 text-white"
>
{submitting ? (
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
className="w-5 h-5 border-2 border-white border-t-transparent rounded-full"
></motion.div>
) : (
<Send className="h-4 w-4" />
)}
</Button>
</div>
</div>
)}
</CardContent>
</>
)}
</Card>
))}
</div>
)}
</div>
);
};
export default SupportTicketComponent;