commit 714f8ab8ffae654565d001a343e017f864c95c23 Author: Bhanu Prakash Sai Potteri Date: Mon Jun 22 10:36:16 2026 +0530 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0857eda --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +node_modules +dist +dist-ssr +*.local +.DS_Store +.vite + +# Editor +.vscode/* +!.vscode/extensions.json +.idea + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env (base URL etc.) +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..8c5b7ba --- /dev/null +++ b/README.md @@ -0,0 +1,93 @@ +# Aria Console + +AI-employee-powered life-insurance **lead-to-policy** console for an Indian insurer. +Vite + React + TypeScript + Tailwind v4, built on the **Aria design system**. + +## Run + +```bash +npm install +npm run dev # http://localhost:5173 +npm run build # type-check + production build +npm run typecheck # type-check only +``` + +Create `.env` (gitignored) pointing at the gateway: + +``` +VITE_ZINO_API_URL=https://sandbox.getzino.in +``` + +Sign in with a platform user (e.g. `admin@getzino.com` / `Zino`); the JWT is +persisted in localStorage under `lead_to_policy_token`. + +## Backend wiring (live) + +This console is wired to the **sm2 Zino gateway** — sandbox **app 385 "Lead to +Policy"** (org 53), workflow `e29c3c33-…` (v1). No mock data on the four screens. + +`src/api/` is a thin SDK over the gateway: + +| File | Purpose | +| --- | --- | +| `config.ts` | App/workflow/RV/DV/activity UIDs, field ids, select options, state↔stage maps | +| `client.ts` | `ZinoClient` — login, app-scoped view/workflow calls, JWT persistence | +| `provider.tsx` | `ZinoProvider` + `useZino` + a dependency-free `useQuery` hook | +| `adapters.ts` | Map live records → the Aria domain types (`Lead`, `Decision`, timeline) | + +Endpoints used (all on the gateway): + +- `POST /usr/login` — auth +- `POST /app/385/view/recordview` — pipeline + lead lookup (`rv_template_uid`) +- `GET /app/385/view/audit?instance_id=` — activity log +- `GET /monitor/decisions?instance_id=` — AI-employee decision stream +- `POST /app/385/activity` — perform Recommend Product / Collect Documents + +Writes are real: submitting Recommend advances a lead Meeting Scheduled → +Product Recommended; submitting Documents advances Product Recommended → +Documents Collected. Document upload + OCR auto-extraction is intentionally +out of scope here (field-based KYC capture only). + +## Screens + +| Route | Screen | What it shows | +| --- | --- | --- | +| `/pipeline` | **Lead Pipeline** | KPI row, pipeline funnel, segment donut, leads table / kanban toggle. Click a lead → Lead 360. | +| `/leads/:id` | **Lead 360** | Profile, workflow stepper, current activity, AI decision stream + escalation callout. | +| `/recommend` | **Recommend Product** | Deep-linked (or picker-selected) lead; live indicative-premium helper, the lead's real AI suggestion, submits the Recommend Product activity. | +| `/documents` | **Documents** | Field-based KYC capture (PAN/Aadhaar/income/status), checklist + completion meter, submits the Collect Documents activity. | + +## Architecture + +``` +src/ +├── components/ +│ ├── core/ Button, Card, Badge, Avatar, Input, Select, MultiSelect +│ ├── insurance/ ConfidenceRing, RupeeAmount, SegmentBadge, ChannelBadge, +│ │ KpiCard, WorkflowStepper, TimelineEntry, AIDecisionCard, +│ │ LeadRow, DocumentUploadCard +│ └── index.ts single import surface — `import { Button } from '@/components'` +├── layout/Shell.tsx navy sidebar (nav + AI roster) + topbar, routed via +├── screens/ the four screens +├── data/mockData.ts typed mock data (Indian context) +├── types.ts shared domain types (Lead, Decision, Segment, Channel…) +├── lib/cn.ts className join helper +└── styles/ styles.css + tokens/ (colors, typography, spacing, fonts) +``` + +Every screen composes design-system components — they never re-implement primitives. +Components are typed, take a `className` for layout overrides, and are reusable across the app. + +## Design tokens → Tailwind + +`src/styles/styles.css` imports the Aria tokens (CSS custom properties) and wires them into +Tailwind's theme via `@theme inline`, so utilities resolve straight to the design system: + +- `bg-navy-900`, `text-sunrise-600`, `bg-emerald-100` … full brand + semantic palette +- `bg-card`, `text-strong`, `text-muted`, `border-border-subtle` … semantic aliases +- `rounded-lg` (16px card), `rounded-md` (12px control), `rounded-pill` +- `shadow-sm/md/lg`, `shadow-sunrise`; `font-sans` (Inter), `font-numeric` (Inter Tight) +- brand gradients via `.bg-sunrise` / `.bg-navy-grad`; tabular figures via `.nums` + +Brand: deep navy `#0B1B3B`, sunrise accent `#F26B3A → #FBA94C`, emerald success / +amber escalation; ₹ lakh/crore formatting; sentence case; no emoji. diff --git a/index.html b/index.html new file mode 100644 index 0000000..10d4b3f --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Aria — Lead-to-policy console + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4fe9e0f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2391 @@ +{ + "name": "aria-console", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aria-console", + "version": "0.1.0", + "dependencies": { + "lucide-react": "^1.21.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "tailwindcss": "^4.0.0", + "typescript": "^5.5.4", + "vite": "^5.4.6" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz", + "integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "tailwindcss": "4.3.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.21.0.tgz", + "integrity": "sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz", + "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", + "integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz", + "integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.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", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b8c7af4 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "aria-console", + "private": true, + "version": "0.1.0", + "type": "module", + "description": "Aria — AI-employee-powered life-insurance lead-to-policy console", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "typecheck": "tsc -b --noEmit" + }, + "dependencies": { + "lucide-react": "^1.21.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "@vitejs/plugin-react": "^4.3.1", + "tailwindcss": "^4.0.0", + "typescript": "^5.5.4", + "vite": "^5.4.6" + } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..59bf513 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,46 @@ +import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; +import type { ReactNode } from 'react'; +import { Shell } from './layout/Shell'; +import { PipelineScreen } from './screens/PipelineScreen'; +import { Lead360Screen } from './screens/Lead360Screen'; +import { AssignScreen } from './screens/AssignScreen'; +import { RecommendScreen } from './screens/RecommendScreen'; +import { DocumentsScreen } from './screens/DocumentsScreen'; +import { NewLeadScreen } from './screens/NewLeadScreen'; +import { Login } from './pages/Login'; +import { useZino } from './api/provider'; +import { LeadsProvider } from './api/leads'; + +function RequireAuth({ children }: { children: ReactNode }) { + const { user } = useZino(); + const location = useLocation(); + if (!user) return ; + return <>{children}; +} + +export function App() { + return ( + + } /> + + + + + + } + > + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} diff --git a/src/api/adapters.ts b/src/api/adapters.ts new file mode 100644 index 0000000..b6b485b --- /dev/null +++ b/src/api/adapters.ts @@ -0,0 +1,183 @@ +// Map live API shapes onto the Aria design-system domain types. +import type { Channel, Decision, Lead, Segment, Tone } from '../types'; +import { + ACTIVITY_NAME_BY_UID, + AI_EMPLOYEES, + STATE_NAME_BY_UID, + aiEmployeeForState, + stageOfState, +} from './config'; +import type { AiDecision, AuditEntry, LeadRecord } from './types'; + +const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +export function formatDate(iso?: string | null): string { + if (!iso) return '—'; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return '—'; + return `${d.getDate().toString().padStart(2, '0')} ${MONTHS[d.getMonth()]} ${d.getFullYear()}`; +} + +export function formatTime(iso?: string | null): string { + if (!iso) return ''; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return ''; + return d.toLocaleTimeString('en-IN', { hour: 'numeric', minute: '2-digit', hour12: true }); +} + +function ageFrom(iso?: string | null): number { + if (!iso) return 0; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return 0; + const now = new Date(); + let age = now.getFullYear() - d.getFullYear(); + const m = now.getMonth() - d.getMonth(); + if (m < 0 || (m === 0 && now.getDate() < d.getDate())) age--; + return age; +} + +function toSegment(raw?: string | null): Segment { + switch ((raw ?? '').toLowerCase()) { + case 'hot': + return 'Hot'; + case 'cold': + return 'Cold'; + default: + return 'Warm'; + } +} + +const CHANNELS: Record = { + whatsapp: 'WhatsApp', + web: 'Web', + referral: 'Referral', + agency: 'Agency', + bancassurance: 'Bancassurance', + direct: 'Direct', + event: 'Event', +}; + +function toChannel(raw?: string | null): Channel { + return CHANNELS[(raw ?? '').toLowerCase()] ?? 'Web'; +} + +// state -> a friendly "last action" line + tone for the pipeline list. +const STATE_ACTION: Record = { + 'New Lead': { action: 'Awaiting qualification', tone: 'neutral' }, + Qualified: { action: 'Qualified — ready to assign', tone: 'success' }, + Assigned: { action: 'Assigned to agent', tone: 'success' }, + Contacted: { action: 'First contact logged', tone: 'info' }, + 'Meeting Scheduled': { action: 'Meeting scheduled', tone: 'accent' }, + 'Product Recommended': { action: 'Product recommended', tone: 'accent' }, + 'Documents Collected': { action: 'KYC documents collected', tone: 'info' }, + 'Eligibility Assessed': { action: 'Eligibility assessed', tone: 'accent' }, + Underwriting: { action: 'Underwriting in progress', tone: 'accent' }, + 'Policy Issued': { action: 'Policy issued', tone: 'success' }, + 'Customer 360': { action: 'Onboarded — Customer 360', tone: 'success' }, + Disqualified: { action: 'Disqualified', tone: 'warning' }, +}; + +export function recordToLead(r: LeadRecord): Lead { + const state = r.current_state_name ?? 'New Lead'; + const ai = aiEmployeeForState(state); + const human = r.owner_agent?.name; + const owner = human ?? ai?.name ?? 'Unassigned'; + const meta = STATE_ACTION[state] ?? { action: state, tone: 'neutral' as Tone }; + + return { + id: String(r.instance_id), + name: r.lead_name ?? `Lead ${r.instance_id}`, + city: r.city ?? r.state_region ?? r.assigned_region ?? '—', + phone: r.phone ?? '—', + email: r.email ?? '—', + dob: formatDate(r.date_of_birth), + age: ageFrom(r.date_of_birth), + income: r.annual_income ?? 0, + occupation: r.occupation ?? '—', + language: r.preferred_language ?? '—', + interest: r.product_interest ?? '—', + score: r.lead_score ?? 0, + segment: toSegment(r.lead_segment), + channel: toChannel(r.channel_source), + owner, + ownerAi: !human && !!ai, + stage: Math.max(0, stageOfState(state)), + lastAction: meta.action, + lastTone: meta.tone, + }; +} + +// --- AI decision stream --- + +function asStringArray(v: unknown): string[] { + if (!v) return []; + if (Array.isArray(v)) { + return v + .map((x) => (typeof x === 'string' ? x : (x as { name?: string; title?: string })?.name ?? (x as { title?: string })?.title ?? '')) + .filter(Boolean); + } + return []; +} + +function firstSentence(text: string): string { + const t = text.trim(); + const m = t.match(/^.*?[.!?](\s|$)/); + return (m ? m[0] : t).trim(); +} + +export function decisionToCard(d: AiDecision): Decision { + const emp = AI_EMPLOYEES[d.ai_user_id] ?? { name: `AI ${d.ai_user_id}`, role: 'AI Employee' }; + const reasoning = (d.reasoning ?? '').trim(); + const activityName = + ACTIVITY_NAME_BY_UID[d.activity_id] ?? + (d.activity_name && !d.activity_name.includes('-') ? d.activity_name : null) ?? + d.instance_state ?? + 'Decision'; + return { + employee: emp.name, + role: emp.role, + activity: activityName, + time: formatTime(d.created_at), + confidence: typeof d.confidence === 'number' ? d.confidence : 0, + summary: reasoning ? firstSentence(reasoning) : 'Decision recorded.', + reasoning, + citations: asStringArray(d.knowledge_sources), + }; +} + +// --- audit timeline --- + +export interface TimelineItem { + actor: string; + ai: boolean; + action: string; + time: string; + tone: Tone; +} + +export function auditToTimeline(entries: AuditEntry[]): TimelineItem[] { + const items = [...entries] + .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + .map((e) => { + const ai = AI_EMPLOYEES[e.user_id]; + const activity = ACTIVITY_NAME_BY_UID[e.activity_id]; + const stateName = STATE_NAME_BY_UID[e.execution_state ?? '']; + const action = activity + ? `performed ${activity}` + : stateName + ? `advanced to ${stateName}` + : 'performed an activity'; + return { + actor: ai?.name ?? (e.user_id === '1' ? 'Admin' : `User ${e.user_id}`), + ai: !!ai, + action, + time: formatTime(e.created_at), + tone: (ai ? 'accent' : 'neutral') as Tone, + }; + }); + + // Collapse consecutive identical (actor + action) entries from trigger fan-out. + return items.filter( + (it, i) => i === 0 || it.actor !== items[i - 1].actor || it.action !== items[i - 1].action, + ); +} diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..639d791 --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,241 @@ +import type { + ActivityResult, + AiDecision, + ApiError, + AuditEntry, + LoginResponse, + RecordViewParams, + RecordViewResponse, + User, +} from './types'; +import { APP_ID, DETAIL_VIEW_IDS, WORKFLOW_UUID } from './config'; + +const TOKEN_KEY = 'lead_to_policy_token'; + +/** + * HTTP client for the Zino gateway (sandbox). + * + * Routes are app-scoped (`/app/385/...`); login + ai-employee monitor are not. + * JWT persists in localStorage so the session survives refresh. + */ +export class ZinoClient { + readonly baseUrl: string; + private token: string | null = null; + private onAuthError?: () => void; + + constructor(baseUrl: string, onAuthError?: () => void) { + this.baseUrl = baseUrl.replace(/\/+$/, ''); + this.onAuthError = onAuthError; + if (typeof window !== 'undefined') this.token = localStorage.getItem(TOKEN_KEY); + } + + setAuthErrorHandler(fn: () => void): void { + this.onAuthError = fn; + } + + setToken(token: string | null): void { + this.token = token; + if (typeof window === 'undefined') return; + if (token) localStorage.setItem(TOKEN_KEY, token); + else localStorage.removeItem(TOKEN_KEY); + } + + getToken(): string | null { + return this.token; + } + + private async request(method: string, path: string, body?: unknown): Promise { + const headers: Record = { 'Content-Type': 'application/json' }; + if (this.token) headers['Authorization'] = `Bearer ${this.token}`; + + return this.send(method, path, { + headers, + body: body !== undefined ? JSON.stringify(body) : undefined, + }); + } + + // Multipart variant — no Content-Type header (the browser sets the boundary). + private async requestForm(path: string, form: FormData): Promise { + const headers: Record = {}; + if (this.token) headers['Authorization'] = `Bearer ${this.token}`; + return this.send('POST', path, { headers, body: form }); + } + + private async send(method: string, path: string, init: RequestInit): Promise { + const res = await fetch(`${this.baseUrl}${path}`, { method, ...init }); + + if (res.status === 401) { + this.setToken(null); + this.onAuthError?.(); + throw { status: 401, message: 'Unauthorized' } as ApiError; + } + if (!res.ok) { + let message = res.statusText; + try { + const j = (await res.json()) as { error?: string }; + if (j.error) message = j.error; + } catch { + /* non-JSON body */ + } + throw { status: res.status, message } as ApiError; + } + if (res.status === 204) return undefined as T; + return (await res.json()) as T; + } + + // --- Auth --- + + async login(email: string, password: string, orgId?: string): Promise { + const res = await this.request('POST', '/usr/login', { + email, + password, + ...(orgId ? { org_id: Number(orgId) } : {}), + }); + this.setToken(res.token); + return res; + } + + logout(): void { + this.setToken(null); + } + + /** Decode the persisted JWT into a User (no network). */ + currentUser(): User | null { + if (!this.token) return null; + try { + const p = JSON.parse(atob(this.token.split('.')[1])); + return { + id: String(p.user_id ?? p.sub ?? ''), + org_id: String(p.org_id ?? ''), + name: p.name ?? '', + email: p.email ?? '', + roles: p.roles ?? [], + groups: p.groups ?? [], + }; + } catch { + return null; + } + } + + // --- Views --- + + recordView(rvUid: string, params: RecordViewParams = {}): Promise { + return this.request('POST', `/app/${APP_ID}/view/recordview`, { + rv_template_uid: rvUid, + search_query: { + page: params.page ?? 1, + limit: params.limit ?? 50, + sort_by: params.sortBy ?? '', + sort_dir: params.sortDir ?? 'desc', + search: params.search ?? '', + filters: (params.filters ?? []).map((f) => ({ + field_key: f.field_key, + value: f.value, + value2: '', + data_type: f.data_type ?? 'string', + })), + }, + }); + } + + detailView(dvUid: string, instanceId: number | string): Promise<{ + config: { fields: Array<{ field_key: string; output_label: string; data_type: string }> }; + data: Record; + }> { + return this.request( + 'GET', + `/app/${APP_ID}/view/detailview/${dvUid}?instance_id=${encodeURIComponent(String(instanceId))}`, + ); + } + + audit(instanceId: number | string): Promise { + return this.request( + 'GET', + `/app/${APP_ID}/view/audit?instance_id=${encodeURIComponent(String(instanceId))}`, + ); + } + + /** AI-employee decision stream for an instance (public monitor endpoint). */ + aiDecisions(instanceId: number | string): Promise { + return this.request<{ decisions: AiDecision[] }>( + 'GET', + `/monitor/decisions?instance_id=${encodeURIComponent(String(instanceId))}`, + ).then((r) => r.decisions ?? []); + } + + // --- Workflow execution (core) --- + + startInstance(activityUid: string, data: Record): Promise { + return this.request('POST', `/app/${APP_ID}/start`, { + workflow_uuid: WORKFLOW_UUID, + activity_id: activityUid, + data, + }); + } + + performActivity( + instanceId: number | string, + activityUid: string, + data: Record, + ): Promise { + return this.request('POST', `/app/${APP_ID}/activity`, { + workflow_uuid: WORKFLOW_UUID, + instance_id: typeof instanceId === 'string' ? Number(instanceId) || instanceId : instanceId, + activity_id: activityUid, + data, + }); + } + + // Convenience — the single lead detail view. + leadDetail(instanceId: number | string) { + return this.detailView(DETAIL_VIEW_IDS.LEAD, instanceId); + } + + // --- RDBMS lookup records (owner-agent picker etc.) --- + + /** Search an RDBMS lookup template. Returns the matching rows. */ + lookupRecords( + templateUid: string, + opts: { activityId: string; fieldId: string; search?: string; instanceId?: number | string; limit?: number }, + ): Promise<{ records: Array> }> { + return this.request('POST', `/app/${APP_ID}/rdbms-templates/${templateUid}/records`, { + workflow_uuid: WORKFLOW_UUID, + activity_id: opts.activityId, + field_id: opts.fieldId, + instance_id: opts.instanceId || undefined, + search: opts.search ?? '', + limit: opts.limit ?? 50, + offset: 0, + form_data: {}, + }); + } + + // --- File upload + OCR (multipart, field-scoped) --- + + private fieldForm(file: File, ctx: FieldContext): FormData { + const form = new FormData(); + form.append('file', file); + form.append('workflow_uuid', WORKFLOW_UUID); + form.append('activity_id', ctx.activityId); + form.append('field_id', ctx.fieldId); + if (ctx.instanceId != null) form.append('instance_id', String(ctx.instanceId)); + return form; + } + + /** Upload a form-field file; returns the FileMeta to store as the field value. */ + uploadFile(file: File, ctx: FieldContext): Promise> { + return this.requestForm(`/app/${APP_ID}/upload`, this.fieldForm(file, ctx)); + } + + /** Run OCR on a document; `extracted` is keyed by the field's extraction keys. */ + extractOcr(file: File, ctx: FieldContext): Promise<{ extracted: Record; raw?: string; parse_error?: string }> { + return this.requestForm(`/app/${APP_ID}/ocr-extract`, this.fieldForm(file, ctx)); + } +} + +/** Identifiers the field-scoped CORE endpoints resolve config + RBAC from. */ +export interface FieldContext { + activityId: string; + fieldId: string; + instanceId?: number | string; +} diff --git a/src/api/config.ts b/src/api/config.ts new file mode 100644 index 0000000..64fab9f --- /dev/null +++ b/src/api/config.ts @@ -0,0 +1,161 @@ +// Aria Lead-to-Policy — sandbox app 385, org 53. +// Workflow `e29c3c33` (v1). All ids verified against postgres-sandbox. + +export const ORG_ID = '53'; +export const APP_ID = '385'; +export const WORKFLOW_UUID = 'e29c3c33-e294-4051-8325-a20de9ed99fc'; + +// Record views (POST /app/385/view/recordview, body.rv_template_uid). +export const RECORD_VIEW_IDS = { + ALL_LEADS: '49a5d651-8055-451a-8348-b1ea65cc6357', // comprehensive — drives the pipeline +} as const; + +// Detail views (GET /app/385/view/detailview/{uid}). +export const DETAIL_VIEW_IDS = { + LEAD: 'aa96f572-b480-4623-ae9c-9ce0c44b8626', +} as const; + +// Onboard activity uid — its submitted fields (welcome_pack_sent, onboarding_notes) +// are `local`, so they never reach the lead recordview. They DO persist in the +// activity submission, surfaced via the audit feed; Customer 360 reads them there. +export const ONBOARD_ACTIVITY_UID = 'ae415f4e-a33e-432e-882f-733238de8bcc'; + +// Activity uids + their field ids (data{} keys). From introspect. +export const ACTIVITIES = { + RECOMMEND_PRODUCT: { + uid: 'c0e2004e-2d70-45f2-9cd5-734331010906', + fields: { + product: 'recommended_product_input', // select + sumAssured: 'sum_assured_input', // number + premiumAmount: 'premium_amount_input', // number + premiumFrequency: 'premium_frequency_input', // select + riders: 'riders_input', // multiselect + notes: 'recommendation_notes', // longtext + }, + }, + COLLECT_DOCUMENTS: { + uid: 'a8eb0faa-17f9-4491-a161-bd6b533adfd1', + fields: { + panOcr: 'pan_ocr_2', // ocr + aadhaarOcr: 'aadhar_ocr_2', // ocr + salarySlip: 'salary_slip_doc_input', // file + aadhaarNumber: 'aadhaar_number_input', // text + panNumber: 'pan_number_input', // text + verifiedIncome: 'verified_income_input', // number + docStatus: 'doc_status_input', // select (req) + }, + }, +} as const; + +// Allowed select/multiselect values (label -> stored value). +export const PRODUCT_OPTIONS = [ + { label: 'Term', value: 'term' }, + { label: 'Whole Life', value: 'whole_life' }, + { label: 'ULIP', value: 'ulip' }, + { label: 'Endowment', value: 'endowment' }, + { label: 'Child', value: 'child' }, + { label: 'Pension', value: 'pension' }, +] as const; + +export const FREQUENCY_OPTIONS = [ + { label: 'Monthly', value: 'monthly' }, + { label: 'Quarterly', value: 'quarterly' }, + { label: 'Half-Yearly', value: 'half_yearly' }, + { label: 'Annual', value: 'annual' }, +] as const; + +export const RIDER_OPTIONS = [ + { label: 'Critical Illness', value: 'critical_illness' }, + { label: 'Accidental Death', value: 'accidental_death' }, + { label: 'Waiver of Premium', value: 'waiver_premium' }, + { label: 'Income Benefit', value: 'income_benefit' }, +] as const; + +export const DOC_STATUS_OPTIONS = [ + { label: 'Pending', value: 'pending' }, + { label: 'Verified', value: 'verified' }, + { label: 'Mismatch', value: 'mismatch' }, + { label: 'Incomplete', value: 'incomplete' }, +] as const; + +// Canonical ordered lifecycle — matches live `current_state_name` 1:1 so a +// state name maps straight to a stepper/kanban index. Terminal "Disqualified" +// is handled out-of-band (STAGE_OF_STATE returns -1). +export const STAGES = [ + 'New Lead', + 'Qualified', + 'Assigned', + 'Contacted', + 'Meeting Scheduled', + 'Product Recommended', + 'Documents Collected', + 'Eligibility Assessed', + 'Underwriting', + 'Policy Issued', + 'Customer 360', +] as const; + +export function stageOfState(stateName?: string): number { + if (!stateName) return 0; + const i = STAGES.indexOf(stateName as (typeof STAGES)[number]); + return i; // -1 for Disqualified / unknown +} + +// UID -> human name maps, for rendering the audit trail (which stores raw +// activity + state UIDs). +export const STATE_NAME_BY_UID: Record = { + 'e469b82f-924e-47d9-9a9f-65b23d270047': 'New Lead', + '41c7a19f-f2fa-4b54-9dd5-310ae982d6c6': 'Qualified', + '8f038dc4-81b7-48c6-ac5b-b28c42b83214': 'Assigned', + 'f2aa6dbd-2149-455a-9466-58bd5beb6997': 'Contacted', + 'd5a2e33a-d2fe-4bfb-816f-61e064b77e97': 'Meeting Scheduled', + '2f33444a-1af0-4fa0-9461-eb8fc79ec175': 'Product Recommended', + 'a0d60916-c332-4d0a-8d02-501e22616b8f': 'Documents Collected', + '0a2a0a66-129a-4b2a-8e4e-1defc5ef85f6': 'Eligibility Assessed', + 'f8158695-5ac7-4e3a-9eda-bbab3fd8bbb3': 'Underwriting', + 'c964fe5e-c2a8-4d30-825c-fefa97a74368': 'Policy Issued', + '3d783ea2-b3d0-4eca-9424-9f8451c5d986': 'Customer 360', + '0ce7794e-30ba-4f09-b81e-26d5c9fec5b7': 'Disqualified', +}; + +export const ACTIVITY_NAME_BY_UID: Record = { + 'cc1a73d5-61e1-4416-af30-2b1eeb623a58': 'Capture Lead', + '6b741e44-37f2-4bb0-90ab-ed139f03b7b3': 'Qualify Lead', + 'da61aaa1-003b-4601-9f42-f93ff230497f': 'Assign Lead', + 'fea6b3a8-846d-4f6f-8e92-033c4cf4b6e2': 'Log First Contact', + 'c444d32f-ed6a-4e36-b8b7-b82e73a141d4': 'Schedule Meeting', + 'c0e2004e-2d70-45f2-9cd5-734331010906': 'Recommend Product', + 'a8eb0faa-17f9-4491-a161-bd6b533adfd1': 'Collect Documents', + 'ba3d3e7f-0d3f-49f2-a156-6cace33c228a': 'Assess Eligibility', + 'ed8ec0dc-4c2c-4d9e-b798-757f6a9d4730': 'Submit Underwriting', + '7b8fb734-a780-4c6a-837b-c5e7ca28e489': 'Issue Policy', + 'ae415f4e-a33e-432e-882f-733238de8bcc': 'Onboard Customer', + 'ea99fc60-caba-45c1-afc1-7386a2fa9220': 'Disqualify', +}; + +// AI employees (user_id -> identity). Used to label the decision stream and +// to attribute the "owner" of an in-flight lead. +export const AI_EMPLOYEES: Record = { + '29180': { name: 'Aria', role: 'Lead Qualifier' }, + '29181': { name: 'Aria Engage', role: 'Calls & Scheduling' }, + '29182': { name: 'Aria Underwrite', role: 'Underwriting' }, +}; + +// Which AI employee is driving a given state (for owner attribution when no +// human sales agent is set). +export function aiEmployeeForState(stateName?: string): { name: string; role: string } | null { + switch (stateName) { + case 'New Lead': + return AI_EMPLOYEES['29180']; + case 'Assigned': + case 'Contacted': + case 'Meeting Scheduled': + case 'Product Recommended': + return AI_EMPLOYEES['29181']; + case 'Documents Collected': + case 'Eligibility Assessed': + return AI_EMPLOYEES['29182']; + default: + return null; + } +} diff --git a/src/api/forms.ts b/src/api/forms.ts new file mode 100644 index 0000000..6370215 --- /dev/null +++ b/src/api/forms.ts @@ -0,0 +1,330 @@ +// Declarative activity-form specs for app 385, mirrored 1:1 from the deployed +// workflow config (`activity_data_fields`). Drives the generic +// and the contextual "Next action" panel on Lead 360. + +export type FieldType = + | 'text' + | 'email' + | 'phone' + | 'number' + | 'date' + | 'datetime' + | 'longtext' + | 'select' + | 'multiselect' + | 'boolean' + | 'lookup' + | 'ocr' + | 'file'; + +export interface FieldOption { + value: string; + label: string; +} + +export interface OcrMapping { + extraction_key: string; + target_field: string; +} + +export interface FieldDef { + id: string; + label: string; + type: FieldType; + required?: boolean; + options?: FieldOption[]; + prefix?: string; + placeholder?: string; + /** Companion/record key to seed from (defaults to id minus the _input suffix). */ + seedKey?: string; + // lookup + lookupTemplate?: string; + lookupDisplay?: string[]; // columns shown in the label + lookupStorage?: string[]; // columns persisted on submit + // ocr + docLabel?: string; // "PAN" / "Aadhaar" + ocrMappings?: OcrMapping[]; +} + +export interface ActivityDef { + uid: string; + name: string; + submitLabel: string; + /** Past-tense confirmation, e.g. "advanced to Qualified". */ + done: string; + fields: FieldDef[]; +} + +const CHANNEL_OPTIONS: FieldOption[] = [ + { value: 'whatsapp', label: 'WhatsApp' }, + { value: 'web', label: 'Web' }, + { value: 'referral', label: 'Referral' }, + { value: 'agency', label: 'Agency' }, + { value: 'bancassurance', label: 'Bancassurance' }, + { value: 'direct', label: 'Direct' }, + { value: 'event', label: 'Event' }, +]; + +const LANGUAGE_OPTIONS: FieldOption[] = [ + { value: 'english', label: 'English' }, + { value: 'hindi', label: 'Hindi' }, + { value: 'tamil', label: 'Tamil' }, + { value: 'telugu', label: 'Telugu' }, + { value: 'kannada', label: 'Kannada' }, + { value: 'marathi', label: 'Marathi' }, + { value: 'bengali', label: 'Bengali' }, +]; + +const PRODUCT_OPTIONS: FieldOption[] = [ + { value: 'term', label: 'Term' }, + { value: 'whole_life', label: 'Whole Life' }, + { value: 'ulip', label: 'ULIP' }, + { value: 'endowment', label: 'Endowment' }, + { value: 'child', label: 'Child' }, + { value: 'pension', label: 'Pension' }, +]; + +// Activity keys are stable, human-readable handles used by STATE_NEXT. +export const ACTIVITY_DEFS = { + capture: { + uid: 'cc1a73d5-61e1-4416-af30-2b1eeb623a58', + name: 'Capture Lead', + submitLabel: 'Create lead', + done: 'created — added to the pipeline as a New Lead', + fields: [ + { id: 'lead_name_input', label: 'Lead name', type: 'text', required: true, placeholder: 'Full name' }, + { id: 'phone_input', label: 'Phone', type: 'phone', required: true, placeholder: '+91 …' }, + { id: 'email_input', label: 'Email', type: 'email', placeholder: 'name@example.com' }, + { id: 'date_of_birth_input', label: 'Date of birth', type: 'date', required: true }, + { id: 'annual_income_input', label: 'Annual income', type: 'number', required: true, prefix: '₹' }, + { id: 'occupation_input', label: 'Occupation', type: 'text' }, + { id: 'city_input', label: 'City', type: 'text' }, + { id: 'state_region_input', label: 'State / region', type: 'text' }, + { id: 'preferred_language_input', label: 'Preferred language', type: 'select', options: LANGUAGE_OPTIONS }, + { id: 'product_interest_input', label: 'Product interest', type: 'select', options: PRODUCT_OPTIONS }, + { id: 'channel_source_input', label: 'Channel source', type: 'select', required: true, options: CHANNEL_OPTIONS }, + ], + }, + qualify: { + uid: '6b741e44-37f2-4bb0-90ab-ed139f03b7b3', + name: 'Qualify Lead', + submitLabel: 'Qualify lead', + done: 'advanced to Qualified', + fields: [ + { id: 'lead_score_input', label: 'Lead score', type: 'number', required: true, placeholder: '0–100' }, + { + id: 'lead_segment_input', + label: 'Lead segment', + type: 'select', + required: true, + options: [ + { value: 'hot', label: 'Hot' }, + { value: 'warm', label: 'Warm' }, + { value: 'cold', label: 'Cold' }, + ], + }, + { id: 'annual_income_input', label: 'Annual income', type: 'number', required: true, prefix: '₹' }, + { id: 'date_of_birth_input', label: 'Date of birth', type: 'date', required: true }, + { id: 'qualify_notes', label: 'Qualification notes', type: 'longtext' }, + ], + }, + assign: { + uid: 'da61aaa1-003b-4601-9f42-f93ff230497f', + name: 'Assign Lead', + submitLabel: 'Assign lead', + done: 'advanced to Assigned', + fields: [ + { id: 'assigned_region_input', label: 'Assigned region', type: 'text', required: true, placeholder: 'e.g. West / Maharashtra' }, + { + id: 'owner_agent_input', + label: 'Owner agent', + type: 'lookup', + seedKey: 'owner_agent', + lookupTemplate: '6ad51dbe-f0b7-4b78-a5f0-00555fac1597', + lookupDisplay: ['name', 'region'], + lookupStorage: ['id', 'name'], + }, + ], + }, + contact: { + uid: 'fea6b3a8-846d-4f6f-8e92-033c4cf4b6e2', + name: 'Log First Contact', + submitLabel: 'Log contact', + done: 'advanced to Contacted', + fields: [ + { + id: 'contact_outcome_input', + label: 'Contact outcome', + type: 'select', + required: true, + options: [ + { value: 'connected', label: 'Connected' }, + { value: 'no_answer', label: 'No Answer' }, + { value: 'call_back', label: 'Call Back' }, + { value: 'not_interested', label: 'Not Interested' }, + { value: 'wrong_number', label: 'Wrong Number' }, + ], + }, + { id: 'next_followup_at_input', label: 'Next follow-up at', type: 'datetime' }, + { id: 'contact_notes_input', label: 'Contact notes', type: 'longtext' }, + ], + }, + schedule: { + uid: 'c444d32f-ed6a-4e36-b8b7-b82e73a141d4', + name: 'Schedule Meeting', + submitLabel: 'Schedule meeting', + done: 'advanced to Meeting Scheduled', + fields: [ + { id: 'meeting_datetime_input', label: 'Meeting date & time', type: 'datetime', required: true }, + { + id: 'meeting_mode_input', + label: 'Meeting mode', + type: 'select', + required: true, + options: [ + { value: 'branch', label: 'Branch' }, + { value: 'video', label: 'Video' }, + { value: 'home_visit', label: 'Home Visit' }, + { value: 'phone', label: 'Phone' }, + ], + }, + { id: 'meeting_address_input', label: 'Meeting address', type: 'text', placeholder: 'Branch / link / address' }, + ], + }, + assess: { + uid: 'ba3d3e7f-0d3f-49f2-a156-6cace33c228a', + name: 'Assess Eligibility', + submitLabel: 'Submit assessment', + done: 'advanced to Eligibility Assessed', + fields: [ + { + id: 'eligibility_status_input', + label: 'Eligibility status', + type: 'select', + required: true, + options: [ + { value: 'Eligible', label: 'Eligible' }, + { value: 'Refer', label: 'Refer' }, + { value: 'Ineligible', label: 'Ineligible' }, + ], + }, + { id: 'eligibility_notes_input', label: 'Eligibility notes', type: 'longtext' }, + ], + }, + underwrite: { + uid: 'ed8ec0dc-4c2c-4d9e-b798-757f6a9d4730', + name: 'Submit Underwriting', + submitLabel: 'Submit underwriting', + done: 'advanced to Underwriting', + fields: [ + { id: 'underwriting_ref_input', label: 'Underwriting reference', type: 'text', required: true, placeholder: 'UW-…' }, + { + id: 'underwriting_status_input', + label: 'Underwriting status', + type: 'select', + required: true, + options: [ + { value: 'submitted', label: 'Submitted' }, + { value: 'approved', label: 'Approved' }, + { value: 'counter_offer', label: 'Counter-Offer' }, + { value: 'declined', label: 'Declined' }, + { value: 'referred', label: 'Referred' }, + ], + }, + ], + }, + issue: { + uid: '7b8fb734-a780-4c6a-837b-c5e7ca28e489', + name: 'Issue Policy', + submitLabel: 'Issue policy', + done: 'advanced to Policy Issued', + fields: [ + { id: 'policy_number_input', label: 'Policy number', type: 'text', required: true, placeholder: 'POL-…' }, + { id: 'policy_issue_date_input', label: 'Policy issue date', type: 'date', required: true }, + { id: 'premium_amount_confirm', label: 'Premium amount', type: 'number', prefix: '₹', seedKey: 'premium_amount' }, + ], + }, + onboard: { + uid: 'ae415f4e-a33e-432e-882f-733238de8bcc', + name: 'Onboard Customer', + submitLabel: 'Complete onboarding', + done: 'advanced to Customer 360', + fields: [ + { id: 'welcome_pack_sent', label: 'Welcome pack sent', type: 'boolean' }, + { id: 'onboarding_notes', label: 'Onboarding notes', type: 'longtext' }, + ], + }, + disqualify: { + uid: 'ea99fc60-caba-45c1-afc1-7386a2fa9220', + name: 'Disqualify', + submitLabel: 'Disqualify lead', + done: 'moved to Disqualified', + fields: [ + { + id: 'disqualify_reason_input', + label: 'Disqualify reason', + type: 'select', + required: true, + options: [ + { value: 'budget', label: 'Budget' }, + { value: 'not_interested', label: 'Not Interested' }, + { value: 'ineligible', label: 'Ineligible' }, + { value: 'unreachable', label: 'Unreachable' }, + { value: 'duplicate', label: 'Duplicate' }, + { value: 'bought_elsewhere', label: 'Bought Elsewhere' }, + ], + }, + { id: 'disqualify_notes_input', label: 'Disqualify notes', type: 'longtext' }, + ], + }, +} satisfies Record; + +export type ActivityKey = keyof typeof ACTIVITY_DEFS; + +// OCR field configs for Collect Documents (extraction → mapped target inputs). +export const OCR_FIELDS: Record = { + pan: { + id: 'pan_ocr_2', + docLabel: 'PAN', + mappings: [{ extraction_key: 'pan_number', target_field: 'pan_number_input' }], + }, + aadhaar: { + id: 'aadhar_ocr_2', + docLabel: 'Aadhaar', + mappings: [{ extraction_key: 'aadhaar_number', target_field: 'aadhaar_number_input' }], + }, +}; + +// Per-state next action. `route` → dedicated rich screen; else inline form. +export interface NextAction { + activity: ActivityKey; + route?: string; + /** Button label for routed steps. */ + label?: string; +} + +export const STATE_NEXT: Record = { + 'New Lead': { activity: 'qualify' }, + Qualified: { activity: 'assign', route: '/assign', label: 'Assign lead' }, + Assigned: { activity: 'contact' }, + Contacted: { activity: 'schedule' }, + 'Meeting Scheduled': { activity: 'qualify', route: '/recommend', label: 'Recommend product' }, + 'Product Recommended': { activity: 'qualify', route: '/documents', label: 'Collect documents' }, + 'Documents Collected': { activity: 'assess' }, + 'Eligibility Assessed': { activity: 'underwrite' }, + Underwriting: { activity: 'issue' }, + 'Policy Issued': { activity: 'onboard' }, +}; + +// States from which Disqualify is a valid (secondary) action. +export const DISQUALIFY_STATES = new Set([ + 'New Lead', + 'Qualified', + 'Assigned', + 'Contacted', + 'Meeting Scheduled', + 'Product Recommended', + 'Documents Collected', + 'Eligibility Assessed', + 'Underwriting', +]); diff --git a/src/api/leads.tsx b/src/api/leads.tsx new file mode 100644 index 0000000..cecc560 --- /dev/null +++ b/src/api/leads.tsx @@ -0,0 +1,47 @@ +import { createContext, useContext, useMemo } from 'react'; +import type { ReactNode } from 'react'; +import type { Lead } from '../types'; +import { useQuery, useZino } from './provider'; +import { recordToLead } from './adapters'; +import { RECORD_VIEW_IDS } from './config'; +import type { LeadRecord } from './types'; + +interface LeadsContextValue { + /** Domain-mapped leads (pipeline list, roster, KPIs). */ + leads: Lead[]; + /** Raw recordview rows — action screens read region/income/etc. from these. */ + records: LeadRecord[]; + loading: boolean; + error: string | null; + refetch: () => void; +} + +const LeadsContext = createContext(null); + +/** + * Fetch the full lead recordview once and share it. Both the sidebar roster + * (AI-employee counts + pipeline subtitle) and the pipeline screen read from + * here, so the list loads with a single request. + */ +export function LeadsProvider({ children }: { children: ReactNode }) { + const { client } = useZino(); + const { data, loading, error, refetch } = useQuery( + () => client.recordView(RECORD_VIEW_IDS.ALL_LEADS, { limit: 200, sortBy: 'instance_id', sortDir: 'desc' }), + [], + ); + + const records = useMemo(() => (data?.data ?? []) as LeadRecord[], [data]); + const leads = useMemo(() => records.map(recordToLead), [records]); + const value = useMemo( + () => ({ leads, records, loading, error, refetch }), + [leads, records, loading, error, refetch], + ); + + return {children}; +} + +export function useLeads(): LeadsContextValue { + const ctx = useContext(LeadsContext); + if (!ctx) throw new Error('useLeads must be used within '); + return ctx; +} diff --git a/src/api/provider.tsx b/src/api/provider.tsx new file mode 100644 index 0000000..9324e04 --- /dev/null +++ b/src/api/provider.tsx @@ -0,0 +1,115 @@ +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import type { ReactNode } from 'react'; +import { ZinoClient } from './client'; +import type { User } from './types'; + +interface ZinoContextValue { + client: ZinoClient; + user: User | null; + login: (email: string, password: string, orgId?: string) => Promise; + logout: () => void; +} + +const ZinoContext = createContext(null); + +export function ZinoProvider({ baseUrl, children }: { baseUrl: string; children: ReactNode }) { + // One client per provider instance. onAuthError clears the user so routes + // bounce back to /login. + const clientRef = useRef(); + if (!clientRef.current) { + clientRef.current = new ZinoClient(baseUrl); + } + const client = clientRef.current; + + // Restore the session synchronously from the persisted JWT so a hard + // navigation to a protected route doesn't flash through /login. + const [user, setUser] = useState(() => client.currentUser()); + + useEffect(() => { + client.setAuthErrorHandler(() => setUser(null)); + }, [client]); + + const value = useMemo( + () => ({ + client, + user, + login: async (email, password, orgId) => { + const res = await client.login(email, password, orgId); + setUser(res.user ?? client.currentUser()); + }, + logout: () => { + client.logout(); + setUser(null); + }, + }), + [client, user], + ); + + return {children}; +} + +export function useZino(): ZinoContextValue { + const ctx = useContext(ZinoContext); + if (!ctx) throw new Error('useZino must be used within '); + return ctx; +} + +// --- minimal data-fetching hook (no extra deps) --- + +export interface QueryState { + data: T | null; + loading: boolean; + error: string | null; + refetch: () => void; +} + +/** + * Fire `fn` whenever a dependency in `deps` changes. Tracks loading/error and + * ignores results from a stale call. `enabled=false` short-circuits. + */ +export function useQuery( + fn: () => Promise, + deps: unknown[], + enabled = true, +): QueryState { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(enabled); + const [error, setError] = useState(null); + const [tick, setTick] = useState(0); + + const refetch = useCallback(() => setTick((t) => t + 1), []); + + useEffect(() => { + if (!enabled) { + setLoading(false); + return; + } + let live = true; + setLoading(true); + setError(null); + fn() + .then((d) => { + if (live) setData(d); + }) + .catch((e: unknown) => { + if (live) setError(e instanceof Error ? e.message : ((e as { message?: string })?.message ?? 'Request failed')); + }) + .finally(() => { + if (live) setLoading(false); + }); + return () => { + live = false; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [...deps, tick, enabled]); + + return { data, loading, error, refetch }; +} diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..5c7e88d --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,122 @@ +// Live API shapes + the SDK's domain types. + +export interface ApiError { + status: number; + message: string; +} + +export interface User { + id: string; + org_id: string; + name: string; + email: string; + roles: string[]; + groups: string[]; +} + +export interface LoginResponse { + token: string; + user: User; +} + +// --- Record view (POST /app/{appId}/view/recordview) --- + +export interface RecordViewField { + field_key: string; + output_label: string; + data_type: string; + is_filter: boolean; + is_search: boolean; +} + +export interface RecordViewResponse { + config: { fields: RecordViewField[] }; + data: Record[]; + pagination?: { page: number; limit: number; total_count: number; total_pages: number }; +} + +export interface RecordViewParams { + page?: number; + limit?: number; + sortBy?: string; + sortDir?: 'asc' | 'desc'; + search?: string; + filters?: Array<{ field_key: string; value: string; data_type?: string }>; +} + +// --- Audit (GET /app/{appId}/view/audit) --- + +export interface AuditEntry { + id: number; + user_id: string; + user_roles: string[]; + activity_id: string; + data: Record; + execution_state?: string; + created_at: string; +} + +// --- AI decisions (GET /monitor/decisions) --- + +export interface AiDecision { + id: number; + ai_user_id: string; + instance_id: string; + activity_id: string; + activity_name?: string; + reasoning: string; + confidence: number; + status: string; + instance_state?: string; + knowledge_sources?: unknown; + tool_calls?: unknown; + created_at: string; +} + +// --- Workflow execution (core) --- + +export interface ActivityResult { + success?: boolean; + instance_id?: number | string; + data?: Record; + message?: string; + status_code?: number; + error?: string; +} + +// A row from the all-leads record view, typed for what we read. +export interface LeadRecord { + instance_id: number; + current_state_name?: string | null; + lead_name?: string | null; + phone?: string | null; + email?: string | null; + city?: string | null; + state_region?: string | null; + date_of_birth?: string | null; + annual_income?: number | null; + occupation?: string | null; + preferred_language?: string | null; + product_interest?: string | null; + lead_score?: number | null; + lead_segment?: string | null; + channel_source?: string | null; + owner_agent?: { id: number; name: string } | null; + assigned_region?: string | null; + sum_assured?: number | null; + premium_amount?: number | null; + premium_frequency?: string | null; + recommended_product?: string | null; + riders?: string | string[] | null; + recommendation_notes?: string | null; + meeting_datetime?: string | null; + meeting_mode?: string | null; + doc_status?: string | null; + eligibility_status?: string | null; + eligibility_notes?: string | null; + pan_number?: string | null; + aadhaar_number?: string | null; + policy_number?: string | null; + policy_issue_date?: string | null; + [key: string]: unknown; +} diff --git a/src/components/core/Avatar.tsx b/src/components/core/Avatar.tsx new file mode 100644 index 0000000..be97292 --- /dev/null +++ b/src/components/core/Avatar.tsx @@ -0,0 +1,56 @@ +import type { CSSProperties } from 'react'; +import { Sparkles } from 'lucide-react'; +import { cn } from '../../lib/cn'; + +export interface AvatarProps { + name?: string; + src?: string; + /** Diameter in px. @default 40 */ + size?: number; + /** Render the AI-employee sunrise glyph instead of initials. @default false */ + ai?: boolean; + className?: string; + style?: CSSProperties; +} + +const COLORS = ['#F26B3A', '#0E9466', '#2480DB', '#7C5BD6', '#D98512', '#D63A52']; + +function hashIdx(s = ''): number { + let h = 0; + for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0; + return h % COLORS.length; +} + +function initials(name = ''): string { + const p = name.trim().split(/\s+/); + return ((p[0]?.[0] || '') + (p[1]?.[0] || '')).toUpperCase(); +} + +/** Circular avatar with deterministic color from name; supports AI-employee glyph. */ +export function Avatar({ name = '', src, size = 40, ai = false, className, style }: AvatarProps) { + const dim: CSSProperties = { width: size, height: size, fontSize: Math.round(size * 0.4) }; + + if (src) { + return ( + {name} + ); + } + + return ( + + {ai ? : initials(name)} + + ); +} diff --git a/src/components/core/Badge.tsx b/src/components/core/Badge.tsx new file mode 100644 index 0000000..7af90d9 --- /dev/null +++ b/src/components/core/Badge.tsx @@ -0,0 +1,51 @@ +import type { ReactNode } from 'react'; +import { cn } from '../../lib/cn'; + +export type BadgeTone = + | 'neutral' + | 'navy' + | 'success' + | 'warning' + | 'danger' + | 'info' + | 'accent'; + +export interface BadgeProps { + children?: ReactNode; + /** @default "neutral" */ + tone?: BadgeTone; + /** Show a leading status dot. @default false */ + dot?: boolean; + /** @default "md" */ + size?: 'sm' | 'md'; + className?: string; +} + +const TONES: Record = { + neutral: { wrap: 'bg-slate-100 text-slate-700', dot: 'bg-slate-500' }, + navy: { wrap: 'bg-navy-900 text-white', dot: 'bg-sunrise-400' }, + success: { wrap: 'bg-emerald-100 text-emerald-700', dot: 'bg-emerald-600' }, + warning: { wrap: 'bg-amber-100 text-amber-700', dot: 'bg-amber-600' }, + danger: { wrap: 'bg-ruby-100 text-ruby-700', dot: 'bg-ruby-600' }, + info: { wrap: 'bg-sky-100 text-sky-700', dot: 'bg-sky-600' }, + accent: { wrap: 'bg-sunrise-100 text-sunrise-600', dot: 'bg-sunrise-500' }, +}; + +/** Compact status pill with optional leading dot. */ +export function Badge({ children, tone = 'neutral', dot = false, size = 'md', className }: BadgeProps) { + const t = TONES[tone]; + const sm = size === 'sm'; + return ( + + {dot && } + {children} + + ); +} diff --git a/src/components/core/Button.tsx b/src/components/core/Button.tsx new file mode 100644 index 0000000..9cbf5a3 --- /dev/null +++ b/src/components/core/Button.tsx @@ -0,0 +1,65 @@ +import type { ButtonHTMLAttributes, ReactNode } from 'react'; +import { cn } from '../../lib/cn'; + +export type ButtonVariant = 'primary' | 'navy' | 'secondary' | 'ghost' | 'danger'; +export type ButtonSize = 'sm' | 'md' | 'lg'; + +export interface ButtonProps extends ButtonHTMLAttributes { + /** Visual style. @default "primary" */ + variant?: ButtonVariant; + /** @default "md" */ + size?: ButtonSize; + iconLeft?: ReactNode; + iconRight?: ReactNode; + /** Stretch to container width. @default false */ + full?: boolean; +} + +const SIZES: Record = { + sm: 'h-[34px] px-3.5 text-[13px] rounded-sm', + md: 'h-[42px] px-[18px] text-base rounded-md', + lg: 'h-[50px] px-6 text-md rounded-md', +}; + +const VARIANTS: Record = { + primary: 'bg-sunrise text-white border border-transparent shadow-sunrise', + navy: 'bg-navy-900 text-on-navy border border-transparent shadow-sm', + secondary: 'bg-card text-strong border border-border-default shadow-xs', + ghost: 'bg-transparent text-body border border-transparent', + danger: 'bg-ruby-600 text-white border border-transparent shadow-sm', +}; + +/** + * Aria primary action button. Sunrise gradient for primary, + * solid navy for secondary, quiet ghost for tertiary. + */ +export function Button({ + children, + variant = 'primary', + size = 'md', + iconLeft, + iconRight, + full = false, + className, + ...rest +}: ButtonProps) { + return ( + + ); +} diff --git a/src/components/core/Card.tsx b/src/components/core/Card.tsx new file mode 100644 index 0000000..6f29d67 --- /dev/null +++ b/src/components/core/Card.tsx @@ -0,0 +1,58 @@ +import type { CSSProperties, ReactNode } from 'react'; +import { cn } from '../../lib/cn'; + +export type Elevation = 'none' | 'xs' | 'sm' | 'md' | 'lg'; + +export interface CardProps { + children?: ReactNode; + /** Optional header title. */ + title?: ReactNode; + /** Optional header trailing node (button, menu). */ + action?: ReactNode; + /** Apply body padding. @default true */ + pad?: boolean; + /** @default "sm" */ + elevation?: Elevation; + className?: string; + bodyClassName?: string; + style?: CSSProperties; +} + +const SHADOW: Record = { + none: 'shadow-none', + xs: 'shadow-xs', + sm: 'shadow-sm', + md: 'shadow-md', + lg: 'shadow-lg', +}; + +/** Surface container with soft elevation. Optional header & padding control. */ +export function Card({ + children, + title, + action, + pad = true, + elevation = 'sm', + className, + bodyClassName, + style, +}: CardProps) { + return ( +
+ {title && ( +
+

{title}

+ {action} +
+ )} +
{children}
+
+ ); +} diff --git a/src/components/core/Input.tsx b/src/components/core/Input.tsx new file mode 100644 index 0000000..6a68bd4 --- /dev/null +++ b/src/components/core/Input.tsx @@ -0,0 +1,54 @@ +import type { InputHTMLAttributes, ReactNode } from 'react'; +import { cn } from '../../lib/cn'; + +export interface InputProps extends Omit, 'prefix'> { + label?: string; + hint?: string; + error?: string; + /** Leading adornment, e.g. "₹". */ + prefix?: ReactNode; + /** Trailing adornment, e.g. "/ year". */ + suffix?: ReactNode; + /** Class for the outer label wrapper. */ + className?: string; +} + +/** Labeled text/number input with optional prefix, suffix, hint and error. */ +export function Input({ + label, + hint, + error, + prefix, + suffix, + required, + className, + ...rest +}: InputProps) { + return ( + + ); +} diff --git a/src/components/core/MultiSelect.tsx b/src/components/core/MultiSelect.tsx new file mode 100644 index 0000000..c99737e --- /dev/null +++ b/src/components/core/MultiSelect.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import { Check, Plus } from 'lucide-react'; +import { cn } from '../../lib/cn'; + +export interface MultiSelectOption { + value: string; + label: string; +} + +export interface MultiSelectProps { + label?: string; + options?: Array; + /** Controlled selected values. */ + value?: string[]; + defaultValue?: string[]; + onChange?: (next: string[]) => void; + className?: string; +} + +/** Chip-based multiselect. Click options to toggle; selected render as filled chips. */ +export function MultiSelect({ + label, + options = [], + value, + defaultValue = [], + onChange, + className, +}: MultiSelectProps) { + const controlled = value !== undefined; + const [internal, setInternal] = useState(defaultValue); + const selected = controlled ? value : internal; + + const set = (next: string[]) => { + if (!controlled) setInternal(next); + onChange?.(next); + }; + const toggle = (v: string) => + set(selected.includes(v) ? selected.filter((x) => x !== v) : [...selected, v]); + + return ( +
+ {label && {label}} +
+ {options.map((o) => { + const val = typeof o === 'string' ? o : o.value; + const lab = typeof o === 'string' ? o : o.label; + const on = selected.includes(val); + return ( + + ); + })} +
+
+ ); +} diff --git a/src/components/core/Select.tsx b/src/components/core/Select.tsx new file mode 100644 index 0000000..ca8921e --- /dev/null +++ b/src/components/core/Select.tsx @@ -0,0 +1,50 @@ +import type { SelectHTMLAttributes } from 'react'; +import { ChevronDown } from 'lucide-react'; +import { cn } from '../../lib/cn'; + +export interface SelectOption { + value: string; + label: string; +} + +export interface SelectProps extends SelectHTMLAttributes { + label?: string; + hint?: string; + /** Either strings or {value,label} objects. */ + options?: Array; + /** Class for the outer label wrapper. */ + className?: string; +} + +/** Labeled native select styled to match Input. */ +export function Select({ label, hint, options = [], required, className, ...rest }: SelectProps) { + return ( + + ); +} diff --git a/src/components/core/index.ts b/src/components/core/index.ts new file mode 100644 index 0000000..c58b84c --- /dev/null +++ b/src/components/core/index.ts @@ -0,0 +1,14 @@ +export { Button } from './Button'; +export type { ButtonProps, ButtonVariant, ButtonSize } from './Button'; +export { Card } from './Card'; +export type { CardProps, Elevation } from './Card'; +export { Badge } from './Badge'; +export type { BadgeProps, BadgeTone } from './Badge'; +export { Avatar } from './Avatar'; +export type { AvatarProps } from './Avatar'; +export { Input } from './Input'; +export type { InputProps } from './Input'; +export { Select } from './Select'; +export type { SelectProps, SelectOption } from './Select'; +export { MultiSelect } from './MultiSelect'; +export type { MultiSelectProps, MultiSelectOption } from './MultiSelect'; diff --git a/src/components/form/ActivityForm.tsx b/src/components/form/ActivityForm.tsx new file mode 100644 index 0000000..16ddcf1 --- /dev/null +++ b/src/components/form/ActivityForm.tsx @@ -0,0 +1,259 @@ +import { useMemo, useState } from 'react'; +import { CheckCircle2 } from 'lucide-react'; +import { Button } from '../core/Button'; +import { Input } from '../core/Input'; +import { Select } from '../core/Select'; +import { MultiSelect } from '../core/MultiSelect'; +import { LookupField } from './LookupField'; +import type { LookupValue } from './LookupField'; +import { useZino } from '../../api/provider'; +import type { ActivityDef, FieldDef } from '../../api/forms'; +import type { LeadRecord } from '../../api/types'; +import type { ActivityResult } from '../../api/types'; + +export interface ActivityFormProps { + def: ActivityDef; + /** Omit for the INIT activity (Capture Lead) — submits via /start. */ + instanceId?: number | string; + /** Seed values from an existing record. */ + record?: LeadRecord | null; + onSuccess?: (res: ActivityResult) => void; + /** Override the green confirmation line. */ + successMessage?: string; +} + +// datetime-local ("YYYY-MM-DDTHH:MM") → RFC 3339 with local TZ offset, the +// format the workflow engine validates datetime fields against. +function toRfc3339(local: string): string { + const d = new Date(local); + if (Number.isNaN(d.getTime())) return local; + const pad = (n: number) => String(n).padStart(2, '0'); + const off = -d.getTimezoneOffset(); + const sign = off >= 0 ? '+' : '-'; + const abs = Math.abs(off); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`; +} + +function seedFor(field: FieldDef, record?: LeadRecord | null): unknown { + const key = field.seedKey ?? field.id.replace(/_input$/, ''); + const fromRecord = record ? (record as Record)[key] : undefined; + if (fromRecord !== undefined && fromRecord !== null) return fromRecord; + switch (field.type) { + case 'multiselect': + return []; + case 'boolean': + return false; + default: + return ''; + } +} + +/** Renders an activity's fields from its declarative spec and submits via + * performActivity (or startInstance when no instanceId is given). */ +export function ActivityForm({ def, instanceId, record, onSuccess, successMessage }: ActivityFormProps) { + const { client } = useZino(); + const [values, setValues] = useState>(() => { + const init: Record = {}; + def.fields.forEach((f) => (init[f.id] = seedFor(f, record))); + return init; + }); + const [submitting, setSubmitting] = useState(false); + const [result, setResult] = useState<{ ok: boolean; msg: string } | null>(null); + + const set = (id: string, v: unknown) => setValues((p) => ({ ...p, [id]: v })); + + const missing = useMemo( + () => + def.fields.filter((f) => { + if (!f.required) return false; + const v = values[f.id]; + if (Array.isArray(v)) return v.length === 0; + return v === '' || v === null || v === undefined; + }), + [def.fields, values], + ); + + async function submit() { + if (missing.length) { + setResult({ ok: false, msg: `Fill required: ${missing.map((m) => m.label).join(', ')}` }); + return; + } + setSubmitting(true); + setResult(null); + + // Drop empty optional values so we don't overwrite with blanks. + const data: Record = {}; + for (const f of def.fields) { + const v = values[f.id]; + if (v === '' || v === null || v === undefined) continue; + if (Array.isArray(v) && v.length === 0) continue; + data[f.id] = + f.type === 'number' ? Number(v) || 0 : f.type === 'datetime' ? toRfc3339(String(v)) : v; + } + + try { + const res = + instanceId != null + ? await client.performActivity(instanceId, def.uid, data) + : await client.startInstance(def.uid, data); + setResult({ ok: true, msg: successMessage ?? `${def.name} ${def.done}.` }); + onSuccess?.(res); + } catch (e) { + setResult({ ok: false, msg: e instanceof Error ? e.message : ((e as { message?: string })?.message ?? 'Submit failed') }); + } finally { + setSubmitting(false); + } + } + + return ( +
+
+ {def.fields.map((f) => ( + set(f.id, v)} + activityId={def.uid} + instanceId={instanceId} + /> + ))} +
+ + {result && ( +
+ {result.ok && } + {result.msg} +
+ )} + +
+ +
+
+ ); +} + +function Field({ + field, + value, + onChange, + activityId, + instanceId, +}: { + field: FieldDef; + value: unknown; + onChange: (v: unknown) => void; + activityId: string; + instanceId?: number | string; +}) { + const full = field.type === 'longtext' || field.type === 'multiselect' || field.type === 'lookup'; + const wrap = full ? 'col-span-full' : ''; + + switch (field.type) { + case 'longtext': + return ( +