diff --git a/frontend/app/(auth)/admin/analytics/AnalyticsClient.tsx b/frontend/app/(auth)/admin/analytics/AnalyticsClient.tsx deleted file mode 100644 index f760e16..0000000 --- a/frontend/app/(auth)/admin/analytics/AnalyticsClient.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { ShoppingCart, DollarSign, TrendingUp } from "lucide-react"; -import { adminApi } from "@/lib/api/admin"; -import { AdminRole } from "@/lib/types/user"; -import { RoleProtectedPage } from "@/app/components/admin/RoleProtectedPage"; -import { MetricCard } from "@/app/components/admin/analytics/MetricCard"; -import { CategoryDistribution } from "@/app/components/admin/analytics/CategoryDistribution"; -import { TopPerformers } from "@/app/components/admin/analytics/TopPerformers"; -import ContactTrends from "@/app/components/admin/analytics/ContactTrends"; - -export default function AnalyticsClient() { - const [period, setPeriod] = useState(30); - - const { data: analytics, isLoading: loadingAnalytics, isError } = useQuery({ - queryKey: ["analytics", period], - queryFn: () => adminApi.getAnalytics(period), - }); - - const { data: menuAnalytics, isLoading: loadingMenu } = useQuery({ - queryKey: ["menuAnalytics"], - queryFn: () => adminApi.getMenuAnalytics(), - }); - - const { data: contactAnalytics, isLoading: loadingContact } = useQuery({ - queryKey: ["contactAnalytics"], - queryFn: () => adminApi.getContactAnalytics(), - }); - - const { data: trendingItems, isLoading: loadingTrending } = useQuery({ - queryKey: ["trendingItems"], - queryFn: () => adminApi.getTrendingItems(), - }); - - const isLoading = loadingAnalytics || loadingMenu || loadingContact || loadingTrending; - - return ( - -
-
-

Analytics

-
- {[7, 30, 90].map((d) => ( - - ))} -
-
- - {isLoading ? ( -
- {[1, 2, 3].map((i) => ( -
- ))} -
- ) : isError ? ( -

Failed to load analytics data.

- ) : ( - <> -
- - - -
- -
- - -
- - - - )} -
- - ); -} diff --git a/frontend/app/(auth)/admin/analytics/page.tsx b/frontend/app/(auth)/admin/analytics/page.tsx deleted file mode 100644 index d9c7bd7..0000000 --- a/frontend/app/(auth)/admin/analytics/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Metadata } from "next"; -import AnalyticsClient from "./AnalyticsClient"; - -export const metadata: Metadata = { title: "Analytics" }; - -export default function AnalyticsPage() { - return ; -} diff --git a/frontend/app/(auth)/admin/audit-logs/AuditLogsClient.tsx b/frontend/app/(auth)/admin/audit-logs/AuditLogsClient.tsx deleted file mode 100644 index 4e20a39..0000000 --- a/frontend/app/(auth)/admin/audit-logs/AuditLogsClient.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { useQuery } from "@tanstack/react-query"; -import { isToday } from "date-fns"; -import { ClipboardList, Calendar, Users, Tag } from "lucide-react"; -import { adminApi } from "@/lib/api/admin"; -import { AdminRole } from "@/lib/types/user"; -import { RoleProtectedPage } from "@/app/components/admin/RoleProtectedPage"; -import { AuditLogsTable } from "@/app/components/admin/audit/AuditLogsTable"; - -function StatCard({ - label, - value, - icon: Icon, -}: { - label: string; - value: number; - icon: React.ElementType; -}) { - return ( -
-
-
-

{label}

-

{value}

-
-
- -
-
-
- ); -} - -export default function AuditLogsClient() { - const { data: logs = [], isLoading, isError } = useQuery({ - queryKey: ["auditLogs"], - queryFn: () => adminApi.getAuditLogs(500), - }); - - const today = logs.filter((log: any) => isToday(new Date(log.createdAt))).length; - const activeAdmins = new Set(logs.map((log: any) => log.actorId)).size; - const entityTypes = new Set(logs.map((log: any) => log.entityType)).size; - - return ( - -
-

Audit Logs

- - {isLoading ? ( -
- {[1, 2, 3, 4].map((i) => ( -
- ))} -
- ) : isError ? ( -

Failed to load audit logs.

- ) : ( - <> -
- - - - -
- -
- Audit logs provide a complete, read-only record of all system actions taken by administrators. - These entries cannot be modified or deleted. -
- - - - )} -
- - ); -} diff --git a/frontend/app/(auth)/admin/audit-logs/page.tsx b/frontend/app/(auth)/admin/audit-logs/page.tsx deleted file mode 100644 index 7ae0345..0000000 --- a/frontend/app/(auth)/admin/audit-logs/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Metadata } from "next"; -import AuditLogsClient from "./AuditLogsClient"; - -export const metadata: Metadata = { title: "Audit Logs" }; - -export default function AuditLogsPage() { - return ; -} diff --git a/frontend/app/(auth)/admin/contacts/ContactsClient.tsx b/frontend/app/(auth)/admin/contacts/ContactsClient.tsx deleted file mode 100644 index 4f13a93..0000000 --- a/frontend/app/(auth)/admin/contacts/ContactsClient.tsx +++ /dev/null @@ -1,151 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { Mail, MailOpen, MessageSquare, Users } from "lucide-react"; -import { adminApi } from "@/lib/api/admin"; -import { AdminRole } from "@/lib/types/user"; -import { ContactSubmission, ContactStatus } from "@/lib/types/contact"; -import { RoleProtectedPage } from "@/app/components/admin/RoleProtectedPage"; -import { ContactTable } from "@/app/components/admin/contacts/ContactTable"; -import { ContactDetailsModal } from "@/app/components/admin/contacts/ContactDetailsModal"; -import DeleteConfirmDialog from "@/app/components/admin/contacts/DeleteConfirmDialog"; - -function StatCard({ - label, - value, - icon: Icon, -}: { - label: string; - value: number; - icon: React.ElementType; -}) { - return ( -
-
-
-

{label}

-

{value}

-
-
- -
-
-
- ); -} - -export default function ContactsClient() { - const queryClient = useQueryClient(); - const [selectedContact, setSelectedContact] = useState(null); - const [deleteId, setDeleteId] = useState(null); - const [modalOpen, setModalOpen] = useState(false); - - const { data: contacts = [], isLoading, isError } = useQuery({ - queryKey: ["contacts"], - queryFn: () => adminApi.getAllContacts(), - }); - - const markReadMutation = useMutation({ - mutationFn: (id: string) => adminApi.updateContactStatus(id, ContactStatus.READ), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["contacts"] }), - }); - - const updateStatusMutation = useMutation({ - mutationFn: ({ id, status }: { id: string; status: ContactStatus }) => - adminApi.updateContactStatus(id, status), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["contacts"] }), - }); - - const deleteMutation = useMutation({ - mutationFn: (id: string) => adminApi.deleteContact(id), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["contacts"] }); - setDeleteId(null); - setModalOpen(false); - setSelectedContact(null); - }, - }); - - const handleViewContact = async (contact: ContactSubmission) => { - if (contact.status === ContactStatus.NEW) { - await markReadMutation.mutateAsync(contact.id); - } - setSelectedContact(contact); - setModalOpen(true); - }; - - const handleDelete = async (id: string) => { - setDeleteId(id); - }; - - const total = contacts.length; - const newCount = contacts.filter( - (c: ContactSubmission) => c.status === ContactStatus.NEW - ).length; - const readCount = contacts.filter( - (c: ContactSubmission) => c.status === ContactStatus.READ - ).length; - const repliedCount = contacts.filter( - (c: ContactSubmission) => c.status === ContactStatus.REPLIED - ).length; - - return ( - -
-

Contacts

- - {isLoading ? ( -
- {[1, 2, 3, 4].map((i) => ( -
- ))} -
- ) : isError ? ( -

Failed to load contacts.

- ) : ( - <> -
- - - - -
- - - - )} -
- - { - setModalOpen(false); - setSelectedContact(null); - }} - onUpdateStatus={(status) => - updateStatusMutation.mutate({ id: selectedContact!.id, status }) - } - onDelete={() => setDeleteId(selectedContact?.id ?? null)} - isUpdating={updateStatusMutation.isPending} - /> - - c.id === deleteId)?.subject ?? "Contact" - } - onConfirm={() => deleteId && deleteMutation.mutate(deleteId)} - onCancel={() => setDeleteId(null)} - isDeleting={deleteMutation.isPending} - /> - - ); -} diff --git a/frontend/app/(auth)/admin/contacts/page.tsx b/frontend/app/(auth)/admin/contacts/page.tsx deleted file mode 100644 index 6bc961d..0000000 --- a/frontend/app/(auth)/admin/contacts/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Metadata } from "next"; -import ContactsClient from "./ContactsClient"; - -export const metadata: Metadata = { title: "Contacts" }; - -export default function ContactsPage() { - return ; -} diff --git a/frontend/app/(auth)/admin/dashboard/DashboardClient.tsx b/frontend/app/(auth)/admin/dashboard/DashboardClient.tsx deleted file mode 100644 index 8b82f1c..0000000 --- a/frontend/app/(auth)/admin/dashboard/DashboardClient.tsx +++ /dev/null @@ -1,131 +0,0 @@ -"use client"; - -import { useQuery } from "@tanstack/react-query"; -import { adminApi } from "@/lib/api/admin"; -import { useAuthStore } from "@/lib/store/authStore"; -import { AdminRole } from "@/lib/types/user"; -import RoleProtectedPage from "@/app/components/admin/RoleProtectedPage"; -import PopularItemsChart from "@/app/components/admin/dashboard/PopularItemsChart"; -import RecentActivity from "@/app/components/admin/dashboard/RecentActivity"; - -function StatCard({ label, value }: { label: string; value: number | string }) { - return ( -
-

{label}

-

{value}

-
- ); -} - -function QuickActions() { - const links = [ - { label: "Add Menu Item", href: "/admin/menu/new" }, - { label: "View Contacts", href: "/admin/contacts" }, - { label: "Manage Users", href: "/admin/users" }, - ]; - return ( -
-

Quick Actions

- -
- ); -} - -export default function DashboardClient() { - const user = useAuthStore((s) => s.user); - const isStaff = user?.role === AdminRole.STAFF; - - const { - data: stats, - isLoading: loadingStats, - error: statsError, - } = useQuery({ - queryKey: ["admin-dashboard"], - queryFn: adminApi.getDashboard, - staleTime: 60_000, - }); - - const { - data: analytics, - isLoading: loadingAnalytics, - error: analyticsError, - } = useQuery({ - queryKey: ["menu-analytics"], - queryFn: adminApi.getMenuAnalytics, - staleTime: 60_000, - }); - - const { - data: auditLogs, - isLoading: loadingLogs, - error: logsError, - } = useQuery({ - queryKey: ["audit-logs", 10], - queryFn: () => adminApi.getAuditLogs(10), - staleTime: 30_000, - }); - - const isLoading = loadingStats || loadingAnalytics || loadingLogs; - const error = statsError || analyticsError || logsError; - - return ( - -
-

Dashboard

- - {error && ( -
- Failed to load dashboard data. -
- )} - - {isLoading ? ( -
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
- ) : ( -
- - - - {!isStaff && ( - - )} -
- )} - -
- {loadingAnalytics ? ( -
- ) : ( - - )} - -
- - {loadingLogs ? ( -
- ) : ( - - )} -
- - ); -} diff --git a/frontend/app/(auth)/admin/dashboard/page.tsx b/frontend/app/(auth)/admin/dashboard/page.tsx deleted file mode 100644 index 134d7f5..0000000 --- a/frontend/app/(auth)/admin/dashboard/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Metadata } from "next"; -import DashboardClient from "./DashboardClient"; - -export const metadata: Metadata = { title: "Dashboard" }; - -export default function DashboardPage() { - return ; -} diff --git a/frontend/app/(auth)/admin/error.tsx b/frontend/app/(auth)/admin/error.tsx deleted file mode 100644 index 9b72049..0000000 --- a/frontend/app/(auth)/admin/error.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { motion } from "framer-motion"; -import { AlertTriangle } from "lucide-react"; - -interface AdminErrorBoundaryProps { - error: Error & { digest?: string }; - reset: () => void; -} - -export default function AdminErrorBoundary({ error, reset }: AdminErrorBoundaryProps) { - const router = useRouter(); - const showDevDetails = process.env.NODE_ENV === "development"; - - useEffect(() => { - console.error(error); - }, [error]); - - return ( -
- - - - -
-

Something went wrong

-

- We hit a snag while loading this admin page. You can try to continue below or head back to a safer place. -

-
- -
- - - -
- - {showDevDetails && ( -
- - Development details - -
-

- Message: {error.message} -

-

- Digest: {error.digest ?? "N/A"} -

-
-
- )} -
- ); -} diff --git a/frontend/app/(auth)/admin/gallery/GalleryClient.tsx b/frontend/app/(auth)/admin/gallery/GalleryClient.tsx deleted file mode 100644 index 21b077b..0000000 --- a/frontend/app/(auth)/admin/gallery/GalleryClient.tsx +++ /dev/null @@ -1,120 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import Link from "next/link"; -import { ImageIcon, Eye, EyeOff, Tag, Plus } from "lucide-react"; -import { adminApi, GalleryImage } from "@/lib/api/admin"; -import { AdminRole } from "@/lib/types/user"; -import { RoleProtectedPage } from "@/app/components/admin/RoleProtectedPage"; -import { GalleryTable } from "@/app/components/admin/gallery/GalleryTable"; -import DeleteConfirmDialog from "@/app/components/admin/gallery/DeleteConfirmDialog"; - -function StatCard({ - label, - value, - icon: Icon, -}: { - label: string; - value: number; - icon: React.ElementType; -}) { - return ( -
-
-
-

{label}

-

{value}

-
-
- -
-
-
- ); -} - -export default function GalleryClient() { - const queryClient = useQueryClient(); - const [deleteTarget, setDeleteTarget] = useState(null); - - const { data: images = [], isLoading, isError } = useQuery({ - queryKey: ["galleryImages"], - queryFn: () => adminApi.getAllGalleryImages(), - }); - - const toggleMutation = useMutation({ - mutationFn: (id: string) => adminApi.toggleGalleryImageVisibility(id), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["galleryImages"] }), - }); - - const deleteMutation = useMutation({ - mutationFn: (id: string) => adminApi.deleteGalleryImage(id), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["galleryImages"] }); - setDeleteTarget(null); - }, - }); - - const total = images.length; - const visible = images.filter((img: GalleryImage) => img.isVisible).length; - const hidden = images.filter((img: GalleryImage) => !img.isVisible).length; - const categories = new Set(images.map((img: GalleryImage) => img.category)).size; - - return ( - -
-
-

Gallery

- - - Add Image - -
- - {isLoading ? ( -
- {[1, 2, 3, 4].map((i) => ( -
- ))} -
- ) : isError ? ( -

Failed to load gallery images.

- ) : ( - <> -
- - - - -
- - toggleMutation.mutate(id)} - onDelete={async (id) => setDeleteTarget(images.find((img) => img.id === id) ?? null)} - /> - - )} -
- - deleteTarget && deleteMutation.mutate(deleteTarget.id)} - onCancel={() => setDeleteTarget(null)} - isDeleting={deleteMutation.isPending} - /> - - ); -} diff --git a/frontend/app/(auth)/admin/gallery/[id]/page.tsx b/frontend/app/(auth)/admin/gallery/[id]/page.tsx deleted file mode 100644 index 53261d7..0000000 --- a/frontend/app/(auth)/admin/gallery/[id]/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import GalleryFormWrapper from "@/components/admin/gallery/GalleryFormWrapper"; -import { RoleProtectedPage } from "@/components/admin/RoleProtectedPage"; -import { AdminRole } from "@/lib/types/user"; - -export default function EditGalleryImagePage() { - const params = useParams(); - const id = params.id as string; - - return ( - - - - ); -} diff --git a/frontend/app/(auth)/admin/gallery/new/page.tsx b/frontend/app/(auth)/admin/gallery/new/page.tsx deleted file mode 100644 index e173465..0000000 --- a/frontend/app/(auth)/admin/gallery/new/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Metadata } from "next"; -import GalleryFormWrapper from "@/components/admin/gallery/GalleryFormWrapper"; -import { RoleProtectedPage } from "@/components/admin/RoleProtectedPage"; -import { AdminRole } from "@/lib/types/user"; - -export const metadata: Metadata = { - title: "Add Gallery Image", -}; - -export default function NewGalleryImagePage() { - return ( - - - - ); -} diff --git a/frontend/app/(auth)/admin/gallery/page.tsx b/frontend/app/(auth)/admin/gallery/page.tsx deleted file mode 100644 index d313edd..0000000 --- a/frontend/app/(auth)/admin/gallery/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Metadata } from "next"; -import GalleryClient from "./GalleryClient"; - -export const metadata: Metadata = { title: "Gallery" }; - -export default function GalleryPage() { - return ; -} diff --git a/frontend/app/(auth)/admin/instagram/InstagramClient.tsx b/frontend/app/(auth)/admin/instagram/InstagramClient.tsx deleted file mode 100644 index b3513fd..0000000 --- a/frontend/app/(auth)/admin/instagram/InstagramClient.tsx +++ /dev/null @@ -1,246 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { useRouter } from "next/navigation"; -import { Plus, Instagram as InstagramIcon, Eye, EyeOff, Loader2 } from "lucide-react"; -import { motion } from "framer-motion"; -import toast from "react-hot-toast"; -import { adminApi } from "@/lib/api/admin"; -import { InstagramTable } from "@/components/admin/instagram/InstagramTable"; -import { Button } from "@/components/ui/Button"; -import type { InstagramPost } from "@/lib/api/admin"; - -export default function InstagramClient() { - const router = useRouter(); - const queryClient = useQueryClient(); - - // Fetch Instagram posts - const { - data: posts = [], - isLoading, - error, - } = useQuery({ - queryKey: ["instagram-posts"], - queryFn: () => adminApi.getAllInstagramPosts(), - }); - - // Toggle visibility mutation - const toggleVisibilityMutation = useMutation({ - mutationFn: adminApi.toggleInstagramPostVisibility, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["instagram-posts"] }); - toast.success("Post visibility updated successfully"); - }, - onError: (error: any) => { - toast.error(error.message || "Failed to update post visibility"); - }, - }); - - // Delete mutation - const deleteMutation = useMutation({ - mutationFn: adminApi.deleteInstagramPost, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["instagram-posts"] }); - toast.success("Instagram post deleted successfully"); - }, - onError: (error: any) => { - toast.error(error.message || "Failed to delete post"); - }, - }); - - // Calculate stats - const stats = { - total: posts.length, - visible: posts.filter((post: InstagramPost) => post.isVisible).length, - hidden: posts.filter((post: InstagramPost) => !post.isVisible).length, - }; - - const handleToggleVisibility = (id: string) => { - toggleVisibilityMutation.mutate(id); - }; - - const handleDelete = async (id: string) => { - await deleteMutation.mutateAsync(id); - }; - - // Loading skeleton - if (isLoading) { - return ( -
-
-
-

Instagram Posts

-

Manage your Instagram feed

-
-
-
- - {/* Stats skeletons */} -
- {[1, 2, 3].map((i) => ( -
-
-
-
- ))} -
- - {/* Table skeleton */} -
-
- {[1, 2, 3, 4, 5].map((i) => ( -
-
-
-
-
-
-
- ))} -
-
-
- ); - } - - // Error state - if (error) { - return ( -
-
-
- -
-

