Files
ace-step-ui/context/AuthContext.tsx
T

118 lines
3.8 KiB
TypeScript

import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
import { authApi, User } from '../services/api';
interface AuthContextType {
user: User | null;
token: string | null;
isLoading: boolean;
isAuthenticated: boolean;
setupUser: (username: string) => Promise<void>;
updateUsername: (username: string) => Promise<void>;
logout: () => void;
refreshUser: () => Promise<void>;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
const TOKEN_KEY = 'acestep_token';
const USER_KEY = 'acestep_user';
export function AuthProvider({ children }: { children: ReactNode }): React.ReactElement {
// Start with null - we'll auto-login from database on mount
const [user, setUser] = useState<User | null>(null);
const [token, setToken] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const isAuthenticated = !!user && !!token;
// Auto-login on mount: Try to get existing user from database
useEffect(() => {
async function initAuth(): Promise<void> {
try {
// First, try auto-login from database (for local single-user app)
const { user: userData, token: newToken } = await authApi.auto();
setUser(userData);
setToken(newToken);
localStorage.setItem(TOKEN_KEY, newToken);
localStorage.setItem(USER_KEY, JSON.stringify(userData));
} catch (error: unknown) {
// No user in database (404) or server error - that's okay
// Clear any stale localStorage data
const err = error as { message?: string };
if (err.message?.startsWith('404:')) {
// No user exists yet - frontend will show username setup
console.log('No user in database, need to set up username');
} else {
console.warn('Auto-login failed:', error);
}
// Clear stale data
setToken(null);
setUser(null);
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
} finally {
setIsLoading(false);
}
}
initAuth();
}, []);
const setupUser = useCallback(async (username: string): Promise<void> => {
const { user: userData, token: newToken } = await authApi.setup(username);
setUser(userData);
setToken(newToken);
localStorage.setItem(TOKEN_KEY, newToken);
localStorage.setItem(USER_KEY, JSON.stringify(userData));
}, []);
const updateUsername = useCallback(async (username: string): Promise<void> => {
if (!token) throw new Error('Not authenticated');
const { user: userData, token: newToken } = await authApi.updateUsername(username, token);
setUser(userData);
setToken(newToken);
localStorage.setItem(TOKEN_KEY, newToken);
localStorage.setItem(USER_KEY, JSON.stringify(userData));
}, [token]);
const logout = useCallback((): void => {
authApi.logout().catch(() => {});
setUser(null);
setToken(null);
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
}, []);
const refreshUser = useCallback(async (): Promise<void> => {
if (!token) return;
try {
const { user: userData } = await authApi.me(token);
setUser(userData);
localStorage.setItem(USER_KEY, JSON.stringify(userData));
} catch (error) {
console.error('Failed to refresh user:', error);
}
}, [token]);
const value: AuthContextType = {
user,
token,
isLoading,
isAuthenticated,
setupUser,
updateUsername,
logout,
refreshUser,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}