From 8044c57a85e64f3fd0d3560f6c75d49657c96acb Mon Sep 17 00:00:00 2001 From: YaronZaki <234431852+YaronZaki@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:20:09 +0000 Subject: [PATCH] feat(admin): build /admin/settings page for hub configuration --- frontend/app/admin/settings/page.tsx | 371 ++++++++++++++++++ .../components/dashboard/DashboardSidebar.tsx | 1 + .../admin/hub-settings/useGetHubSettings.ts | 11 + .../hub-settings/useUpdateHubSettings.ts | 12 + 4 files changed, 395 insertions(+) create mode 100644 frontend/app/admin/settings/page.tsx create mode 100644 frontend/lib/react-query/hooks/admin/hub-settings/useGetHubSettings.ts create mode 100644 frontend/lib/react-query/hooks/admin/hub-settings/useUpdateHubSettings.ts diff --git a/frontend/app/admin/settings/page.tsx b/frontend/app/admin/settings/page.tsx new file mode 100644 index 0000000..8f7033e --- /dev/null +++ b/frontend/app/admin/settings/page.tsx @@ -0,0 +1,371 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; +import { Save, Loader2 } from "lucide-react"; +import DashboardLayout from "@/components/dashboard/DashboardLayout"; +import { useAuthState } from "@/lib/store/authStore"; +import { useGetHubSettings } from "@/lib/react-query/hooks/admin/hub-settings/useGetHubSettings"; +import { useUpdateHubSettings } from "@/lib/react-query/hooks/admin/hub-settings/useUpdateHubSettings"; + +const DAYS: { key: string; label: string }[] = [ + { key: "monday", label: "Monday" }, + { key: "tuesday", label: "Tuesday" }, + { key: "wednesday", label: "Wednesday" }, + { key: "thursday", label: "Thursday" }, + { key: "friday", label: "Friday" }, + { key: "saturday", label: "Saturday" }, + { key: "sunday", label: "Sunday" }, +]; + +const TIMEZONES = [ + "Africa/Lagos", + "Africa/Nairobi", + "Africa/Cairo", + "Africa/Johannesburg", + "Africa/Accra", + "UTC", + "Europe/London", + "America/New_York", + "America/Los_Angeles", +]; + +type DayHours = { open: string; close: string; isOpen: boolean }; +type BusinessHours = Record; + +const DEFAULT_DAY = (): DayHours => ({ + open: "08:00", + close: "20:00", + isOpen: true, +}); + +function normalizeBusinessHours(input: unknown): BusinessHours { + const out: BusinessHours = {}; + for (const d of DAYS) { + const v = (input as Record | null | undefined)?.[d.key]; + out[d.key] = + v && typeof v === "object" + ? { + open: v.open ?? "08:00", + close: v.close ?? "20:00", + isOpen: v.isOpen !== false, + } + : DEFAULT_DAY(); + } + return out; +} + +export default function AdminSettingsPage() { + const router = useRouter(); + const { user } = useAuthState(); + const isAdmin = user?.role === "admin" || user?.role === "super_admin"; + + useEffect(() => { + if (user && !isAdmin) router.replace("/dashboard"); + }, [user, isAdmin, router]); + + const { data, isLoading } = useGetHubSettings(); + const update = useUpdateHubSettings(); + const settings: any = (data as any)?.data ?? data; + + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [contactEmail, setContactEmail] = useState(""); + const [contactPhone, setContactPhone] = useState(""); + const [logoUrl, setLogoUrl] = useState(""); + const [address, setAddress] = useState(""); + const [taxRate, setTaxRate] = useState("0"); + const [timezone, setTimezone] = useState("Africa/Lagos"); + const [hours, setHours] = useState( + Object.fromEntries(DAYS.map((d) => [d.key, DEFAULT_DAY()])), + ); + + const [hydrated, setHydrated] = useState(false); + useEffect(() => { + if (!settings || hydrated) return; + setName(settings.hubName ?? ""); + setDescription(settings.description ?? ""); + setContactEmail(settings.contactEmail ?? ""); + setContactPhone(settings.contactPhone ?? ""); + setLogoUrl(settings.logoUrl ?? ""); + setAddress(settings.address ?? ""); + setTaxRate(String(settings.taxRate ?? 0)); + setTimezone(settings.timezone ?? "Africa/Lagos"); + setHours(normalizeBusinessHours(settings.businessHours)); + setHydrated(true); + }, [settings, hydrated]); + + if (!user || !isAdmin) return null; + if (isLoading || !hydrated) { + return ( + +
+ +
+
+ ); + } + + const setDay = (key: string, patch: Partial) => { + setHours((prev) => ({ ...prev, [key]: { ...prev[key], ...patch } })); + }; + + const handleSave = async () => { + try { + await update.mutateAsync({ + hubName: name, + description, + contactEmail, + contactPhone, + logoUrl, + address, + taxRate: Number(taxRate) || 0, + timezone, + businessHours: hours, + }); + toast.success("Hub settings saved"); + } catch (e: any) { + toast.error(e?.message ?? "Failed to save hub settings"); + } + }; + + return ( + +
+
+

Hub Settings

+

+ Configure the basic details, opening hours, tax and timezone for + this hub. +

+
+ +
+ + {/* Basic Info */} +
+

Basic Info

+

+ Shown on the hub page, invoices and member receipts. +

+
+
+ + setName(e.target.value)} + className="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-gray-200" + /> +
+
+ +