import { Outlet, useMatches, useNavigate } from "react-router-dom" import { ThemeSwitcher } from "../components/ThemeSwitcher" import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { User, Settings, LogOut, SquareArrowOutUpRight, TableOfContents, CircleQuestionMark, Gift, Zap, Download, Copy, Check, } from "lucide-react" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Button } from "@/components/ui/button" import { Toaster } from "sonner" import { useAuth } from "@/contexts/AuthContext" import { ClientProvider } from "@/contexts/provider/ClientProvider" import { useProfile } from "@/contexts/ProfileProvider" import { useClientTiers } from "@/contexts/ClientTiersProvider" import { useGroup } from "@/contexts/ClientGroupContext" import { useEffect, useState } from "react" import { ROLE_CONFIG, AVATAR_COLORS } from "@/data/profile.data" import { Badge } from "@/components/ui/badge" import ClientNotificationBell from "@/components/generic/ClientNotificationBell" import { QRCodeCanvas } from "qrcode.react" // ─── Refer / Invite dialog ──────────────────────────────────────────────────── function ReferDialog({ open, onOpenChange }) { const { groups, fetchGroups, loading } = useGroup() const [copiedLink, setCopiedLink] = useState(false) useEffect(() => { if (open) fetchGroups() }, [open]) const group = groups[0] ?? null const inviteUrl = group?.group_code ? `${window.location.origin}/signup?group_code=${group.group_code}` : null const handleCopyLink = async () => { if (!inviteUrl) return await navigator.clipboard.writeText(inviteUrl) setCopiedLink(true) setTimeout(() => setCopiedLink(false), 2000) } const handleDownload = () => { const canvas = document.querySelector("[data-qr='refer-invite']") if (!canvas) return const url = canvas.toDataURL("image/png") const link = document.createElement("a") link.href = url link.download = `QR_${group.group_code}.png` link.click() } return ( Invite link {loading ? (

Loading…

) : !inviteUrl ? (

No group found.

) : (
{/* QR code */}
{/* Download QR */}
or share the link
{/* Invite link */}

{inviteUrl}

)}
) } // ─── Helpers ────────────────────────────────────────────────────────────────── function getInitials(name = "") { return name.trim().split(/\s+/).map((n) => n[0]?.toUpperCase() ?? "").slice(0, 2).join("") } // ─── Inner nav — uses contexts available inside ClientProvider ──────────────── function ClientNav() { const navigate = useNavigate() const { user, logout } = useAuth() // Background fetches only — nav rendering never waits on these const { achievements, getAchievements } = useProfile() const { myTier, getMyTier } = useClientTiers() const [referOpen, setReferOpen] = useState(false) useEffect(() => { if (!user) return; if (achievements.length === 0) getAchievements(); if (!myTier) getMyTier(); }, [user]); // ── Derive directly from auth user — same pattern as admin UserMenu ────── // No extra fetch, no loading state, no flicker on reload. const given = user?.personal_info?.name?.given_name ?? "" const last = user?.personal_info?.name?.last_name ?? "" const fullName = given && last ? `${given} ${last}` : (user?.email ?? "") const avatarUrl = user?.personal_info?.avatar?.url ?? user?.personal_info?.avatar ?? "" const email = user?.email ?? "" const role = ROLE_CONFIG[user?.acc_type] ?? { label: user?.acc_type, variant: 'outline' } const avatarColor = AVATAR_COLORS[user?.acc_type] ?? 'bg-muted text-muted-foreground' const initials = given && last ? (given[0] + last[0]).toUpperCase() : getInitials(fullName) const handleLogout = async () => { await logout() navigate("/login") } // console.log("USER OBJECT:", JSON.stringify(user, null, 2)) return ( <> ) } // ─── Layout ─────────────────────────────────────────────────────────────────── const ClientLayout = () => { const matches = useMatches() const currentHandle = matches.at(-1)?.handle ?? {} const showFooter = currentHandle.showFooter ?? true return ( {showFooter && ( )} ) } export default ClientLayout