const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:3001'; let authToken: string | null = localStorage.getItem('ai-tycoon-auth-token'); export function setAuthToken(token: string) { authToken = token; localStorage.setItem('ai-tycoon-auth-token', token); } export function getAuthToken() { return authToken; } export function clearAuthToken() { authToken = null; localStorage.removeItem('ai-tycoon-auth-token'); } export interface TokenPayload { sub: string; email: string | null; role: string; username: string | null; mustResetPassword: boolean; } export function decodeTokenPayload(token: string): TokenPayload | null { try { const parts = token.split('.'); if (parts.length !== 3) return null; const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'))); return { sub: payload.sub, email: payload.email ?? null, role: payload.role ?? 'user', username: payload.username ?? null, mustResetPassword: payload.mustResetPassword ?? false, }; } catch { return null; } } export function getTokenPayload(): TokenPayload | null { const token = getAuthToken(); if (!token) return null; return decodeTokenPayload(token); } export function isRegistered(): boolean { const payload = getTokenPayload(); if (!payload) return false; return payload.email != null || payload.role === 'admin'; } export function isAdmin(): boolean { const payload = getTokenPayload(); return payload?.role === 'admin'; } export function needsPasswordReset(): boolean { const payload = getTokenPayload(); return payload?.mustResetPassword === true; } async function request(path: string, options: RequestInit = {}): Promise { const headers: Record = { 'Content-Type': 'application/json', ...(options.headers as Record), }; if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } const res = await fetch(`${API_BASE}${path}`, { ...options, headers, }); if (!res.ok) { const body = await res.json().catch(() => ({ error: 'Unknown error' })); throw new Error(body.error || `HTTP ${res.status}`); } return res.json(); } export const api = { auth: { anonymous: () => request<{ userId: string; token: string }>('/api/auth/anonymous', { method: 'POST' }), login: (login: string, password: string) => request<{ userId: string; token: string }>('/api/auth/login', { method: 'POST', body: JSON.stringify({ login, password }), }), register: (email: string, password: string, inviteCode: string) => request<{ userId: string; token: string }>('/api/auth/register', { method: 'POST', body: JSON.stringify({ email, password, inviteCode }), }), changePassword: (newPassword: string, currentPassword?: string) => request<{ success: boolean; token: string }>('/api/auth/change-password', { method: 'POST', body: JSON.stringify({ newPassword, currentPassword }), }), changeUsername: (username: string) => request<{ success: boolean; token: string }>('/api/auth/change-username', { method: 'POST', body: JSON.stringify({ username }), }), }, config: { get: () => request<{ requireInvite: boolean; userInvitations: number }>('/api/config'), }, invites: { create: () => request<{ code: string }>('/api/invites', { method: 'POST' }), validate: (code: string) => request<{ valid: boolean }>(`/api/invites/validate/${encodeURIComponent(code)}`), list: () => request<{ invitations: Array<{ id: string; code: string; createdBy: { username: string | null; email: string | null }; usedBy: { username: string | null; email: string | null } | null; createdAt: string; expiresAt: string | null; used: boolean; }>; }>('/api/invites'), remaining: () => request<{ remaining: number }>('/api/invites/remaining'), }, saves: { list: () => request<{ saves: Array<{ id: string; companyName: string; era: string; tickCount: number; updatedAt: string }> }>('/api/saves'), get: (id: string) => request<{ save: { id: string; gameData: unknown } }>(`/api/saves/${id}`), put: (data: { companyName: string; saveVersion: number; gameData: unknown; tickCount: number; era: string }) => request<{ id: string }>('/api/saves', { method: 'PUT', body: JSON.stringify(data) }), delete: (id: string) => request<{ deleted: boolean }>(`/api/saves/${id}`, { method: 'DELETE' }), }, leaderboard: { get: (category: string) => request<{ entries: Array<{ companyName: string; score: number; era: string; tickCount: number }> }>(`/api/leaderboard/${category}`), submit: (data: { companyName: string; category: string; score: number; era: string; tickCount: number }) => request<{ entry: unknown }>('/api/leaderboard', { method: 'POST', body: JSON.stringify(data) }), }, };