- Failed to load Instagram posts -

-

- Please check your connection and try again. -

- -
-
- ); - } - - return ( -
- {/* Header */} -
-
-

Instagram Posts

-

Manage your Instagram feed

-
- -
- - {/* Stats Cards */} -
- -
-
-
- -
-
-
-

Total Posts

-

{stats.total}

-
-
-
- - -
-
-
- -
-
-
-

Visible

-

{stats.visible}

-
-
-
- - -
-
-
- -
-
-
-

Hidden

-

{stats.hidden}

-
-
-
-
- - {/* Table */} - - - - - {/* Empty state */} - {posts.length === 0 && ( - -
- -
-

- No Instagram posts yet -

-

- Get started by adding your first Instagram post to the feed. -

- -
- )} -
- ); -} diff --git a/frontend/app/(auth)/admin/instagram/[id]/page.tsx b/frontend/app/(auth)/admin/instagram/[id]/page.tsx deleted file mode 100644 index 2c18eb3..0000000 --- a/frontend/app/(auth)/admin/instagram/[id]/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; - -import { useParams } from "next/navigation"; -import InstagramFormWrapper from "../../../../components/admin/instagram/InstagramFormWrapper"; -import { RoleProtectedPage } from "../../../../components/admin/RoleProtectedPage"; -import { AdminRole } from "@/lib/types/user"; - -export default function EditInstagramPostPage() { - const params = useParams(); - const id = params.id as string; - - return ( - - - - ); -} diff --git a/frontend/app/(auth)/admin/instagram/new/page.tsx b/frontend/app/(auth)/admin/instagram/new/page.tsx deleted file mode 100644 index 339a2e0..0000000 --- a/frontend/app/(auth)/admin/instagram/new/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Metadata } from "next"; -import InstagramFormWrapper from "../../../../components/admin/instagram/InstagramFormWrapper"; -import { RoleProtectedPage } from "../../../../components/admin/RoleProtectedPage"; -import { AdminRole } from "@/lib/types/user"; - -export const metadata: Metadata = { - title: "Add Instagram Post", -}; - -export default function NewInstagramPostPage() { - return ( - - - - ); -} diff --git a/frontend/app/(auth)/admin/instagram/page.tsx b/frontend/app/(auth)/admin/instagram/page.tsx deleted file mode 100644 index da65278..0000000 --- a/frontend/app/(auth)/admin/instagram/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Metadata } from "next"; -import InstagramClient from "./InstagramClient"; -import { RoleProtectedPage } from "@/components/admin/RoleProtectedPage"; -import { AdminRole } from "@/lib/types/user"; - -export const metadata: Metadata = { - title: "Instagram", -}; - -export default function InstagramManagementPage() { - return ( - - - - ); -} diff --git a/frontend/app/(auth)/admin/layout.tsx b/frontend/app/(auth)/admin/layout.tsx deleted file mode 100644 index 951e06a..0000000 --- a/frontend/app/(auth)/admin/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { ReactNode } from "react"; - -import { AdminLayout } from "@/components/admin/AdminLayout"; -import { ToastProvider } from "@/providers/ToastProvider"; - -export const metadata = { - robots: { index: false, follow: false }, - title: { template: "%s | Admin | Savoria", default: "Admin | Savoria" }, -}; - -interface AdminRootLayoutProps { - children: ReactNode; -} - -export default function AdminRootLayout({ children }: AdminRootLayoutProps) { - return ( - - {children} - - ); -} diff --git a/frontend/app/(auth)/admin/menu/MenuClient.tsx b/frontend/app/(auth)/admin/menu/MenuClient.tsx deleted file mode 100644 index b2805b6..0000000 --- a/frontend/app/(auth)/admin/menu/MenuClient.tsx +++ /dev/null @@ -1,83 +0,0 @@ -"use client"; - -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import Link from "next/link"; -import { adminApi } from "@/lib/api/admin"; -import MenuTable from "@/app/components/admin/menu/MenuTable"; - -function StatCard({ label, value }: { label: string; value: number }) { - return ( -
-

{label}

-

{value}

-
- ); -} - -export default function MenuClient() { - const queryClient = useQueryClient(); - - const { data: items = [], isLoading, error } = useQuery({ - queryKey: ["admin-menu-items"], - queryFn: adminApi.getAllMenuItems, - staleTime: 60_000, - }); - - const toggleMutation = useMutation({ - mutationFn: (id: string) => adminApi.toggleMenuItemAvailability(id), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["admin-menu-items"] }), - }); - - const deleteMutation = useMutation({ - mutationFn: (id: string) => adminApi.deleteMenuItem(id), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["admin-menu-items"] }), - }); - - const total = items.length; - const available = items.filter((i: any) => i.isAvailable).length; - const unavailable = total - available; - - return ( -
-
-

Menu Management

- - Add Item - -
- - {error && ( -
- Failed to load menu items. -
- )} - - {isLoading ? ( -
- {Array.from({ length: 3 }).map((_, i) => ( -
- ))} -
- ) : ( -
- - - -
- )} - - {isLoading ? ( -
- ) : ( - toggleMutation.mutate(id)} - onDelete={(id) => deleteMutation.mutateAsync(id)} - /> - )} -
- ); -} diff --git a/frontend/app/(auth)/admin/menu/[id]/page.tsx b/frontend/app/(auth)/admin/menu/[id]/page.tsx deleted file mode 100644 index 8e33724..0000000 --- a/frontend/app/(auth)/admin/menu/[id]/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useParams, useRouter } from "next/navigation"; -import { useQuery } from "@tanstack/react-query"; -import { adminApi } from "@/lib/api/admin"; -import { MenuForm } from "@/app/components/admin/menu/MenuForm"; - -export default function EditMenuItemPage() { - const { id } = useParams<{ id: string }>(); - const router = useRouter(); - const [isSubmitting, setIsSubmitting] = useState(false); - - const { data: item, isLoading, isError } = useQuery({ - queryKey: ["menu-item", id], - queryFn: () => adminApi.getMenuItem(id), - }); - - const handleSubmit = async (data: any) => { - setIsSubmitting(true); - try { - await adminApi.updateMenuItem(id, data); - router.push("/admin/menu"); - } finally { - setIsSubmitting(false); - } - }; - - if (isLoading) return

Loading...

; - if (isError || !item) return

Failed to load menu item.

; - - return ( -
-

Edit Menu Item

- -
- ); -} diff --git a/frontend/app/(auth)/admin/menu/new/page.tsx b/frontend/app/(auth)/admin/menu/new/page.tsx deleted file mode 100644 index 25fc550..0000000 --- a/frontend/app/(auth)/admin/menu/new/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useRouter } from "next/navigation"; -import { adminApi } from "@/lib/api/admin"; -import { MenuForm } from "@/app/components/admin/menu/MenuForm"; - -export default function NewMenuItemPage() { - const router = useRouter(); - const [isSubmitting, setIsSubmitting] = useState(false); - - const handleSubmit = async (data: any) => { - setIsSubmitting(true); - try { - await adminApi.createMenuItem(data); - router.push("/admin/menu"); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
-

Add Menu Item

- -
- ); -} diff --git a/frontend/app/(auth)/admin/menu/page.tsx b/frontend/app/(auth)/admin/menu/page.tsx deleted file mode 100644 index 8e2393e..0000000 --- a/frontend/app/(auth)/admin/menu/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Metadata } from "next"; -import MenuClient from "./MenuClient"; - -export const metadata: Metadata = { title: "Menu" }; - -export default function MenuPage() { - return ; -} diff --git a/frontend/app/(auth)/admin/not-found.tsx b/frontend/app/(auth)/admin/not-found.tsx deleted file mode 100644 index d494e4d..0000000 --- a/frontend/app/(auth)/admin/not-found.tsx +++ /dev/null @@ -1,44 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { motion } from "framer-motion"; - -export default function AdminNotFound() { - const router = useRouter(); - - return ( - -

- 404 🔒 -

-

- This page doesn't exist in the admin panel. -

-
- - - -
-
- ); -} diff --git a/frontend/app/(auth)/admin/page.tsx b/frontend/app/(auth)/admin/page.tsx deleted file mode 100644 index 1f81840..0000000 --- a/frontend/app/(auth)/admin/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; - -export default function AdminIndexPage() { - const router = useRouter(); - - useEffect(() => { - router.push("/admin/dashboard"); - }, [router]); - - return null; -} diff --git a/frontend/app/(auth)/admin/profile/ProfileClient.tsx b/frontend/app/(auth)/admin/profile/ProfileClient.tsx deleted file mode 100644 index 2c8ce7b..0000000 --- a/frontend/app/(auth)/admin/profile/ProfileClient.tsx +++ /dev/null @@ -1,88 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { format } from "date-fns"; -import { useAuthStore } from "@/lib/store/authStore"; -import { ProfileForm } from "@/app/components/admin/profile/ProfileForm"; -import { ChangePasswordForm } from "@/app/components/admin/profile/ChangePasswordForm"; -import { RoleBadge } from "@/app/components/admin/users/RoleBadge"; - -type Tab = "profile" | "security"; - -export default function ProfileClient() { - const user = useAuthStore((s) => s.user); - const [tab, setTab] = useState("profile"); - const [isSubmitting, setIsSubmitting] = useState(false); - - if (!user) return null; - - const initials = user.username.slice(0, 2).toUpperCase(); - - const handleProfileSubmit = async (data: { username: string; email: string }) => { - setIsSubmitting(true); - try { - // TODO: call update profile API - console.log("Profile update", data); - } finally { - setIsSubmitting(false); - } - }; - - const handlePasswordSubmit = async (data: { currentPassword: string; newPassword: string; confirmPassword: string }) => { - setIsSubmitting(true); - try { - // TODO: call change password API - console.log("Password change", data); - } finally { - setIsSubmitting(false); - } - }; - - return ( -
- {/* Left: Avatar card */} -
-
- {initials} -
-
-

{user.username}

-

{user.email}

-
- - {user.lastLoginAt && ( -

- Last login: {format(new Date(user.lastLoginAt), "PPP")} -

- )} -
- - {/* Right: Tabs */} -
-
- {(["profile", "security"] as Tab[]).map((t) => ( - - ))} -
- - {tab === "profile" ? ( - - ) : ( - - )} -
-
- ); -} diff --git a/frontend/app/(auth)/admin/profile/page.tsx b/frontend/app/(auth)/admin/profile/page.tsx deleted file mode 100644 index 9bd0c6b..0000000 --- a/frontend/app/(auth)/admin/profile/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { Metadata } from "next"; -import ProfileClient from "./ProfileClient"; - -export const metadata: Metadata = { title: "Profile" }; - -export default function ProfilePage() { - return ; -} diff --git a/frontend/app/(auth)/admin/users/UsersClient.tsx b/frontend/app/(auth)/admin/users/UsersClient.tsx deleted file mode 100644 index a3f7d34..0000000 --- a/frontend/app/(auth)/admin/users/UsersClient.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; - -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import Link from "next/link"; -import { adminApi } from "@/lib/api/admin"; -import { useAuthStore } from "@/lib/store/authStore"; -import { AdminRole, AdminUser } from "@/lib/types/user"; -import RoleProtectedPage from "@/app/components/admin/RoleProtectedPage"; -import UserTable from "@/app/components/admin/users/UserTable"; - -function StatCard({ label, value }: { label: string; value: number }) { - return ( -
-

{label}

-

{value}

-
- ); -} - -export default function UsersClient() { - const queryClient = useQueryClient(); - const user = useAuthStore((s) => s.user); - const isSuperAdmin = user?.role === AdminRole.SUPER_ADMIN; - - const { data: allUsers = [], isLoading, error } = useQuery({ - queryKey: ["admin-users"], - queryFn: adminApi.getAdmins, - staleTime: 60_000, - }); - - const users = isSuperAdmin - ? allUsers - : allUsers.filter((u) => u.role !== AdminRole.SUPER_ADMIN); - - const toggleMutation = useMutation({ - mutationFn: ({ id, isActive }: { id: string; isActive: boolean }) => - adminApi.toggleAdminStatus(id, isActive), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["admin-users"] }), - }); - - const roleMutation = useMutation({ - mutationFn: ({ id, role }: { id: string; role: AdminRole }) => - adminApi.updateAdminRole(id, role), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["admin-users"] }), - }); - - const total = users.length; - const active = users.filter((u) => u.isActive).length; - const inactive = total - active; - const adminCount = users.filter( - (u) => u.role === AdminRole.ADMIN || u.role === AdminRole.SUPER_ADMIN - ).length; - - return ( - -
-
-

Users Management

- {isSuperAdmin && ( - - Add User - - )} -
- - {error && ( -
- Failed to load users. -
- )} - - {isLoading ? ( -
- {Array.from({ length: 4 }).map((_, i) => ( -
- ))} -
- ) : ( -
- - - - -
- )} - - {isLoading ? ( -
- ) : ( - toggleMutation.mutate({ id, isActive })} - onRoleChange={(id, role) => roleMutation.mutate({ id, role })} - /> - )} -
- - ); -} diff --git a/frontend/app/(auth)/admin/users/new/page.tsx b/frontend/app/(auth)/admin/users/new/page.tsx deleted file mode 100644 index 1b3871d..0000000 --- a/frontend/app/(auth)/admin/users/new/page.tsx +++ /dev/null @@ -1,139 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useRouter } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { useMutation } from "@tanstack/react-query"; -import { Eye, EyeOff } from "lucide-react"; -import { adminApi } from "@/lib/api/admin"; -import { AdminRole } from "@/lib/types/user"; - -const schema = z.object({ - username: z.string().min(1, "Username is required"), - email: z.string().email("Valid email required"), - password: z.string().min(8, "Password must be at least 8 characters"), - role: z.enum([AdminRole.STAFF, AdminRole.MANAGER, AdminRole.ADMIN], { - required_error: "Role is required", - }), -}); - -type FormData = z.infer; - -const rolePermissions = [ - { role: "Staff", desc: "View orders, manage table status, limited menu access." }, - { role: "Manager", desc: "All Staff permissions + manage menu items and view reports." }, - { role: "Admin", desc: "All Manager permissions + manage users and system settings." }, -]; - -export default function CreateUserPage() { - const router = useRouter(); - const [showPassword, setShowPassword] = useState(false); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); - - const mutation = useMutation({ - mutationFn: (data: FormData) => adminApi.createUser(data), - onSuccess: () => { - alert("User created successfully!"); - router.push("/admin/users"); - }, - onError: (err: any) => { - alert(err?.message ?? "Failed to create user."); - }, - }); - - return ( -
-

Create Admin User

- -
mutation.mutate(d))} className="space-y-4"> -
- - - {errors.username &&

{errors.username.message}

} -
- -
- - - {errors.email &&

{errors.email.message}

} -
- -
- -
- - -
- {errors.password &&

{errors.password.message}

} -
- -
- - - {errors.role &&

{errors.role.message}

} -
- -
- - -
-
- -
-

