diff --git a/nest-intra/nginx.conf b/nest-intra/nginx.conf index bd355f9..6385a07 100644 --- a/nest-intra/nginx.conf +++ b/nest-intra/nginx.conf @@ -3,6 +3,12 @@ server { root /usr/share/nginx/html; index index.html; + location /api/ { + proxy_pass http://api:3000/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } + location / { try_files $uri $uri/ /index.html; } diff --git a/nest-intra/src/contexts/AuthContext.tsx b/nest-intra/src/contexts/AuthContext.tsx index f7c5ab4..ae016f7 100644 --- a/nest-intra/src/contexts/AuthContext.tsx +++ b/nest-intra/src/contexts/AuthContext.tsx @@ -5,8 +5,7 @@ import React, { useMemo, useState, } from 'react'; -import type { User, UserRole } from '../types'; -import { MOCK_USERS } from '../data/mockData'; +import type { User } from '../types'; // ── Types ────────────────────────────────────────────────────────────────────── @@ -18,20 +17,20 @@ interface AuthContextValue { login: (email: string, password: string) => Promise<{ success: boolean; error?: string }>; logout: () => void; updateUsername: (username: string) => void; - devSetRole: (role: UserRole) => void; } // ── Context ──────────────────────────────────────────────────────────────────── const AuthContext = createContext(null); -// ── Provider ─────────────────────────────────────────────────────────────────── +// ── Storage helpers ──────────────────────────────────────────────────────────── -const STORAGE_KEY = 'crowmate_intra_user'; +const USER_KEY = 'crowmate_intra_user'; +const TOKEN_KEY = 'crowmate_intra_token'; function loadUserFromStorage(): User | null { try { - const raw = localStorage.getItem(STORAGE_KEY); + const raw = localStorage.getItem(USER_KEY); if (!raw) return null; return JSON.parse(raw) as User; } catch { @@ -41,12 +40,19 @@ function loadUserFromStorage(): User | null { function saveUserToStorage(user: User | null): void { if (user) { - localStorage.setItem(STORAGE_KEY, JSON.stringify(user)); + localStorage.setItem(USER_KEY, JSON.stringify(user)); } else { - localStorage.removeItem(STORAGE_KEY); + localStorage.removeItem(USER_KEY); } } +/** Returns the stored JWT for use in authenticated fetch calls. */ +export function getToken(): string | null { + return localStorage.getItem(TOKEN_KEY); +} + +// ── Provider ─────────────────────────────────────────────────────────────────── + export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(loadUserFromStorage); @@ -55,26 +61,35 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { 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)); + async (email: string, password: string): Promise<{ success: boolean; error?: string }> => { + let data: { token?: string; user?: User; error?: string }; - const found = MOCK_USERS.find( - (u) => u.email.toLowerCase() === email.toLowerCase() - ); + try { + const res = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }), + }); + data = await res.json(); + if (!res.ok) { + return { success: false, error: data.error ?? 'Login failed.' }; + } + } catch { + return { success: false, error: 'Cannot reach the server. Please try again.' }; + } - if (!found) { - return { success: false, error: 'No account found with that email address.' }; - } - if (found.isBanned) { - return { success: false, error: 'This account has been suspended.' }; - } - if (found.role === 'user') { + const loggedInUser = data.user!; + + if (loggedInUser.role === 'user') { return { success: false, error: 'Access denied. Staff accounts only.' }; } + if (loggedInUser.isBanned) { + return { success: false, error: 'This account has been suspended.' }; + } - setUser(found); - saveUserToStorage(found); + localStorage.setItem(TOKEN_KEY, data.token!); + saveUserToStorage(loggedInUser); + setUser(loggedInUser); return { success: true }; }, [] @@ -83,6 +98,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const logout = useCallback(() => { setUser(null); saveUserToStorage(null); + localStorage.removeItem(TOKEN_KEY); }, []); const updateUsername = useCallback((username: string) => { @@ -94,18 +110,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { }); }, []); - const devSetRole = useCallback((role: UserRole) => { - setUser((prev) => { - if (!prev) return prev; - const updated = { ...prev, role, isAdmin: role === 'dev' }; - saveUserToStorage(updated); - return updated; - }); - }, []); - const value = useMemo( - () => ({ user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername, devSetRole }), - [user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername, devSetRole] + () => ({ user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername }), + [user, isAuthenticated, isStaff, isAdmin, login, logout, updateUsername] ); return {children}; diff --git a/nest-intra/vite.config.ts b/nest-intra/vite.config.ts index 67c7139..ea4b42d 100644 --- a/nest-intra/vite.config.ts +++ b/nest-intra/vite.config.ts @@ -9,5 +9,8 @@ export default defineConfig({ ], server: { port: 5174, + proxy: { + '/api': 'http://localhost:3000', + }, }, })