| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import React, { createContext, useCallback, useContext, useEffect, useState } from 'react'; |
| import AuthScreen from './AuthScreen'; |
| import OnboardingWizard from './OnboardingWizard'; |
| import { resolveBackendUrl } from '../lib/backendUrl'; |
| const LS_TOKEN_KEY = 'homepilot_auth_token'; |
| const LS_USER_KEY = 'homepilot_auth_user'; |
| const LS_RECENT_USERS_KEY = 'homepilot_recent_users'; |
| function getBackendUrl() { |
| return resolveBackendUrl(); |
| } |
| function loadRecentUsers() { |
| try { |
| const raw = localStorage.getItem(LS_RECENT_USERS_KEY); |
| if (!raw) |
| return []; |
| const parsed = JSON.parse(raw); |
| |
| return parsed.sort((a, b) => b.lastLogin - a.lastLogin).slice(0, 5); |
| } |
| catch { |
| return []; |
| } |
| } |
| function saveRecentUser(user) { |
| const existing = loadRecentUsers().filter(u => u.username !== user.username); |
| const entry = { |
| username: user.username, |
| display_name: user.display_name, |
| avatar_url: user.avatar_url, |
| lastLogin: Date.now(), |
| }; |
| const updated = [entry, ...existing].slice(0, 5); |
| localStorage.setItem(LS_RECENT_USERS_KEY, JSON.stringify(updated)); |
| } |
| const AuthContext = createContext({ |
| user: null, |
| token: '', |
| logout: async () => { }, |
| updateUser: () => { }, |
| }); |
| export function useAuth() { |
| return useContext(AuthContext); |
| } |
| export default function AuthGate({ children }) { |
| const [state, setState] = useState('loading'); |
| const [user, setUser] = useState(null); |
| const [token, setToken] = useState(''); |
| const [loggedOutMessage, setLoggedOutMessage] = useState(''); |
| const [recentUsers, setRecentUsers] = useState([]); |
| const backendUrl = getBackendUrl(); |
| useEffect(() => { |
| checkAuth(); |
| }, []); |
| async function checkAuth() { |
| const savedToken = localStorage.getItem(LS_TOKEN_KEY) || ''; |
| try { |
| const res = await fetch(`${backendUrl}/v1/auth/me`, { |
| headers: savedToken ? { 'Authorization': `Bearer ${savedToken}` } : {}, |
| credentials: 'include', |
| }); |
| if (!res.ok) { |
| |
| |
| setState('ready'); |
| return; |
| } |
| const data = await res.json(); |
| if (data.needs_setup) { |
| |
| setState('login'); |
| return; |
| } |
| if (data.needs_login) { |
| |
| setRecentUsers(loadRecentUsers()); |
| setState('login'); |
| return; |
| } |
| if (data.user) { |
| const u = data.user; |
| const t = data.token || savedToken; |
| setUser(u); |
| setToken(t); |
| if (t) |
| localStorage.setItem(LS_TOKEN_KEY, t); |
| localStorage.setItem(LS_USER_KEY, JSON.stringify(u)); |
| saveRecentUser(u); |
| if (!u.onboarding_complete) { |
| setState('onboarding'); |
| } |
| else { |
| setState('ready'); |
| } |
| return; |
| } |
| |
| setState('ready'); |
| } |
| catch { |
| |
| setState('ready'); |
| } |
| } |
| function handleAuthenticated(u, t) { |
| setUser(u); |
| setToken(t); |
| setLoggedOutMessage(''); |
| localStorage.setItem(LS_TOKEN_KEY, t); |
| localStorage.setItem(LS_USER_KEY, JSON.stringify(u)); |
| saveRecentUser(u); |
| if (!u.onboarding_complete) { |
| setState('onboarding'); |
| } |
| else { |
| setState('ready'); |
| } |
| } |
| function handleOnboardingComplete(displayName) { |
| if (user) { |
| const updated = { ...user, display_name: displayName, onboarding_complete: true }; |
| setUser(updated); |
| localStorage.setItem(LS_USER_KEY, JSON.stringify(updated)); |
| } |
| setState('ready'); |
| } |
| |
| const updateUser = useCallback((partial) => { |
| setUser(prev => { |
| if (!prev) |
| return prev; |
| const updated = { ...prev, ...partial }; |
| localStorage.setItem(LS_USER_KEY, JSON.stringify(updated)); |
| |
| saveRecentUser(updated); |
| return updated; |
| }); |
| }, []); |
| |
| const logout = useCallback(async () => { |
| const savedToken = localStorage.getItem(LS_TOKEN_KEY) || ''; |
| const currentDisplayName = user?.display_name || user?.username || ''; |
| |
| if (savedToken) { |
| try { |
| await fetch(`${backendUrl}/v1/auth/logout`, { |
| method: 'POST', |
| headers: { 'Authorization': `Bearer ${savedToken}` }, |
| credentials: 'include', |
| }); |
| } |
| catch { } |
| } |
| |
| localStorage.removeItem(LS_TOKEN_KEY); |
| localStorage.removeItem(LS_USER_KEY); |
| |
| setUser(null); |
| setToken(''); |
| setLoggedOutMessage(currentDisplayName |
| ? `Signed out as ${currentDisplayName}` |
| : 'Signed out successfully'); |
| setRecentUsers(loadRecentUsers()); |
| setState('login'); |
| }, [user, backendUrl]); |
| |
| if (state === 'loading') { |
| return (<div style={{ |
| minHeight: '100vh', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| background: '#050506', |
| color: 'rgba(255, 255, 255, 0.30)', |
| fontFamily: 'Inter, system-ui, sans-serif', |
| fontSize: 14, |
| }}> |
| Loading... |
| </div>); |
| } |
| |
| if (state === 'login') { |
| return (<AuthScreen backendUrl={backendUrl} onAuthenticated={handleAuthenticated} logoutMessage={loggedOutMessage} recentUsers={recentUsers}/>); |
| } |
| |
| if (state === 'onboarding' && user) { |
| return (<OnboardingWizard backendUrl={backendUrl} token={token} username={user.username} onComplete={handleOnboardingComplete}/>); |
| } |
| |
| return (<AuthContext.Provider value={{ user, token, logout, updateUser }}> |
| {children} |
| </AuthContext.Provider>); |
| } |
|
|