120 lines
3.1 KiB
TypeScript
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;
|
|
}
|