02 — admin_console_R stack¶
The modern repo. If the rest of the platform feels stuck in 2018, this one feels like 2025.
At a glance¶
| Concern | Choice | Version |
|---|---|---|
| Framework | React | ^18.3.1 |
| Language | TypeScript | ^5.5.3 |
| Bundler | Vite | ^5.4.1 |
| React plugin | @vitejs/plugin-react-swc |
^3.5.0 |
| Module system | ESM ("type": "module" in package.json) |
— |
| Routing | react-router-dom |
^6.26.2 |
| Server-state cache | @tanstack/react-query |
^5.56.2 |
| Forms | react-hook-form + @hookform/resolvers + zod |
latest |
| CSS | Tailwind 3 + tailwindcss-animate + class-variance-authority |
^3.4.11 |
| UI primitives | shadcn/ui (50+ Radix wrappers in src/components/ui/) |
varies |
| Icons | lucide-react |
^0.462.0 |
| Toasts | sonner + shadcn toast |
^1.5.0 |
| Charts | recharts |
^2.12.7 |
| Date | date-fns |
^3.6.0 |
| Linter | ESLint 9 (flat config) + eslint-plugin-react-hooks + eslint-plugin-react-refresh + typescript-eslint |
latest |
| Prettier / pre-commit | Not configured | — |
| Tests | None configured | — |
TypeScript, not JavaScript — and the consequences¶
| Aspect | Notes |
|---|---|
tsconfig.app.json |
Application TS config. Verify strict: true — many Vite-React templates ship with strict: false. |
| Type errors | Caught at build time (npm run build), not at runtime |
| Path alias | @ → src/ (configured in both tsconfig and vite.config.ts) |
vite-env.d.ts |
Vite type augmentation |
Junior win: TypeScript catches the field-name typos that bite you elsewhere on the platform. Take advantage — don't use
as any.
React 18 + Vite¶
| Concern | Where |
|---|---|
| Entry | src/main.tsx — createRoot(...).render(<App />) |
| Root app | src/App.tsx — provider stack + Routes |
| HMR | Vite + @vitejs/plugin-react-swc — fast |
| Dev port | 8080 (set in vite.config.ts) |
| Build output | dist/ |
vite.config.ts highlights¶
export default defineConfig(({ mode }) => ({
server: {
host: "::", // IPv6 + IPv4 dual-stack
port: 8080,
allowedHosts: ["admin.someli.ai"], // only relevant if reverse-proxied
fs: { strict: true, deny: ['.env', '.git'] },
hmr: { overlay: false }, // overlay disabled — errors land in terminal
},
plugins: [
react(),
mode === 'development' && componentTagger(), // lovable-tagger, dev only
].filter(Boolean),
resolve: {
alias: { "@": path.resolve(__dirname, "./src") },
},
}));
componentTagger()is fromlovable-tagger. The repo was bootstrapped with Lovable.dev (an AI app-builder). The runtime scriptcdn.gpteng.co/gptengineer.jsis loaded inindex.htmleven in production — without SRI. Known finding. See../../audit/admin_console_R/security.md.
Routing — react-router-dom v6¶
All routes declared in src/App.tsx as JSX:
<Routes>
<Route path="/" element={<Navigate to="/login" replace />} />
<Route path="/login" element={<Login />} />
<Route path="/accounts" element={<ProtectedRoute><AppLayout><Accounts /></AppLayout></ProtectedRoute>} />
{/* ...repeated for ~10 routes */}
</Routes>
Every protected route is wrapped in ProtectedRoute → AppLayout → <Page />. Verbose but explicit. A refactor to a single <ProtectedRoutes> layout-route would tighten this without changing behaviour — but don't do it as your first PR.
Auth — hand-rolled AuthContext¶
Not @nuxtjs/auth style. Not OAuth. Just:
| Piece | Where |
|---|---|
| Context | src/context/AuthContext.tsx (~150 lines) |
| Hook | src/hooks/useAuth.tsx |
| Provider | Wrapped in App.tsx |
| Guard | src/components/auth/ProtectedRoute.tsx |
| Token storage | localStorage (key TBD — read AuthContext.tsx) |
| Login form | src/components/auth/LoginForm.tsx |
| Login page | src/pages/Login.tsx (mounts LoginForm) |
Token in
localStorageis more vulnerable to XSS than cookies. Known platform-wide trade-off. Don'tdangerouslySetInnerHTMLuser content.
API consumption — src/services/api.ts¶
A single 184-line file. Fetch wrapper + ~25 named API methods.
// Typical pattern
export async function getAccounts(): Promise<Account[]> {
const res = await fetch(`${ENV.API_URL}/accounts`, {
headers: {
'Authorization': `Bearer ${getToken()}`,
'Apptype': btoa('admin-console'), // HARDCODED — should come from VITE_APP_TYPE
}
});
return res.json();
}
Junior gotcha: the
Apptypeheader is hardcoded as"admin-console"rather than coming fromVITE_APP_TYPE. Known finding (F-2 in the security audit). If you fix this, do it carefully — backends may have hardcoded expectations.
See ../../audit/admin_console_R/api-consumption.md.
Server state — TanStack Query¶
QueryClient is configured in App.tsx. Queries use useQuery, mutations use useMutation.
Compared to someli-platform's manual store/api.js cache, this is much cleaner. Use TanStack Query for any backend data — don't add a parallel state mechanism.
shadcn/ui¶
src/components/ui/ is generated by the shadcn CLI (components.json). 50+ Radix-based primitives: Button, Dialog, Card, Input, Select, etc.
These files are owned by you, not imported from a package. If a primitive needs a tweak, edit the file directly.
Junior gotcha: don't
npm install @radix-ui/react-buttonand replace shadcn primitives — that breaks the conventional structure. If you need a new primitive, run the shadcn CLI to add it (npx shadcn-ui@latest add <component>).
Tailwind¶
tailwind.config.ts:
darkMode: 'class'- SF Pro Display font stack
- Design-token-driven HSL palette via CSS variables (
index.csshas:root { --primary: 220 90% 56%; ... }etc.)
Use Tailwind classes inline. Use cn(...) (from src/lib/utils.ts) to compose conditional class lists — that's the shadcn convention.
Forms — react-hook-form + zod¶
const schema = z.object({ email: z.string().email() });
const form = useForm({ resolver: zodResolver(schema) });
Used in LoginForm.tsx. For new forms, follow the same pattern — it gets you validation + accessibility for free via shadcn's form primitives.
Configuration — typed env¶
src/config/env.ts:
export const ENV = {
API_URL: import.meta.env.VITE_API_URL,
BASE_URL: import.meta.env.VITE_BASE_URL,
APP_URL: import.meta.env.VITE_APP_URL,
APP_TYPE: import.meta.env.VITE_APP_TYPE,
USER_APP_TYPE: import.meta.env.VITE_USER_APP_TYPE,
ACCOUNT_MANAGER: Number(import.meta.env.VITE_ACCOUNT_MANAGER),
SUPER_ADMIN: Number(import.meta.env.VITE_SUPER_ADMIN),
ADMIN: Number(import.meta.env.VITE_ADMIN),
DEVELOPER: Number(import.meta.env.VITE_DEVELOPER),
DESIGNER: Number(import.meta.env.VITE_DESIGNER),
SOMELI_DEV_URL: import.meta.env.VITE_SOMELI_DEV_URL,
};
Use ENV.API_URL in code, not import.meta.env.VITE_API_URL directly. Centralised, typed.
Build, lint, test¶
| Build | npm run build |
| Dev build | npm run build:dev (sourcemaps + unminified) |
| Lint | npm run lint (the only repo with a working lint command) |
| Tests | None |
| CI | None in repo |
| Deploy | Opaque from the repo |
| Branches | dev, uat, main |
Run
npm run lintbefore pushing. It's the only automated check available.
The Lovable.dev runtime script¶
The index.html includes:
This loads Lovable.dev's runtime, which monkey-patches React internals for live editing in Lovable's web IDE. It runs in production as well as in dev — known finding.
Decision points for the team:
- Is the Lovable.dev integration still in active use, or vestigial?
- If unused, delete the script tag (one line in
index.html). - If used, add SRI and consider scoping to dev-only loads.
See ../../audit/admin_console_R/security.md.