M1: implement operational core
Demo auth, seed import/reset, dashboard, vehicle/booking list+detail, audit trail. Backend: 19 tests passing, ruff clean. Frontend: React Router shell, typed API client, responsive pages. Verified end-to-end via curl and browser.
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { createContext, useCallback, useContext, useState, type ReactNode } from "react";
|
||||
import { api, ApiError } from "../api/client";
|
||||
import type { CurrentUser, Role } from "../api/types";
|
||||
|
||||
interface AuthState {
|
||||
user: CurrentUser | null;
|
||||
loading: boolean;
|
||||
loginAs: (role: Role) => Promise<void>;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthState | undefined>(undefined);
|
||||
|
||||
const STORAGE_KEY = "mobilityops.demo-user";
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<CurrentUser | null>(() => {
|
||||
const stored = sessionStorage.getItem(STORAGE_KEY);
|
||||
return stored ? (JSON.parse(stored) as CurrentUser) : null;
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const loginAs = useCallback(async (role: Role) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const loggedIn = await api.post<CurrentUser>("/api/v1/demo/login", { role });
|
||||
setUser(loggedIn);
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(loggedIn));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
setUser(null);
|
||||
sessionStorage.removeItem(STORAGE_KEY);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, loading, loginAs, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth(): AuthState {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function isSessionExpired(error: unknown): boolean {
|
||||
return error instanceof ApiError && error.status === 401;
|
||||
}
|
||||
Reference in New Issue
Block a user