feat: enhance AuthContext to integrate API for authentication and user management
This commit is contained in:
@@ -2,11 +2,12 @@ import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import type { User, UserRole } from '../types';
|
||||
import { MOCK_USERS } from '../data/mockData';
|
||||
import type { User } from '../types';
|
||||
import { authApi, usersApi, getToken, setToken, clearToken } from '../utils/api';
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -15,12 +16,11 @@ interface AuthContextValue {
|
||||
isAuthenticated: boolean;
|
||||
isStaff: boolean;
|
||||
isAdmin: boolean;
|
||||
isLoading: boolean;
|
||||
login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>;
|
||||
register: (username: string, email: string, password: string) => Promise<{ success: boolean; error?: string }>;
|
||||
logout: () => void;
|
||||
updateUsername: (username: string) => void;
|
||||
// Dev helper: quickly switch role for testing
|
||||
devSetRole: (role: UserRole) => void;
|
||||
updateUsername: (username: string) => Promise<{ success: boolean; error?: string }>;
|
||||
}
|
||||
|
||||
// ── Context ────────────────────────────────────────────────────────────────────
|
||||
@@ -31,16 +31,6 @@ const AuthContext = createContext<AuthContextValue | null>(null);
|
||||
|
||||
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 {
|
||||
if (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 }) {
|
||||
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 isStaff = user?.role === 'dev' || user?.role === 'com';
|
||||
const isAdmin = user?.isAdmin === true;
|
||||
|
||||
const login = useCallback(
|
||||
async (email: string, _password: string): Promise<{ success: boolean; error?: string }> => {
|
||||
// Simulate network delay
|
||||
await new Promise((r) => setTimeout(r, 400));
|
||||
|
||||
const found = MOCK_USERS.find(
|
||||
(u) => u.email.toLowerCase() === email.toLowerCase()
|
||||
);
|
||||
|
||||
if (!found) {
|
||||
return { success: false, error: 'No account found with that email address.' };
|
||||
async (email: string, password: string): Promise<{ success: boolean; error?: string }> => {
|
||||
try {
|
||||
const { token, user: loggedInUser } = await authApi.login(email, password);
|
||||
setToken(token);
|
||||
setUser(loggedInUser);
|
||||
saveUserToStorage(loggedInUser);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Login failed.';
|
||||
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(
|
||||
async (username: string, email: string, _password: string): Promise<{ success: boolean; error?: string }> => {
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
const emailTaken = MOCK_USERS.some(
|
||||
(u) => u.email.toLowerCase() === email.toLowerCase()
|
||||
);
|
||||
if (emailTaken) {
|
||||
return { success: false, error: 'An account with this email already exists.' };
|
||||
async (username: string, email: string, password: string): Promise<{ success: boolean; error?: string }> => {
|
||||
try {
|
||||
const { token, user: newUser } = await authApi.register(username, email, password);
|
||||
setToken(token);
|
||||
setUser(newUser);
|
||||
saveUserToStorage(newUser);
|
||||
return { success: true };
|
||||
} 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(() => {
|
||||
clearToken();
|
||||
setUser(null);
|
||||
saveUserToStorage(null);
|
||||
}, []);
|
||||
|
||||
const updateUsername = useCallback((username: string) => {
|
||||
setUser((prev) => {
|
||||
if (!prev) return prev;
|
||||
const updated = { ...prev, username };
|
||||
saveUserToStorage(updated);
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const devSetRole = useCallback((role: UserRole) => {
|
||||
setUser((prev) => {
|
||||
if (!prev) return prev;
|
||||
const updated = { ...prev, role, isAdmin: role === 'dev' };
|
||||
saveUserToStorage(updated);
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
const updateUsername = useCallback(
|
||||
async (username: string): Promise<{ success: boolean; error?: string }> => {
|
||||
try {
|
||||
const updatedUser = await usersApi.updateUsername(username);
|
||||
setUser(updatedUser);
|
||||
saveUserToStorage(updatedUser);
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to update username.';
|
||||
return { success: false, error: message };
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const value = useMemo<AuthContextValue>(
|
||||
() => ({ user, isAuthenticated, isStaff, isAdmin, login, register, logout, updateUsername, devSetRole }),
|
||||
[user, isAuthenticated, isStaff, isAdmin, login, register, logout, updateUsername, devSetRole]
|
||||
() => ({ user, isAuthenticated, isStaff, isAdmin, isLoading, login, register, logout, updateUsername }),
|
||||
[user, isAuthenticated, isStaff, isAdmin, isLoading, login, register, logout, updateUsername]
|
||||
);
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
|
||||
Reference in New Issue
Block a user