The management console for ThinkWatch, built with React 19, TypeScript, and Vite.
- React 19 with TypeScript
- TanStack Router for file-based routing
- TanStack Query for server state — see Data fetching
- shadcn/ui (Radix UI + Tailwind CSS 4) for components
- react-i18next for internationalization (English + Chinese)
- Vitest + React Testing Library for testing
- Web Crypto API for HMAC-SHA256 request signing
pnpm install
pnpm dev # Start dev server on http://localhost:5173
pnpm build # Production build
pnpm test # Run tests
pnpm exec tsc --noEmit # Type checkServer state goes through TanStack Query. A
component asks for what it renders with useQuery, and the query layer owns
the rest: the loading flag, the cache, cancelling a request nobody is waiting
for, and keeping a late response from landing under filters that have since
changed. No load runs in an effect.
- Keys follow the endpoint.
GET /api/admin/teams/{id}/membersis keyed['admin', 'teams', id, 'members'], with query-string parameters in a trailing object. A key names everything itsqueryFnreads —@tanstack/query/exhaustive-depsenforces that — and invalidating a prefix such as['admin', 'teams']reaches every query under it. Passexact: truewhen a prefix would reach further than the write did. - Writes invalidate. After a mutation,
await queryClient.invalidateQueries({ queryKey })for what the screen shows. Awaiting keeps orderings like "close the dialog once the list is fresh". Screens that aren't open need nothing: every successful write throughapi()drops the cached queries no screen is using (onSuccessfulWrite, wired up inmain.tsx), so none of them reopens onto pre-write data. - Pass the signal.
queryFn: ({ signal }) => api(path, { signal })lets the layer cancel a request once no component needs its answer. - Derive, don't copy. Read
query.datawhere it is rendered. When a screen needs local state that follows what was loaded — a form draft, a selection — adjust it during render, the wayuseResetOnChangedoes.
src/lib/query-client.ts builds the client and says why its defaults differ
from the library's. Tests render through renderWithQueryClient from
src/test/render.tsx, which gives every call an empty cache.
Do not add new suppressions of react-hooks/set-state-in-effect — every
finding it reports is a real one, and the codebase is clean of them.
src/
├── components/
│ ├── layout/ # AppShell, Sidebar, Header, LanguageSwitcher
│ └── ui/ # shadcn/ui components (Button, Card, Dialog, Table, etc.)
├── hooks/
│ ├── use-auth.ts # Authentication state & token management
│ └── use-mobile.ts # Responsive breakpoint detection
├── lib/
│ ├── api.ts # HTTP client with HMAC signing & auto token refresh
│ ├── query-client.ts # TanStack Query client & its defaults
│ └── utils.ts # Utility functions
├── i18n/
│ ├── en.json # English translations
│ ├── zh.json # Chinese translations
│ └── index.ts # i18next configuration
├── routes/
│ ├── setup.tsx # First-run setup wizard
│ ├── login.tsx # Login page (email/password + SSO)
│ ├── register.tsx # Registration page
│ ├── dashboard.tsx # Overview dashboard
│ ├── profile.tsx # User profile & password change
│ ├── gateway/
│ │ ├── providers.tsx # LLM provider CRUD
│ │ ├── models.tsx # Model listing
│ │ ├── api-keys.tsx # API key lifecycle management
│ │ └── logs.tsx # Gateway request logs
│ ├── mcp/
│ │ ├── servers.tsx # MCP server management
│ │ ├── tools.tsx # MCP tool discovery
│ │ └── logs.tsx # MCP invocation logs
│ ├── analytics/
│ │ ├── usage.tsx # Token usage analytics
│ │ ├── costs.tsx # Cost tracking
│ │ └── audit.tsx # Audit log viewer
│ └── admin/
│ ├── users.tsx # User management
│ ├── roles.tsx # Role definitions
│ ├── settings.tsx # Dynamic system settings (7 tabs)
│ └── log-forwarders.tsx # Log forwarding configuration
├── test/
│ ├── render.tsx # render() inside a fresh query cache
│ └── setup.ts # Test setup (jest-dom + i18n)
└── router.tsx # Route definitions & setup redirect logic
Shown on first run when no users exist. Guides admin through:
- Welcome + language selection
- Admin account creation
- Site name configuration
- Optional first AI provider setup
- API key display (shown once)
7-tab configuration panel:
- General — System info + site name
- Auth — JWT TTLs, signature parameters
- Gateway — Cache TTL, timeouts
- Security — Content filter rules, PII redactor patterns
- Budget — Alert thresholds, webhook URL
- API Keys — Default expiry, rotation, inactivity policies
- Data — Usage/audit log retention periods
Full lifecycle management:
- Create, edit, revoke, rotate keys
- Status badges (active/expired/inactive/rotated/revoked)
- Expiry warnings (yellow < 7d, red < 1d)
- "Expiring Soon" filter
The API client (src/lib/api.ts) handles:
- Bearer token authentication via localStorage
- HMAC-SHA256 request signing for POST/PATCH/DELETE operations
- Automatic token refresh on 401 responses
- Deduplication of concurrent refresh attempts
pnpm test # Run all tests in watch mode
pnpm test -- --run # Run once (CI mode)Test files follow the pattern *.test.tsx / *.test.ts alongside source files.