feat: migrate frontend from Vue 3 to React 18 with TanStack ecosystem
- 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
This commit is contained in:
104
frontend/src/context/AuthContext.tsx
Normal file
104
frontend/src/context/AuthContext.tsx
Normal file
@@ -0,0 +1,104 @@
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user