// ============== auth context + modals ============== const AuthContext = React.createContext({ user: null, loading: true, login: null, register: null, logout: null }); function AuthProvider({ children }) { const [user, setUser] = React.useState(null); const [loading, setLoading] = React.useState(true); const fetchMe = React.useCallback(async () => { if (!window.W3J_API.tokens.access) { setUser(null); setLoading(false); return; } try { const me = await window.W3J_API.apiFetch("/auth/me"); setUser(me); } catch { window.W3J_API.tokens.clear(); setUser(null); } finally { setLoading(false); } }, []); React.useEffect(() => { fetchMe(); const onChange = () => fetchMe(); window.addEventListener("w3j:auth-changed", onChange); return () => window.removeEventListener("w3j:auth-changed", onChange); }, [fetchMe]); const login = React.useCallback(async (email, password) => { const tokens = await window.W3J_API.apiFetch("/auth/login", { method: "POST", body: { email, password }, auth: false }); window.W3J_API.tokens.set(tokens); }, []); const register = React.useCallback(async (email, password, full_name) => { const tokens = await window.W3J_API.apiFetch("/auth/register", { method: "POST", body: { email, password, full_name }, auth: false }); window.W3J_API.tokens.set(tokens); }, []); const logout = React.useCallback(() => { window.W3J_API.tokens.clear(); }, []); const loginWithGoogle = React.useCallback(async (credential) => { const tokens = await window.W3J_API.apiFetch("/auth/google", { method: "POST", body: { credential }, auth: false }); window.W3J_API.tokens.set(tokens); }, []); return ( {children} ); } // --- Google Sign-In button --- let _gisLoaded = null; function loadGoogleScript() { if (_gisLoaded) return _gisLoaded; _gisLoaded = new Promise((resolve, reject) => { if (window.google?.accounts?.id) return resolve(); const s = document.createElement("script"); s.src = "https://accounts.google.com/gsi/client"; s.async = true; s.defer = true; s.onload = () => resolve(); s.onerror = () => reject(new Error("failed to load Google Sign-In")); document.head.appendChild(s); }); return _gisLoaded; } function GoogleButton({ onSuccess }) { const ref = React.useRef(null); const { loginWithGoogle } = useAuth(); const [clientId, setClientId] = React.useState(null); const [err, setErr] = React.useState(""); React.useEffect(() => { window.W3J_API.apiFetch("/auth/google/config", { auth: false }) .then((cfg) => { if (cfg.enabled) setClientId(cfg.client_id); }) .catch(() => {}); }, []); React.useEffect(() => { if (!clientId || !ref.current) return; let cancelled = false; loadGoogleScript().then(() => { if (cancelled) return; window.google.accounts.id.initialize({ client_id: clientId, callback: async (response) => { try { await loginWithGoogle(response.credential); onSuccess && onSuccess(); } catch (ex) { setErr(ex.message || "Google sign-in failed"); } }, }); window.google.accounts.id.renderButton(ref.current, { theme: "filled_black", size: "large", type: "standard", shape: "pill", text: "continue_with", width: 320, }); }).catch((ex) => setErr(ex.message)); return () => { cancelled = true; }; }, [clientId, loginWithGoogle, onSuccess]); if (!clientId) return null; return (
{err &&
{err}
}
or
); } window.GoogleButton = GoogleButton; const useAuth = () => React.useContext(AuthContext); function AuthModal({ mode: initialMode = "login", onClose, onSuccess }) { const [mode, setMode] = React.useState(initialMode); const [email, setEmail] = React.useState(""); const [password, setPassword] = React.useState(""); const [fullName, setFullName] = React.useState(""); const [err, setErr] = React.useState(""); const [busy, setBusy] = React.useState(false); const { login, register } = useAuth(); React.useEffect(() => { const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); const submit = async (e) => { e.preventDefault(); setErr(""); setBusy(true); try { if (mode === "login") await login(email, password); else await register(email, password, fullName); onSuccess && onSuccess(); onClose(); } catch (ex) { setErr(ex.message || "Something went wrong"); } finally { setBusy(false); } }; return (
e.stopPropagation()} role="dialog" aria-modal="true">
{mode === "login" ? "Sign in" : "Create account"}

{mode === "login" ? "Welcome back." : <>Build a candidate file.}

{mode === "register" && ( )} {err &&
{err}
}
{mode === "login" ? ( <>New here? ) : ( <>Have an account? )}
); } window.AuthProvider = AuthProvider; window.useAuth = useAuth; window.AuthModal = AuthModal;