From 682a9dce44afba556a8317d4443dfb782a3b5984 Mon Sep 17 00:00:00 2001 From: Bas van Rossem Date: Wed, 17 Jun 2026 18:56:28 +0200 Subject: [PATCH] feat(admin): scaffold Vite+React admin workspace --- apps/admin/.gitignore | 2 ++ apps/admin/index.html | 15 ++++++++++++ apps/admin/package.json | 34 +++++++++++++++++++++++++++ apps/admin/src/App.test.tsx | 20 ++++++++++++++++ apps/admin/src/App.tsx | 3 +++ apps/admin/src/lib/api.ts | 37 ++++++++++++++++++++++++++++++ apps/admin/src/lib/auth-storage.ts | 13 +++++++++++ apps/admin/src/main.tsx | 15 ++++++++++++ apps/admin/src/styles.css | 0 apps/admin/src/test/setup.ts | 1 + apps/admin/src/vite-env.d.ts | 9 ++++++++ apps/admin/tsconfig.app.json | 17 ++++++++++++++ apps/admin/tsconfig.json | 4 ++++ apps/admin/tsconfig.node.json | 12 ++++++++++ apps/admin/vite.config.ts | 7 ++++++ apps/admin/vitest.config.ts | 7 ++++++ yarn.lock | 23 +++++++++++++++++++ 17 files changed, 219 insertions(+) create mode 100644 apps/admin/.gitignore create mode 100644 apps/admin/index.html create mode 100644 apps/admin/package.json create mode 100644 apps/admin/src/App.test.tsx create mode 100644 apps/admin/src/App.tsx create mode 100644 apps/admin/src/lib/api.ts create mode 100644 apps/admin/src/lib/auth-storage.ts create mode 100644 apps/admin/src/main.tsx create mode 100644 apps/admin/src/styles.css create mode 100644 apps/admin/src/test/setup.ts create mode 100644 apps/admin/src/vite-env.d.ts create mode 100644 apps/admin/tsconfig.app.json create mode 100644 apps/admin/tsconfig.json create mode 100644 apps/admin/tsconfig.node.json create mode 100644 apps/admin/vite.config.ts create mode 100644 apps/admin/vitest.config.ts diff --git a/apps/admin/.gitignore b/apps/admin/.gitignore new file mode 100644 index 0000000..542cb8e --- /dev/null +++ b/apps/admin/.gitignore @@ -0,0 +1,2 @@ +dist +*.tsbuildinfo diff --git a/apps/admin/index.html b/apps/admin/index.html new file mode 100644 index 0000000..42c3b44 --- /dev/null +++ b/apps/admin/index.html @@ -0,0 +1,15 @@ + + + + + + + + + SoleLog Admin + + +
+ + + diff --git a/apps/admin/package.json b/apps/admin/package.json new file mode 100644 index 0000000..b6ed4db --- /dev/null +++ b/apps/admin/package.json @@ -0,0 +1,34 @@ +{ + "name": "@solelog/admin", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "typecheck": "tsc -b", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@solelog/shared": "workspace:*", + "@tanstack/react-query": "^5.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.0" + }, + "devDependencies": { + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.4.0", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.0", + "jsdom": "^25.0.0", + "typescript": "^5.7.2", + "vite": "^7.0.0", + "vitest": "^3.0.0" + } +} diff --git a/apps/admin/src/App.test.tsx b/apps/admin/src/App.test.tsx new file mode 100644 index 0000000..187343a --- /dev/null +++ b/apps/admin/src/App.test.tsx @@ -0,0 +1,20 @@ +import { render, screen } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import App from './App'; + +function renderApp() { + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + return render( + + + + ); +} + +describe('App', () => { + it('renders the admin app name', () => { + renderApp(); + expect(screen.getByText('SoleLog Admin')).toBeInTheDocument(); + }); +}); diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx new file mode 100644 index 0000000..5fa5b84 --- /dev/null +++ b/apps/admin/src/App.tsx @@ -0,0 +1,3 @@ +export default function App() { + return
SoleLog Admin
; +} diff --git a/apps/admin/src/lib/api.ts b/apps/admin/src/lib/api.ts new file mode 100644 index 0000000..27b6469 --- /dev/null +++ b/apps/admin/src/lib/api.ts @@ -0,0 +1,37 @@ +import { getToken, setToken } from './auth-storage'; + +export const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:3000'; + +export class ApiError extends Error { + constructor( + public status: number, + message: string + ) { + super(message); + this.name = 'ApiError'; + } +} + +export async function apiFetch(path: string, init: RequestInit = {}): Promise { + const token = getToken(); + const headers = new Headers(init.headers); + if (token) headers.set('Authorization', `Bearer ${token}`); + if (init.body && !headers.has('Content-Type')) headers.set('Content-Type', 'application/json'); + const res = await fetch(`${API_URL}${path}`, { ...init, headers }); + if (!res.ok) throw new ApiError(res.status, `Request failed: ${res.status}`); + const text = await res.text(); + return (text ? JSON.parse(text) : undefined) as T; +} + +// Sign in: POST /api/auth/sign-in/email, capture the bearer token from the response header. +export async function signIn(email: string, password: string): Promise { + const res = await fetch(`${API_URL}/api/auth/sign-in/email`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }), + }); + if (!res.ok) throw new ApiError(res.status, 'Inloggen mislukt'); + const token = res.headers.get('set-auth-token'); + if (!token) throw new ApiError(500, 'Geen token ontvangen'); + setToken(token); +} diff --git a/apps/admin/src/lib/auth-storage.ts b/apps/admin/src/lib/auth-storage.ts new file mode 100644 index 0000000..71c7cc1 --- /dev/null +++ b/apps/admin/src/lib/auth-storage.ts @@ -0,0 +1,13 @@ +const TOKEN_KEY = 'solelog.token'; + +export function getToken(): string | null { + return localStorage.getItem(TOKEN_KEY); +} + +export function setToken(token: string): void { + localStorage.setItem(TOKEN_KEY, token); +} + +export function clearToken(): void { + localStorage.removeItem(TOKEN_KEY); +} diff --git a/apps/admin/src/main.tsx b/apps/admin/src/main.tsx new file mode 100644 index 0000000..2fb2974 --- /dev/null +++ b/apps/admin/src/main.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import App from './App'; +import './styles.css'; + +const queryClient = new QueryClient(); + +createRoot(document.getElementById('root')!).render( + + + + + +); diff --git a/apps/admin/src/styles.css b/apps/admin/src/styles.css new file mode 100644 index 0000000..e69de29 diff --git a/apps/admin/src/test/setup.ts b/apps/admin/src/test/setup.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/apps/admin/src/test/setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/apps/admin/src/vite-env.d.ts b/apps/admin/src/vite-env.d.ts new file mode 100644 index 0000000..c57d674 --- /dev/null +++ b/apps/admin/src/vite-env.d.ts @@ -0,0 +1,9 @@ +/// + +interface ImportMetaEnv { + readonly VITE_API_URL?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/apps/admin/tsconfig.app.json b/apps/admin/tsconfig.app.json new file mode 100644 index 0000000..8e71c4c --- /dev/null +++ b/apps/admin/tsconfig.app.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "composite": true, + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "types": ["vitest/globals", "@testing-library/jest-dom"] + }, + "include": ["src"] +} diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json new file mode 100644 index 0000000..d32ff68 --- /dev/null +++ b/apps/admin/tsconfig.json @@ -0,0 +1,4 @@ +{ + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] +} diff --git a/apps/admin/tsconfig.node.json b/apps/admin/tsconfig.node.json new file mode 100644 index 0000000..056de49 --- /dev/null +++ b/apps/admin/tsconfig.node.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "composite": true, + "noEmit": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "target": "ES2022", + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["vite.config.ts", "vitest.config.ts"] +} diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts new file mode 100644 index 0000000..43ff302 --- /dev/null +++ b/apps/admin/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { host: true, port: 5174 }, +}); diff --git a/apps/admin/vitest.config.ts b/apps/admin/vitest.config.ts new file mode 100644 index 0000000..8594b6a --- /dev/null +++ b/apps/admin/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + test: { environment: 'jsdom', globals: true, setupFiles: ['./src/test/setup.ts'] }, +}); diff --git a/yarn.lock b/yarn.lock index beac8ff..c501c3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1761,6 +1761,29 @@ __metadata: languageName: node linkType: hard +"@solelog/admin@workspace:apps/admin": + version: 0.0.0-use.local + resolution: "@solelog/admin@workspace:apps/admin" + dependencies: + "@solelog/shared": "workspace:*" + "@tanstack/react-query": "npm:^5.0.0" + "@testing-library/dom": "npm:^10.4.1" + "@testing-library/jest-dom": "npm:^6.4.0" + "@testing-library/react": "npm:^16.0.0" + "@testing-library/user-event": "npm:^14.5.0" + "@types/react": "npm:^18.3.0" + "@types/react-dom": "npm:^18.3.0" + "@vitejs/plugin-react": "npm:^4.3.0" + jsdom: "npm:^25.0.0" + react: "npm:^18.3.1" + react-dom: "npm:^18.3.1" + react-router-dom: "npm:^6.26.0" + typescript: "npm:^5.7.2" + vite: "npm:^7.0.0" + vitest: "npm:^3.0.0" + languageName: unknown + linkType: soft + "@solelog/api@workspace:apps/api": version: 0.0.0-use.local resolution: "@solelog/api@workspace:apps/api"