"use client" import { createContext, useContext, useEffect, useMemo, useState } from "react" import { customSessionClient } from "better-auth/client/plugins" import { createAuthClient } from "better-auth/react" import type { AppAuth } from "@/lib/auth" import { useMutation } from "convex/react" import { api } from "@/convex/_generated/api" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { isAdmin, isStaff } from "@/lib/authz" export type AppSession = { user: { id: string name?: string | null email: string role: string tenantId: string | null avatarUrl: string | null } session: { id: string expiresAt: number } } const authClient = createAuthClient({ plugins: [customSessionClient()], fetchOptions: { credentials: "include", }, }) type AuthContextValue = { session: AppSession | null isLoading: boolean convexUserId: string | null role: string | null isAdmin: boolean isStaff: boolean isCustomer: boolean } const AuthContext = createContext({ session: null, isLoading: true, convexUserId: null, role: null, isAdmin: false, isStaff: false, isCustomer: false, }) export function useAuth() { return useContext(AuthContext) } export const { signIn, signOut, useSession } = authClient export function AuthProvider({ children }: { children: React.ReactNode }) { const { data: session, isPending } = useSession() const ensureUser = useMutation(api.users.ensureUser) const [convexUserId, setConvexUserId] = useState(null) useEffect(() => { if (!session?.user) { setConvexUserId(null) } }, [session?.user]) useEffect(() => { if (!session?.user || convexUserId) return const controller = new AbortController() ;(async () => { try { const ensured = await ensureUser({ tenantId: session.user.tenantId ?? DEFAULT_TENANT_ID, name: session.user.name ?? session.user.email, email: session.user.email, avatarUrl: session.user.avatarUrl ?? undefined, role: session.user.role.toUpperCase(), }) if (!controller.signal.aborted) { setConvexUserId(ensured?._id ?? null) } } catch (error) { if (!controller.signal.aborted) { console.error("Failed to sync user with Convex", error) } } })() return () => { controller.abort() } // eslint-disable-next-line react-hooks/exhaustive-deps }, [ensureUser, session?.user?.email, session?.user?.tenantId, session?.user?.role, convexUserId]) const normalizedRole = session?.user?.role ? session.user.role.toLowerCase() : null const value = useMemo( () => ({ session: session ?? null, isLoading: isPending, convexUserId, role: normalizedRole, isAdmin: isAdmin(normalizedRole), isStaff: isStaff(normalizedRole), isCustomer: false, }), [session, isPending, convexUserId, normalizedRole] ) return {children} }