Role Permissions

-
    - {rolePermissions.map(({ role, desc }) => ( -
  • - {role}: - {desc} -
  • - ))} -
-
-
- ); -} diff --git a/frontend/app/(auth)/admin/users/page.tsx b/frontend/app/(auth)/admin/users/page.tsx deleted file mode 100644 index 8734132..0000000 --- a/frontend/app/(auth)/admin/users/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Metadata } from "next"; -import UsersClient from "./UsersClient"; - -export const metadata: Metadata = { title: "Users" }; - -export default function UsersPage() { - return ; -} diff --git a/frontend/app/(auth)/layout.tsx b/frontend/app/(auth)/layout.tsx deleted file mode 100644 index cf4c5bb..0000000 --- a/frontend/app/(auth)/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from "react"; - -export default function AuthLayout({ children }: { children: React.ReactNode }) { - return <>{children}; -} diff --git a/frontend/app/(auth)/login/LoginClient.tsx b/frontend/app/(auth)/login/LoginClient.tsx deleted file mode 100644 index 367a7dd..0000000 --- a/frontend/app/(auth)/login/LoginClient.tsx +++ /dev/null @@ -1,162 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { Eye, EyeOff, Loader2 } from "lucide-react"; -import { adminApi } from "@/lib/api/api"; -import { useAuthStore } from "@/lib/store/authStore"; -import Link from "next/link"; - -const loginSchema = z.object({ - username: z.string().min(1, "Username is required"), - password: z.string().min(1, "Password is required"), -}); - -type LoginFormData = z.infer; - -export default function LoginClient() { - const router = useRouter(); - const { isAuthenticated, setAuth } = useAuthStore(); - const [showPassword, setShowPassword] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - resolver: zodResolver(loginSchema), - }); - - useEffect(() => { - if (isAuthenticated) { - router.push("/admin/dashboard"); - } - }, [isAuthenticated, router]); - - const onSubmit = async (data: LoginFormData) => { - setIsLoading(true); - setError(null); - - try { - const response = await adminApi.login(data); - setAuth(response.user, response.token); - router.push("/admin/dashboard"); - } catch (err) { - setError(err instanceof Error ? err.message : "Login failed"); - } finally { - setIsLoading(false); - } - }; - - const allowRegistration = process.env.NEXT_PUBLIC_ALLOW_REGISTRATION === "true"; - - return ( -
-
-
-
-
-
-

- Admin Login -

-

- Sign in to your admin account -

-
- -
-
-
- - - {errors.username && ( -

- {errors.username.message} -

- )} -
-
- - - - {errors.password && ( -

- {errors.password.message} -

- )} -
-
- - {error && ( -
-

{error}

-
- )} - -
- -
- - {allowRegistration && ( -
-

- Don't have an account?{" "} - - Register - -

-
- )} -
-
-
- ); -} diff --git a/frontend/app/(auth)/login/page.tsx b/frontend/app/(auth)/login/page.tsx deleted file mode 100644 index 5b56c65..0000000 --- a/frontend/app/(auth)/login/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Metadata } from "next"; -import LoginClient from "./LoginClient"; - -export const metadata: Metadata = { - title: "Admin Login", -}; - -export default function LoginPage() { - return ; -} diff --git a/frontend/app/(public)/about/page.tsx b/frontend/app/(public)/about/page.tsx deleted file mode 100644 index 30e523f..0000000 --- a/frontend/app/(public)/about/page.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { Metadata } from "next"; -import { AboutSection } from "@/components/features/AboutSection"; -import { OpeningHours } from "@/components/features/OpeningHours"; - -export const metadata: Metadata = { - title: "About Us | Savoria Restaurant", - description: - "Learn the story behind Savoria Restaurant and plan your visit with our opening hours and location details.", - openGraph: { - title: "About Savoria Restaurant", - description: - "Discover our story, our passion for hospitality, and the best time to visit Savoria Restaurant.", - type: "website", - url: "/about", - }, -}; - -export default function AboutPage() { - return ( -
- - -
-
-
-

- Visit Us -

-

- Come By and Make Yourself at Home -

-

- Stop in for a relaxed meal, a special occasion, or your next favorite - dish. Here are our opening hours so you can plan the perfect visit. -

-
- -
- -
-
-
-
- ); -} diff --git a/frontend/app/(public)/admin-regitration/page.tsx b/frontend/app/(public)/admin-regitration/page.tsx deleted file mode 100644 index 1b3871d..0000000 --- a/frontend/app/(public)/admin-regitration/page.tsx +++ /dev/null @@ -1,139 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { useRouter } from "next/navigation"; -import { useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; -import { useMutation } from "@tanstack/react-query"; -import { Eye, EyeOff } from "lucide-react"; -import { adminApi } from "@/lib/api/admin"; -import { AdminRole } from "@/lib/types/user"; - -const schema = z.object({ - username: z.string().min(1, "Username is required"), - email: z.string().email("Valid email required"), - password: z.string().min(8, "Password must be at least 8 characters"), - role: z.enum([AdminRole.STAFF, AdminRole.MANAGER, AdminRole.ADMIN], { - required_error: "Role is required", - }), -}); - -type FormData = z.infer; - -const rolePermissions = [ - { role: "Staff", desc: "View orders, manage table status, limited menu access." }, - { role: "Manager", desc: "All Staff permissions + manage menu items and view reports." }, - { role: "Admin", desc: "All Manager permissions + manage users and system settings." }, -]; - -export default function CreateUserPage() { - const router = useRouter(); - const [showPassword, setShowPassword] = useState(false); - - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ resolver: zodResolver(schema) }); - - const mutation = useMutation({ - mutationFn: (data: FormData) => adminApi.createUser(data), - onSuccess: () => { - alert("User created successfully!"); - router.push("/admin/users"); - }, - onError: (err: any) => { - alert(err?.message ?? "Failed to create user."); - }, - }); - - return ( -
-

Create Admin User

- -
mutation.mutate(d))} className="space-y-4"> -
- - - {errors.username &&

{errors.username.message}

} -
- -
- - - {errors.email &&

{errors.email.message}

} -
- -
- -
- - -
- {errors.password &&

{errors.password.message}

} -
- -
- - - {errors.role &&

{errors.role.message}

} -
- -
- - -
-
- -
-

