diff --git a/src/app/dashboard/contact-messages/page.tsx b/src/app/dashboard/contact-messages/page.tsx new file mode 100644 index 0000000..f3a5277 --- /dev/null +++ b/src/app/dashboard/contact-messages/page.tsx @@ -0,0 +1,193 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { contactService, type ContactMessage, type ContactCounts } from '@/services/contact.service'; + +export default function ContactMessagesPage() { + const [messages, setMessages] = useState([]); + const [counts, setCounts] = useState(null); + const [loading, setLoading] = useState(true); + const [filter, setFilter] = useState<'all' | 'unread' | 'read'>('all'); + const [selectedMessage, setSelectedMessage] = useState(null); + + const fetchData = async () => { + try { + setLoading(true); + const isRead = filter === 'unread' ? false : filter === 'read' ? true : undefined; + const [msgs, cnts] = await Promise.all([ + contactService.getAllMessages(isRead), + contactService.getCounts(), + ]); + setMessages(msgs); + setCounts(cnts); + } catch (err) { + console.error('Failed to fetch contact messages:', err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { fetchData(); }, [filter]); + + const handleMarkAsRead = async (id: string) => { + try { + await contactService.markAsRead(id); + setMessages(prev => prev.map(m => m.id === id ? { ...m, isRead: true } : m)); + setCounts(prev => prev ? { ...prev, unread: Math.max(0, prev.unread - 1) } : prev); + if (selectedMessage?.id === id) setSelectedMessage(prev => prev ? { ...prev, isRead: true } : prev); + } catch (err) { + console.error('Failed to mark as read:', err); + } + }; + + const handleDelete = async (id: string) => { + if (!confirm('Delete this message?')) return; + try { + await contactService.deleteMessage(id); + setMessages(prev => prev.filter(m => m.id !== id)); + if (selectedMessage?.id === id) setSelectedMessage(null); + const cnts = await contactService.getCounts(); + setCounts(cnts); + } catch (err) { + console.error('Failed to delete:', err); + } + }; + + const formatDate = (dateStr: string) => { + return new Date(dateStr).toLocaleDateString('en-US', { + month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', + }); + }; + + return ( +
+

Contact Messages

+ + {/* Counts */} + {counts && ( +
+
+

Total

+

{counts.total}

+
+
+

Unread

+

{counts.unread}

+
+
+ )} + + {/* Filter */} +
+ {(['all', 'unread', 'read'] as const).map(f => ( + + ))} +
+ + {loading ? ( +
+
+
+ ) : messages.length === 0 ? ( +
+

No contact messages found

+
+ ) : ( +
+ + + + + + + + + + + + {messages.map(msg => ( + + + + + + + + ))} + +
NameEmailMessageDateActions
+
+ {!msg.isRead && } + {msg.name} +
+
{msg.email}{msg.message}{formatDate(msg.createdAt)} +
+ + {!msg.isRead && ( + + )} + +
+
+
+ )} + + {/* View Modal */} + {selectedMessage && ( +
+
+
+

Contact Message

+ +
+
+
+

Name

+

{selectedMessage.name}

+
+
+

Email

+

{selectedMessage.email}

+
+ {selectedMessage.phone && ( +
+

Phone

+

{selectedMessage.phone}

+
+ )} +
+

Message

+

{selectedMessage.message}

+
+
+

Received

+

{formatDate(selectedMessage.createdAt)}

+
+
+
+ {!selectedMessage.isRead && ( + + )} + +
+
+
+ )} +
+ ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 8a89f2d..79fd8ed 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -91,6 +91,20 @@ const menuItems = [ ), }, + { + name: 'Contact Messages', + href: '/dashboard/contact-messages', + icon: ( + + + + ), + }, { name: 'User Reports', href: '/dashboard/reports', diff --git a/src/services/contact.service.ts b/src/services/contact.service.ts new file mode 100644 index 0000000..2a9e0e1 --- /dev/null +++ b/src/services/contact.service.ts @@ -0,0 +1,41 @@ +import api, { ApiResponse } from './api'; + +export interface ContactMessage { + id: string; + name: string; + email: string; + phone: string | null; + message: string; + isRead: boolean; + createdAt: string; +} + +export interface ContactCounts { + total: number; + unread: number; +} + +class ContactService { + async getAllMessages(isRead?: boolean): Promise { + const params: Record = {}; + if (isRead !== undefined) params.isRead = String(isRead); + const response = await api.get>('/contact/admin/all', { params }); + return response.data.data; + } + + async getCounts(): Promise { + const response = await api.get>('/contact/admin/counts'); + return response.data.data; + } + + async markAsRead(id: string): Promise { + const response = await api.patch>(`/contact/admin/${id}/read`); + return response.data.data; + } + + async deleteMessage(id: string): Promise { + await api.delete(`/contact/admin/${id}`); + } +} + +export const contactService = new ContactService();