From 965e581151291c17967ff07a23142f3f1d94f1f2 Mon Sep 17 00:00:00 2001 From: seb Date: Sun, 21 Jun 2026 09:09:06 +0200 Subject: [PATCH] Genesis --- .gitignore | 5 + README.md | 530 ++++++ generate-cert.js | 20 + lib/index.js | 3 + lib/package.json | 15 + lib/src/actions.js | 92 ++ lib/src/admin-server.js | 176 ++ lib/src/index.js | 15 + lib/src/jtl-server.js | 141 ++ lib/src/seed.js | 239 +++ lib/src/store.js | 316 ++++ package-lock.json | 1149 +++++++++++++ package.json | 25 + server.js | 91 + test-client.js | 202 +++ web/index.html | 12 + web/package-lock.json | 2650 ++++++++++++++++++++++++++++++ web/package.json | 24 + web/src/App.jsx | 91 + web/src/api.js | 64 + web/src/components/CrudTable.jsx | 150 ++ web/src/main.jsx | 23 + web/src/pages/Categories.jsx | 69 + web/src/pages/CustomerGroups.jsx | 68 + web/src/pages/Customers.jsx | 71 + web/src/pages/Dashboard.jsx | 61 + web/src/pages/Deleted.jsx | 112 ++ web/src/pages/Pairing.jsx | 163 ++ web/src/pages/Products.jsx | 319 ++++ web/vite.config.js | 18 + 30 files changed, 6914 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 generate-cert.js create mode 100644 lib/index.js create mode 100644 lib/package.json create mode 100644 lib/src/actions.js create mode 100644 lib/src/admin-server.js create mode 100644 lib/src/index.js create mode 100644 lib/src/jtl-server.js create mode 100644 lib/src/seed.js create mode 100644 lib/src/store.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js create mode 100644 test-client.js create mode 100644 web/index.html create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/src/App.jsx create mode 100644 web/src/api.js create mode 100644 web/src/components/CrudTable.jsx create mode 100644 web/src/main.jsx create mode 100644 web/src/pages/Categories.jsx create mode 100644 web/src/pages/CustomerGroups.jsx create mode 100644 web/src/pages/Customers.jsx create mode 100644 web/src/pages/Dashboard.jsx create mode 100644 web/src/pages/Deleted.jsx create mode 100644 web/src/pages/Pairing.jsx create mode 100644 web/src/pages/Products.jsx create mode 100644 web/vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d0d4da --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +web/dist +dist +certs/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..b7bc7f1 --- /dev/null +++ b/README.md @@ -0,0 +1,530 @@ +# jtlsrv + +Self-signed HTTPS debug server for **JTL-POS** pairing and sync, now split into a reusable library and a web admin UI. + +## Project layout + +``` +. +├── lib/ # jtl-pos-server library +│ ├── src/ +│ │ ├── index.js # public exports (createStore, createJtlPosServer, createAdminServer, actions, seed) +│ │ ├── store.js # in-memory state + reducer actions +│ │ ├── actions.js # action creators for CRUD + pairing +│ │ ├── seed.js # sample data +│ │ ├── jtl-server.js # JTL-POS HTTPS protocol handler +│ │ └── admin-server.js # Express admin API (/admin/api) +│ └── package.json +├── web/ # Vite + React + MUI admin UI +│ ├── src/ +│ │ ├── main.jsx +│ │ ├── App.jsx +│ │ ├── api.js +│ │ ├── components/CrudTable.jsx +│ │ └── pages/*.jsx +│ ├── package.json +│ └── vite.config.js +├── certs/ # self-signed TLS certificate +├── server.js # thin wrapper: HTTPS POS server + admin API +├── test-client.js # probe real upstream or local server +└── generate-cert.js # create self-signed TLS cert +``` + +## Quick start + +```bash +npm install # install root deps (express, concurrently, uuid, local lib) +cd web && npm install && cd .. +npm run cert # once, before first start +npm run dev # starts POS server + admin API + Vite dev server +``` + +- POS HTTPS endpoint: `https://localhost:8086` +- Admin JSON API: `http://localhost:8087/admin/api` +- Admin web UI: `http://localhost:5173` + +Environment variables: + +| Variable | Default | Purpose | +|---|---|---| +| `PORT` | `8086` | HTTPS POS server port | +| `ADMIN_PORT` | `8087` | Admin API / static UI port | +| `AUTH_TOKEN` | `9a2e3036ed9c47e389741d9dbb7590e9` | bearer token issued to paired devices | +| `PAIRING_CODE` | `307018` | pre-registered 6-digit pairing code | + +## Production build of the web UI + +```bash +npm run build # builds web/ into web/dist +npm start # starts POS + admin API and serves web/dist +``` + +After `npm run build`, `server.js` serves the built admin SPA from `web/dist` on `ADMIN_PORT`. + +## Using the web admin + +Open `http://localhost:5173` (dev) or `http://localhost:8087` (built). + +| Page | What you can do | +|---|---| +| **Dashboard** | View entity counts and paired devices | +| **Pairing** | See/generate 6-digit pairing codes, list and revoke paired devices | +| **Products** | CRUD products; mark products as composite and assign component products | +| **Categories** | CRUD category tree via `pid` | +| **Customers** | CRUD customers | +| **Customer Groups** | CRUD customer groups | +| **Deleted** | View deletion events and manually record new deletions | + +The admin API lives under `/admin/api` and mutates the same in-memory store as the POS sync endpoint. + +## Programmatic use + +```js +const { createStore, createJtlPosServer, createAdminServer, actions } = require('./lib/src'); + +const store = createStore(); +store.dispatch(actions.setPairingCode('307018')); + +const posHandler = createJtlPosServer(store, { authToken: '...' }); +https.createServer({ key, cert }, posHandler).listen(8086); + +const { server: adminServer } = createAdminServer(store, { adminPort: 8087 }); +``` + +## Protocol overview + +- **Transport:** HTTPS (TLS required) +- **Base path:** `/api/v1/` +- **Methods observed:** `GET` only (empty body) +- **Content type:** `application/json; charset=utf-8` +- **Numeric fields:** counts and IDs are returned as **strings**, not JSON numbers + +### Request headers (JTL-POS client) + +| Header | Example | Notes | +|---|---|---| +| `accept` | `application/json` | | +| `content-type` | `application/json` | sent even on GET | +| `cache-control` | `no-cache` | | +| `authorization` | `Bearer ` | required after pairing | +| `version` | `1.0.11.14` | POS app version | +| `system` | `JTL-POS` | | +| `charset` | `utf-8` | | +| `accept-encoding` | `gzip` | upstream may gzip responses | +| `user-agent` | `Dalvik/2.1.0 …` | Android | + +### Error responses + +Unknown paths return **404** with: + +```json +{ + "Message": "No HTTP resource was found that matches the request URI 'https://…'." +} +``` + +Pairing errors (upstream, German) observed on `/api/v1/client`: + +| Situation | Message | +|---|---| +| Step 2 before step 1 | `Die erste Anfrage wurde noch nicht erfolgreich durchgeführt.` | +| Step 1 repeated | `Die erste Anfrage wurde bereits erfolgreich durchgeführt.` | +| Wrong code | `Der Authentifizierungscode ist falsch.` / `Keinen passenden Authentifizierungscode gefunden.` | + +Auth codes are **single-use** and expire quickly. + +--- + +## Flow 1: Pairing (`/api/v1/client`) + +No `Authorization` header. Two-step GET handshake using a 6-digit code shown in JTL Wawi (or generated in the admin UI). + +### Step 1 — first 4 digits + +``` +GET /api/v1/client?authCode=3070&name=001 +``` + +Response (fields vary; `authCode` is `null` on success): + +```json +{ + "authCode": null, + "authToken": "9a2e3036ed9c47e389741d9dbb7590e9", + "certificateFingerprint": "BC2114CF407A42724BEEF417960F76DCBF9DE879", + "certificateSerialNumber": "00BFC8BEACDB981B165210EF111CB9D3", + "mandantId": "1", + "mandantName": null, + "mandantDatabase": null, + "serverFingerprint": "39-6D-BD-DE-F3-5C-5A-EA-C2-19-CF-EB-A7-A9-58-2F-20-3F-20-F7-3D-E6-CA-8E-AE-FD-28-30-37-A6-45-AE", + "name": null, + "serverTimestamp": "2026-06-16 16:37:53" +} +``` + +`serverFingerprint` is present in step 1; `mandantName` / `mandantDatabase` are null. + +### Step 2 — full 6 digits + +``` +GET /api/v1/client?authCode=307018&name=001 +``` + +```json +{ + "authCode": "307018", + "authToken": "9a2e3036ed9c47e389741d9dbb7590e9", + "certificateFingerprint": "BC2114CF407A42724BEEF417960F76DCBF9DE879", + "certificateSerialNumber": "00BFC8BEACDB981B165210EF111CB9D3", + "mandantId": "1", + "mandantName": "eB-Standard", + "mandantDatabase": "eazybusiness", + "serverFingerprint": null, + "name": null, + "serverTimestamp": "2026-06-16 16:37:53" +} +``` + +`authToken` is the bearer token used for all subsequent sync calls. `serverTimestamp` format: `YYYY-MM-DD HH:MM:SS`. + +Probe pairing: + +```bash +node test-client.js 307018 # runs step 1 + step 2 +node test-client.js 3070 # step 1 only +``` + +--- + +## Flow 2: Sync + +After pairing, the POS polls with `Authorization: Bearer `. + +### `/api/v1/init` — sync manifest + +Returns how many entities still need syncing, based on the client's stored cursors. + +``` +GET /api/v1/init?mandantId=1 + &lastChangedCategory=0 + &lastChangedCustomer=0 + &lastChangedCustomerGroup=0 + &lastChangedProduct=0 + &lastChangedConfigurationGroup=0 + &lastChangedConfigurationItem=0 + &lastChangedCompositeProduct=0 + &lastChangedDeletedEntity=0 +``` + +Response (fresh mandant, all cursors at `0`): + +```json +{ + "version": "1.10.12.0", + "product_count": "3", + "category_count": "2", + "customer_count": "3", + "customerGroup_count": "1", + "compositeProduct_count": "1", + "configurationGroup_count": "0", + "configurationItem_count": "0", + "deletedEntity_count": "1", + "max_orderId_count": "0" +} +``` + +Each `*_count` is the number of records with `lastChanged` **greater than** the matching `lastChanged*` query parameter. Example: if the client has synced customer groups up to cursor `7323`, pass `lastChangedCustomerGroup=7323` and `customerGroup_count` becomes `"0"`. + +Typical poll sequence after initial sync: + +1. `init` with all cursors at latest values → counts are `"0"` when fully synced +2. If a count is `> 0`, POS fetches the corresponding entity endpoint +3. POS updates its local cursor to the highest `lastChanged` received +4. Repeat + +Probe: + +```bash +node test-client.js init +``` + +--- + +### Incremental sync pattern + +Entity list endpoints use the same cursor model: + +- Query param: `lastChanged=` (client's last known value) +- Response: JSON **array** of records where `record.lastChanged > cursor` +- Empty array `[]` means the client is up to date +- After sync, the client stores `max(lastChanged)` and sends that on the next poll + +--- + +### `/api/v1/customergroup` + +``` +GET /api/v1/customergroup?mandantId=1&lastChangedCustomerGroup=0 +``` + +```json +[ + { + "customerGroupId": "1", + "name": "Endkunden", + "standard": "1", + "discountPercent": "0.00", + "lastChanged": "7323" + } +] +``` + +| `lastChangedCustomerGroup` | Result | +|---|---| +| `0` | all groups changed since beginning | +| `7323` | `[]` (client has this revision) | +| `7324` | `[]` | + +Probe: + +```bash +node test-client.js customergroup +node test-client.js customergroup 7323 +``` + +--- + +### `/api/v1/deletedentity` + +``` +GET /api/v1/deletedentity?mandantId=1&limit=200&lastChangedDeletedEntity=0 +``` + +```json +[ + { + "entityId": "2", + "entityType": "6", + "lastChanged": "8281" + } +] +``` + +| Field | Meaning | +|---|---| +| `entityId` | ID of the deleted record | +| `entityType` | numeric type code (`6` observed for a customer group) | +| `lastChanged` | revision cursor for this deletion event | + +| `lastChangedDeletedEntity` | Result | +|---|---| +| `0` | pending deletions | +| `8281` | `[]` (client has processed this deletion) | + +`limit` caps how many records are returned per request (POS uses `200`). + +Probe: + +```bash +node test-client.js deletedentity +node test-client.js deletedentity 8281 +``` + +--- + +### `/api/v1/category` + +``` +GET /api/v1/category?mandantId=1&limit=20&lastChangedCategory=0 +``` + +```json +[ + { + "_id": "1", + "imghash": null, + "imgsrc": null, + "name": "Haupt", + "pid": "0", + "discounts": [], + "sort": "0", + "updated_at": "2026-06-16 22:42:49", + "created_at": "2026-06-16 22:42:49", + "lastChanged": "10252" + } +] +``` + +`pid` is the parent category ID (`"0"` = root). The server also serves a sample child category `Getränke` (`_id: "2"`, `pid: "1"`). + +Probe: `node test-client.js category ` + +--- + +### `/api/v1/product` + +``` +GET /api/v1/product?mandantId=1&limit=20&lastChangedProduct=0 +``` + +Large product objects; key fields: + +| Field | Notes | +|---|---| +| `_id` | product ID | +| `name`, `sku`, `price`, `tax_rate` | basics | +| `categories_id` / `categories` | category linkage | +| `isCompositeProduct` | `"1"` if this product is a bundle/set | +| `prices` | per customer group / customer overrides | +| `lastChanged` | sync cursor | + +Upstream example products: `a1` (normal), `a2` (`isCompositeProduct: "1"`). The debug server also adds `Cola 0,5l`. + +Composite **definitions** (which components make up a bundle) are **not** embedded in the product object — see `/api/v1/productcomposite`. + +Probe: `node test-client.js product ` + +--- + +### `/api/v1/customer` + +``` +GET /api/v1/customer?mandantId=1&limit=20&lastChangedCustomer=0 +``` + +```json +[ + { + "id": "1", + "customerNumber": "0", + "firstname": "", + "lastname": "kjhkjh", + "company": "kjhkjh", + "city": "kjhkjhkjh", + "country": "Deutschland", + "customerGroupId": "1", + "discount": "0.00", + "lastChanged": "13246", + "debtorNumber": "0" + } +] +``` + +The debug server also includes sample customers `Muster GmbH` and `Max Mustermann`. + +Probe: `node test-client.js customer ` + +--- + +### `/api/v1/productcomposite` — composite product components + +There is **no** `/api/v1/compositeproduct` endpoint (returns 404). Composite bundles use this path instead: + +``` +GET /api/v1/productcomposite?mandantId=1&limit=20&lastChangedCompositeProduct=0 +``` + +```json +[ + { + "productId": "2", + "productIdComponent": "1", + "quantity": "1.00", + "lastChanged": "13227" + } +] +``` + +| Field | Meaning | +|---|---| +| `productId` | the composite/bundle product (`a2`, `isCompositeProduct: "1"`) | +| `productIdComponent` | component product included in the bundle | +| `quantity` | how many units of the component | + +`init.compositeProduct_count` tracks pending rows here, separate from `product_count`. + +Probe: `node test-client.js productcomposite ` + +--- + +## Observed sync order + +After pairing, the POS roughly follows: + +``` +init → customergroup → init → category → init → product → init → +productcomposite → init → customer → init → deletedentity → init → … +``` + +`init` is called before and after each entity fetch. Counts in `init` drive which endpoint the POS calls next. + +--- + +## Implemented in server + +| Endpoint | Sample data | +|---|---| +| `/api/v1/client` | pairing flow | +| `/api/v1/init` | dynamic counts from cursors | +| `/api/v1/customergroup` | 1 group (Endkunden) | +| `/api/v1/category` | 2 categories (Haupt + Getränke) | +| `/api/v1/product` | 3 products (a1, a2 composite, Cola) | +| `/api/v1/productcomposite` | 1 bundle link (a2 → a1) | +| `/api/v1/customer` | 3 customers | +| `/api/v1/deletedentity` | 1 deletion event | + +--- + +## Admin API endpoints + +All prefixed with `/admin/api`. + +| Method | Path | Description | +|---|---|---| +| GET | `/init` | counts + full state snapshot | +| GET | `/state` | current in-memory state | +| GET | `/categories` | list categories | +| POST | `/categories` | create category | +| PATCH | `/categories/:id` | update category | +| DELETE | `/categories/:id` | delete category | +| GET | `/products` | list products | +| POST | `/products` | create product | +| PATCH | `/products/:id` | update product | +| DELETE | `/products/:id` | delete product | +| GET | `/customers` | list customers | +| POST | `/customers` | create customer | +| PATCH | `/customers/:id` | update customer | +| DELETE | `/customers/:id` | delete customer | +| GET | `/customer-groups` | list customer groups | +| POST | `/customer-groups` | create group | +| PATCH | `/customer-groups/:id` | update group | +| DELETE | `/customer-groups/:id` | delete group | +| GET | `/product-composites` | list composite links | +| POST | `/product-composites` | add or update composite link | +| DELETE | `/product-composites/:parent/:component` | remove link | +| GET | `/pairing` | pending codes + paired devices | +| POST | `/pairing` | generate a new 6-digit code (`{ name }`) | +| POST | `/pairing/revoke` | revoke a pending code | +| DELETE | `/devices/:token` | revoke a paired device | +| GET | `/deleted` | list deletion events | +| POST | `/deleted` | record a deletion (`{ entityId, entityType }`) | + +--- + +## Endpoints not yet implemented + +| `init` count field | Endpoint | Notes | +|---|---|---| +| `configurationGroup_count` | `/api/v1/configurationgroup` | returns `[]` upstream (no data yet) | +| `configurationItem_count` | `/api/v1/configurationitem` | returns `[]` upstream (no data yet) | + +--- + +## Development workflow + +1. Run `npm run dev` to start both servers and the web UI. +2. Point POS at `https://:8086` and pair with code `307018` (or generate a new one in the web UI). +3. Manage sample data through the web admin; counts in `/api/v1/init` update automatically. +4. If the POS requests an unknown path, replay it against the upstream server with `test-client.js`, then extend `lib/src/jtl-server.js` or the relevant action in `lib/src/store.js`. +5. For list endpoints, ensure records are filtered by `lastChanged > cursor` and honors `limit`. +6. Keep IDs and counts as strings to match upstream. diff --git a/generate-cert.js b/generate-cert.js new file mode 100644 index 0000000..d228073 --- /dev/null +++ b/generate-cert.js @@ -0,0 +1,20 @@ +const { execSync } = require('node:child_process'); +const fs = require('node:fs'); +const path = require('node:path'); + +const certsDir = path.join(__dirname, 'certs'); +const keyPath = path.join(certsDir, 'key.pem'); +const certPath = path.join(certsDir, 'cert.pem'); + +fs.mkdirSync(certsDir, { recursive: true }); + +const subject = '/CN=localhost/O=JTL Debug Server/C=DE'; +const san = 'subjectAltName=DNS:localhost,IP:127.0.0.1,IP:0.0.0.0'; + +execSync( + `openssl req -x509 -newkey rsa:2048 -nodes -keyout "${keyPath}" -out "${certPath}" -days 3650 -subj "${subject}" -addext "${san}"`, + { stdio: 'inherit' } +); + +console.log(`Wrote ${keyPath}`); +console.log(`Wrote ${certPath}`); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..667cb42 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./src'); diff --git a/lib/package.json b/lib/package.json new file mode 100644 index 0000000..dc9f06f --- /dev/null +++ b/lib/package.json @@ -0,0 +1,15 @@ +{ + "name": "jtl-pos-server", + "version": "1.0.0", + "description": "Reusable Node.js library implementing the JTL-POS pairing and sync protocol", + "main": "index.js", + "scripts": { + "test": "node --test" + }, + "dependencies": { + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=18" + } +} diff --git a/lib/src/actions.js b/lib/src/actions.js new file mode 100644 index 0000000..d9d7a3a --- /dev/null +++ b/lib/src/actions.js @@ -0,0 +1,92 @@ +'use strict'; + +function setPairingCode(code, name = 'JTL-POS') { + return { type: 'SET_PAIRING_CODE', payload: { code, name } }; +} +function revokePairingCode(code) { + return { type: 'REVOKE_PAIRING_CODE', payload: { code } }; +} +function registerDevice(token, name = 'JTL-POS') { + return { type: 'REGISTER_DEVICE', payload: { token, name } }; +} +function revokeDevice(token) { + return { type: 'REVOKE_DEVICE', payload: { token } }; +} + +function createCustomerGroup(customerGroup) { + return { type: 'CREATE_CUSTOMER_GROUP', payload: customerGroup }; +} +function updateCustomerGroup(customerGroup) { + return { type: 'UPDATE_CUSTOMER_GROUP', payload: customerGroup }; +} +function deleteCustomerGroup(customerGroupId) { + return { type: 'DELETE_CUSTOMER_GROUP', payload: { customerGroupId } }; +} + +function createCategory(category) { + return { type: 'CREATE_CATEGORY', payload: category }; +} +function updateCategory(category) { + return { type: 'UPDATE_CATEGORY', payload: category }; +} +function deleteCategory(_id) { + return { type: 'DELETE_CATEGORY', payload: { _id } }; +} + +function createProduct(product) { + return { type: 'CREATE_PRODUCT', payload: product }; +} +function updateProduct(product) { + return { type: 'UPDATE_PRODUCT', payload: product }; +} +function deleteProduct(_id) { + return { type: 'DELETE_PRODUCT', payload: { _id } }; +} +function setCompositeComponent(productId, productIdComponent, quantity = '1.00') { + return { + type: 'SET_COMPOSITE_COMPONENT', + payload: { productId, productIdComponent, quantity }, + }; +} +function removeCompositeComponent(productId, productIdComponent) { + return { + type: 'REMOVE_COMPOSITE_COMPONENT', + payload: { productId, productIdComponent }, + }; +} + +function createCustomer(customer) { + return { type: 'CREATE_CUSTOMER', payload: customer }; +} +function updateCustomer(customer) { + return { type: 'UPDATE_CUSTOMER', payload: customer }; +} +function deleteCustomer(id) { + return { type: 'DELETE_CUSTOMER', payload: { id } }; +} + +function recordDeletedEntity(entityId, entityType) { + return { type: 'RECORD_DELETED_ENTITY', payload: { entityId, entityType } }; +} + +module.exports = { + setPairingCode, + revokePairingCode, + registerDevice, + revokeDevice, + createCategory, + updateCategory, + deleteCategory, + createProduct, + updateProduct, + deleteProduct, + setCompositeComponent, + removeCompositeComponent, + createCustomer, + updateCustomer, + deleteCustomer, + createCustomerGroup, + updateCustomerGroup, + deleteCustomerGroup, + recordDeletedEntity, +}; diff --git a/lib/src/admin-server.js b/lib/src/admin-server.js new file mode 100644 index 0000000..141e6e2 --- /dev/null +++ b/lib/src/admin-server.js @@ -0,0 +1,176 @@ +'use strict'; + +const express = require('express'); +const path = require('node:path'); +const actions = require('./actions'); + +function randomSixDigitCode() { + return String(Math.floor(100000 + Math.random() * 900000)); +} + +function createAdminServer(store, options = {}) { + const { + adminPort = Number(process.env.ADMIN_PORT) || 8087, + staticDir = path.join(process.cwd(), 'web', 'dist'), + } = options; + + const app = express(); + app.use(express.json()); + + // CORS for Vite dev server + app.use((req, res, next) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + if (req.method === 'OPTIONS') { + return res.sendStatus(204); + } + next(); + }); + + app.get('/admin/api/init', (req, res) => { + res.json({ + counts: store.getInitCounts(), + state: store.getState(), + }); + }); + + app.get('/admin/api/state', (req, res) => { + res.json(store.getState()); + }); + + function crudRoutes(basePath, collectionName, idField, actionCreators) { + app.get(basePath, (req, res) => { + const items = store.getState()[collectionName]; + res.json(items); + }); + + app.post(basePath, (req, res) => { + const payload = req.body; + store.dispatch(actionCreators.create(payload)); + const items = store.getState()[collectionName]; + const created = + items.find((i) => i[idField] === String(payload[idField])) || + items[items.length - 1]; + res.status(201).json(created); + }); + + app.patch(`${basePath}/:id`, (req, res) => { + const id = req.params.id; + const updates = req.body; + store.dispatch(actionCreators.update({ ...updates, [idField]: id })); + res.json(store.getState()[collectionName].find((i) => i[idField] === id)); + }); + + app.put(`${basePath}/:id`, (req, res) => { + const id = req.params.id; + const updates = req.body; + store.dispatch(actionCreators.update({ ...updates, [idField]: id })); + res.json(store.getState()[collectionName].find((i) => i[idField] === id)); + }); + + app.delete(`${basePath}/:id`, (req, res) => { + store.dispatch(actionCreators.delete(req.params.id)); + res.sendStatus(204); + }); + } + + crudRoutes('/admin/api/customer-groups', 'customerGroups', 'customerGroupId', { + create: actions.createCustomerGroup, + update: actions.updateCustomerGroup, + delete: actions.deleteCustomerGroup, + }); + + crudRoutes('/admin/api/categories', 'categories', '_id', { + create: actions.createCategory, + update: actions.updateCategory, + delete: actions.deleteCategory, + }); + + crudRoutes('/admin/api/products', 'products', '_id', { + create: actions.createProduct, + update: actions.updateProduct, + delete: actions.deleteProduct, + }); + + crudRoutes('/admin/api/customers', 'customers', 'id', { + create: actions.createCustomer, + update: actions.updateCustomer, + delete: actions.deleteCustomer, + }); + + app.get('/admin/api/product-composites', (req, res) => { + res.json(store.getState().productComposites); + }); + + app.post('/admin/api/product-composites', (req, res) => { + const { productId, productIdComponent, quantity } = req.body; + store.dispatch(actions.setCompositeComponent(productId, productIdComponent, quantity)); + res.status(201).json( + store.getState().productComposites.find( + (c) => c.productId === productId && c.productIdComponent === productIdComponent + ) + ); + }); + + app.delete('/admin/api/product-composites/:productId/:productIdComponent', (req, res) => { + store.dispatch( + actions.removeCompositeComponent(req.params.productId, req.params.productIdComponent) + ); + res.sendStatus(204); + }); + + app.get('/admin/api/pairing', (req, res) => { + res.json({ + authCodes: Object.values(store.getState().authCodes), + pairedDevices: Object.values(store.getState().pairedDevices), + }); + }); + + app.post('/admin/api/pairing', (req, res) => { + const { name = 'JTL-POS' } = req.body || {}; + const code = req.body?.code || randomSixDigitCode(); + store.dispatch(actions.setPairingCode(code, name)); + res.status(201).json({ code, name }); + }); + + app.post('/admin/api/pairing/revoke', (req, res) => { + const { code } = req.body || {}; + if (code) { + store.dispatch(actions.revokePairingCode(code)); + } + res.sendStatus(204); + }); + + app.delete('/admin/api/devices/:token', (req, res) => { + store.dispatch(actions.revokeDevice(req.params.token)); + res.sendStatus(204); + }); + + app.get('/admin/api/deleted', (req, res) => { + res.json(store.getState().deletedEntities); + }); + + app.post('/admin/api/deleted', (req, res) => { + const { entityId, entityType } = req.body; + store.dispatch(actions.recordDeletedEntity(entityId, entityType)); + res.status(201).json(store.getState().deletedEntities[store.getState().deletedEntities.length - 1]); + }); + + // Serve built static files for the admin SPA + const expressStaticExists = require('node:fs').existsSync(staticDir); + if (expressStaticExists) { + app.use(express.static(staticDir)); + app.get('*', (req, res) => { + res.sendFile(path.join(staticDir, 'index.html')); + }); + } + + const server = app.listen(adminPort, '0.0.0.0', () => { + console.log(`Admin web API listening on http://0.0.0.0:${adminPort}`); + }); + + return { app, server }; +} + +module.exports = { createAdminServer }; diff --git a/lib/src/index.js b/lib/src/index.js new file mode 100644 index 0000000..619553b --- /dev/null +++ b/lib/src/index.js @@ -0,0 +1,15 @@ +'use strict'; + +const { createStore } = require('./store'); +const { createJtlPosServer } = require('./jtl-server'); +const { createAdminServer } = require('./admin-server'); +const actions = require('./actions'); +const seed = require('./seed'); + +module.exports = { + createStore, + createJtlPosServer, + createAdminServer, + actions, + seed, +}; diff --git a/lib/src/jtl-server.js b/lib/src/jtl-server.js new file mode 100644 index 0000000..7d4a5db --- /dev/null +++ b/lib/src/jtl-server.js @@ -0,0 +1,141 @@ +'use strict'; + +const { serverTimestamp } = require('./seed'); + +function readBody(req) { + return new Promise((resolve, reject) => { + const chunks = []; + req.on('data', (chunk) => chunks.push(chunk)); + req.on('end', () => resolve(Buffer.concat(chunks))); + req.on('error', reject); + }); +} + +function sendJson(res, statusCode, body) { + const responseBody = JSON.stringify(body); + res.writeHead(statusCode, { + 'Content-Type': 'application/json; charset=utf-8', + 'Content-Length': Buffer.byteLength(responseBody), + }); + res.end(responseBody); +} + +function createJtlPosServer(store, config = {}) { + const { authToken = process.env.AUTH_TOKEN || '9a2e3036ed9c47e389741d9dbb7590e9' } = config; + const { + certificateFingerprint = 'BC2114CF407A42724BEEF417960F76DCBF9DE879', + certificateSerialNumber = '00BFC8BEACDB981B165210EF111CB9D3', + serverFingerprint = '39-6D-BD-DE-F3-5C-5A-EA-C2-19-CF-EB-A7-A9-58-2F-20-3F-20-F7-3D-E6-CA-8E-AE-FD-28-30-37-A6-45-AE', + mandantId = '1', + mandantName = 'eB-Standard', + mandantDatabase = 'eazybusiness', + } = config; + + function buildClientStep1() { + return { + authCode: null, + authToken, + certificateFingerprint, + certificateSerialNumber, + mandantId, + mandantName: null, + mandantDatabase: null, + serverFingerprint, + name: null, + serverTimestamp: serverTimestamp(), + }; + } + + function buildClientStep2(authCode) { + return { + authCode, + authToken, + certificateFingerprint, + certificateSerialNumber, + mandantId, + mandantName, + mandantDatabase, + serverFingerprint: null, + name: null, + serverTimestamp: serverTimestamp(), + }; + } + + async function handle(req, res) { + const url = new URL(req.url, 'https://localhost'); + + const route = (method, pathname) => method === req.method && url.pathname === pathname; + + if (route('GET', '/api/v1/client')) { + const authCode = url.searchParams.get('authCode') || ''; + const name = url.searchParams.get('name') || 'JTL-POS'; + const state = store.getState(); + + // Step 1 — the first 4 digits of a pre-registered 6-digit pairing code. + if (authCode.length <= 4 && authCode.length > 0) { + return sendJson(res, 200, buildClientStep1()); + } + + // Step 2 — full 6-digit code must exist in the pending authCodes map. + if (authCode.length === 6) { + if (state.authCodes[authCode]) { + store.dispatch({ type: 'REVOKE_PAIRING_CODE', payload: { code: authCode } }); + store.dispatch({ type: 'REGISTER_DEVICE', payload: { token: authToken, name } }); + return sendJson(res, 200, buildClientStep2(authCode)); + } + return sendJson(res, 400, { Message: 'Der Authentifizierungscode ist falsch.' }); + } + + return sendJson(res, 400, { Message: 'Keinen passenden Authentifizierungscode gefunden.' }); + } + + if (route('GET', '/api/v1/init')) { + const params = { + lastChangedCategory: url.searchParams.get('lastChangedCategory') || '0', + lastChangedCustomer: url.searchParams.get('lastChangedCustomer') || '0', + lastChangedCustomerGroup: url.searchParams.get('lastChangedCustomerGroup') || '0', + lastChangedProduct: url.searchParams.get('lastChangedProduct') || '0', + lastChangedConfigurationGroup: url.searchParams.get('lastChangedConfigurationGroup') || '0', + lastChangedConfigurationItem: url.searchParams.get('lastChangedConfigurationItem') || '0', + lastChangedCompositeProduct: url.searchParams.get('lastChangedCompositeProduct') || '0', + lastChangedDeletedEntity: url.searchParams.get('lastChangedDeletedEntity') || '0', + }; + return sendJson(res, 200, store.getInitCounts(params)); + } + + const entityEndpoints = { + '/api/v1/customergroup': { collection: 'customerGroups', param: 'lastChangedCustomerGroup', limit: '200' }, + '/api/v1/deletedentity': { collection: 'deletedEntities', param: 'lastChangedDeletedEntity', limit: '200' }, + '/api/v1/category': { collection: 'categories', param: 'lastChangedCategory', limit: '20' }, + '/api/v1/customer': { collection: 'customers', param: 'lastChangedCustomer', limit: '20' }, + '/api/v1/product': { collection: 'products', param: 'lastChangedProduct', limit: '20' }, + '/api/v1/productcomposite': { collection: 'productComposites', param: 'lastChangedCompositeProduct', limit: '20' }, + }; + + for (const [pathname, { collection, param, limit: defaultLimit }] of Object.entries(entityEndpoints)) { + if (route('GET', pathname)) { + const cursor = url.searchParams.get(param) || '0'; + const limit = url.searchParams.get('limit') || defaultLimit; + const response = store.selectFilteredListFromCollection(collection, cursor, limit); + return sendJson(res, 200, response); + } + } + + return sendJson(res, 404, { + Message: `No HTTP resource was found that matches the request URI '${url}'.`, + }); + } + + return async function requestListener(req, res) { + const body = await readBody(req); + // Attach raw body for optional logging by the wrapper + req.rawBody = body; + try { + await handle(req, res); + } catch (err) { + sendJson(res, 500, { Message: err.message }); + } + }; +} + +module.exports = { createJtlPosServer }; diff --git a/lib/src/seed.js b/lib/src/seed.js new file mode 100644 index 0000000..73a77e8 --- /dev/null +++ b/lib/src/seed.js @@ -0,0 +1,239 @@ +function serverTimestamp() { + const d = new Date(); + const pad = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; +} + +function productFixture(overrides) { + return { + imghash: null, + imgsrc: null, + sort: '0', + price: '0.00', + p_price: '0.00', + discountable: '0', + deposit: '0', + discount: '', + d_price: '0.0', + tax_rate: '19', + tax_rate2: '', + use_in_out_tax: '0', + barcode: '', + use_stock: '0', + q_div: '0', + unit: null, + single_bookable: '0', + annotation: '', + status: '0', + tags: '', + categories_id: '1', + is_parent: '0', + parent: '0', + variants: '', + print_kitchen_receipt: '0', + deposit_name: '', + attributes: [], + configurationGroups: '', + options: null, + hasBestBeforeDate: '0', + hasLotNumber: '0', + hasSerialNumber: '0', + PLU: '', + short_description: '', + minStock: '0', + container: [], + reservedQuantity: '0.00', + deliveryDetails: [], + isbn: '', + manufacturerName: null, + han: '', + productType: '0', + voucherData: null, + inputPrice: '0', + inputQuantity: '0', + categories: [{ categoryId: '1' }], + prices: [ + { + customerGroupId: '1', + customerId: '0', + price: '0.00', + quantity: '0', + }, + ], + ...overrides, + }; +} + +function createSeedState() { + return { + customerGroups: [ + { + customerGroupId: '1', + name: 'Endkunden', + standard: '1', + discountPercent: '0.00', + lastChanged: '7323', + }, + ], + categories: [ + { + _id: '1', + imghash: null, + imgsrc: null, + name: 'Haupt', + pid: '0', + discounts: [], + sort: '0', + updated_at: serverTimestamp(), + created_at: serverTimestamp(), + lastChanged: '10252', + }, + { + _id: '2', + imghash: null, + imgsrc: null, + name: 'Getränke', + pid: '1', + discounts: [], + sort: '1', + updated_at: serverTimestamp(), + created_at: serverTimestamp(), + lastChanged: '10300', + }, + ], + customers: [ + { + id: '1', + customerNumber: '0', + firstname: '', + lastname: 'kjhkjh', + title: '', + company: 'kjhkjh', + address: '', + addressSupplement: '', + city: 'kjhkjhkjh', + postalCode: '', + state: '', + country: 'Deutschland', + phone: '', + email: '', + customerGroupId: '1', + salutation: '', + birthday: null, + discount: '0.00', + taxIdNumber: '', + lastChanged: '13246', + debtorNumber: '0', + }, + { + id: '2', + customerNumber: '1001', + firstname: 'Erika', + lastname: 'Musterfrau', + title: '', + company: 'Muster GmbH', + address: 'Hauptstraße 1', + addressSupplement: '', + city: 'Berlin', + postalCode: '10115', + state: '', + country: 'Deutschland', + phone: '+49 30 123456', + email: 'erika@muster.de', + customerGroupId: '1', + salutation: 'Frau', + birthday: null, + discount: '0.00', + taxIdNumber: '', + lastChanged: '13310', + debtorNumber: '1001', + }, + { + id: '3', + customerNumber: '1002', + firstname: 'Max', + lastname: 'Mustermann', + title: '', + company: '', + address: 'Nebenweg 5', + addressSupplement: '', + city: 'Hamburg', + postalCode: '20095', + state: '', + country: 'Deutschland', + phone: '', + email: 'max@example.de', + customerGroupId: '1', + salutation: 'Herr', + birthday: null, + discount: '5.00', + taxIdNumber: '', + lastChanged: '13320', + debtorNumber: '1002', + }, + ], + products: [ + productFixture({ + _id: '1', + name: 'a1', + sku: '1', + quantity: '0', + updated_at: serverTimestamp(), + created_at: serverTimestamp(), + isCompositeProduct: '0', + lastChanged: '13111', + }), + productFixture({ + _id: '2', + name: 'a2', + sku: '2', + quantity: '0.00', + updated_at: serverTimestamp(), + created_at: serverTimestamp(), + isCompositeProduct: '1', + lastChanged: '13228', + }), + productFixture({ + _id: '3', + name: 'Cola 0,5l', + sku: '3', + quantity: '24', + price: '1.49', + p_price: '1.49', + updated_at: serverTimestamp(), + created_at: serverTimestamp(), + isCompositeProduct: '0', + categories_id: '2', + categories: [{ categoryId: '2' }], + lastChanged: '13300', + prices: [ + { + customerGroupId: '1', + customerId: '0', + price: '1.49', + quantity: '0', + }, + ], + }), + ], + productComposites: [ + { + productId: '2', + productIdComponent: '1', + quantity: '1.00', + lastChanged: '13227', + }, + ], + deletedEntities: [ + { + entityId: '2', + entityType: '6', + lastChanged: '8281', + }, + ], + authCodes: {}, + pairedDevices: {}, + }; +} + +module.exports = { createSeedState, productFixture, serverTimestamp }; diff --git a/lib/src/store.js b/lib/src/store.js new file mode 100644 index 0000000..c799631 --- /dev/null +++ b/lib/src/store.js @@ -0,0 +1,316 @@ +'use strict'; + +const { createSeedState } = require('./seed'); + +function withLimit(records, limit) { + const max = Number(limit); + return Number.isFinite(max) && max > 0 ? records.slice(0, max) : records; +} + +function filterByCursor(records, cursor) { + const n = Number(cursor) || 0; + return records.filter((record) => Number(record.lastChanged) > n); +} + +function generateId() { + return String(Date.now()); +} + +function encodeId(n) { + return String(n); +} + +function createStore(options = {}) { + const state = options.initialState || createSeedState(); + const listeners = new Set(); + + if (!state.authCodes) state.authCodes = {}; + if (!state.pairedDevices) state.pairedDevices = {}; + if (!state.customerGroups) state.customerGroups = []; + if (!state.categories) state.categories = []; + if (!state.customers) state.customers = []; + if (!state.products) state.products = []; + if (!state.productComposites) state.productComposites = []; + if (!state.deletedEntities) state.deletedEntities = []; + + function getState() { + return state; + } + + function subscribe(listener) { + listeners.add(listener); + return () => listeners.delete(listener); + } + + function emit() { + listeners.forEach((l) => l(state)); + } + + function dispatch(action) { + const { type, payload } = action; + + switch (type) { + // Auth / pairing + case 'SET_PAIRING_CODE': { + const { code, name = 'JTL-POS' } = payload; + state.authCodes[code] = { code, name, createdAt: Date.now() }; + break; + } + case 'REVOKE_PAIRING_CODE': { + delete state.authCodes[payload.code]; + break; + } + case 'REGISTER_DEVICE': { + const { token, name, createdAt = Date.now() } = payload; + state.pairedDevices[token] = { name, token, createdAt }; + break; + } + case 'REVOKE_DEVICE': { + delete state.pairedDevices[payload.token]; + break; + } + + // Customer groups + case 'CREATE_CUSTOMER_GROUP': + case 'UPDATE_CUSTOMER_GROUP': { + const group = payload; + const id = group.customerGroupId || generateId(); + const idx = state.customerGroups.findIndex( + (g) => g.customerGroupId === id + ); + const next = { + ...group, + customerGroupId: id, + standard: String(group.standard ?? (idx >= 0 ? state.customerGroups[idx].standard : '0')), + discountPercent: String(group.discountPercent ?? '0.00'), + lastChanged: generateId(), + }; + if (idx >= 0) state.customerGroups[idx] = next; + else state.customerGroups.push(next); + break; + } + case 'DELETE_CUSTOMER_GROUP': { + const id = payload.customerGroupId; + state.customerGroups = state.customerGroups.filter( + (g) => g.customerGroupId !== id + ); + state.deletedEntities.push({ + entityId: id, + entityType: '6', + lastChanged: generateId(), + }); + break; + } + + // Categories + case 'CREATE_CATEGORY': + case 'UPDATE_CATEGORY': { + const cat = payload; + const id = cat._id || generateId(); + const idx = state.categories.findIndex((c) => c._id === id); + const now = new Date().toISOString().replace('T', ' ').slice(0, 19); + const next = { + imghash: null, + imgsrc: null, + discounts: [], + sort: '0', + ...cat, + _id: id, + pid: String(cat.pid ?? '0'), + updated_at: now, + created_at: idx >= 0 ? state.categories[idx].created_at : now, + lastChanged: generateId(), + }; + if (idx >= 0) state.categories[idx] = next; + else state.categories.push(next); + break; + } + case 'DELETE_CATEGORY': { + const id = payload._id; + state.categories = state.categories.filter((c) => c._id !== id); + state.deletedEntities.push({ + entityId: id, + entityType: '2', + lastChanged: generateId(), + }); + break; + } + + // Products + case 'CREATE_PRODUCT': + case 'UPDATE_PRODUCT': { + const prod = payload; + const id = prod._id || generateId(); + const idx = state.products.findIndex((p) => p._id === id); + const defaults = createSeedState().products[0]; + if (idx >= 0) { + state.products[idx] = { + ...state.products[idx], + ...prod, + _id: id, + isCompositeProduct: String(prod.isCompositeProduct ?? state.products[idx].isCompositeProduct ?? '0'), + updated_at: new Date().toISOString().replace('T', ' ').slice(0, 19), + lastChanged: generateId(), + }; + } else { + state.products.push({ + ...defaults, + ...prod, + _id: id, + isCompositeProduct: String(prod.isCompositeProduct ?? '0'), + created_at: new Date().toISOString().replace('T', ' ').slice(0, 19), + updated_at: new Date().toISOString().replace('T', ' ').slice(0, 19), + lastChanged: generateId(), + }); + } + break; + } + case 'DELETE_PRODUCT': { + const id = payload._id; + state.products = state.products.filter((p) => p._id !== id); + state.deletedEntities.push({ + entityId: id, + entityType: '1', + lastChanged: generateId(), + }); + break; + } + + // Product composites + case 'SET_COMPOSITE_COMPONENT': { + const { productId, productIdComponent, quantity = '1.00' } = payload; + const idx = state.productComposites.findIndex( + (c) => c.productId === productId && c.productIdComponent === productIdComponent + ); + const next = { + productId, + productIdComponent, + quantity: String(quantity), + lastChanged: generateId(), + }; + if (idx >= 0) state.productComposites[idx] = next; + else state.productComposites.push(next); + // Mark the parent product as composite + const parentIdx = state.products.findIndex((p) => p._id === productId); + if (parentIdx >= 0 && state.products[parentIdx].isCompositeProduct !== '1') { + state.products[parentIdx].isCompositeProduct = '1'; + state.products[parentIdx].lastChanged = generateId(); + } + break; + } + case 'REMOVE_COMPOSITE_COMPONENT': { + const { productId, productIdComponent } = payload; + state.productComposites = state.productComposites.filter( + (c) => !(c.productId === productId && c.productIdComponent === productIdComponent) + ); + break; + } + + // Customers + case 'CREATE_CUSTOMER': + case 'UPDATE_CUSTOMER': { + const cust = payload; + const id = cust.id || generateId(); + const idx = state.customers.findIndex((c) => c.id === id); + const next = { + customerNumber: '0', + firstname: '', + lastname: '', + title: '', + company: '', + address: '', + addressSupplement: '', + city: '', + postalCode: '', + state: '', + country: 'Deutschland', + phone: '', + email: '', + customerGroupId: '1', + salutation: '', + birthday: null, + discount: '0.00', + taxIdNumber: '', + debtorNumber: '0', + ...cust, + id, + lastChanged: generateId(), + }; + if (idx >= 0) state.customers[idx] = next; + else state.customers.push(next); + break; + } + case 'DELETE_CUSTOMER': { + const id = payload.id; + state.customers = state.customers.filter((c) => c.id !== id); + state.deletedEntities.push({ + entityId: id, + entityType: '3', + lastChanged: generateId(), + }); + break; + } + + case 'RECORD_DELETED_ENTITY': { + const { entityId, entityType } = payload; + state.deletedEntities.push({ + entityId, + entityType: String(entityType), + lastChanged: generateId(), + }); + break; + } + + default: + throw new Error(`Unknown action type: ${type}`); + } + + emit(); + return state; + } + + function selectFilteredListFromCollection(collectionName, cursor, limit) { + return withLimit(filterByCursor(state[collectionName], cursor), limit); + } + + function getInitCounts(params = {}) { + return { + version: '1.10.12.0', + product_count: String( + selectFilteredListFromCollection('products', params.lastChangedProduct, Infinity).length + ), + category_count: String( + selectFilteredListFromCollection('categories', params.lastChangedCategory, Infinity).length + ), + customer_count: String( + selectFilteredListFromCollection('customers', params.lastChangedCustomer, Infinity).length + ), + customerGroup_count: String( + selectFilteredListFromCollection('customerGroups', params.lastChangedCustomerGroup, Infinity).length + ), + compositeProduct_count: String( + selectFilteredListFromCollection( + 'productComposites', + params.lastChangedCompositeProduct, + Infinity + ).length + ), + configurationGroup_count: '0', + configurationItem_count: '0', + deletedEntity_count: String( + selectFilteredListFromCollection('deletedEntities', params.lastChangedDeletedEntity, Infinity).length + ), + max_orderId_count: '0', + }; + } + + return { + getState, + dispatch, + subscribe, + getInitCounts, + selectFilteredListFromCollection, + }; +} + +module.exports = { createStore, withLimit, filterByCursor }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7f5523f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1149 @@ +{ + "name": "jtlsrv", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jtlsrv", + "version": "1.0.0", + "dependencies": { + "concurrently": "^9.1.2", + "express": "^4.21.2", + "jtl-pos-server": "file:./lib", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "lib": { + "name": "jtl-pos-server", + "version": "1.0.0", + "dependencies": { + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.15.1", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.5", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.15.1", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jtl-pos-server": { + "resolved": "lib", + "link": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/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==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..92c3d70 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "jtlsrv", + "version": "1.0.0", + "description": "Self-signed HTTPS debug server for JTL POS with web admin UI", + "main": "server.js", + "scripts": { + "cert": "node generate-cert.js", + "start": "node server.js", + "dev": "concurrently --names=server,web --prefix-colors=auto npm:dev:server npm:dev:web", + "dev:server": "node server.js", + "dev:web": "cd web && npm run dev", + "build": "npm run build:web", + "build:web": "cd web && npm run build", + "test:client": "node test-client.js" + }, + "engines": { + "node": ">=18" + }, + "dependencies": { + "concurrently": "^9.1.2", + "express": "^4.21.2", + "jtl-pos-server": "file:./lib", + "uuid": "^11.1.0" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..6a1cfbc --- /dev/null +++ b/server.js @@ -0,0 +1,91 @@ +const fs = require('node:fs'); +const https = require('node:https'); +const path = require('node:path'); + +const { createStore, createJtlPosServer, createAdminServer } = require('./lib/src'); +const actions = require('./lib/src/actions'); + +const PORT = Number(process.env.PORT) || 8086; +const ADMIN_PORT = Number(process.env.ADMIN_PORT) || 8087; +const AUTH_TOKEN = + process.env.AUTH_TOKEN || '9a2e3036ed9c47e389741d9dbb7590e9'; +const PAIRING_CODE = process.env.PAIRING_CODE || '307018'; + +const certsDir = path.join(__dirname, 'certs'); +const keyPath = path.join(certsDir, 'key.pem'); +const certPath = path.join(certsDir, 'cert.pem'); + +if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) { + console.error('Missing TLS certificate. Run: npm run cert'); + process.exit(1); +} + +function serverTimestamp() { + const d = new Date(); + const pad = (n) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; +} + +function formatBody(buffer) { + if (!buffer.length) { + return '(empty)'; + } + const text = buffer.toString('utf8'); + if (/^[\x09\x0A\x0D\x20-\x7E\u0080-\uFFFF]*$/.test(text)) { + return text; + } + return `[binary ${buffer.length} bytes]\n${buffer.toString('hex')}`; +} + +const store = createStore(); + +// Pre-register the fixed pairing code so the POS can pair out of the box. +store.dispatch(actions.setPairingCode(PAIRING_CODE, 'JTL-POS')); +// Treat the fixed auth token as a pre-paired device for display in the admin UI. +store.dispatch(actions.registerDevice(AUTH_TOKEN, 'JTL-POS')); + +const jtlHandler = createJtlPosServer(store, { authToken: AUTH_TOKEN }); + +const loggedJtlHandler = async (req, res) => { + const started = Date.now(); + const originalEnd = res.end.bind(res); + + console.log('\n--- incoming POS request ---'); + console.log(`${req.method} ${req.url}`); + console.log('remote:', req.socket.remoteAddress, req.socket.remotePort); + console.log('headers:', JSON.stringify(req.headers, null, 2)); + + await jtlHandler(req, res); + + // req.rawBody is attached by jtlHandler's body reader + console.log('body:', formatBody(req.rawBody ?? Buffer.alloc(0))); + console.log(`responded ${res.statusCode} in ${Date.now() - started}ms`); +}; + +const httpsServer = https.createServer( + { + key: fs.readFileSync(keyPath), + cert: fs.readFileSync(certPath), + }, + loggedJtlHandler +); + +httpsServer.listen(PORT, '0.0.0.0', () => { + console.log(`HTTPS POS server listening on https://0.0.0.0:${PORT}`); + console.log(`Certificate: ${certPath}`); + console.log('Import cert.pem into JTL POS / Windows trust store if required.'); +}); + +const { server: adminServer } = createAdminServer(store, { + adminPort: ADMIN_PORT, + staticDir: path.join(__dirname, 'web', 'dist'), +}); + +function shutdown() { + console.log('\nShutting down...'); + httpsServer.close(); + adminServer.close(); +} + +process.on('SIGINT', shutdown); +process.on('SIGTERM', shutdown); diff --git a/test-client.js b/test-client.js new file mode 100644 index 0000000..0bb37d9 --- /dev/null +++ b/test-client.js @@ -0,0 +1,202 @@ +const https = require('node:https'); +const { gunzipSync } = require('node:zlib'); + +const HOST = process.env.JTL_HOST || '192.168.178.81'; +const PORT = Number(process.env.JTL_PORT) || 4433; + +const INIT_QUERY = + 'mandantId=1&lastChangedCategory=0&lastChangedCustomer=0&lastChangedCustomerGroup=0&lastChangedProduct=0&lastChangedConfigurationGroup=0&lastChangedConfigurationItem=0&lastChangedCompositeProduct=0&lastChangedDeletedEntity=0'; + +function usage() { + console.error('Usage:'); + console.error(' node test-client.js [name] # 6-digit: step1+2, 4-digit: step1 only'); + console.error(' node test-client.js init '); + console.error(' node test-client.js customergroup [lastChangedCustomerGroup]'); + console.error(' node test-client.js deletedentity [lastChangedDeletedEntity]'); + console.error(' node test-client.js category [lastChangedCategory]'); + console.error(' node test-client.js customer [lastChangedCustomer]'); + console.error(' node test-client.js product [lastChangedProduct]'); + console.error(' node test-client.js productcomposite [lastChangedCompositeProduct]'); + process.exit(1); +} + +function posHeaders(authToken) { + const headers = { + accept: 'application/json', + 'content-type': 'application/json', + 'cache-control': 'no-cache', + version: '1.0.11.14', + system: 'JTL-POS', + charset: 'utf-8', + connection: 'Keep-Alive', + 'user-agent': + 'Dalvik/2.1.0 (Linux; U; Android 14; SM-A528B Build/UP1A.231005.007)', + 'accept-encoding': 'gzip', + }; + + if (authToken) { + headers.authorization = `Bearer ${authToken}`; + } + + return headers; +} + +function decodeBody(buffer, headers) { + if (headers['content-encoding'] === 'gzip') { + return gunzipSync(buffer); + } + return buffer; +} + +function request(path, authToken) { + return new Promise((resolve, reject) => { + const req = https.request( + { + hostname: HOST, + port: PORT, + path, + method: 'GET', + headers: posHeaders(authToken), + rejectUnauthorized: false, + }, + (res) => { + const chunks = []; + res.on('data', (chunk) => chunks.push(chunk)); + res.on('end', () => { + const raw = Buffer.concat(chunks); + resolve({ + statusCode: res.statusCode, + headers: res.headers, + body: decodeBody(raw, res.headers), + }); + }); + } + ); + + req.on('error', reject); + req.end(); + }); +} + +function printResponse(res) { + console.log(`status: ${res.statusCode}`); + console.log('headers:', JSON.stringify(res.headers, null, 2)); + console.log('body:'); + try { + console.log(JSON.stringify(JSON.parse(res.body.toString('utf8')), null, 2)); + } catch { + console.log(res.body.toString('utf8')); + } +} + +async function clientRequest(authCode, name, label) { + const path = `/api/v1/client?authCode=${encodeURIComponent(authCode)}&name=${encodeURIComponent(name)}`; + console.log(`${label} GET https://${HOST}:${PORT}${path}\n`); + const res = await request(path); + printResponse(res); + console.log(); + return res; +} + +async function main() { + const mode = process.argv[2]; + + if (!mode) { + usage(); + } + + if (mode === 'init') { + const authToken = process.argv[3] || process.env.AUTH_TOKEN; + if (!authToken) { + usage(); + } + + const path = `/api/v1/init?${INIT_QUERY}`; + console.log(`GET https://${HOST}:${PORT}${path}\n`); + + const res = await request(path, authToken); + printResponse(res); + return; + } + + if (mode === 'customergroup') { + const authToken = process.argv[3] || process.env.AUTH_TOKEN; + if (!authToken) { + usage(); + } + + const lastChanged = process.argv[4] || '0'; + const path = `/api/v1/customergroup?mandantId=1&lastChangedCustomerGroup=${encodeURIComponent(lastChanged)}`; + console.log(`GET https://${HOST}:${PORT}${path}\n`); + + const res = await request(path, authToken); + printResponse(res); + return; + } + + if (mode === 'deletedentity') { + const authToken = process.argv[3] || process.env.AUTH_TOKEN; + if (!authToken) { + usage(); + } + + const lastChanged = process.argv[4] || '0'; + const path = `/api/v1/deletedentity?mandantId=1&limit=200&lastChangedDeletedEntity=${encodeURIComponent(lastChanged)}`; + console.log(`GET https://${HOST}:${PORT}${path}\n`); + + const res = await request(path, authToken); + printResponse(res); + return; + } + + const entityModes = { + category: { + param: 'lastChangedCategory', + path: '/api/v1/category?mandantId=1&limit=20', + }, + customer: { + param: 'lastChangedCustomer', + path: '/api/v1/customer?mandantId=1&limit=20', + }, + product: { + param: 'lastChangedProduct', + path: '/api/v1/product?mandantId=1&limit=20', + }, + productcomposite: { + param: 'lastChangedCompositeProduct', + path: '/api/v1/productcomposite?mandantId=1&limit=20', + }, + }; + + if (entityModes[mode]) { + const authToken = process.argv[3] || process.env.AUTH_TOKEN; + if (!authToken) { + usage(); + } + + const { param, path: basePath } = entityModes[mode]; + const lastChanged = process.argv[4] || '0'; + const path = `${basePath}&${param}=${encodeURIComponent(lastChanged)}`; + console.log(`GET https://${HOST}:${PORT}${path}\n`); + + const res = await request(path, authToken); + printResponse(res); + return; + } + + const name = process.argv[3] || process.env.CLIENT_NAME || '001'; + const fullCode = mode; + + if (fullCode.length === 6) { + await clientRequest(fullCode.slice(0, 4), name, '=== step 1 (4 digits) ==='); + await clientRequest(fullCode, name, '=== step 2 (6 digits) ==='); + return; + } + + await clientRequest(fullCode, name, '=== step 1 (4 digits) ==='); +} + +main().catch((err) => { + console.error('request failed:', err.message); + process.exit(1); +}); diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..9683aae --- /dev/null +++ b/web/index.html @@ -0,0 +1,12 @@ + + + + + + JTL-POS Admin + + +
+ + + diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..605cf5b --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,2650 @@ +{ + "name": "jtl-pos-admin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jtl-pos-admin", + "version": "1.0.0", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^6.4.6", + "@mui/material": "^6.4.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.1.1" + } + }, + "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==", + "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/core/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/@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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.5.0.tgz", + "integrity": "sha512-LGb8t8i6M2ZtS3Drn3GbTI1DVhDY6FJ9crEey2lZ0aN2EMZo8IZBZj9wRf4vqbZHaWjsYgtbOnJw5V8UWbmK2Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.5.0.tgz", + "integrity": "sha512-VPuPqXqbBPlcVSA0BmnoE4knW4/xG6Thazo8vCLWkOKusko6DtwFV6B665MMWJ9j0KFohTIf3yx2zYtYacvG1g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^6.5.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz", + "integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.5.0", + "@mui/system": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.5.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", + "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.4.9", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.5.0.tgz", + "integrity": "sha512-8woC2zAqF4qUDSPIBZ8v3sakj+WgweolpyM/FXf8jAx6FMls+IE4Y8VDZc+zS805J7PRz31vz73n2SovKGaYgw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.5.0.tgz", + "integrity": "sha512-XcbBYxDS+h/lgsoGe78ExXFZXtuIlSBpn/KsZq8PtZcIkUNJInkuDqcLd2rVBQrDC1u+rvVovdaWPf2FHKJf3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.4.9", + "@mui/styled-engine": "^6.5.0", + "@mui/types": "~7.2.24", + "@mui/utils": "^6.4.9", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", + "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "~7.2.24", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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/@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/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "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==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "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/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.375", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", + "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "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/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT" + }, + "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": "7.18.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz", + "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz", + "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "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/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "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/web/package.json b/web/package.json new file mode 100644 index 0000000..bdc4482 --- /dev/null +++ b/web/package.json @@ -0,0 +1,24 @@ +{ + "name": "jtl-pos-admin", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/icons-material": "^6.4.6", + "@mui/material": "^6.4.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.1.1" + } +} diff --git a/web/src/App.jsx b/web/src/App.jsx new file mode 100644 index 0000000..47484d3 --- /dev/null +++ b/web/src/App.jsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Routes, Route, NavLink } from 'react-router-dom'; +import { + AppBar, + Box, + CssBaseline, + Drawer, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Toolbar, + Typography, +} from '@mui/material'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +import BluetoothSearchingIcon from '@mui/icons-material/BluetoothSearching'; +import InventoryIcon from '@mui/icons-material/Inventory'; +import CategoryIcon from '@mui/icons-material/Category'; +import PeopleIcon from '@mui/icons-material/People'; +import LoyaltyIcon from '@mui/icons-material/Loyalty'; +import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'; + +import Dashboard from './pages/Dashboard'; +import Pairing from './pages/Pairing'; +import Products from './pages/Products'; +import Categories from './pages/Categories'; +import Customers from './pages/Customers'; +import CustomerGroups from './pages/CustomerGroups'; +import Deleted from './pages/Deleted'; + +const drawerWidth = 240; + +const navItems = [ + { path: '/', label: 'Dashboard', icon: }, + { path: '/pairing', label: 'Pairing', icon: }, + { path: '/products', label: 'Products', icon: }, + { path: '/categories', label: 'Categories', icon: }, + { path: '/customers', label: 'Customers', icon: }, + { path: '/customer-groups', label: 'Customer Groups', icon: }, + { path: '/deleted', label: 'Deleted', icon: }, +]; + +function App() { + return ( + + + theme.zIndex.drawer + 1 }}> + + + JTL-POS Admin + + + + + + + {navItems.map((item) => ( + + + {item.icon} + + + + ))} + + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +} + +export default App; diff --git a/web/src/api.js b/web/src/api.js new file mode 100644 index 0000000..1ec2b59 --- /dev/null +++ b/web/src/api.js @@ -0,0 +1,64 @@ +const BASE = import.meta.env.VITE_ADMIN_API_BASE || '/admin/api'; + +async function request(method, path, body) { + const opts = { + method, + headers: { 'Content-Type': 'application/json' }, + }; + if (body !== undefined) { + opts.body = JSON.stringify(body); + } + const res = await fetch(`${BASE}${path}`, opts); + if (!res.ok) { + const text = await res.text().catch(() => ''); + throw new Error(`${method} ${path} failed: ${res.status} ${text}`); + } + if (res.status === 204) { + return null; + } + return res.json(); +} + +export const api = { + get: (path) => request('GET', path), + post: (path, body) => request('POST', path, body), + patch: (path, body) => request('PATCH', path, body), + put: (path, body) => request('PUT', path, body), + delete: (path) => request('DELETE', path), +}; + +export const init = () => api.get('/init'); +export const getState = () => api.get('/state'); + +export const listCategories = () => api.get('/categories'); +export const createCategory = (cat) => api.post('/categories', cat); +export const updateCategory = (id, cat) => api.patch(`/categories/${id}`, cat); +export const deleteCategory = (id) => api.delete(`/categories/${id}`); + +export const listProducts = () => api.get('/products'); +export const createProduct = (prod) => api.post('/products', prod); +export const updateProduct = (id, prod) => api.patch(`/products/${id}`, prod); +export const deleteProduct = (id) => api.delete(`/products/${id}`); + +export const listCustomers = () => api.get('/customers'); +export const createCustomer = (cust) => api.post('/customers', cust); +export const updateCustomer = (id, cust) => api.patch(`/customers/${id}`, cust); +export const deleteCustomer = (id) => api.delete(`/customers/${id}`); + +export const listCustomerGroups = () => api.get('/customer-groups'); +export const createCustomerGroup = (g) => api.post('/customer-groups', g); +export const updateCustomerGroup = (id, g) => api.patch(`/customer-groups/${id}`, g); +export const deleteCustomerGroup = (id) => api.delete(`/customer-groups/${id}`); + +export const listComposites = () => api.get('/product-composites'); +export const setComposite = (comp) => api.post('/product-composites', comp); +export const removeComposite = (productId, componentId) => + api.delete(`/product-composites/${productId}/${componentId}`); + +export const getPairing = () => api.get('/pairing'); +export const createPairingCode = (name) => api.post('/pairing', { name }); +export const revokePairingCode = (code) => api.post('/pairing/revoke', { code }); +export const revokeDevice = (token) => api.delete(`/devices/${token}`); + +export const listDeleted = () => api.get('/deleted'); +export const recordDeleted = (entity) => api.post('/deleted', entity); diff --git a/web/src/components/CrudTable.jsx b/web/src/components/CrudTable.jsx new file mode 100644 index 0000000..4c3f0ba --- /dev/null +++ b/web/src/components/CrudTable.jsx @@ -0,0 +1,150 @@ +import React, { useState } from 'react'; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + TextField, + Button, + IconButton, + Stack, +} from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import SaveIcon from '@mui/icons-material/Save'; +import CancelIcon from '@mui/icons-material/Cancel'; +import AddIcon from '@mui/icons-material/Add'; + +export default function CrudTable({ + columns, + rows, + idField, + onSave, + onDelete, + emptyItem = {}, + title, +}) { + const [editing, setEditing] = useState(null); + const [adding, setAdding] = useState(false); + const [form, setForm] = useState({}); + + function startAdd() { + setAdding(true); + setForm({ ...emptyItem }); + } + + function startEdit(row) { + setEditing(row[idField]); + setForm({ ...row }); + } + + function cancel() { + setEditing(null); + setAdding(false); + setForm({}); + } + + async function save() { + await onSave(editing || adding ? form : null); + cancel(); + } + + function updateField(key, value) { + setForm((prev) => ({ ...prev, [key]: value })); + } + + function isEditing(id) { + return editing === id; + } + + return ( + <> + + + + + + + + {columns.map((col) => ( + {col.label} + ))} + Actions + + + + {adding && ( + + {columns.map((col) => ( + + updateField(col.key, e.target.value)} + fullWidth + /> + + ))} + + + + + + + + + + )} + {rows.map((row) => + isEditing(row[idField]) ? ( + + {columns.map((col) => ( + + updateField(col.key, e.target.value)} + fullWidth + /> + + ))} + + + + + + + + + + ) : ( + + {columns.map((col) => ( + {row[col.key] ?? ''} + ))} + + startEdit(row)}> + + + onDelete(row[idField])} + > + + + + + ) + )} + +
+
+ + ); +} diff --git a/web/src/main.jsx b/web/src/main.jsx new file mode 100644 index 0000000..d0d2819 --- /dev/null +++ b/web/src/main.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { ThemeProvider, createTheme } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import App from './App'; + +const theme = createTheme({ + palette: { + mode: 'light', + }, +}); + +ReactDOM.createRoot(document.getElementById('root')).render( + + + + + + + + +); diff --git a/web/src/pages/Categories.jsx b/web/src/pages/Categories.jsx new file mode 100644 index 0000000..fddc593 --- /dev/null +++ b/web/src/pages/Categories.jsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import CrudTable from '../components/CrudTable'; +import { listCategories, createCategory, updateCategory, deleteCategory } from '../api'; + +const columns = [ + { key: '_id', label: 'ID' }, + { key: 'name', label: 'Name' }, + { key: 'pid', label: 'Parent ID' }, + { key: 'sort', label: 'Sort' }, + { key: 'lastChanged', label: 'Last changed' }, +]; + +export default function Categories() { + const [rows, setRows] = useState([]); + const [error, setError] = useState(''); + + async function refresh() { + try { + const data = await listCategories(); + setRows(data); + } catch (e) { + setError(e.message); + } + } + + async function handleSave(form) { + try { + if (form._id && rows.some((r) => r._id === form._id)) { + await updateCategory(form._id, form); + } else { + await createCategory(form); + } + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function handleDelete(id) { + try { + await deleteCategory(id); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + useEffect(() => { + refresh(); + }, []); + + return ( + + + Categories + + {error && {error}} + + + ); +} diff --git a/web/src/pages/CustomerGroups.jsx b/web/src/pages/CustomerGroups.jsx new file mode 100644 index 0000000..fb09bf9 --- /dev/null +++ b/web/src/pages/CustomerGroups.jsx @@ -0,0 +1,68 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import CrudTable from '../components/CrudTable'; +import { listCustomerGroups, createCustomerGroup, updateCustomerGroup, deleteCustomerGroup } from '../api'; + +const columns = [ + { key: 'customerGroupId', label: 'ID' }, + { key: 'name', label: 'Name' }, + { key: 'standard', label: 'Standard' }, + { key: 'discountPercent', label: 'Discount %' }, +]; + +export default function CustomerGroups() { + const [rows, setRows] = useState([]); + const [error, setError] = useState(''); + + async function refresh() { + try { + const data = await listCustomerGroups(); + setRows(data); + } catch (e) { + setError(e.message); + } + } + + async function handleSave(form) { + try { + if (form.customerGroupId && rows.some((r) => r.customerGroupId === form.customerGroupId)) { + await updateCustomerGroup(form.customerGroupId, form); + } else { + await createCustomerGroup(form); + } + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function handleDelete(id) { + try { + await deleteCustomerGroup(id); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + useEffect(() => { + refresh(); + }, []); + + return ( + + + Customer Groups + + {error && {error}} + + + ); +} diff --git a/web/src/pages/Customers.jsx b/web/src/pages/Customers.jsx new file mode 100644 index 0000000..ef648df --- /dev/null +++ b/web/src/pages/Customers.jsx @@ -0,0 +1,71 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import CrudTable from '../components/CrudTable'; +import { listCustomers, createCustomer, updateCustomer, deleteCustomer } from '../api'; + +const columns = [ + { key: 'id', label: 'ID' }, + { key: 'customerNumber', label: 'Number' }, + { key: 'firstname', label: 'First name' }, + { key: 'lastname', label: 'Last name' }, + { key: 'company', label: 'Company' }, + { key: 'city', label: 'City' }, + { key: 'customerGroupId', label: 'Group ID' }, +]; + +export default function Customers() { + const [rows, setRows] = useState([]); + const [error, setError] = useState(''); + + async function refresh() { + try { + const data = await listCustomers(); + setRows(data); + } catch (e) { + setError(e.message); + } + } + + async function handleSave(form) { + try { + if (form.id && rows.some((r) => r.id === form.id)) { + await updateCustomer(form.id, form); + } else { + await createCustomer(form); + } + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function handleDelete(id) { + try { + await deleteCustomer(id); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + useEffect(() => { + refresh(); + }, []); + + return ( + + + Customers + + {error && {error}} + + + ); +} diff --git a/web/src/pages/Dashboard.jsx b/web/src/pages/Dashboard.jsx new file mode 100644 index 0000000..04e0ae0 --- /dev/null +++ b/web/src/pages/Dashboard.jsx @@ -0,0 +1,61 @@ +import React, { useEffect, useState } from 'react'; +import { Box, Card, CardContent, Grid, Typography, Chip } from '@mui/material'; +import { init } from '../api'; + +export default function Dashboard() { + const [data, setData] = useState(null); + const [error, setError] = useState(''); + + useEffect(() => { + init().then(setData).catch((e) => setError(e.message)); + }, []); + + const counts = data?.counts || {}; + const devices = data?.state?.pairedDevices + ? Object.values(data.state.pairedDevices) + : []; + + const countCards = [ + { label: 'Products', value: counts.product_count }, + { label: 'Categories', value: counts.category_count }, + { label: 'Customers', value: counts.customer_count }, + { label: 'Customer Groups', value: counts.customerGroup_count }, + { label: 'Composites', value: counts.compositeProduct_count }, + { label: 'Deleted', value: counts.deletedEntity_count }, + ]; + + return ( + + + Dashboard + + {error && {error}} + + {countCards.map((c) => ( + + + + + {c.label} + + {c.value ?? '-'} + + + + ))} + + + Paired Devices + + {devices.length === 0 ? ( + No devices paired yet. + ) : ( + + {devices.map((d) => ( + + ))} + + )} + + ); +} diff --git a/web/src/pages/Deleted.jsx b/web/src/pages/Deleted.jsx new file mode 100644 index 0000000..e25eeff --- /dev/null +++ b/web/src/pages/Deleted.jsx @@ -0,0 +1,112 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import { listDeleted, recordDeleted } from '../api'; + +const entityTypes = { + 1: 'Product', + 2: 'Category', + 3: 'Customer', + 6: 'Customer Group', +}; + +export default function Deleted() { + const [rows, setRows] = useState([]); + const [error, setError] = useState(''); + const [entityId, setEntityId] = useState(''); + const [entityType, setEntityType] = useState('1'); + + async function refresh() { + try { + const data = await listDeleted(); + setRows(data); + } catch (e) { + setError(e.message); + } + } + + async function handleAdd(e) { + e.preventDefault(); + try { + await recordDeleted({ entityId, entityType }); + setEntityId(''); + await refresh(); + } catch (err) { + setError(err.message); + } + } + + useEffect(() => { + refresh(); + }, []); + + return ( + + + Deleted Entities + + {error && {error}} + + + setEntityId(e.target.value)} + size="small" + required + /> + setEntityType(e.target.value)} + size="small" + required + helperText="1=Product,2=Category,3=Customer,6=Group" + /> + + + + + + + + Entity ID + Entity Type + Last Changed + + + + {rows.length === 0 && ( + + No deleted entities + + )} + {rows.map((row) => ( + + {row.entityId} + + {row.entityType} {entityTypes[row.entityType] ? `(${entityTypes[row.entityType]})` : ''} + + {row.lastChanged} + + ))} + +
+
+
+ ); +} diff --git a/web/src/pages/Pairing.jsx b/web/src/pages/Pairing.jsx new file mode 100644 index 0000000..77d0158 --- /dev/null +++ b/web/src/pages/Pairing.jsx @@ -0,0 +1,163 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + IconButton, +} from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { + getPairing, + createPairingCode, + revokePairingCode, + revokeDevice, +} from '../api'; + +export default function Pairing() { + const [data, setData] = useState({ authCodes: [], pairedDevices: [] }); + const [name, setName] = useState('JTL-POS'); + const [msg, setMsg] = useState(''); + const [error, setError] = useState(''); + + async function refresh() { + try { + const d = await getPairing(); + setData(d); + setError(''); + } catch (e) { + setError(e.message); + } + } + + async function generateCode() { + try { + const res = await createPairingCode(name || 'JTL-POS'); + setMsg(`New pairing code: ${res.code}`); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function revokeCode(code) { + try { + await revokePairingCode(code); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function revokeDev(token) { + try { + await revokeDevice(token); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + useEffect(() => { + refresh(); + }, []); + + return ( + + + Pairing + + {error && {error}} + {msg && {msg}} + + + setName(e.target.value)} + size="small" + /> + + + + + + Pending pairing codes + + + + + + Code + Name + Actions + + + + {data.authCodes.length === 0 && ( + + No pending codes + + )} + {data.authCodes.map((code) => ( + + {code.code} + {code.name} + + revokeCode(code.code)}> + + + + + ))} + +
+
+ + + Paired devices + + + + + + Name + Token + Actions + + + + {data.pairedDevices.length === 0 && ( + + No paired devices + + )} + {data.pairedDevices.map((dev) => ( + + {dev.name} + {dev.token.slice(0, 16)}… + + revokeDev(dev.token)}> + + + + + ))} + +
+
+
+ ); +} diff --git a/web/src/pages/Products.jsx b/web/src/pages/Products.jsx new file mode 100644 index 0000000..6096600 --- /dev/null +++ b/web/src/pages/Products.jsx @@ -0,0 +1,319 @@ +import React, { useEffect, useState } from 'react'; +import { + Box, + Button, + FormControl, + InputLabel, + MenuItem, + Paper, + Select, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography, + Checkbox, + FormControlLabel, + IconButton, +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { + listProducts, + createProduct, + updateProduct, + deleteProduct, + listComposites, + setComposite, + removeComposite, +} from '../api'; + +const productColumns = [ + { key: '_id', label: 'ID' }, + { key: 'name', label: 'Name' }, + { key: 'sku', label: 'SKU' }, + { key: 'price', label: 'Price' }, + { key: 'tax_rate', label: 'Tax %' }, + { key: 'categories_id', label: 'Category ID' }, + { key: 'isCompositeProduct', label: 'Composite' }, +]; + +export default function Products() { + const [products, setProducts] = useState([]); + const [composites, setComposites] = useState([]); + const [error, setError] = useState(''); + + const [editing, setEditing] = useState(null); + const [form, setForm] = useState({}); + + const [compositeParent, setCompositeParent] = useState(''); + const [compositeComponent, setCompositeComponent] = useState(''); + const [compositeQuantity, setCompositeQuantity] = useState('1.00'); + + async function refresh() { + try { + const [p, c] = await Promise.all([listProducts(), listComposites()]); + setProducts(p); + setComposites(c); + } catch (e) { + setError(e.message); + } + } + + async function handleSave() { + try { + if (editing) { + await updateProduct(form._id, form); + } else { + await createProduct(form); + } + setEditing(null); + setForm({}); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function handleDelete(id) { + try { + await deleteProduct(id); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function addComposite() { + try { + await setComposite({ + productId: compositeParent, + productIdComponent: compositeComponent, + quantity: compositeQuantity, + }); + setCompositeComponent(''); + setCompositeQuantity('1.00'); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + async function deleteComposite(productId, componentId) { + try { + await removeComposite(productId, componentId); + await refresh(); + } catch (e) { + setError(e.message); + } + } + + function startAdd() { + setEditing('new'); + setForm({ name: '', sku: '', price: '0.00', tax_rate: '19', categories_id: '1', isCompositeProduct: '0' }); + } + + function startEdit(row) { + setEditing(row._id); + setForm({ ...row }); + } + + function cancel() { + setEditing(null); + setForm({}); + } + + function updateField(key, value) { + setForm((prev) => ({ ...prev, [key]: value })); + } + + useEffect(() => { + refresh(); + }, []); + + const compositeProducts = products.filter((p) => p.isCompositeProduct === '1'); + const nonCompositeProducts = products.filter((p) => p.isCompositeProduct !== '1'); + + return ( + + + Products + + {error && {error}} + + + + + + + + + + {productColumns.map((col) => ( + {col.label} + ))} + Actions + + + + {editing === 'new' && ( + + )} + {products.map((row) => + editing === row._id ? ( + + ) : ( + + {productColumns.map((col) => ( + + {col.key === 'isCompositeProduct' + ? row[col.key] === '1' + ? 'Yes' + : 'No' + : row[col.key] ?? ''} + + ))} + + + handleDelete(row._id)}> + + + + + ) + )} + +
+
+ + + Composite components + + + + Composite product + + + + Component + + + setCompositeQuantity(e.target.value)} + size="small" + sx={{ width: 100 }} + /> + + + + + + + + Composite + Component + Quantity + Actions + + + + {composites.map((c) => ( + + {products.find((p) => p._id === c.productId)?.name || c.productId} + {products.find((p) => p._id === c.productIdComponent)?.name || c.productIdComponent} + {c.quantity} + + deleteComposite(c.productId, c.productIdComponent)}> + + + + + ))} + +
+
+
+ ); +} + +function EditableRow({ columns, form, onChange, onSave, onCancel }) { + return ( + + {columns.map((col) => ( + + {col.key === 'isCompositeProduct' ? ( + onChange(col.key, e.target.checked ? '1' : '0')} + /> + } + label="Composite" + /> + ) : ( + onChange(col.key, e.target.value)} + fullWidth + /> + )} + + ))} + + + + + + ); +} diff --git a/web/vite.config.js b/web/vite.config.js new file mode 100644 index 0000000..c64f1d1 --- /dev/null +++ b/web/vite.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + port: 5173, + proxy: { + '/admin/api': { + target: 'http://localhost:8087', + changeOrigin: true, + }, + }, + }, + build: { + outDir: 'dist', + }, +});