feat: Initialize frontend and backend structure with essential configurations
- Added TypeScript build info for frontend. - Created Vite configuration for React application. - Implemented pre-commit hook to run checks before commits. - Set up PostgreSQL Dockerfile with PostGIS support and initialization scripts. - Added database creation script for PostgreSQL with necessary extensions. - Established Python project configuration with dependencies and development tools. - Developed pre-commit script to enforce code quality checks for backend and frontend. - Created PowerShell script to set up Git hooks path.
This commit is contained in:
149
frontend/src/state/AuthContext.tsx
Normal file
149
frontend/src/state/AuthContext.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
createContext,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import { login as loginRequest, register as registerRequest } from '../services/api';
|
||||
import type { AuthenticatedUser } from '../types/auth';
|
||||
|
||||
const STORAGE_TOKEN_KEY = 'rail-game/auth/token';
|
||||
const STORAGE_USER_KEY = 'rail-game/auth/user';
|
||||
|
||||
type AuthStatus = 'unauthenticated' | 'authenticating' | 'authenticated';
|
||||
|
||||
interface AuthContextValue {
|
||||
readonly token: string | null;
|
||||
readonly user: AuthenticatedUser | null;
|
||||
readonly status: AuthStatus;
|
||||
readonly error: string | null;
|
||||
login: (username: string, password: string) => Promise<boolean>;
|
||||
register: (
|
||||
username: string,
|
||||
password: string,
|
||||
fullName?: string | null
|
||||
) => Promise<boolean>;
|
||||
logout: (reason?: string) => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextValue | undefined>(undefined);
|
||||
|
||||
function readFromStorage<T>(key: string): T | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
const raw = window.localStorage.getItem(key);
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(raw) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeToStorage(key: string, value: unknown): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
window.localStorage.setItem(key, JSON.stringify(value));
|
||||
}
|
||||
|
||||
function removeFromStorage(key: string): void {
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
window.localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
interface AuthProviderProps {
|
||||
readonly children: ReactNode;
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: AuthProviderProps): JSX.Element {
|
||||
const [token, setToken] = useState<string | null>(null);
|
||||
const [user, setUser] = useState<AuthenticatedUser | null>(null);
|
||||
const [status, setStatus] = useState<AuthStatus>('unauthenticated');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const storedToken = readFromStorage<string>(STORAGE_TOKEN_KEY);
|
||||
const storedUser = readFromStorage<AuthenticatedUser>(STORAGE_USER_KEY);
|
||||
|
||||
if (storedToken && storedUser) {
|
||||
setToken(storedToken);
|
||||
setUser(storedUser);
|
||||
setStatus('authenticated');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback((reason?: string) => {
|
||||
setToken(null);
|
||||
setUser(null);
|
||||
setStatus('unauthenticated');
|
||||
setError(reason ?? null);
|
||||
removeFromStorage(STORAGE_TOKEN_KEY);
|
||||
removeFromStorage(STORAGE_USER_KEY);
|
||||
}, []);
|
||||
|
||||
const login = useCallback(async (username: string, password: string) => {
|
||||
setStatus('authenticating');
|
||||
setError(null);
|
||||
try {
|
||||
const response = await loginRequest({ username, password });
|
||||
setToken(response.accessToken);
|
||||
setUser(response.user);
|
||||
setStatus('authenticated');
|
||||
writeToStorage(STORAGE_TOKEN_KEY, response.accessToken);
|
||||
writeToStorage(STORAGE_USER_KEY, response.user);
|
||||
return true;
|
||||
} catch (err) {
|
||||
setStatus('unauthenticated');
|
||||
const message = err instanceof Error ? err.message : 'Authentication failed';
|
||||
setError(message);
|
||||
return false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const register = useCallback(
|
||||
async (username: string, password: string, fullName?: string | null) => {
|
||||
setStatus('authenticating');
|
||||
setError(null);
|
||||
try {
|
||||
const response = await registerRequest({ username, password, fullName });
|
||||
setToken(response.accessToken);
|
||||
setUser(response.user);
|
||||
setStatus('authenticated');
|
||||
writeToStorage(STORAGE_TOKEN_KEY, response.accessToken);
|
||||
writeToStorage(STORAGE_USER_KEY, response.user);
|
||||
return true;
|
||||
} catch (err) {
|
||||
setStatus('unauthenticated');
|
||||
const message = err instanceof Error ? err.message : 'Registration failed';
|
||||
setError(message);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const value = useMemo<AuthContextValue>(
|
||||
() => ({ token, user, status, error, login, register, logout }),
|
||||
[token, user, status, error, login, register, logout]
|
||||
);
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
}
|
||||
|
||||
export function useAuth(): AuthContextValue {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
Reference in New Issue
Block a user