feat: add React frontend with auth, project list, import, and project detail pages
Converts packages/web from vanilla TypeScript Vite scaffold to React with: - React 19, react-router-dom v7, @tanstack/react-query v5, Tailwind CSS v4 - JWT auth context with auto-refresh token support - Login/Register pages, protected Layout with auth guard - Projects list with grid cards and delete action - ImportDialog supporting URL or file upload with API key display - ProjectDetail with 4 tabs: Documentation, Modules, MCP Integration, Settings - All TypeScript compiles cleanly (noEmit check passes) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
62
packages/web/src/lib/auth.tsx
Normal file
62
packages/web/src/lib/auth.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
|
||||
import { getAccessToken, clearTokens, setTokens, apiFetch } from './api';
|
||||
|
||||
type User = { id: string; email: string; name: string };
|
||||
|
||||
type AuthContextType = {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
register: (email: string, password: string, name: string) => Promise<void>;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType | null>(null);
|
||||
|
||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (getAccessToken()) {
|
||||
apiFetch<User>('/auth/me')
|
||||
.then(setUser)
|
||||
.catch(() => clearTokens())
|
||||
.finally(() => setLoading(false));
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const login = async (email: string, password: string) => {
|
||||
const data = await apiFetch<{ user: User; accessToken: string; refreshToken: string }>(
|
||||
'/auth/login',
|
||||
{ method: 'POST', body: JSON.stringify({ email, password }) },
|
||||
);
|
||||
setTokens(data.accessToken, data.refreshToken);
|
||||
setUser(data.user);
|
||||
};
|
||||
|
||||
const register = async (email: string, password: string, name: string) => {
|
||||
const data = await apiFetch<{ user: User; accessToken: string; refreshToken: string }>(
|
||||
'/auth/register',
|
||||
{ method: 'POST', body: JSON.stringify({ email, password, name }) },
|
||||
);
|
||||
setTokens(data.accessToken, data.refreshToken);
|
||||
setUser(data.user);
|
||||
};
|
||||
|
||||
const logout = () => { clearTokens(); setUser(null); };
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, loading, login, register, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAuth() {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user