feature : Connect front to backend #1

Merged
BoxOfPandor merged 25 commits from feat/connect-front-to-backend into main 2026-03-19 14:56:19 +01:00
Showing only changes of commit 039b9c1ff4 - Show all commits

View File

@@ -2,11 +2,12 @@ import React, {
createContext, createContext,
useCallback, useCallback,
useContext, useContext,
useEffect,
useMemo, useMemo,
useState, useState,
} from 'react'; } from 'react';
import type { User, UserRole } from '../types'; import type { User } from '../types';
import { MOCK_USERS } from '../data/mockData'; import { authApi, usersApi, getToken, setToken, clearToken } from '../utils/api';
// ── Types ────────────────────────────────────────────────────────────────────── // ── Types ──────────────────────────────────────────────────────────────────────
@@ -15,12 +16,11 @@ interface AuthContextValue {
isAuthenticated: boolean; isAuthenticated: boolean;
isStaff: boolean; isStaff: boolean;
isAdmin: boolean; isAdmin: boolean;
isLoading: boolean;
login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>; login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
register: (username: string, email: string, password: string) => Promise<{ success: boolean; error?: string }>; register: (username: string, email: string, password: string) => Promise<{ success: boolean; error?: string }>;
logout: () => void; logout: () => void;
updateUsername: (username: string) => void; updateUsername: (username: string) => Promise<{ success: boolean; error?: string }>;
// Dev helper: quickly switch role for testing
devSetRole: (role: UserRole) => void;
} }
// ── Context ──────────────────────────────────────────────────────────────────── // ── Context ────────────────────────────────────────────────────────────────────
@@ -31,16 +31,6 @@ const AuthContext = createContext<AuthContextValue | null>(null);
const STORAGE_KEY = 'crowmate_auth_user'; const STORAGE_KEY = 'crowmate_auth_user';
function loadUserFromStorage(): User | null {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return null;
return JSON.parse(raw) as User;
} catch {
return null;
}
}
function saveUserToStorage(user: User | null): void { function saveUserToStorage(user: User | null): void {
if (user) { if (user) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(user)); localStorage.setItem(STORAGE_KEY, JSON.stringify(user));
@@ -50,96 +40,89 @@ function saveUserToStorage(user: User | null): void {
} }
export function AuthProvider({ children }: { children: React.ReactNode }) { export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(loadUserFromStorage); const [user, setUser] = useState<User | null>(null);
const [isLoading, setIsLoading] = useState(true);
// On mount: validate stored token and restore session
useEffect(() => {
const token = getToken();
if (!token) {
setIsLoading(false);
return;
}
authApi.me()
.then((freshUser) => {
setUser(freshUser);
saveUserToStorage(freshUser);
})
.catch(() => {
clearToken();
saveUserToStorage(null);
})
.finally(() => setIsLoading(false));
}, []);
const isAuthenticated = user !== null; const isAuthenticated = user !== null;
const isStaff = user?.role === 'dev' || user?.role === 'com'; const isStaff = user?.role === 'dev' || user?.role === 'com';
const isAdmin = user?.isAdmin === true; const isAdmin = user?.isAdmin === true;
const login = useCallback( const login = useCallback(
async (email: string, _password: string): Promise<{ success: boolean; error?: string }> => { async (email: string, password: string): Promise<{ success: boolean; error?: string }> => {
// Simulate network delay try {
await new Promise((r) => setTimeout(r, 400)); const { token, user: loggedInUser } = await authApi.login(email, password);
setToken(token);
const found = MOCK_USERS.find( setUser(loggedInUser);
(u) => u.email.toLowerCase() === email.toLowerCase() saveUserToStorage(loggedInUser);
); return { success: true };
} catch (err) {
if (!found) { const message = err instanceof Error ? err.message : 'Login failed.';
return { success: false, error: 'No account found with that email address.' }; return { success: false, error: message };
} }
if (found.isBanned) {
return { success: false, error: 'This account has been suspended.' };
}
setUser(found);
saveUserToStorage(found);
return { success: true };
}, },
[] []
); );
const register = useCallback( const register = useCallback(
async (username: string, email: string, _password: string): Promise<{ success: boolean; error?: string }> => { async (username: string, email: string, password: string): Promise<{ success: boolean; error?: string }> => {
await new Promise((r) => setTimeout(r, 500)); try {
const { token, user: newUser } = await authApi.register(username, email, password);
const emailTaken = MOCK_USERS.some( setToken(token);
(u) => u.email.toLowerCase() === email.toLowerCase() setUser(newUser);
); saveUserToStorage(newUser);
if (emailTaken) { return { success: true };
return { success: false, error: 'An account with this email already exists.' }; } catch (err) {
const message = err instanceof Error ? err.message : 'Registration failed.';
return { success: false, error: message };
} }
const usernameTaken = MOCK_USERS.some(
(u) => u.username.toLowerCase() === username.toLowerCase()
);
if (usernameTaken) {
return { success: false, error: 'This username is already taken.' };
}
const newUser: User = {
id: `u${Date.now()}`,
username,
email,
role: 'user',
isAdmin: false,
isBanned: false,
createdAt: new Date().toISOString(),
};
setUser(newUser);
saveUserToStorage(newUser);
return { success: true };
}, },
[] []
); );
const logout = useCallback(() => { const logout = useCallback(() => {
clearToken();
setUser(null); setUser(null);
saveUserToStorage(null); saveUserToStorage(null);
}, []); }, []);
const updateUsername = useCallback((username: string) => { const updateUsername = useCallback(
setUser((prev) => { async (username: string): Promise<{ success: boolean; error?: string }> => {
if (!prev) return prev; try {
const updated = { ...prev, username }; const updatedUser = await usersApi.updateUsername(username);
saveUserToStorage(updated); setUser(updatedUser);
return updated; saveUserToStorage(updatedUser);
}); return { success: true };
}, []); } catch (err) {
const message = err instanceof Error ? err.message : 'Failed to update username.';
const devSetRole = useCallback((role: UserRole) => { return { success: false, error: message };
setUser((prev) => { }
if (!prev) return prev; },
const updated = { ...prev, role, isAdmin: role === 'dev' }; []
saveUserToStorage(updated); );
return updated;
});
}, []);
const value = useMemo<AuthContextValue>( const value = useMemo<AuthContextValue>(
() => ({ user, isAuthenticated, isStaff, isAdmin, login, register, logout, updateUsername, devSetRole }), () => ({ user, isAuthenticated, isStaff, isAdmin, isLoading, login, register, logout, updateUsername }),
[user, isAuthenticated, isStaff, isAdmin, login, register, logout, updateUsername, devSetRole] [user, isAuthenticated, isStaff, isAdmin, isLoading, login, register, logout, updateUsername]
); );
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;