diff --git a/apps/hq-web/index.html b/apps/hq-web/index.html new file mode 100644 index 0000000..d21cb9e --- /dev/null +++ b/apps/hq-web/index.html @@ -0,0 +1,12 @@ + + + + + + SiMS HQ + + +
+ + + diff --git a/apps/hq-web/package.json b/apps/hq-web/package.json new file mode 100644 index 0000000..e723952 --- /dev/null +++ b/apps/hq-web/package.json @@ -0,0 +1,26 @@ +{ + "name": "@sims/hq-web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "typecheck": "tsc -p tsconfig.json" + }, + "dependencies": { + "@sims/domain": "*", + "@sims/printing": "*", + "@sims/ui": "*", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "vite": "^6.0.0" + } +} diff --git a/apps/hq-web/src/Layout.tsx b/apps/hq-web/src/Layout.tsx new file mode 100644 index 0000000..ecd14b2 --- /dev/null +++ b/apps/hq-web/src/Layout.tsx @@ -0,0 +1,57 @@ +import { useEffect, useState } from 'react' +import { Navigate, NavLink, Outlet, useNavigate } from 'react-router-dom' +import { Badge, ThemeSwitcher } from '@sims/ui' +import { clearSession, displayName, getEmailStatus, hasSession } from './api' + +const NAV = [ + { to: '/', label: 'Clients' }, + { to: '/modules', label: 'Modules' }, + { to: '/documents/new', label: 'New Document' }, +] + +/** App shell: left nav + top bar + the spec's Gmail-health banner. */ +export function Layout() { + const nav = useNavigate() + const [gmailDown, setGmailDown] = useState(false) + + useEffect(() => { + if (!hasSession()) return + getEmailStatus() + .then((s) => setGmailDown(!s.connected || s.dead)) + .catch(() => { /* 401 already redirected; other failures keep the banner hidden */ }) + }, []) + + if (!hasSession()) return + + return ( +
+ +
+ {gmailDown && ( +
+ Gmail disconnected — sends are queued to manual. Run gmail-connect on the server. +
+ )} +
+ + + {displayName()} + +
+
+ +
+
+
+ ) +} diff --git a/apps/hq-web/src/Login.tsx b/apps/hq-web/src/Login.tsx new file mode 100644 index 0000000..67c8155 --- /dev/null +++ b/apps/hq-web/src/Login.tsx @@ -0,0 +1,49 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { Button, Field, Notice } from '@sims/ui' +import { login } from './api' + +/** HQ login — email + password against POST /api/auth/login (server-side scrypt). */ +export function Login() { + const nav = useNavigate() + const [email, setEmail] = useState('') + const [pw, setPw] = useState('') + const [busy, setBusy] = useState(false) + const [error, setError] = useState() + + const submit = async () => { + if (busy) return + setBusy(true) + setError(undefined) + try { + await login(email.trim(), pw) + nav('/') + } catch (err) { + setError(err instanceof Error ? err.message : String(err)) + setBusy(false) + } + } + + return ( +
+
+

SiMS HQ

+ + setEmail(e.target.value)} + /> + + + setPw(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && void submit()} + /> + + + {error !== undefined && {error}} +
+
+ ) +} diff --git a/apps/hq-web/src/api.ts b/apps/hq-web/src/api.ts new file mode 100644 index 0000000..5982a5f --- /dev/null +++ b/apps/hq-web/src/api.ts @@ -0,0 +1,63 @@ +/** HQ web ↔ hq-server client; token survives reloads via localStorage['hq.token']. */ + +const TOKEN_KEY = 'hq.token' +const NAME_KEY = 'hq.name' +const ROLE_KEY = 'hq.role' + +export function hasSession(): boolean { + return (localStorage.getItem(TOKEN_KEY) ?? '') !== '' +} + +export function displayName(): string { + return localStorage.getItem(NAME_KEY) ?? '' +} + +/** 'owner' | 'staff' — Modules page renders read-only for staff. */ +export function role(): string { + return localStorage.getItem(ROLE_KEY) ?? 'staff' +} + +export function clearSession(): void { + localStorage.removeItem(TOKEN_KEY) + localStorage.removeItem(NAME_KEY) + localStorage.removeItem(ROLE_KEY) +} + +/** + * Fetch `/api${path}` with the session bearer token. On 401 (outside the login + * call itself) the session is cleared and the app redirects to /login. + */ +export async function apiFetch(path: string, opts?: RequestInit): Promise { + const token = localStorage.getItem(TOKEN_KEY) ?? '' + const res = await fetch(`/api${path}`, { + ...opts, + headers: { + 'content-type': 'application/json', + ...(token !== '' ? { authorization: `Bearer ${token}` } : {}), + ...(opts?.headers ?? {}), + }, + }).catch(() => undefined) + if (res === undefined) throw new Error('HQ server unreachable — is it running on port 5182?') + if (res.status === 401 && !path.startsWith('/auth/')) { + clearSession() + window.location.hash = '#/login' + throw new Error('Not signed in') + } + const body = (await res.json().catch(() => ({}))) as T & { error?: string } + if (!res.ok) throw new Error(body.error ?? `Server error HTTP ${res.status}`) + return body +} + +export async function login(email: string, password: string): Promise<{ displayName: string; role: string }> { + const out = await apiFetch<{ token: string; staff: { id: string; displayName: string; role: string } }>( + '/auth/login', + { method: 'POST', body: JSON.stringify({ email, password }) }, + ) + localStorage.setItem(TOKEN_KEY, out.token) + localStorage.setItem(NAME_KEY, out.staff.displayName) + localStorage.setItem(ROLE_KEY, out.staff.role) + return { displayName: out.staff.displayName, role: out.staff.role } +} + +export interface EmailStatus { connected: boolean; address?: string; dead: boolean } +export const getEmailStatus = (): Promise => apiFetch('/email/status') diff --git a/apps/hq-web/src/app.css b/apps/hq-web/src/app.css new file mode 100644 index 0000000..57cf2ab --- /dev/null +++ b/apps/hq-web/src/app.css @@ -0,0 +1,25 @@ +/* HQ shell — same structure language as the back-office (bo-*) shell. */ +.hq-shell { display: grid; grid-template-columns: 210px 1fr; height: 100vh; } +.hq-side { background: var(--bg-raised); border-right: 1px solid var(--border); overflow-y: auto; padding: 12px 0; } +.hq-brand { font-weight: 700; font-size: 16px; padding: 4px 16px 12px; } +.hq-brand small { display: block; color: var(--text-dim); font-weight: 400; } +.hq-side a { + display: flex; align-items: center; gap: 8px; padding: 7px 16px; color: var(--text); + text-decoration: none; font-size: 13.5px; +} +.hq-side a:hover { background: var(--bg-hover); } +.hq-side a.active { background: var(--bg-hover); box-shadow: inset 3px 0 0 var(--accent); } +.hq-main { display: flex; flex-direction: column; min-width: 0; } +.hq-banner { + padding: 8px 20px; font-size: 13px; color: var(--warn); background: var(--warn-soft); + border-bottom: 1px solid color-mix(in srgb, var(--warn) 35%, transparent); +} +.hq-top { + display: flex; gap: 12px; align-items: center; padding: 8px 20px; + border-bottom: 1px solid var(--border); background: var(--bg-raised); +} +.hq-content { overflow-y: auto; flex: 1; padding: 4px 20px 20px; } + +/* Login */ +.login-wrap { height: 100vh; display: flex; align-items: center; justify-content: center; } +.login-card { background: var(--bg-raised); border: 1px solid var(--border); border-radius: 12px; padding: 28px; width: 380px; } diff --git a/apps/hq-web/src/main.tsx b/apps/hq-web/src/main.tsx new file mode 100644 index 0000000..1c6234b --- /dev/null +++ b/apps/hq-web/src/main.tsx @@ -0,0 +1,35 @@ +import { createRoot } from 'react-dom/client' +import { HashRouter, Navigate, Route, Routes } from 'react-router-dom' +import '@fontsource-variable/inter' +import '@fontsource-variable/jetbrains-mono' +import '../../../packages/ui/src/tokens.css' +import './app.css' +import { initTheme, PageHeader } from '@sims/ui' +import { Layout } from './Layout' +import { Login } from './Login' + +/** Placeholder pages — Task 16 replaces these with the five real pages. */ +function Placeholder(props: { title: string }) { + return +} + +function App() { + return ( + + + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ) +} + +initTheme() +createRoot(document.getElementById('root')!).render() diff --git a/apps/hq-web/tsconfig.json b/apps/hq-web/tsconfig.json new file mode 100644 index 0000000..bf5a36d --- /dev/null +++ b/apps/hq-web/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*"] +} diff --git a/apps/hq-web/vite.config.ts b/apps/hq-web/vite.config.ts new file mode 100644 index 0000000..1f9104e --- /dev/null +++ b/apps/hq-web/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { fileURLToPath } from 'node:url' + +const p = (rel: string) => fileURLToPath(new URL(rel, import.meta.url)) + +export default defineConfig({ + base: './', + plugins: [react()], + resolve: { + alias: { + '@sims/domain': p('../../packages/domain/src/index.ts'), + '@sims/ui': p('../../packages/ui/src/index.ts'), + '@sims/printing': p('../../packages/printing/src/index.ts'), + }, + }, + // 5182 = apps/hq server (5181 store-server, 5180 backoffice dev). + server: { port: 5183, proxy: { '/api': 'http://localhost:5182' } }, +}) diff --git a/package-lock.json b/package-lock.json index 40ab682..8e9d94a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "dependencies": { "@sims/billing-engine": "*", "@sims/domain": "*", + "@sims/printing": "*", "@sims/scanning": "*", "@sims/ui": "*", "react": "^19.0.0", @@ -36,7 +37,602 @@ "vite": "^6.0.0" } }, - "apps/backoffice/node_modules/@esbuild/aix-ppc64": { + "apps/backoffice/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "apps/backoffice/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "apps/backoffice/node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "apps/hq": { + "name": "@sims/hq", + "version": "0.1.0", + "dependencies": { + "@sims/auth": "*", + "@sims/billing-engine": "*", + "@sims/domain": "*", + "better-sqlite3": "^11.7.0", + "express": "^4.21.0", + "puppeteer": "^23.11.0" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.11", + "@types/express": "^4.17.21", + "esbuild": "^0.25.0", + "tsx": "^4.19.0" + } + }, + "apps/hq-web": { + "name": "@sims/hq-web", + "version": "0.1.0", + "dependencies": { + "@sims/domain": "*", + "@sims/printing": "*", + "@sims/ui": "*", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "vite": "^6.0.0" + } + }, + "apps/hq-web/node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", @@ -53,7 +649,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/android-arm": { + "apps/hq-web/node_modules/@esbuild/android-arm": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", @@ -70,7 +666,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/android-arm64": { + "apps/hq-web/node_modules/@esbuild/android-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", @@ -87,7 +683,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/android-x64": { + "apps/hq-web/node_modules/@esbuild/android-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", @@ -104,7 +700,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/darwin-arm64": { + "apps/hq-web/node_modules/@esbuild/darwin-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", @@ -121,7 +717,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/darwin-x64": { + "apps/hq-web/node_modules/@esbuild/darwin-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", @@ -138,7 +734,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/freebsd-arm64": { + "apps/hq-web/node_modules/@esbuild/freebsd-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", @@ -155,7 +751,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/freebsd-x64": { + "apps/hq-web/node_modules/@esbuild/freebsd-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", @@ -172,7 +768,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-arm": { + "apps/hq-web/node_modules/@esbuild/linux-arm": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", @@ -189,7 +785,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-arm64": { + "apps/hq-web/node_modules/@esbuild/linux-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", @@ -206,7 +802,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-ia32": { + "apps/hq-web/node_modules/@esbuild/linux-ia32": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", @@ -223,7 +819,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-loong64": { + "apps/hq-web/node_modules/@esbuild/linux-loong64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", @@ -240,7 +836,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-mips64el": { + "apps/hq-web/node_modules/@esbuild/linux-mips64el": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", @@ -257,7 +853,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-ppc64": { + "apps/hq-web/node_modules/@esbuild/linux-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", @@ -274,7 +870,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-riscv64": { + "apps/hq-web/node_modules/@esbuild/linux-riscv64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", @@ -291,7 +887,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-s390x": { + "apps/hq-web/node_modules/@esbuild/linux-s390x": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", @@ -308,7 +904,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/linux-x64": { + "apps/hq-web/node_modules/@esbuild/linux-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", @@ -325,7 +921,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/netbsd-arm64": { + "apps/hq-web/node_modules/@esbuild/netbsd-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", @@ -342,7 +938,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/netbsd-x64": { + "apps/hq-web/node_modules/@esbuild/netbsd-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", @@ -359,7 +955,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/openbsd-arm64": { + "apps/hq-web/node_modules/@esbuild/openbsd-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", @@ -376,7 +972,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/openbsd-x64": { + "apps/hq-web/node_modules/@esbuild/openbsd-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", @@ -393,7 +989,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/openharmony-arm64": { + "apps/hq-web/node_modules/@esbuild/openharmony-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", @@ -410,7 +1006,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/sunos-x64": { + "apps/hq-web/node_modules/@esbuild/sunos-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", @@ -427,7 +1023,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/win32-arm64": { + "apps/hq-web/node_modules/@esbuild/win32-arm64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", @@ -444,7 +1040,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/win32-ia32": { + "apps/hq-web/node_modules/@esbuild/win32-ia32": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", @@ -461,7 +1057,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/@esbuild/win32-x64": { + "apps/hq-web/node_modules/@esbuild/win32-x64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", @@ -478,7 +1074,7 @@ "node": ">=18" } }, - "apps/backoffice/node_modules/esbuild": { + "apps/hq-web/node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", @@ -520,7 +1116,7 @@ "@esbuild/win32-x64": "0.25.12" } }, - "apps/backoffice/node_modules/vite": { + "apps/hq-web/node_modules/vite": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", @@ -595,24 +1191,6 @@ } } }, - "apps/hq": { - "name": "@sims/hq", - "version": "0.1.0", - "dependencies": { - "@sims/auth": "*", - "@sims/billing-engine": "*", - "@sims/domain": "*", - "better-sqlite3": "^11.7.0", - "express": "^4.21.0", - "puppeteer": "^23.11.0" - }, - "devDependencies": { - "@types/better-sqlite3": "^7.6.11", - "@types/express": "^4.17.21", - "esbuild": "^0.25.0", - "tsx": "^4.19.0" - } - }, "apps/hq/node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -3460,6 +4038,10 @@ "resolved": "apps/hq", "link": true }, + "node_modules/@sims/hq-web": { + "resolved": "apps/hq-web", + "link": true + }, "node_modules/@sims/pos": { "resolved": "apps/pos", "link": true @@ -7136,7 +7718,8 @@ "name": "@sims/printing", "version": "0.1.0", "dependencies": { - "@sims/domain": "*" + "@sims/domain": "*", + "@sims/scanning": "*" } }, "packages/scanning": {