import { oauthProviderClient } from "@better-auth/oauth-provider/client"; import { adminClient } from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; import type { Session, User } from "better-auth/types"; import { useEffect, useState } from "react"; import { hasAdminRole } from "@/shared/role"; export const authClient = createAuthClient({ baseURL: typeof window !== "undefined" ? window.location.origin : undefined, plugins: [adminClient(), oauthProviderClient()], }); export const { useSession } = authClient; export interface SessionUser extends User { role?: string | null; preferredUsername?: string | null; } export interface SessionState { data: { session: Session; user: SessionUser } | null; error: { message?: string } | null; isPending: boolean; refetch: () => void; } export const isAdminSession = (session: SessionState): boolean => Boolean(session.data && hasAdminRole(session.data.user.role)); export const useSessionWithInitialPending = (): SessionState & { isInitialPending: boolean } => { const session = useSession(); const resolved = Boolean(session.data) || !session.isPending; const [hasResolved, setHasResolved] = useState(resolved); useEffect(() => { if (!resolved || hasResolved) return; // This is a one-way lifecycle latch, not derived UI state. It prevents // later session refetches from re-entering the initial loading chrome. // eslint-disable-next-line react-hooks/set-state-in-effect setHasResolved(true); }, [resolved, hasResolved]); return { ...session, isInitialPending: !hasResolved && !resolved && session.isPending && !session.data, }; };