import { createContext, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'; import { useCollection } from '@feltdb/core/react'; import { createAccount, createSession, db, memberships, organizations, sessions, users, type Membership, type Organization, type Session, type User } from '../feltdb'; const SESSION_KEY = 'feltdb.example.session'; interface AuthValue { loading: boolean; authenticated: boolean; user?: User; session?: Session; organization?: Organization; membership?: Membership; signUp(input: { name: string; email: string; password: string; organization: string }): Promise; signIn(email: string, password: string): Promise; signOut(): Promise } const AuthContext = createContext(undefined); export function AuthProvider({ children }: { children: ReactNode }) { const sessionRecords = useCollection(sessions); const userRecords = useCollection(users); const membershipRecords = useCollection(memberships); const organizationRecords = useCollection(organizations); const [sessionId, setSessionId] = useState(null); useEffect(() => setSessionId(sessionStorage.getItem(SESSION_KEY)), []); const session = sessionRecords.data.find(item => item.id === sessionId && Date.parse(item.expires_at) > Date.now()); const user = userRecords.data.find(item => item.id === session?.user); const membership = membershipRecords.data.find(item => item.user === user?.id); const organization = organizationRecords.data.find(item => item.id === membership?.organization); const loading = sessionRecords.loading || userRecords.loading || membershipRecords.loading || organizationRecords.loading; const establish = (next: Session) => { sessionStorage.setItem(SESSION_KEY, next.id); setSessionId(next.id); }; const value = useMemo(() => ({ loading, authenticated: Boolean(session && user && membership && organization), session, user, membership, organization, signUp: async input => { try { await db.auth.signUp({ email: input.email, password: input.password, display_name: input.name }); } catch (error) { if ((error as { code?: string }).code !== 'CONFLICT') throw error; await db.auth.signIn({ email: input.email, password: input.password }); } establish(await createAccount(input)); }, signIn: async (email, password) => { await db.auth.signIn({ email, password }); establish(await createSession(email)); }, signOut: async () => { if (session) await sessions.delete(session.id); await db.auth.signOut(); sessionStorage.removeItem(SESSION_KEY); setSessionId(null); }, }), [loading, session, user, membership, organization]); return {children}; } export function useAuth() { const value = useContext(AuthContext); if (!value) throw new Error('useAuth must be used inside AuthProvider'); return value; }