// /composables/useAuth.ts import { computed, watch } from 'vue'; import { useVisitTracker } from './useVisitTracker'; interface User { id: number; email: string | null; // Can be null for anonymous users nickname: string | null; avatar: string | null; coins: number; exp: number; dailyStreak: number; soundOn: boolean; confettiOn: boolean; createdAt: string; updatedAt: string; isAnonymous?: boolean; // Flag to distinguish anonymous users anonymousSessionId?: string; } export function useAuth() { const user = useState('user', () => null); const initialized = useState('auth_initialized', () => false); const api = useApi(); const { visitCalled } = useVisitTracker(); // A user is fully authenticated only if they exist and are NOT anonymous. const isAuthenticated = computed(() => !!user.value && !user.value.isAnonymous); // A user is anonymous if they exist and have the isAnonymous flag. const isAnonymous = computed(() => !!user.value && !!user.value.isAnonymous); // --- This watcher is the new core logic for post-authentication tasks --- watch(isAuthenticated, (newIsAuthenticated, oldIsAuthenticated) => { // We only care about the transition from logged-out to logged-in if (newIsAuthenticated && !oldIsAuthenticated) { if (!visitCalled.value) { visitCalled.value = true; const gameDay = new Date().toISOString().slice(0, 10); // --- 1. Trigger Daily Visit & Streak Calculation --- api('/api/user/visit', { method: 'POST', body: { gameDay } }).then(updatedUser => { if (updatedUser) { updateUser(updatedUser); } }).catch(e => { console.error('Failed to register daily visit:', e); visitCalled.value = false; // Allow retrying on next navigation if it failed }); // --- 2. Trigger Village Tick --- api('/api/village/tick', { method: 'POST' }) .catch(e => { console.error('Failed to trigger village tick:', e); }); } } }); /** * Initializes the authentication state for EXISTING users. * It should be called once in app.vue. * It will only try to fetch a logged-in user via /api/auth/me. * If it fails, the user state remains null. */ const initAuth = async () => { if (initialized.value) return; try { const response = await api<{ user: User }>('/auth/me'); if (response.user) { user.value = { ...response.user, isAnonymous: false }; } else { user.value = null; } } catch (error) { // It's expected this will fail for non-logged-in users. user.value = null; } finally { initialized.value = true; } }; /** * Starts the onboarding process by creating a new anonymous user. */ const startOnboarding = async () => { try { const anonymousUserData = await api('/onboarding/initiate', { method: 'POST' }); // Explicitly set isAnonymous to true for robustness user.value = { ...anonymousUserData, isAnonymous: true }; } catch (anonError) { console.error('Could not initiate anonymous session:', anonError); // Optionally, show an error message to the user user.value = null; } }; const register = async (email, password, nickname) => { await api('/auth/register', { method: 'POST', body: { email, password, nickname }, }); // After a successful registration, force a re-fetch of the new user state. initialized.value = false; await initAuth(); }; const login = async (email, password) => { await api('/auth/login', { method: 'POST', body: { email, password }, }); // After a successful login, force a re-fetch of the new user state. initialized.value = false; await initAuth(); }; const logout = async () => { try { await api('/auth/logout', { method: 'POST' }); } finally { user.value = null; visitCalled.value = false; // Reset for the next session await navigateTo('/'); } }; const updateUser = (partialUser: Partial) => { if (user.value) { user.value = { ...user.value, ...partialUser }; } }; return { user, isAuthenticated, isAnonymous, // Expose this new state initialized, initAuth, // Called from app.vue startOnboarding, // Called from index.vue register, // Expose register function login, logout, updateUser, }; }