- Complete rewrite of frontend using React 18 + TypeScript in strict mode - Implement TanStack Router for file-based routing matching URL structure - Use TanStack Query for server state management with smart caching - Replace Pinia stores with React Context API for auth and UI state - Adopt Tailwind CSS + shadcn/ui components for consistent styling - Switch from pnpm to Bun for faster package management and builds - Configure Vite to support React, TypeScript, and modern tooling - Create frontend.go with Go embed package for embedding dist/ in binary - Implement comprehensive TypeScript interfaces (strict mode, no 'any' types) - Add dark mode support throughout with Tailwind CSS dark: classes - Set up i18n infrastructure (English translations included) - Remove all Vue 3 code, components, stores, CSS, and assets - Includes 18 new files with ~2000 lines of production-ready code
105 lines
2.4 KiB
TypeScript
105 lines
2.4 KiB
TypeScript
import {
|
|
createContext,
|
|
useContext,
|
|
useEffect,
|
|
useState,
|
|
ReactNode,
|
|
} from "react";
|
|
import type { IUser } from "@/types";
|
|
import { auth as authAPI } from "@/api";
|
|
|
|
interface AuthContextType {
|
|
user: IUser | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
login: (username: string, password: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
}
|
|
|
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [user, setUser] = useState<IUser | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// Initialize auth state on mount
|
|
useEffect(() => {
|
|
async function initAuth() {
|
|
try {
|
|
const currentUser = await authAPI.getCurrentUser();
|
|
setUser(currentUser);
|
|
setError(null);
|
|
} catch (err) {
|
|
setUser(null);
|
|
// Not an error if not authenticated — just not logged in
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
|
|
initAuth();
|
|
|
|
// Listen for logout events from other tabs/contexts
|
|
const handleLogout = () => {
|
|
setUser(null);
|
|
};
|
|
|
|
window.addEventListener("auth:logout", handleLogout);
|
|
return () => window.removeEventListener("auth:logout", handleLogout);
|
|
}, []);
|
|
|
|
const login = async (username: string, password: string) => {
|
|
try {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
await authAPI.login(username, password);
|
|
const currentUser = await authAPI.getCurrentUser();
|
|
setUser(currentUser);
|
|
} catch (err) {
|
|
const message =
|
|
err instanceof Error ? err.message : "Login failed";
|
|
setError(message);
|
|
throw err;
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const logout = async () => {
|
|
try {
|
|
await authAPI.logout();
|
|
setUser(null);
|
|
setError(null);
|
|
} catch (err) {
|
|
console.error("Logout error:", err);
|
|
// Clear local state even if logout fails
|
|
setUser(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AuthContext.Provider
|
|
value={{
|
|
user,
|
|
isAuthenticated: !!user,
|
|
isLoading,
|
|
error,
|
|
login,
|
|
logout,
|
|
}}
|
|
>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useAuth() {
|
|
const context = useContext(AuthContext);
|
|
if (!context) {
|
|
throw new Error("useAuth must be used within AuthProvider");
|
|
}
|
|
return context;
|
|
}
|