// --------------------------------------------------------------------------- // AuthContext — pre-built auth provider wired to AnyDB users collection // --------------------------------------------------------------------------- // PRE-INSTALLED in every AUTO_DB React project with auth: true. // CodeWriterAgent just wraps App in and uses: // import { useAuth } from "@/contexts/AuthContext" // const { user, login, signup, logout, loading } = useAuth() // // Interface already knows: // - users collection has: email (unique), name, avatar_url, plan, role, created_at // - login: findOne by email + password hash // - signup: insertOne with default plan='free' // - session: localStorage token with user object // --------------------------------------------------------------------------- import React, { createContext, useContext, useState, useEffect, useCallback } from "react"; import anydb from "@/lib/anydb"; // Toast helper — uses window alert as fallback if useToast not available const notify = (title: string, desc?: string) => { try { // Try to dispatch a custom event that a Toaster component can pick up window.dispatchEvent(new CustomEvent('app-toast', { detail: { title, description: desc } })); } catch { console.log(`[Auth] ${title}: ${desc || ''}`); } }; interface User { _id: string; email: string; name: string; avatar_url?: string; plan?: string; role?: string; created_at?: string; } interface AuthContextType { user: User | null; loading: boolean; error: string | null; login: (email: string, password: string) => Promise; signup: (name: string, email: string, password: string) => Promise; logout: () => void; updateProfile: (updates: Partial) => Promise; isAuthenticated: boolean; } const AuthContext = createContext(undefined); const AUTH_KEY = "anydb_auth_user"; export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const toast = (opts: { title: string; description?: string; variant?: string }) => notify(opts.title, opts.description); // Restore session from localStorage on mount useEffect(() => { try { const stored = localStorage.getItem(AUTH_KEY); if (stored) { setUser(JSON.parse(stored)); } } catch { localStorage.removeItem(AUTH_KEY); } setLoading(false); }, []); const login = useCallback(async (email: string, password: string): Promise => { setLoading(true); setError(null); try { const col = await anydb.collection("users"); const result = await col.find({ email: email.toLowerCase() }, { limit: 1 }); const found = result.data?.[0]; if (!found) { setError("No account found with that email"); toast({ title: "Login failed", description: "No account found with that email", variant: "destructive" }); return false; } // Simple password check (in production, use bcrypt on server) if (found.password_hash !== password && found.password !== password) { setError("Incorrect password"); toast({ title: "Login failed", description: "Incorrect password", variant: "destructive" }); return false; } const { password_hash, password: _pw, ...safeUser } = found; setUser(safeUser); localStorage.setItem(AUTH_KEY, JSON.stringify(safeUser)); toast({ title: "Welcome back!", description: `Signed in as ${safeUser.name || safeUser.email}` }); return true; } catch (err: any) { setError(err.message || "Login failed"); toast({ title: "Error", description: "Something went wrong. Please try again.", variant: "destructive" }); return false; } finally { setLoading(false); } }, [toast]); const signup = useCallback(async (name: string, email: string, password: string): Promise => { setLoading(true); setError(null); try { const col = await anydb.collection("users"); // Check if email already exists const existing = await col.find({ email: email.toLowerCase() }, { limit: 1 }); if (existing.data?.length > 0) { setError("An account with that email already exists"); toast({ title: "Signup failed", description: "An account with that email already exists", variant: "destructive" }); return false; } // Create user const newUser = await col.insertOne({ name, email: email.toLowerCase(), password_hash: password, // In production: hash with bcrypt on server plan: "free", role: "user", avatar_url: "", created_at: new Date().toISOString(), }); const safeUser: User = { _id: newUser._id || newUser.insertedId, name, email: email.toLowerCase(), plan: "free", role: "user", created_at: new Date().toISOString(), }; setUser(safeUser); localStorage.setItem(AUTH_KEY, JSON.stringify(safeUser)); toast({ title: "Account created!", description: `Welcome, ${name}!` }); return true; } catch (err: any) { setError(err.message || "Signup failed"); toast({ title: "Error", description: "Could not create account. Please try again.", variant: "destructive" }); return false; } finally { setLoading(false); } }, [toast]); const logout = useCallback(() => { setUser(null); localStorage.removeItem(AUTH_KEY); toast({ title: "Signed out", description: "You have been signed out." }); }, [toast]); const updateProfile = useCallback(async (updates: Partial): Promise => { if (!user?._id) return false; try { const col = await anydb.collection("users"); await col.updateOne({ _id: user._id }, updates); const updatedUser = { ...user, ...updates }; setUser(updatedUser); localStorage.setItem(AUTH_KEY, JSON.stringify(updatedUser)); toast({ title: "Profile updated" }); return true; } catch (err: any) { toast({ title: "Error", description: "Could not update profile", variant: "destructive" }); return false; } }, [user, toast]); return ( {children} ); } /** * Use auth state and actions. * * Usage: * const { user, login, signup, logout, isAuthenticated, loading } = useAuth() * if (!isAuthenticated) return * await login(email, password) * await signup(name, email, password) * logout() */ export function useAuth(): AuthContextType { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within an AuthProvider"); } return context; } /** * Protected route wrapper. Redirects to /login if not authenticated. * * Usage in App.tsx: * } /> */ export function ProtectedRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated, loading } = useAuth(); if (loading) { return (
); } if (!isAuthenticated) { // Redirect to login window.location.href = "/login"; return null; } return <>{children}; }