Role Permissions

-
    - {rolePermissions.map(({ role, desc }) => ( -
  • - {role}: - {desc} -
  • - ))} -
-
-
- ); -} diff --git a/frontend/app/(public)/cart/page.tsx b/frontend/app/(public)/cart/page.tsx deleted file mode 100644 index a05b984..0000000 --- a/frontend/app/(public)/cart/page.tsx +++ /dev/null @@ -1,284 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import DashboardLayout from '@/components/dashboard/DashboardLayout'; -import { useGetActiveCheckIn } from '@/lib/react-query/hooks/workspace-tracking/useGetActiveCheckIn'; -import { useCheckIn } from '@/lib/react-query/hooks/workspace-tracking/useCheckIn'; -import { useCheckOut } from '@/lib/react-query/hooks/workspace-tracking/useCheckOut'; -import { useGetMyBookings } from '@/lib/react-query/hooks/bookings/useGetMyBookings'; -import { - LogIn, - LogOut, - Clock, - MapPin, - Calendar, - CheckCircle2, -} from 'lucide-react'; - -function formatDuration(minutes: number): string { - const h = Math.floor(minutes / 60); - const m = minutes % 60; - if (h === 0) return `${m}m`; - return m === 0 ? `${h}h` : `${h}h ${m}m`; -} - -function formatDateTime(iso: string): string { - return new Date(iso).toLocaleString('en-NG', { - dateStyle: 'medium', - timeStyle: 'short', - }); -} - -export default function CheckInPage() { - const { data: activeData, isLoading: activeLoading } = useGetActiveCheckIn(); - const { data: bookingsData } = useGetMyBookings(1, 20); - const checkIn = useCheckIn(); - const checkOut = useCheckOut(); - - const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(''); - const [selectedBookingId, setSelectedBookingId] = useState(''); - const [notes, setNotes] = useState(''); - const [confirmCheckout, setConfirmCheckout] = useState(false); - - const activeLog = activeData?.data ?? null; - - // Only show bookings that are confirmed - const eligibleBookings = (bookingsData?.data ?? []).filter( - (b) => b.status === 'CONFIRMED', - ); - - const handleCheckIn = async () => { - if (!selectedWorkspaceId) return; - await checkIn.mutateAsync({ - workspaceId: selectedWorkspaceId, - bookingId: selectedBookingId || undefined, - notes: notes || undefined, - }); - setSelectedWorkspaceId(''); - setSelectedBookingId(''); - setNotes(''); - }; - - const handleCheckOut = async () => { - if (!activeLog) return; - await checkOut.mutateAsync(activeLog.id); - setConfirmCheckout(false); - }; - - if (activeLoading) { - return ( - -
-
-
- - ); - } - - return ( - -
-

Check In / Out

-

- Track your workspace sessions. -

-
- -
- {/* Active session */} - {activeLog ? ( -
-
- -

- Active session -

-
- -
- {activeLog.workspace && ( -
- - - {activeLog.workspace.name}{' '} - - ({activeLog.workspace.type}) - - -
- )} -
- - - Checked in at {formatDateTime(activeLog.checkedInAt)} - -
- {activeLog.notes && ( -

- "{activeLog.notes}" -

- )} -
- -
- {!confirmCheckout ? ( - - ) : ( -
- - -
- )} -
-
- ) : ( - /* Check-in form */ -
-
- -

- Check in to a workspace -

-
- -
- {/* Booking selector */} -
- - -
- - {/* Workspace ID (manual if no booking selected) */} - {!selectedBookingId && ( -
- - setSelectedWorkspaceId(e.target.value)} - placeholder="Enter workspace UUID" - className="w-full px-3.5 py-2.5 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-gray-200" - /> -
- )} - - {/* Notes */} -
- -