learning_ai_clock/web/src/lib/auth-context.tsx

120 lines
3.1 KiB
TypeScript

// ── Auth Context ──────────────────────────────────────────────
// Provides authentication state and actions for ChronoMind web.
// Calls platform-service /auth/* endpoints for login/register/me.
'use client';
import { createContext, useContext, useCallback, useEffect, useState, type ReactNode } from 'react';
import {
loginUser,
registerUser,
getMe,
setAuthToken,
setRefreshToken,
setSyncEnabled,
isAuthenticated as checkAuth,
type AuthUser,
} from './platform-sync';
interface AuthState {
user: AuthUser | null;
isLoading: boolean;
isAuthenticated: boolean;
error: string | null;
}
interface AuthActions {
login: (email: string, password: string) => Promise<boolean>;
register: (email: string, password: string, displayName: string) => Promise<boolean>;
logout: () => void;
clearError: () => void;
}
type AuthContextValue = AuthState & AuthActions;
const AuthContext = createContext<AuthContextValue | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Hydrate user from stored token on mount
useEffect(() => {
if (!checkAuth()) {
setIsLoading(false);
return;
}
getMe()
.then((u) => setUser(u))
.catch(() => {
setAuthToken(null);
})
.finally(() => setIsLoading(false));
}, []);
const login = useCallback(async (email: string, password: string): Promise<boolean> => {
setError(null);
try {
const result = await loginUser(email, password);
setAuthToken(result.accessToken);
setRefreshToken(result.refreshToken);
setSyncEnabled(true);
setUser(result.user);
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed');
return false;
}
}, []);
const register = useCallback(
async (email: string, password: string, displayName: string): Promise<boolean> => {
setError(null);
try {
const result = await registerUser(email, password, displayName);
setAuthToken(result.accessToken);
setRefreshToken(result.refreshToken);
setSyncEnabled(true);
setUser(result.user);
return true;
} catch (err) {
setError(err instanceof Error ? err.message : 'Registration failed');
return false;
}
},
[]
);
const logout = useCallback(() => {
setAuthToken(null);
setSyncEnabled(false);
setUser(null);
}, []);
const clearError = useCallback(() => setError(null), []);
return (
<AuthContext.Provider
value={{
user,
isLoading,
isAuthenticated: user !== null,
error,
login,
register,
logout,
clearError,
}}
>
{children}
</AuthContext.Provider>
);
}
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}