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:
2026-04-02 13:36:45 +08:00
parent ac60f0bb49
commit c3f8b598af
26 changed files with 1143 additions and 389 deletions

View 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;
}