/* Voyage Assistant — Auth modal + hook + state. - useAuth() : { user, token, login, signup, googleSignIn, logout, isAuth } - AuthModal : modale 2 onglets (Connexion / Inscription) + bouton Google Persistance via localStorage. */ const AUTH_STORAGE = { TOKEN: 'va.token', USER: 'va.user' }; const useAuth = () => { const [token, setToken] = React.useState(() => { try { return localStorage.getItem(AUTH_STORAGE.TOKEN); } catch { return null; } }); const [user, setUser] = React.useState(() => { try { return JSON.parse(localStorage.getItem(AUTH_STORAGE.USER) || 'null'); } catch { return null; } }); const _persist = (tok, u) => { try { if (tok) localStorage.setItem(AUTH_STORAGE.TOKEN, tok); else localStorage.removeItem(AUTH_STORAGE.TOKEN); if (u) localStorage.setItem(AUTH_STORAGE.USER, JSON.stringify(u)); else localStorage.removeItem(AUTH_STORAGE.USER); } catch {} setToken(tok); setUser(u); }; const login = async (email, password) => { const r = await window.VA_API.login(email, password); _persist(r.token, r.user); return r.user; }; const signup = async (payload) => { const r = await window.VA_API.signup(payload); _persist(r.token, r.user); return r.user; }; const googleSignIn = async () => { return new Promise((resolve, reject) => { const clientId = window.VA_CONFIG && window.VA_CONFIG.GOOGLE_CLIENT_ID; if (!clientId || !window.google || !window.google.accounts) { // Fallback stub si SDK Google non chargé ou client_id manquant const email = (prompt(t('SDK Google indisponible — entrez votre email :')) || '').trim(); if (!email) return resolve(null); const name = email.split('@')[0].replace(/[._]/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); window.VA_API.googleAuth({ google_sub: 'stub-' + btoa(email).slice(0, 24), email, name, given_name: name.split(' ')[0] || 'User', family_name: name.split(' ').slice(1).join(' ') || 'Google', picture: '', }).then(r => { _persist(r.token, r.user); resolve(r.user); }).catch(reject); return; } // Flow OAuth2 popup (évite FedCM qui peut être bloqué par le navigateur) if (!window.google.accounts.oauth2) { reject(new Error(t('Google OAuth2 SDK indisponible'))); return; } const tokenClient = window.google.accounts.oauth2.initTokenClient({ client_id: clientId, scope: 'openid email profile', callback: async (resp) => { if (resp.error) { reject(new Error(resp.error)); return; } try { const ui = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', { headers: { Authorization: `Bearer ${resp.access_token}` }, }).then(r => r.json()); const r = await window.VA_API.googleAuth({ google_sub: ui.sub, email: ui.email, name: ui.name || `${ui.given_name || ''} ${ui.family_name || ''}`.trim(), given_name: ui.given_name || '', family_name: ui.family_name || '', picture: ui.picture || '', }); _persist(r.token, r.user); resolve(r.user); } catch (err) { reject(err); } }, }); tokenClient.requestAccessToken(); }); }; const logout = () => _persist(null, null); return { user, token, isAuth: !!token, login, signup, googleSignIn, logout }; }; const AuthModal = ({ open, onClose, defaultTab = 'login', onSuccess }) => { const auth = window.VA_AUTH; const [tab, setTab] = React.useState(defaultTab); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); React.useEffect(() => { setTab(defaultTab); setError(null); }, [defaultTab, open]); if (!open) return null; const submit = async (e) => { e.preventDefault(); setBusy(true); setError(null); const form = e.target; const data = Object.fromEntries(new FormData(form).entries()); try { let user; if (tab === 'login') { user = await auth.login(data.email, data.password); } else { user = await auth.signup({ nom: data.nom, prenom: data.prenom, date_naissance: data.date_naissance, email: data.email, password: data.password, }); } onSuccess && onSuccess(user); onClose && onClose(); } catch (err) { setError(err.message); } finally { setBusy(false); } }; const doGoogle = async () => { setBusy(true); setError(null); try { const u = await auth.googleSignIn(); if (u) { onSuccess && onSuccess(u); onClose && onClose(); } } catch (err) { setError(err.message); } finally { setBusy(false); } }; return (
{ if (e.target.classList.contains('va-modal-overlay')) onClose(); }}>

{tab === 'login' ? t('Bon retour') : t('Créer un compte')}

{tab === 'login' ? t('Connectez-vous pour réserver et retrouver vos conversations.') : t('Quelques infos suffisent. Vous pourrez réserver immédiatement.')}

{t('ou')}
{tab === 'signup' && ( <>
)} {error &&
{error}
}
{tab === 'login' ? ( <>{t('Pas de compte ?')} setTab('signup')}>{t('S’inscrire')} ) : ( <>{t('Déjà un compte ?')} setTab('login')}>{t('Se connecter')} )}
); }; const WelcomeToast = ({ user, onDismiss }) => { const [leaving, setLeaving] = React.useState(false); React.useEffect(() => { const t = setTimeout(() => setLeaving(true), 4200); const t2 = setTimeout(() => onDismiss && onDismiss(), 4600); return () => { clearTimeout(t); clearTimeout(t2); }; }, [onDismiss]); if (!user) return null; const initials = ((user.prenom || '')[0] || '') + ((user.nom || '')[0] || ''); const isReturning = !!user.created_at && (Date.now() - new Date(user.created_at).getTime() > 60000); return (
{user.picture ? : initials}
{isReturning ? t('Bon retour, {prenom} !', { prenom: user.prenom }) : t('Bienvenue, {prenom} !', { prenom: user.prenom })} {user.email ? t('Vous êtes connecté · {email}.', { email: user.email }) : t('Vous êtes connecté.')}
); }; Object.assign(window, { useAuth, AuthModal, WelcomeToast, AUTH_STORAGE });