import { createContext, useContext, useCallback } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import type { ReactNode } from "react"; import { pb } from "@/lib/pocketbase"; import type { AuthRecord } from "pocketbase"; /** * Represents the shape of the authenticated user object from PocketBase. */ /** * Context value for authentication state and actions. */ export interface AuthContextValue { user: AuthRecord | null; isLoading: boolean; login: (email: string, password: string) => Promise; signup: ( email: string, password: string, passwordConfirm: string, ) => Promise; logout: () => Promise; } const AuthContext = createContext(undefined); /** * Fetches the currently authenticated user from PocketBase. */ async function fetchUser(): Promise { if (pb.authStore.isValid) { return pb.authStore.record; } return null; } /** * Provider for authentication context, using TanStack Query for state management. */ export function AuthProvider({ children }: { children: ReactNode }) { const queryClient = useQueryClient(); const { data: user, isLoading } = useQuery({ queryKey: ["auth", "user"], queryFn: fetchUser, }); const loginMutation = useMutation({ mutationFn: async ({ email, password, }: { email: string; password: string; }) => { await pb.collection("users").authWithPassword(email, password); return fetchUser(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["auth", "user"] }); }, }); const signupMutation = useMutation({ mutationFn: async ({ email, password, passwordConfirm, }: { email: string; password: string; passwordConfirm: string; }) => { await pb.collection("users").create({ email, password, passwordConfirm }); await pb.collection("users").authWithPassword(email, password); return fetchUser(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["auth", "user"] }); }, }); const logoutMutation = useMutation({ mutationFn: async () => { pb.authStore.clear(); return null; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["auth", "user"] }); }, }); const login = useCallback( async (email: string, password: string) => { await loginMutation.mutateAsync({ email, password }); }, [loginMutation], ); const signup = useCallback( async (email: string, password: string, passwordConfirm: string) => { await signupMutation.mutateAsync({ email, password, passwordConfirm }); }, [signupMutation], ); const logout = useCallback(async () => { await logoutMutation.mutateAsync(); }, [logoutMutation]); return ( {children} ); } /** * Hook to access authentication context. * Throws if used outside of AuthProvider. */ export function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used within an AuthProvider"); return ctx; }