diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..00e8d1dda --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,44 @@ +{ + "name": "hashlips-art-engine", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "lts", + "pnpm": "true", + "yarn": "true" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "3.11" + }, + "ghcr.io/devcontainers/features/github-cli:1": {}, + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": "false", + "installOhMyZsh": "false" + } + }, + "remoteUser": "vscode", + "runArgs": ["--init"], + "containerEnv": { + "PLAYWRIGHT_BROWSERS_PATH": "0" + }, + "onCreateCommand": "sudo apt-get update && sudo apt-get install -y build-essential git jq ripgrep libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev librsvg2-dev", + "postCreateCommand": "python3 -m pip install --user pre-commit && npm install && python3 -m pre_commit install && python3 -m pre_commit install --hook-type commit-msg && echo 'Devcontainer ready. Run npm run dev to start the generator or npm run check to validate the toolkit.'", + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-python.python", + "ms-python.vscode-pylance", + "visualstudioexptteam.vscodeintellicode", + "eamodio.gitlens" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "files.trimTrailingWhitespace": true, + "javascript.validate.enable": false + } + } + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..a3880e485 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..8c4f5e173 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Wix auth configuration (used by backend/auth.jsw) +APP_DOMAIN= +SUPPORTED_CHAIN_ID= +WIX_SECRET_JWT_KEY= diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..abf9b1297 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,49 @@ +module.exports = { + root: true, + env: { + node: true, + es2022: true + }, + extends: ['eslint:recommended', 'plugin:promise/recommended', 'plugin:n/recommended'], + plugins: ['import'], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + }, + rules: { + 'no-console': 'off', + 'import/order': [ + 'warn', + { + 'newlines-between': 'always', + alphabetize: { order: 'asc', caseInsensitive: true } + } + ], + 'n/shebang': 'off' + }, + ignorePatterns: [ + 'dist/', + 'build/', + 'node_modules/', + 'layers/', + 'docs/', + 'backend/', + 'pages/', + 'public/', + 'modules/', + 'utils/', + 'bazaarcodexv1.html', + 'index.js' + ], + overrides: [ + { + files: ['__tests__/**/*.js', 'vitest.config.mjs'], + env: { + node: true + }, + rules: { + 'n/no-unpublished-import': 'off' + } + } + ] +}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..0f9cb649b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +* text=auto eol=lf + +*.png binary +*.jpg binary +*.gif binary +*.ico binary +*.psd binary + +*.sh linguist-language=Shell +*.ps1 linguist-language=PowerShell diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..b66fa7eac --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + labels: + - "dependencies" + - "ci" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + labels: + - "dependencies" + ignore: + - dependency-name: "canvas" + update-types: + - "version-update:semver-major" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..17da6f3c3 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,24 @@ +## Summary + +- _ + +## Testing + +- [ ] `npm run lint` +- [ ] `npm run typecheck` +- [ ] `npm test` +- [ ] `npm run build` + +## Screenshots / Videos (if UI) + +_Add before/after images or screen recordings._ + +## Deploy preview + +- URL: _ + +## Checklist + +- [ ] Conventional Commit title +- [ ] Added/updated tests +- [ ] Updated docs (if needed) diff --git a/.github/semantic.yml b/.github/semantic.yml new file mode 100644 index 000000000..0827c9390 --- /dev/null +++ b/.github/semantic.yml @@ -0,0 +1,14 @@ +titleOnly: false +types: + - feat + - fix + - chore + - docs + - refactor + - test + - ci + - build +scopes: [] +allowCustomScopes: true +ignoreLabels: [] +allowEmptyScope: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..f00e749c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [18.x, 20.x] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev librsvg2-dev + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Type check + run: npm run typecheck + + - name: Test + run: npm test -- --reporter=junit --outputFile=vitest-results.xml + + - name: Build + run: npm run build + + - name: Upload coverage + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.node-version }} + path: | + coverage + vitest-results.xml + if-no-files-found: ignore diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 000000000..1ddf4b324 --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,27 @@ +name: PR Title Check + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + lint-pr-title: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + types: | + feat + fix + chore + docs + ci + build + test + refactor + requireScope: false diff --git a/.gitignore b/.gitignore index 09c28a63e..c444173b8 100644 --- a/.gitignore +++ b/.gitignore @@ -140,3 +140,23 @@ dist # OSX .DS_Store +.idea/ +.vs/ +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json + +# virtual envs / tooling +.venv/ +venv/ +.python-version + +# local data and artifacts +*.iml +*.local +*.swp +*.swo +*.orig +tmp/ +temp/ +*.log diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..4176e049a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: local + hooks: + - id: lint + name: lint + entry: npm run lint + language: system + pass_filenames: false + - id: typecheck + name: typecheck + entry: npm run typecheck + language: system + pass_filenames: false + - id: format + name: prettier check + entry: npm run format + language: system + pass_filenames: false + - repo: local + hooks: + - id: commitlint + name: commitlint + entry: npx commitlint --edit "$1" + language: system + pass_filenames: false + stages: [commit-msg] diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..30be97107 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/prettierrc", + "printWidth": 90, + "singleQuote": true, + "trailingComma": "none", + "semi": true, + "arrowParens": "always" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..b16eba900 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "ms-python.python", + "ms-python.vscode-pylance", + "streetsidesoftware.code-spell-checker", + "eamodio.gitlens" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..18aa7850c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "javascript.validate.enable": false, + "search.exclude": { + "node_modules": true, + "dist": true, + "build": true + } +} diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite new file mode 100644 index 000000000..0b58f0dca Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite-shm b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite-shm new file mode 100644 index 000000000..474ee5232 Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite-shm differ diff --git a/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite-wal b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite-wal new file mode 100644 index 000000000..66beac9e8 Binary files /dev/null and b/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/36cd2ad8c1ccad5c208fd64137407e3901de0b271c3cfbd3a8b319d95f483d1e.sqlite-wal differ diff --git a/BazaarBids.csv b/BazaarBids.csv new file mode 100644 index 000000000..ffc21e856 --- /dev/null +++ b/BazaarBids.csv @@ -0,0 +1 @@ +listingId,listingTitle,tokenId,wallet,amount,currency,depositTx,message,status,createdAt,updatedAt,bidderMemberId,bidderDisplayName,verified,createdByIp,createdByUserAgent diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..68e757f26 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# TODO: replace with the real maintainers +* @TODO-ADD-YOUR-GITHUB-USERNAME diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..094bf42fd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,39 @@ +# Contributing + +Thanks for helping keep the DIKPIK Bazaar tooling stable. This repository follows a conventional, test-first workflow with automated quality gates. Please read the sections below before opening a pull request. + +## Getting started + +1. Install the tooling (Codespaces is the fastest path): + - Node 20+ + - Python 3.11 (for `pre-commit`) + - Cairo libs (already provided inside the devcontainer) +2. Run `npm install`. +3. Install and enable `pre-commit`: + ```bash + python -m pip install pre-commit + pre-commit install + pre-commit install --hook-type commit-msg + ``` +4. Copy `.env.example` to `.env` when you need to run Wix-auth helpers locally. + +## Development workflow + +- **Tests & linting**: use `npm run check` before pushing (`lint`, `typecheck`, `test`). +- **Formatting**: `npm run format:fix`. +- **Pre-commit hooks** run lint + typecheck + prettier automatically. Re-run manually with `pre-commit run --all-files` if needed. +- **Commit messages**: follow [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `chore:`, etc.). `commitlint` will block invalid messages. +- **PRs**: + - Target `main`. + - Fill out the template (tests, screenshots, preview link). + - Ensure CI is green. + +## Reporting issues + +File issues with: + +- What you expected vs. what happened. +- Reproduction steps (commands, sample layer config, etc.). +- Logs or stack traces (redact secrets). + +Security-sensitive reports should be emailed to the maintainers instead of filing a public issue. diff --git a/DEPLOY-CLOUDFLARE.md b/DEPLOY-CLOUDFLARE.md new file mode 100644 index 000000000..023fa4555 --- /dev/null +++ b/DEPLOY-CLOUDFLARE.md @@ -0,0 +1,24 @@ + + +--- + +## Custom domain setup (automated via wrangler.jsonc) + +This project uses Workers Custom Domains configured in wrangler.jsonc under outes with custom_domain: true. + +Required: +- The domain must be an **active Cloudflare zone** in the same account. +- You **cannot** create a Custom Domain on a hostname with an existing CNAME record (remove/replace it first). + +If CI deploy fails with a domain/zone error, fix the Cloudflare zone/DNS and re-run the workflow. + +## Google Workspace email safety (manual) + +If you move DNS/nameservers to Cloudflare, ensure these records are present in Cloudflare DNS first: +- MX records for Google Workspace +- SPF TXT +- DKIM TXT (from Google Admin) +- DMARC TXT + +This prevents email breakage for info@sitereadyworkforce.com. + diff --git a/README.md b/README.md index 98a393c2e..6182390ac 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,82 @@ +# HashLips Art Engine (GoblinVerse Bazaar fork) + +[![CI](https://github.com/HashLips/hashlips_art_engine/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/HashLips/hashlips_art_engine/actions/workflows/ci.yml) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=HashLips%2Fhashlips_art_engine) + +Generates layered NFT art for the DIKPIK Bazaar while hosting the Wix/Velo wallet + bidding flows. This fork is now fully Codespaces-ready with automated linting, tests, and guarded merges. + +## Quick start + +1. **Clone or open in Codespaces** (button above) - Codespaces provisions Node 20, Python 3.11, Cairo libs for `canvas`, and installs dependencies automatically. +2. **Install locally** (if not using Codespaces): `npm install` (requires build tools + Cairo). +3. **Run the generator**: `npm run dev` (same as `npm run build`), assets go to `build/`. +4. **Validate before pushing**: `npm run check` (runs lint ? typecheck ? tests). +5. **Format**: `npm run format:fix`. + +## Development environments + +| Scenario | Steps | +| --- | --- | +| **Codespaces / devcontainer** | Open in Codespaces ? wait for post-create to run `npm install` + `pre-commit install` ? `npm run dev` or `npm run check`. | +| **Local (no container)** | Install Node 20+ & Python 3.11, then `npm install`, `python -m pip install pre-commit`, `pre-commit install && pre-commit install --hook-type commit-msg`. Use `npm run dev` for generation, `npm run check` for QA. | + +Recommended VS Code extensions are auto-installed in `.vscode/extensions.json` and the devcontainer. + +## Quality gates & scripts + +Command | What it does +--- | --- +`npm run lint` | ESLint (Node/Promise/Import rules) with `--max-warnings=0`. +`npm run typecheck` | `tsc --noEmit` with `checkJs` for legacy JS. +`npm test` | Vitest + V8 coverage (artifacts uploaded in CI). +`npm run format` / `format:fix` | Prettier verification or write. +`npm run preview`, `npm run rarity`, etc. | Existing art-engine utilities (unchanged). + +`pre-commit` runs lint + typecheck + prettier; `commitlint` enforces Conventional Commits (and PR titles are checked via CI). + +## Continuous Integration + +`.github/workflows/ci.yml` runs on push + PR: + +1. Node matrix **18.x / 20.x** with dependency caching. +2. `npm ci`. +3. `npm run lint`, `npm run typecheck`, `npm test`, `npm run build`. +4. Uploads `coverage/` + `vitest-results.xml` artifacts. + +`pr-title-check.yml` blocks non-Conventional PR titles and `.github/dependabot.yml` keeps Actions + npm dependencies fresh (weekly). + +## Deploy previews + +This repository doesn’t ship a deployable frontend bundle, so previews are disabled by default. To enable Vercel previews for a future Next.js/React UI: + +1. Create a workflow similar to Vercel’s [Deploy Hooks template](https://vercel.com/docs/integrations/git/vercel-for-github#deploy-hooks) that runs `vercel pull`, `vercel build`, and `vercel deploy --prebuilt`. +2. Add repo secrets `VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID`. +3. Document the preview URL in PRs (the existing template already has a slot). + +## Environment variables + +Copy `.env.example` to `.env` (local only) to mirror Velo secrets: + +``` +APP_DOMAIN= +SUPPORTED_CHAIN_ID= +WIX_SECRET_JWT_KEY= +``` + +In Wix, keep those values in the Secrets Manager (`APP_DOMAIN`, `SUPPORTED_CHAIN_ID`, `WIX_SECRET_JWT_KEY`). + +## Conventional commits & PR workflow + +- Use prefixes like `feat:`, `fix:`, `chore:` etc. +- Run `pre-commit` locally (auto-installed inside Codespaces) or `pre-commit run --all-files` before pushing. +- New PRs use `.github/pull_request_template.md` for screenshots, tests, and preview links. +- Release note grouping is handled via `.github/semantic.yml`. + +--- + +# Legacy HashLips instructions + +The historical README from the upstream project is preserved below for reference. # Welcome to HashLips 👄 ![](https://github.com/HashLips/hashlips_art_engine/blob/main/logo.png) @@ -311,3 +390,36 @@ Trait type: Top lid ``` Hope you create some awesome artworks with this code 👄 + +## DIKPIK Bazaar Integration + +This fork powers the GoblinVerse Bazaar/Wanted marketplace with the following capabilities: + +- HTTP and web module services for auctions (Import2) and bids (Import3), including quick increments, withdrawals, buy-now, and admin finalization. +- Email notifications via SendGrid (set the `SENDGRID_API_KEY` secret) for bid confirmations, outbid alerts, winners, and Buy Now purchases. +- Frontend bridge updates for auth-token forwarding, neon bid success toasts, and restricted public data (highest bid, bid count, countdown). +- New REST endpoints: + - `POST /_functions/bids` place bid, `/bids/quick`, `/bids/withdraw` + - `GET /_functions/auctions/{id}` public snapshot, `/admin` for admins + - `POST /_functions/auctions/{id}/buy-now`, `/finalize` +- Web module helpers (`backend/wantedPosts.jsw`) wrap the auction service for Wix client code. + +### Secrets & Configuration + +1. In Velo Secrets Manager set `SENDGRID_API_KEY` with your SendGrid API key. +2. Publish the Import2, Import3, and Import4 collections to Live and ensure backend code has write permissions (`suppressAuth: true`). +3. Optional configuration lives in `backend/wantedUtils.js` via `AUCTION_CONFIG` (increment defaults, email identity, FX provider stub). + +### Testing Checklist + +- Place a bid (regular and quick increment) and confirm the neon popup and HTTP 201 response. +- Outbid the previous bid and confirm a single outbid email. +- Withdraw a bid and verify the highest bid recalculates. +- Finalize an auction as an admin and confirm the winner email. +- Trigger Buy Now and confirm the purchase email. +- Confirm `/_functions/auctions/{id}` exposes only public fields while `/admin` requires admin roles. +- Verify Import3 records mark `isHighest` correctly and `bidCount` updates on the auction record. + + + + diff --git a/RelaySettings.csv b/RelaySettings.csv new file mode 100644 index 000000000..f2c78e513 --- /dev/null +++ b/RelaySettings.csv @@ -0,0 +1 @@ +memberId,chain,contract,createdAt,updatedAt,lastUpdated diff --git a/__tests__/healthcheck.test.js b/__tests__/healthcheck.test.js new file mode 100644 index 000000000..7c8bfb0b6 --- /dev/null +++ b/__tests__/healthcheck.test.js @@ -0,0 +1,11 @@ +import { describe, expect, it } from 'vitest'; + +import health from '../src/healthcheck.js'; + +describe('healthcheck', () => { + it('returns an ok status with an ISO timestamp', () => { + const result = health.getHealthStatus(); + expect(result.status).toBe('ok'); + expect(() => new Date(result.timestamp)).not.toThrow(); + }); +}); diff --git a/backend/auctionService.js b/backend/auctionService.js new file mode 100644 index 000000000..d552b0df7 --- /dev/null +++ b/backend/auctionService.js @@ -0,0 +1,587 @@ +import wixData from 'wix-data'; +import { currentMember } from 'wix-members-backend'; +import { AUCTION_CONFIG, WantedError } from './wantedUtils.js'; +import { + sendBidConfirmation, + sendOutbidNotice, + sendWinningEmail, + sendBuyNowEmail +} from './email.js'; + +const AUCTIONS_COLLECTION = 'Import2'; +const BIDS_COLLECTION = 'Import3'; +const BIDDER_PUBLIC_COLLECTION = 'BidderPublic'; +const BID_STATUS_RECEIVED = 'RECEIVED'; +const BID_STATUS_WITHDRAWN = 'WITHDRAWN'; +const AUCTION_STATUS_OPEN = 'OPEN'; +const AUCTION_STATUS_CLOSED = 'CLOSED'; +const AUCTION_STATUS_SOLD = 'SOLD'; +const RATE_LIMIT_WINDOW_MS = 15_000; +const RATE_LIMIT_MAX = 5; + +const rateLimitMap = new Map(); +const auctionLocks = new Map(); + +/** + * Simple in-memory lock per auction to avoid race conditions between bids. + * @param {string} auctionId + * @param {() => Promise} fn + * @returns {Promise} + */ +async function withAuctionLock(auctionId, fn) { + const prev = auctionLocks.get(auctionId) || Promise.resolve(); + let release; + const next = prev + .catch(() => {}) + .then(async () => { + try { + return await fn(); + } finally { + if (release) release(); + } + }); + auctionLocks.set(auctionId, next); + release = () => { + if (auctionLocks.get(auctionId) === next) { + auctionLocks.delete(auctionId); + } + }; + return next; +} + +function normalizeCurrency(currency) { + if (!currency) return 'ETH'; + const str = String(currency).trim().toUpperCase(); + if (!str) return 'ETH'; + if (!/^[A-Z0-9]{2,12}$/.test(str)) { + throw new WantedError('Unsupported currency.', 400, { field: 'currency' }); + } + return str; +} + +function toNumber(value, field) { + const num = Number(value); + if (!Number.isFinite(num)) { + throw new WantedError(`${field || 'Value'} must be a number.`, 400, { field }); + } + return num; +} + +function ensureAuctionOpen(auction) { + if (!auction) { + throw new WantedError('Auction not found.', 404); + } + const status = String(auction.status || AUCTION_STATUS_OPEN).toUpperCase(); + if (status !== AUCTION_STATUS_OPEN) { + throw new WantedError('Auction is not accepting bids.', 409, { status }); + } + if (auction.endsAt) { + const ends = new Date(auction.endsAt); + if (!Number.isNaN(ends.getTime()) && ends < new Date()) { + throw new WantedError('Auction already ended.', 409); + } + } +} + +function calculateMinimumBid(auction, currentHighest) { + const base = currentHighest ? Number(currentHighest.amount) : Number(auction.startingPrice || auction.price || auction.reservePrice || 0); + const increment = Number(auction.minIncrement ?? AUCTION_CONFIG.MIN_INCREMENT_DEFAULT); + return currentHighest ? base + increment : Math.max(base, increment); +} + +function convertUsdToCurrency(stepUsd, currency) { + const normalizedCurrency = normalizeCurrency(currency); + const usd = Number(stepUsd); + if (!Number.isFinite(usd) || usd <= 0) { + throw new WantedError('Invalid quick increment.', 400); + } + const rate = normalizedCurrency === 'ETH' ? 3000 : 1; // stub conversion + return Number((usd / rate).toFixed(8)); +} + +function resolveAuctionId(input) { + if (!input) return null; + const rawId = + (typeof input.auctionId === 'string' && input.auctionId.trim()) || + (typeof input.listingId === 'string' && input.listingId.trim()) || + null; + return rawId || null; +} + +function normalizeWallet(value) { + if (value === undefined || value === null || value === '') return null; + const raw = String(value).trim(); + if (!raw) return null; + const lower = raw.toLowerCase(); + if (!/^0x[a-f0-9]{40}$/.test(lower)) { + throw new WantedError('Wallet must be a valid 0x address.', 400, { field: 'wallet' }); + } + return lower; +} + +function normalizeTxHash(value) { + if (value === undefined || value === null || value === '') return null; + const raw = String(value).trim(); + if (!raw) return null; + const lower = raw.toLowerCase(); + if (!/^0x[a-f0-9]{64}$/.test(lower)) { + throw new WantedError('Deposit transaction hash is invalid.', 400, { field: 'depositTx' }); + } + return lower; +} + +function normalizeMessage(value, maxLength = 2000) { + if (value === undefined || value === null) return null; + const str = String(value).trim(); + if (!str) return null; + return str.length > maxLength ? str.slice(0, maxLength) : str; +} + +function normalizeTokenId(value) { + if (value === undefined || value === null || value === '') return null; + const num = Number(value); + if (!Number.isFinite(num)) { + throw new WantedError('tokenId must be numeric.', 400, { field: 'tokenId' }); + } + if (num < 0) { + throw new WantedError('tokenId must be zero or greater.', 400, { field: 'tokenId' }); + } + return Math.floor(num); +} + +function keyRateLimit(memberId, ip) { + return memberId ? `member:${memberId}` : `ip:${ip}`; +} + +function applyRateLimit(memberId, ip) { + const key = keyRateLimit(memberId, ip); + if (!key) return; + + const now = Date.now(); + const history = rateLimitMap.get(key) || []; + const recent = history.filter((ts) => now - ts < RATE_LIMIT_WINDOW_MS); + if (recent.length >= RATE_LIMIT_MAX) { + throw new WantedError('Too many bids submitted. Please retry shortly.', 429); + } + recent.push(now); + rateLimitMap.set(key, recent); +} + +function buildManageLink(auction) { + if (auction?.manageLink) return auction.manageLink; + const slug = auction?.slug || auction?._id; + return `https://www.dikpik.io/wanted-and-bazar?auction=${encodeURIComponent(slug)}`; +} + +function shapePublicAuction(auction, highestBid, bidCount) { + return { + id: auction._id, + nftId: auction.nftId || null, + title: auction.title || '', + status: auction.status || AUCTION_STATUS_OPEN, + buyNowPrice: auction.buyNowPrice ?? null, + minIncrement: auction.minIncrement ?? AUCTION_CONFIG.MIN_INCREMENT_DEFAULT, + endsAt: auction.endsAt || null, + highestBidAmount: highestBid?.amount ?? auction.highestBidAmount ?? null, + highestBidCurrency: + highestBid?.currency ?? auction.highestBidCurrency ?? (highestBid?.amount ? 'ETH' : null), + bidCount: bidCount ?? auction.bidCount ?? 0, + updatedAt: auction.updatedAt || auction._updatedDate || null, + createdAt: auction.createdAt || auction._createdDate || null + }; +} + +function shapePrivateAuction(auction, bids) { + return { + ...shapePublicAuction(auction, bids[0], bids.length), + bids: bids.map((bid) => ({ + id: bid._id, + amount: bid.amount, + currency: bid.currency, + createdAt: bid.createdAt || bid._createdDate, + bidderUserId: bid.bidderUserId, + status: bid.status, + isHighest: bid.isHighest, + wallet: bid.wallet || null, + depositTx: bid.depositTx || null, + message: bid.message || null, + tokenId: bid.tokenId != null ? Number(bid.tokenId) : null + })) + }; +} + +async function getAuctionById(auctionId) { + try { + return await wixData.get(AUCTIONS_COLLECTION, auctionId); + } catch (_err) { + return null; + } +} + +async function getBidById(bidId) { + try { + return await wixData.get(BIDS_COLLECTION, bidId, { suppressAuth: true }); + } catch (_err) { + return null; + } +} + +async function listBidsForAuction(auctionId, limit = 200) { + const byAuction = wixData.query(BIDS_COLLECTION).eq('auctionId', auctionId); + const byListing = wixData.query(BIDS_COLLECTION).eq('listingId', auctionId); + const res = await byAuction + .or(byListing) + .descending('amount') + .limit(limit) + .find({ suppressAuth: true }); + return res.items || []; +} + +async function markBidHighest(bidId, isHighest) { + if (!bidId) return; + await wixData.update(BIDS_COLLECTION, { _id: bidId, isHighest, updatedAt: new Date() }, { suppressAuth: true }); +} + +async function updateAuctionHighest(auctionId) { + const bids = await listBidsForAuction(auctionId, 1); + const highest = bids[0] || null; + const countQueryAuction = wixData.query(BIDS_COLLECTION).eq('auctionId', auctionId); + const countQueryListing = wixData.query(BIDS_COLLECTION).eq('listingId', auctionId); + const count = await countQueryAuction.or(countQueryListing).count({ suppressAuth: true }); + const update = { + _id: auctionId, + highestBidAmount: highest ? highest.amount : null, + highestBidCurrency: highest ? highest.currency : null, + highestBidId: highest ? highest._id : null, + bidCount: count, + updatedAt: new Date() + }; + await wixData.update(AUCTIONS_COLLECTION, update, { suppressAuth: true }); + return highest; +} + +async function resolveMember(member) { + if (member?._id) return member; + try { + return await currentMember.getMember({ fieldsets: ['FULL'] }); + } catch (_err) { + return null; + } +} + +function determineDisplayName(member) { + if (!member) return 'Member'; + return ( + member.profile?.nickname || + member.profile?.slug || + member.contact?.name || + member.contact?.emails?.[0]?.email || + 'Member' + ); +} + +/** + * Place a bid on an auction, updating highest bid tracking and sending notifications. + * @param {{auctionId: string, amount: number, currency?: string}} input + * @param {{member?: any, ip?: string}} context + */ +export async function placeBid(input, context = {}) { + const auctionId = resolveAuctionId(input); + const { amount, currency } = input || {}; + if (!auctionId || typeof auctionId !== 'string') { + throw new WantedError('auctionId is required.', 400, { field: 'auctionId' }); + } + + const member = await resolveMember(context.member); + const memberId = member?._id ? String(member._id) : null; + + const normalizedCurrency = normalizeCurrency(currency || 'ETH'); + const numericAmount = toNumber(amount, 'amount'); + if (numericAmount <= 0) { + throw new WantedError('Bid must be greater than zero.', 400, { field: 'amount' }); + } + + applyRateLimit(memberId, context.ip); + + return withAuctionLock(auctionId, async () => { + const auction = await getAuctionById(auctionId); + ensureAuctionOpen(auction); + + const bids = await listBidsForAuction(auctionId, 2); + const currentHighest = bids[0] || null; + + if (currentHighest) { + const min = calculateMinimumBid(auction, currentHighest); + if (numericAmount < min) { + throw new WantedError(`Bid must be at least ${min}.`, 400, { field: 'amount', minimum: min }); + } + } + + const wallet = normalizeWallet(input?.wallet); + const depositTx = normalizeTxHash(input?.depositTx); + const message = normalizeMessage(input?.message); + const tokenId = normalizeTokenId(input?.tokenId); + + if (!memberId) { + if (!wallet) { + throw new WantedError('Wallet address is required for guests.', 400, { field: 'wallet' }); + } + if (!depositTx) { + throw new WantedError('Deposit transaction hash is required for guests.', 400, { + field: 'depositTx' + }); + } + } + + const now = new Date(); + const bidRecord = { + auctionId, + listingId: auctionId, + listingTitle: auction?.title || null, + amount: numericAmount, + currency: normalizedCurrency, + bidderDisplayName: determineDisplayName(member), + status: BID_STATUS_RECEIVED, + isHighest: true, + createdAt: now, + updatedAt: now, + createdByIp: context.ip || null, + createdByUserAgent: context.userAgent || null + }; + if (memberId) bidRecord.bidderUserId = memberId; + if (wallet) bidRecord.wallet = wallet; + if (depositTx) bidRecord.depositTx = depositTx; + if (message) bidRecord.message = message; + if (tokenId != null) bidRecord.tokenId = tokenId; + + const saved = await wixData.insert(BIDS_COLLECTION, bidRecord, { suppressAuth: true }); + + if (currentHighest && currentHighest._id !== saved._id) { + await markBidHighest(currentHighest._id, false); + if (currentHighest.bidderUserId) { + await sendOutbidNotice({ + toUserId: currentHighest.bidderUserId, + auctionTitle: auction.title || 'Auction', + oldAmount: currentHighest.amount, + newAmount: saved.amount, + currency: currentHighest.currency || saved.currency || 'ETH', + manageLink: buildManageLink(auction), + auctionId + }); + } + } + + await updateAuctionHighest(auctionId); + + if (memberId) { + await sendBidConfirmation({ + toUserId: memberId, + auctionTitle: auction.title || 'Auction', + amount: saved.amount, + currency: saved.currency, + bidRef: saved._id, + manageLink: buildManageLink(auction) + }); + } + + return saved; + }); +} + +/** + * Convenience helper to place a bid using the quick USD increment. + * @param {{auctionId: string, stepUSD?: number}} input + * @param {{member?: any, ip?: string}} context + */ +export async function increaseBidQuick(input, context = {}) { + const auctionId = resolveAuctionId(input); + const { stepUSD = AUCTION_CONFIG.QUICK_INCREMENT_USD } = input || {}; + if (!auctionId) { + throw new WantedError('auctionId is required.', 400, { field: 'auctionId' }); + } + const auction = await getAuctionById(auctionId); + ensureAuctionOpen(auction); + const bids = await listBidsForAuction(auctionId, 1); + const currentHighest = bids[0] || null; + const baseAmount = currentHighest?.amount ?? auction.startingPrice ?? AUCTION_CONFIG.MIN_INCREMENT_DEFAULT; + const increment = convertUsdToCurrency(stepUSD, currentHighest?.currency || auction.currency || 'ETH'); + const nextAmount = Number((Number(baseAmount) + increment).toFixed(8)); + return placeBid( + { + auctionId, + amount: nextAmount, + currency: currentHighest?.currency || auction.currency || 'ETH' + }, + context + ); +} + +/** + * Withdraw a bid owned by the current member. + * @param {{bidId: string}} input + * @param {{member?: any}} context + */ +export async function withdrawBid(input, context = {}) { + const { bidId } = input || {}; + if (!bidId) { + throw new WantedError('bidId is required.', 400, { field: 'bidId' }); + } + const member = await resolveMember(context.member); + if (!member?._id) { + throw new WantedError('Login required.', 401); + } + const bid = await getBidById(bidId); + if (!bid) { + throw new WantedError('Bid not found.', 404); + } + if (String(bid.bidderUserId) !== String(member._id)) { + throw new WantedError('You can only withdraw your own bids.', 403); + } + if (bid.status === BID_STATUS_WITHDRAWN) { + return bid; + } + + await wixData.update( + BIDS_COLLECTION, + { _id: bidId, status: BID_STATUS_WITHDRAWN, isHighest: false, updatedAt: new Date() }, + { suppressAuth: true } + ); + + await updateAuctionHighest(bid.auctionId); + return await getBidById(bidId); +} + +export function isAuctionAdmin(member) { + if (!member) return false; + const roles = Array.isArray(member.roles) ? member.roles : []; + return roles.some((role) => role?.name?.toLowerCase?.() === 'admin' || role?.name === 'BazaarAdmin'); +} + +/** + * Close an auction as an admin and notify the winning bidder. + * @param {{auctionId: string}} input + * @param {{member?: any}} context + */ +export async function finalizeAuction(input, context = {}) { + const auctionId = resolveAuctionId(input); + if (!auctionId) { + throw new WantedError('auctionId is required.', 400); + } + const member = await resolveMember(context.member); + if (!isAuctionAdmin(member)) { + throw new WantedError('Admin privileges required.', 403); + } + + return withAuctionLock(auctionId, async () => { + const auction = await getAuctionById(auctionId); + if (!auction) throw new WantedError('Auction not found.', 404); + + const highest = await updateAuctionHighest(auctionId); + + await wixData.update( + AUCTIONS_COLLECTION, + { _id: auctionId, status: AUCTION_STATUS_CLOSED, updatedAt: new Date() }, + { suppressAuth: true } + ); + + if (highest?.bidderUserId) { + await sendWinningEmail({ + toUserId: highest.bidderUserId, + auctionTitle: auction.title || 'Auction', + finalAmount: highest.amount, + currency: highest.currency || auction.highestBidCurrency || 'ETH', + claimLink: buildManageLink(auction) + }); + } + + return { auctionId, status: AUCTION_STATUS_CLOSED, winningBidId: highest?._id || null }; + }); +} + +/** + * Complete an auction instantly using the Buy Now flow. + * @param {{auctionId: string}} input + * @param {{member?: any}} context + */ +export async function buyNow(input, context = {}) { + const auctionId = resolveAuctionId(input); + if (!auctionId) { + throw new WantedError('auctionId is required.', 400); + } + const member = await resolveMember(context.member); + if (!member?._id) { + throw new WantedError('Login required to complete purchase.', 401); + } + + return withAuctionLock(auctionId, async () => { + const auction = await getAuctionById(auctionId); + ensureAuctionOpen(auction); + if (!Number.isFinite(Number(auction.buyNowPrice))) { + throw new WantedError('Buy Now unavailable for this auction.', 409); + } + + await wixData.update( + AUCTIONS_COLLECTION, + { + _id: auctionId, + status: AUCTION_STATUS_SOLD, + buyerMemberId: String(member._id), + soldAt: new Date(), + updatedAt: new Date() + }, + { suppressAuth: true } + ); + + await sendBuyNowEmail({ + toUserId: String(member._id), + auctionTitle: auction.title || 'Auction', + price: auction.buyNowPrice, + currency: auction.buyNowCurrency || auction.currency || 'ETH', + receiptLink: buildManageLink(auction) + }); + + return { auctionId, status: AUCTION_STATUS_SOLD }; + }); +} + +/** + * Return the public-safe snapshot for an auction. + * @param {{auctionId: string}} input + */ +export async function getAuctionPublic(input) { + const auctionId = resolveAuctionId(input); + if (!auctionId) { + throw new WantedError('auctionId is required.', 400); + } + const auction = await getAuctionById(auctionId); + if (!auction) { + throw new WantedError('Auction not found.', 404); + } + const bids = await listBidsForAuction(auctionId, 1); + const bidCount = Number.isFinite(Number(auction.bidCount)) + ? Number(auction.bidCount) + : bids.length; + return shapePublicAuction(auction, bids[0], bidCount); +} + +/** + * Return the full administrative view for an auction. + * @param {{auctionId: string}} input + * @param {{member?: any}} context + */ +export async function getAuctionPrivate(input, context = {}) { + const auctionId = resolveAuctionId(input); + if (!auctionId) { + throw new WantedError('auctionId is required.', 400); + } + const member = await resolveMember(context.member); + if (!isAuctionAdmin(member)) { + throw new WantedError('Admin privileges required.', 403); + } + const auction = await getAuctionById(auctionId); + if (!auction) { + throw new WantedError('Auction not found.', 404); + } + const bids = await listBidsForAuction(auctionId, 200); + return shapePrivateAuction(auction, bids); +} diff --git a/backend/bids.jsw b/backend/bids.jsw new file mode 100644 index 000000000..2e04c858c --- /dev/null +++ b/backend/bids.jsw @@ -0,0 +1,63 @@ +import wixData from 'wix-data'; +import { verify } from 'backend/jwt.jsw'; + +const AUCTIONS = 'Auctions'; +const BIDS = 'Bids'; + +export async function listBids(auctionId) { + const items = await wixData + .query(BIDS) + .eq('auctionId', auctionId) + .descending('amount') + .find(); + return items.items; +} + +export async function placeBid({ auctionId, amount, jwt }) { + if (!jwt) throw new Error('AUTH_REQUIRED'); + const token = await verify(jwt); + if (!auctionId || !amount) throw new Error('INVALID_INPUT'); + + const auction = await wixData.get(AUCTIONS, auctionId); + if (!auction) throw new Error('AUCTION_NOT_FOUND'); + if (auction.status !== 'LIVE') throw new Error('AUCTION_CLOSED'); + + const minAmount = Math.max( + Number(auction.minPrice || 0), + Number(auction.highBidAmount || 0) + Number(auction.minIncrement || 0) + ); + if (Number(amount) < minAmount) throw new Error('BID_TOO_LOW'); + + const bid = await wixData.insert(BIDS, { + auctionId, + memberId: token.sub, + address: token.address, + amount: Number(amount), + createdAt: new Date() + }); + + const updatedAuction = { + ...auction, + highBidAmount: Number(amount), + highBidder: token.sub, + version: (auction.version || 0) + 1 + }; + + try { + await wixData.update(AUCTIONS, updatedAuction, { + condition: wixData + .filter() + .eq('_id', auction._id) + .eq('version', auction.version || 0) + }); + } catch (err) { + await wixData.remove(BIDS, bid._id); + throw new Error('AUCTION_CONFLICT'); + } + + return { + bid, + highBidAmount: updatedAuction.highBidAmount, + version: updatedAuction.version + }; +} diff --git a/backend/dikpikBridge.js b/backend/dikpikBridge.js new file mode 100644 index 000000000..92fd7533e --- /dev/null +++ b/backend/dikpikBridge.js @@ -0,0 +1,83 @@ +import { authentication, currentMember, members } from 'wix-members-backend'; +import { WantedError } from './wantedUtils.js'; + +const AUTHORIZATION_HEADER = 'authorization'; + +export function extractAuthToken(request) { + const headers = request?.headers || {}; + const raw = + headers[AUTHORIZATION_HEADER] || + headers[AUTHORIZATION_HEADER.toUpperCase()] || + headers.Authorization || + headers.AUTHORIZATION; + if (typeof raw !== 'string') return null; + const trimmed = raw.trim(); + if (!trimmed) return null; + if (/^Bearer /i.test(trimmed)) { + return trimmed.slice(7).trim(); + } + return trimmed; +} + +export async function resolveMemberFromRequest(request, tokenOverride) { + const token = tokenOverride || extractAuthToken(request); + if (token) { + try { + const validation = await authentication.validateAuthToken(token); + const memberId = validation?.member?._id || validation?.memberId || validation?.id; + if (memberId) { + const member = await members.getMember(memberId, { fieldsets: ['FULL'] }); + if (member) return member; + } + } catch (err) { + console.warn('[bridge] validateAuthToken failed', err); + } + } + try { + return await currentMember.getMember({ fieldsets: ['FULL'] }); + } catch (_err) { + return null; + } +} + +export async function requireMemberFromRequest(request, tokenOverride) { + const member = await resolveMemberFromRequest(request, tokenOverride); + if (!member) { + throw new WantedError('Login required.', 401); + } + return member; +} + +export function extractClientDetails(request) { + const headers = request?.headers || {}; + const forwarded = + headers['x-real-ip'] || + headers['x-forwarded-for'] || + headers['X-Forwarded-For'] || + headers['client-ip'] || + headers['CF-Connecting-IP'] || + headers['cf-connecting-ip'] || + null; + let ip = null; + if (typeof forwarded === 'string' && forwarded.trim()) { + ip = forwarded.split(',')[0].trim(); + } else if (request?.context?.ip) { + ip = String(request.context.ip).trim(); + } + const userAgentHeader = headers['user-agent'] || headers['User-Agent'] || ''; + const userAgent = + typeof userAgentHeader === 'string' && userAgentHeader.trim() + ? userAgentHeader.trim().slice(0, 1024) + : null; + return { ip, userAgent }; +} + +export function buildRequestContext(request, member, tokenOverride) { + const { ip, userAgent } = extractClientDetails(request); + return { + member, + ip, + userAgent, + authToken: tokenOverride || extractAuthToken(request) + }; +} diff --git a/backend/email.js b/backend/email.js new file mode 100644 index 000000000..70864f0d5 --- /dev/null +++ b/backend/email.js @@ -0,0 +1,242 @@ +import { fetch } from 'wix-fetch'; +import { secrets } from 'wix-secrets-backend'; +import { members } from 'wix-members-backend'; +import { AUCTION_CONFIG } from './wantedUtils.js'; + +const SENDGRID_SECRET_NAME = 'SENDGRID_API_KEY'; +const EMAIL_RATE_WINDOW_MS = 60_000; +const emailDedupCache = new Map(); + +/** + * Resolve the primary contact email for a Wix member. + * @param {string} memberId + * @returns {Promise} + */ +async function resolveMemberEmail(memberId) { + if (!memberId) return null; + try { + const member = await members.getMember(memberId, { fieldsets: ['CONTACT'] }); + const emails = member?.contact?.emails; + const email = Array.isArray(emails) ? emails[0]?.email ?? null : null; + return email && typeof email === 'string' ? email : null; + } catch (err) { + console.warn('[email] failed to resolve member email', { memberId, err }); + return null; + } +} + +/** + * Retrieve the SendGrid API key once and cache in memory. + * @returns {Promise} + */ +let cachedSendgridKeyPromise; +async function getSendgridKey() { + if (!cachedSendgridKeyPromise) { + cachedSendgridKeyPromise = secrets.getSecret(SENDGRID_SECRET_NAME).catch((err) => { + console.warn('[email] failed to load SENDGRID_API_KEY secret', err); + return null; + }); + } + return cachedSendgridKeyPromise; +} + +function composeEmail(subject, htmlBody, textBody) { + return { + personalizations: [ + { + to: [], + subject + } + ], + from: { + email: AUCTION_CONFIG.EMAIL_FROM_ADDRESS, + name: AUCTION_CONFIG.EMAIL_FROM_NAME + }, + content: [ + { type: 'text/plain', value: textBody }, + { type: 'text/html', value: htmlBody } + ] + }; +} + +async function sendEmailPayload(toEmail, subject, htmlBody, textBody) { + const apiKey = await getSendgridKey(); + if (!apiKey) { + console.log('[email] SENDGRID_API_KEY missing – logging payload instead', { + toEmail, + subject, + textBody + }); + return; + } + + const payload = composeEmail(subject, htmlBody, textBody); + payload.personalizations[0].to.push({ email: toEmail }); + + const res = await fetch('https://api.sendgrid.com/v3/mail/send', { + method: 'POST', + headers: { + Authorization: `Bearer ${apiKey}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!res.ok) { + const body = await res.text().catch(() => ''); + console.error('[email] sendgrid send failed', { status: res.status, body }); + } +} + +function shouldSkipDedup(key) { + const now = Date.now(); + const lastSent = emailDedupCache.get(key); + if (lastSent && now - lastSent < EMAIL_RATE_WINDOW_MS) { + return true; + } + emailDedupCache.set(key, now); + return false; +} + +function formatCurrency(amount, currency) { + const safeAmount = Number(amount); + if (!Number.isFinite(safeAmount)) return `${amount} ${currency}`; + return `${safeAmount.toLocaleString(undefined, { + minimumFractionDigits: 0, + maximumFractionDigits: 8 + })} ${currency}`; +} + +function buildManageLink(manageLink) { + return manageLink || 'https://www.dikpik.io/wanted-and-bazar'; +} + +export async function sendBidConfirmation({ toUserId, auctionTitle, amount, currency, bidRef, manageLink }) { + const email = await resolveMemberEmail(toUserId); + if (!email) return; + + const subject = `Bid placed on ${auctionTitle}`; + const amountText = formatCurrency(amount, currency); + const link = buildManageLink(manageLink); + const text = [ + `Congratulations! Your bid for ${auctionTitle} was placed successfully.`, + `Amount: ${amountText}`, + `Reference: ${bidRef}`, + `Manage your bid: ${link}` + ].join('\n'); + + const html = ` +
+

Congratulations!

+

Your bid for ${auctionTitle} was placed successfully.

+

Amount: ${amountText}

+

Reference: ${bidRef}

+

Manage your bid

+
+ `; + + await sendEmailPayload(email, subject, html, text); +} + +export async function sendOutbidNotice({ + toUserId, + auctionTitle, + oldAmount, + newAmount, + currency = 'ETH', + manageLink, + auctionId +}) { + const email = await resolveMemberEmail(toUserId); + if (!email) return; + const dedupKey = `outbid:${toUserId}:${auctionId || auctionTitle}:${newAmount}`; + if (shouldSkipDedup(dedupKey)) return; + + const subject = `You've been outbid on ${auctionTitle}`; + const oldAmountText = formatCurrency(oldAmount, currency); + const newAmountText = formatCurrency(newAmount, currency); + const link = buildManageLink(manageLink); + + const text = [ + `Another bidder just surpassed your offer on ${auctionTitle}.`, + `Your previous bid: ${oldAmountText}`, + `Current leading bid: ${newAmountText}`, + `Respond here: ${link}` + ].join('\n'); + + const html = ` +
+

You've been outbid

+

Your previous bid on ${auctionTitle} has been surpassed.

+

Your bid: ${oldAmountText}

+

Current leading bid: ${newAmountText}

+

Place a new bid

+
+ `; + + await sendEmailPayload(email, subject, html, text); +} + +export async function sendWinningEmail({ + toUserId, + auctionTitle, + finalAmount, + currency = 'ETH', + claimLink +}) { + const email = await resolveMemberEmail(toUserId); + if (!email) return; + + const subject = `You won ${auctionTitle}`; + const amountText = formatCurrency(finalAmount, currency); + const link = claimLink || buildManageLink(claimLink); + + const text = [ + `You won the auction for ${auctionTitle}.`, + `Final amount: ${amountText}`, + `Claim instructions: ${link}` + ].join('\n'); + + const html = ` +
+

You won ${auctionTitle}

+

Great news! You are the winning bidder.

+

Final amount: ${amountText}

+

Claim your NFT

+
+ `; + + await sendEmailPayload(email, subject, html, text); +} + +export async function sendBuyNowEmail({ + toUserId, + auctionTitle, + price, + currency = 'ETH', + receiptLink +}) { + const email = await resolveMemberEmail(toUserId); + if (!email) return; + + const subject = `Purchased ${auctionTitle} via Buy Now`; + const priceText = formatCurrency(price, currency); + const link = receiptLink || buildManageLink(receiptLink); + + const text = [ + `You successfully purchased ${auctionTitle} via Buy Now.`, + `Price: ${priceText}`, + `Receipt / next steps: ${link}` + ].join('\n'); + + const html = ` +
+

Purchase confirmed

+

You bought ${auctionTitle} using Buy Now.

+

Price: ${priceText}

+

View receipt

+
+ `; + + await sendEmailPayload(email, subject, html, text); +} diff --git a/backend/http-functions.js b/backend/http-functions.js new file mode 100644 index 000000000..c76d1af42 --- /dev/null +++ b/backend/http-functions.js @@ -0,0 +1,251 @@ +// Copy this entire file into Wix Dev Mode -> backend/http-functions.js +// It provides simple listings/bids/relay HTTP functions with CORS for preview + prod. +import { response } from 'wix-http-functions'; +import wixData from 'wix-data'; + +const LISTINGS_COLLECTION = 'Import2'; +const BIDS_COLLECTION = 'Import3'; +const RELAY_COLLECTION = 'Import4'; +const FALLBACK_CACHE = { + listings: [], + bids: [], + relay: [] +}; + +const ALLOWED_ORIGINS = [ + 'https://www.dikpik.io', + 'https://www-dikpik-io.filesusr.com', + 'https://editor.wix.com', + 'https://manage.wix.com', + 'https://create.editorx.com', + 'https://manage.editorx.com' +]; +const ORIGIN_SUFFIXES = ['.filesusr.com']; +const DEFAULT_ORIGIN = ALLOWED_ORIGINS[0]; + +function isAllowedOrigin(origin) { + if (!origin) return false; + if (ALLOWED_ORIGINS.includes(origin)) return true; + try { + const { hostname } = new URL(origin); + return ORIGIN_SUFFIXES.some((suffix) => hostname.endsWith(suffix)); + } catch (_err) { + return false; + } +} + +function pickOrigin(request) { + const raw = request?.headers?.origin || request?.headers?.Origin || ''; + const origin = String(raw || '').trim(); + return isAllowedOrigin(origin) ? origin : DEFAULT_ORIGIN; +} + +function corsHeaders(origin, request) { + const acrh = + request?.headers?.['access-control-request-headers'] || + request?.headers?.['Access-Control-Request-Headers'] || + 'Content-Type, Authorization'; + return { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': origin, + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Allow-Methods': 'GET,POST,OPTIONS', + 'Access-Control-Allow-Headers': acrh, + 'Access-Control-Max-Age': '86400', + Vary: 'Origin, Access-Control-Request-Headers' + }; +} + +function json(status, payload, origin, request) { + return response({ + status, + headers: corsHeaders(origin, request), + body: JSON.stringify(payload ?? null) + }); +} + +function options(origin, request) { + return response({ + status: 204, + headers: corsHeaders(origin, request) + }); +} + +async function parseBody(request) { + try { + const raw = await request.body?.text(); + if (!raw) return {}; + return JSON.parse(raw); + } catch (_err) { + throw new Error('INVALID_JSON'); + } +} + +function logDataWarning(context, err) { + console.warn(`[bazaar:http-functions] ${context} failed`, err?.message || err); +} + +async function listCollection(collection, fallbackKey) { + const pageSize = 100; + try { + let results = await wixData.query(collection).limit(pageSize).find(); + const items = [...results.items]; + while (results.hasNext()) { + results = await results.next(); + items.push(...results.items); + } + return { items, fallback: false }; + } catch (err) { + logDataWarning(`list ${collection}`, err); + return { items: [...FALLBACK_CACHE[fallbackKey]], fallback: true }; + } +} + +// ---- listings ---- +export function options_listings(request) { + return options(pickOrigin(request), request); +} + +export async function get_listings(request) { + const origin = pickOrigin(request); + const { items, fallback } = await listCollection(LISTINGS_COLLECTION, 'listings'); + return json(200, { items, fallback }, origin, request); +} + +async function insertWithFallback(collection, payload, fallbackKey) { + try { + return await wixData.insert(collection, payload, { suppressAuth: true }); + } catch (err) { + logDataWarning(`insert ${collection}`, err); + const record = { ...payload, _id: payload._id || `fallback_${fallbackKey}_${Date.now()}` }; + FALLBACK_CACHE[fallbackKey].unshift(record); + return record; + } +} + +export async function post_listings(request) { + const origin = pickOrigin(request); + try { + const body = await parseBody(request); + const payload = { + ...body, + kind: body?.kind || 'auction', + _createdDate: new Date() + }; + const saved = await insertWithFallback(LISTINGS_COLLECTION, payload, 'listings'); + return json(201, saved, origin, request); + } catch (err) { + const status = err.message === 'INVALID_JSON' ? 400 : 500; + return json(status, { error: err.message || 'Failed to save listing.' }, origin, request); + } +} + +// ---- bids ---- +export function options_bids(request) { + return options(pickOrigin(request), request); +} + +export async function get_bids(request) { + const origin = pickOrigin(request); + const [listingId] = request.path || []; + try { + let query = wixData.query(BIDS_COLLECTION).ascending('_createdDate'); + if (listingId) { + query = query.eq('listingId', listingId); + } + const items = await listCollectionFromQuery(query, listingId); + return json(200, { items }, origin, request); + } catch (err) { + logDataWarning('get_bids', err); + const items = FALLBACK_CACHE.bids.filter((bid) => + listingId ? bid.listingId === listingId : true + ); + return json(200, { items, fallback: true }, origin, request); + } +} + +async function listCollectionFromQuery(query, listingId) { + const pageSize = 100; + try { + let results = await query.limit(pageSize).find(); + const items = [...results.items]; + while (results.hasNext()) { + results = await results.next(); + items.push(...results.items); + } + return items; + } catch (err) { + logDataWarning(`query bids ${listingId || 'all'}`, err); + return FALLBACK_CACHE.bids.filter((bid) => + listingId ? bid.listingId === listingId : true + ); + } +} + +export async function post_bids(request) { + const origin = pickOrigin(request); + try { + const body = await parseBody(request); + if (!body?.listingId) { + return json(400, { error: 'listingId is required.' }, origin, request); + } + if (typeof body?.amount !== 'number') { + return json(400, { error: 'amount is required.' }, origin, request); + } + const bid = { + listingId: String(body.listingId), + amount: Number(body.amount), + bidder: String(body.bidder || 'anonymous'), + status: body.status || 'RECEIVED', + _createdDate: new Date() + }; + const saved = await insertWithFallback(BIDS_COLLECTION, bid, 'bids'); + return json(201, saved, origin, request); + } catch (err) { + const status = err.message === 'INVALID_JSON' ? 400 : 500; + return json(status, { error: err.message || 'Failed to save bid.' }, origin, request); + } +} + +// ---- relay settings ---- +export function options_relay(request) { + return options(pickOrigin(request), request); +} + +export async function get_relay(request) { + const origin = pickOrigin(request); + const { items, fallback } = await listCollection(RELAY_COLLECTION, 'relay'); + return json(200, { items, fallback }, origin, request); +} + +async function updateWithFallback(collection, doc, fallbackKey) { + try { + return await wixData.update(collection, doc, { suppressAuth: true }); + } catch (err) { + logDataWarning(`update ${collection}`, err); + if (doc?._id) { + const idx = FALLBACK_CACHE[fallbackKey].findIndex((item) => item._id === doc._id); + if (idx >= 0) { + FALLBACK_CACHE[fallbackKey][idx] = doc; + } else { + FALLBACK_CACHE[fallbackKey].push(doc); + } + } else { + FALLBACK_CACHE[fallbackKey].push({ ...doc, _id: `fallback_${fallbackKey}_${Date.now()}` }); + } + return doc; + } +} + +export async function post_relay(request) { + const origin = pickOrigin(request); + try { + const body = await parseBody(request); + const doc = { ...body, _updatedDate: new Date() }; + const saved = await updateWithFallback(RELAY_COLLECTION, doc, 'relay'); + return json(200, saved, origin, request); + } catch (err) { + const status = err.message === 'INVALID_JSON' ? 400 : 500; + return json(status, { error: err.message || 'Failed to save relay data.' }, origin, request); + } +} diff --git a/backend/jwt.jsw b/backend/jwt.jsw new file mode 100644 index 000000000..74cc76686 --- /dev/null +++ b/backend/jwt.jsw @@ -0,0 +1,53 @@ +import { getSecret } from 'wix-secrets-backend'; + +const secretPromise = getSecret('WIX_SECRET_JWT_KEY'); + +function base64Url(input) { + return Buffer.from(JSON.stringify(input)) + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); +} + +function decode(str) { + const pad = str.length % 4 === 2 ? '==' : str.length % 4 === 3 ? '=' : ''; + const normalized = str.replace(/-/g, '+').replace(/_/g, '/') + pad; + return JSON.parse(Buffer.from(normalized, 'base64').toString('utf8')); +} + +export async function sign(payload, ttlSeconds = 900) { + const secret = await secretPromise; + const header = base64Url({ alg: 'HS256', typ: 'JWT' }); + const now = Math.floor(Date.now() / 1000); + const body = base64Url({ ...payload, iat: now, exp: now + ttlSeconds }); + const data = `${header}.${body}`; + const signature = Buffer.from( + require('crypto').createHmac('sha256', secret).update(data).digest('base64') + ) + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + return `${data}.${signature}`; +} + +export async function verify(token) { + const [headerPart, bodyPart, sigPart] = token.split('.'); + if (!headerPart || !bodyPart || !sigPart) throw new Error('INVALID_JWT'); + const secret = await secretPromise; + const data = `${headerPart}.${bodyPart}`; + const expected = Buffer.from( + require('crypto').createHmac('sha256', secret).update(data).digest('base64') + ) + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + if (expected !== sigPart) throw new Error('INVALID_SIGNATURE'); + const payload = decode(bodyPart); + if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) { + throw new Error('JWT_EXPIRED'); + } + return payload; +} diff --git a/backend/wantedPosts.jsw b/backend/wantedPosts.jsw new file mode 100644 index 000000000..020995f4c --- /dev/null +++ b/backend/wantedPosts.jsw @@ -0,0 +1,207 @@ +import wixData from 'wix-data'; +import { currentMember } from 'wix-members-backend'; +import { + WANTED_COLLECTION, + WantedError, + normalizeWantedPayload, + shapeWantedRecord, + getOwnerId +} from './wantedUtils.js'; +import { + placeBid, + increaseBidQuick, + withdrawBid, + finalizeAuction, + buyNow, + getAuctionPublic, + getAuctionPrivate, + isAuctionAdmin +} from './auctionService.js'; + +function compact(obj) { + return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)); +} + +async function fetchCurrentMember() { + try { + return await currentMember.getMember({ fieldsets: ['FULL'] }); + } catch (_err) { + return null; + } +} + +async function requireMember() { + const member = await fetchCurrentMember(); + if (!member) throw new WantedError('Login required.', 401); + return member; +} + +function resolveMemberName(member) { + if (!member) return 'Member'; + const nickname = member.profile?.nickname; + if (nickname) return nickname; + const slug = member.profile?.slug; + if (slug) return slug; + const name = member.contact?.name; + if (name) return name; + const email = member.contact?.emails?.[0]?.email; + if (email) return email; + return 'Member'; +} + +async function getWantedById(id) { + try { + return await wixData.get(WANTED_COLLECTION, id); + } catch (_err) { + return null; + } +} + +function mergeRecords(oldRec, newRec) { + return { ...(oldRec || {}), ...(newRec || {}) }; +} + +export async function listWantedPosts() { + const member = await fetchCurrentMember(); + const viewerId = member?._id ? String(member._id) : null; + + const results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .descending('_createdDate') + .limit(1000) + .find(); + + return results.items.map((item) => + shapeWantedRecord(item, { includeContact: Boolean(viewerId), viewerId }) + ); +} + +export async function getMyWantedPosts() { + const member = await requireMember(); + const memberId = String(member._id); + + let results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .eq('ownerMemberId', memberId) + .descending('_createdDate') + .limit(1000) + .find(); + + if (!results.items.length) { + results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .eq('_owner', memberId) + .descending('_createdDate') + .limit(1000) + .find(); + } + + return results.items.map((item) => + shapeWantedRecord(item, { includeContact: true, viewerId: memberId }) + ); +} + +export async function createWantedPost(data) { + const member = await requireMember(); + const memberId = String(member._id); + const payload = normalizeWantedPayload(data || {}, { partial: false }); + const now = new Date(); + + const doc = compact({ + ...payload, + ownerMemberId: memberId, + ownerDisplayName: resolveMemberName(member), + createdAt: now, + updatedAt: now + }); + + const saved = await wixData.insert(WANTED_COLLECTION, doc, { suppressAuth: true }); + + return shapeWantedRecord(saved, { includeContact: true, viewerId: memberId }); +} + +export async function updateWantedPost(id, data) { + if (!id || typeof id !== 'string') { + throw new WantedError('ID is required.', 400, { field: 'id' }); + } + + const member = await requireMember(); + const memberId = String(member._id); + const existing = await getWantedById(id); + + if (!existing) { + throw new WantedError('Wanted post not found.', 404); + } + + const ownerId = getOwnerId(existing); + if (!ownerId || String(ownerId) !== memberId) { + throw new WantedError('You can only update your own wanted posts.', 403); + } + + const payload = normalizeWantedPayload(data || {}, { partial: true }); + const updateFields = compact({ + ...payload, + _id: id, + ownerMemberId: ownerId, + updatedAt: new Date() + }); + + const changedKeys = Object.keys(updateFields).filter((key) => key !== '_id'); + if (!changedKeys.length) { + throw new WantedError('No changes provided.', 400); + } + + const updated = await wixData.update(WANTED_COLLECTION, updateFields, { suppressAuth: true }); + const merged = mergeRecords(existing, updated); + + return shapeWantedRecord(merged, { includeContact: true, viewerId: memberId }); +} + +export async function placeAuctionBid(input) { + const member = await requireMember(); + const context = { member }; + return placeBid(input, context); +} + +export async function increaseAuctionBidQuick(input) { + const member = await requireMember(); + const context = { member }; + return increaseBidQuick(input, context); +} + +export async function withdrawAuctionBid(input) { + const member = await requireMember(); + const context = { member }; + return withdrawBid(input, context); +} + +export async function finalizeAuctionAdmin(input) { + const member = await requireMember(); + if (!isAuctionAdmin(member)) { + throw new WantedError('Admin access required.', 403); + } + const context = { member }; + return finalizeAuction(input, context); +} + +export async function buyNowAuction(input) { + const member = await requireMember(); + const context = { member }; + return buyNow(input, context); +} + +export async function getAuctionPublicData(input) { + return getAuctionPublic(input); +} + +export async function getAuctionPrivateData(input) { + const member = await requireMember(); + if (!isAuctionAdmin(member)) { + throw new WantedError('Admin access required.', 403); + } + const context = { member }; + return getAuctionPrivate(input, context); +} diff --git a/backend/wantedUtils.js b/backend/wantedUtils.js new file mode 100644 index 000000000..a5f6cecf3 --- /dev/null +++ b/backend/wantedUtils.js @@ -0,0 +1,369 @@ +// Utility helpers shared by backend wanted-post implementations. +// Handles validation, normalization, and serialization so that both HTTP +// functions and web modules can rely on the same logic. + +const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +const MAX_STATUS_LENGTH = 24; +const DEFAULT_COLLECTION = 'Import2'; + +export const AUCTION_CONFIG = { + MIN_INCREMENT_DEFAULT: 1, + QUICK_INCREMENT_USD: 10, + EMAIL_FROM_NAME: 'GoblinVerse Auctions', + EMAIL_FROM_ADDRESS: 'no-reply@goblinverse.example', + FX_RATE_PROVIDER: 'stub' +}; + +export const WANTED_COLLECTION = DEFAULT_COLLECTION; + +export class WantedError extends Error { + constructor(message, statusCode = 400, details) { + super(message); + this.name = 'WantedError'; + this.statusCode = statusCode; + if (details) this.details = details; + } +} + +function removeUndefined(obj) { + return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)); +} + +function requiredString(value, fieldLabel, maxLength = 255) { + if (value === undefined || value === null) { + throw new WantedError(`${fieldLabel} is required.`, 400, { field: fieldLabel }); + } + const str = String(value).trim(); + if (!str) { + throw new WantedError(`${fieldLabel} is required.`, 400, { field: fieldLabel }); + } + return maxLength && str.length > maxLength ? str.slice(0, maxLength) : str; +} + +function optionalString(value, maxLength = 255) { + if (value === undefined) return undefined; + if (value === null) return null; + const str = String(value).trim(); + if (!str) return null; + return maxLength && str.length > maxLength ? str.slice(0, maxLength) : str; +} + +function optionalMultiline(value, maxLength = 6000) { + if (value === undefined) return undefined; + if (value === null) return null; + let str = String(value).trim(); + if (!str) return null; + str = str.replace(/\r\n/g, '\n'); + return maxLength && str.length > maxLength ? str.slice(0, maxLength) : str; +} + +function optionalEmail(value) { + if (value === undefined) return undefined; + if (value === null) return null; + const str = String(value).trim(); + if (!str) return null; + if (!EMAIL_RE.test(str)) { + throw new WantedError('Invalid email address.', 400, { field: 'contactEmail' }); + } + const email = str.toLowerCase(); + return email.length > 254 ? email.slice(0, 254) : email; +} + +function optionalPhone(value) { + if (value === undefined) return undefined; + if (value === null) return null; + const str = String(value).trim(); + if (!str) return null; + const normalized = str.replace(/[^\d+().\- ]+/g, ''); + return normalized.length > 64 ? normalized.slice(0, 64) : normalized; +} + +function optionalBudget(value) { + if (value === undefined) return undefined; + if (value === null || value === '') return null; + const num = Number(value); + if (!Number.isFinite(num)) { + throw new WantedError('Budget must be a number.', 400, { field: 'budget' }); + } + if (num < 0) { + throw new WantedError('Budget must be zero or greater.', 400, { field: 'budget' }); + } + return Number(num.toFixed(8)); +} + +function optionalTokenId(value) { + if (value === undefined) return undefined; + if (value === null || value === '') return null; + const num = Number(value); + if (!Number.isFinite(num)) { + throw new WantedError('tokenId must be a number.', 400, { field: 'tokenId' }); + } + if (num < 0) { + throw new WantedError('tokenId must be zero or greater.', 400, { field: 'tokenId' }); + } + return Math.floor(num); +} + +function sanitizeCurrency(value) { + if (value === undefined || value === null || value === '') return 'ETH'; + const str = String(value).trim().toUpperCase(); + if (!str) return 'ETH'; + if (str.length > 12) { + throw new WantedError('Currency code is too long.', 400, { field: 'currency' }); + } + return str; +} + +function sanitizeStatus(value, defaultValue) { + if (value === undefined) return defaultValue; + if (value === null || value === '') return null; + const str = String(value).trim(); + if (!str) return null; + const upper = str.toUpperCase(); + if (upper.length > MAX_STATUS_LENGTH) { + throw new WantedError('Status value is too long.', 400, { field: 'status' }); + } + return upper; +} + +function sanitizeChain(value, currency) { + if (value === undefined || value === null || value === '') { + return currency === 'SOL' ? 'solana' : 'evm'; + } + const str = String(value).trim().toLowerCase(); + if (!str) return currency === 'SOL' ? 'solana' : 'evm'; + if (['sol', 'solana', 'sol-mainnet', 'solana-mainnet'].includes(str)) return 'solana'; + return 'evm'; +} + +function sanitizeWalletEntry(entry) { + if (!entry || typeof entry !== 'object') return null; + const address = optionalString(entry.address, 200); + if (address === undefined || address === null) return null; + const chain = sanitizeChain(entry.chain); + const wallet = { + address: chain === 'solana' ? address : address.toLowerCase(), + chain, + verified: Boolean(entry.verified) + }; + + const type = optionalString(entry.type, 32); + if (type) wallet.type = type; + + const signature = optionalMultiline(entry.signature, 4000); + if (signature) wallet.signature = signature; + + const message = optionalMultiline(entry.message, 4000); + if (message) wallet.message = message; + + if (entry.connectedAt) wallet.connectedAt = String(entry.connectedAt); + + return wallet; +} + +function sanitizeWallets(value) { + if (value === undefined) return undefined; + if (value === null) return []; + if (!Array.isArray(value)) return []; + return value.map(sanitizeWalletEntry).filter(Boolean); +} + +function toNumberOrNull(value) { + if (value === undefined || value === null || value === '') return null; + const num = Number(value); + return Number.isFinite(num) ? Number(num) : null; +} + +function toTokenIdOrNull(value) { + if (value === undefined || value === null || value === '') return null; + const num = Number(value); + return Number.isFinite(num) ? Math.floor(num) : null; +} + +export function normalizeWantedPayload(input = {}, { partial = false } = {}) { + if (!input || typeof input !== 'object') { + throw new WantedError('Invalid payload.', 400); + } + + const output = {}; + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'title')) { + output.title = requiredString(input.title, 'title', 140); + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'traitQuery')) { + if (Object.prototype.hasOwnProperty.call(input, 'traitQuery')) { + const trait = optionalString(input.traitQuery, 240); + if (trait !== undefined) output.traitQuery = trait; + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'tokenId')) { + if (Object.prototype.hasOwnProperty.call(input, 'tokenId')) { + const token = optionalTokenId(input.tokenId); + if (token !== undefined) output.tokenId = token; + } + } + + if (!partial) { + const hasToken = Object.prototype.hasOwnProperty.call(output, 'tokenId') && output.tokenId !== null && output.tokenId !== undefined; + const traitSeed = Object.prototype.hasOwnProperty.call(output, 'traitQuery') + ? output.traitQuery + : optionalString(input.traitQuery, 240); + const hasTrait = Boolean(traitSeed && traitSeed.length); + if (!hasToken && !hasTrait) { + throw new WantedError('Provide a tokenId or trait query.', 400, { field: 'tokenId' }); + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'budget') || Object.prototype.hasOwnProperty.call(input, 'price')) { + const budgetSource = Object.prototype.hasOwnProperty.call(input, 'budget') + ? input.budget + : input.price; + const budget = optionalBudget(budgetSource); + if (budget === undefined) { + if (!partial) throw new WantedError('Budget is required.', 400, { field: 'budget' }); + } else if (budget === null) { + output.budget = null; + output.price = null; + } else { + output.budget = budget; + output.price = budget; + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'currency')) { + output.currency = sanitizeCurrency(input.currency); + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'status')) { + output.status = sanitizeStatus( + Object.prototype.hasOwnProperty.call(input, 'status') ? input.status : undefined, + partial ? undefined : 'OPEN' + ); + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'description') || Object.prototype.hasOwnProperty.call(input, 'descriptionRich')) { + const descriptionSource = Object.prototype.hasOwnProperty.call(input, 'descriptionRich') + ? input.descriptionRich + : input.description; + const description = optionalMultiline(descriptionSource, 6000); + if (description === undefined) { + if (!partial) { + output.description = ''; + output.descriptionRich = ''; + } + } else { + output.description = description ?? ''; + output.descriptionRich = description ?? ''; + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactLine')) { + const val = optionalString(input.contactLine, 120); + if (val !== undefined) output.contactLine = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactWhatsApp')) { + const val = optionalString(input.contactWhatsApp, 120); + if (val !== undefined) output.contactWhatsApp = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactTelegram')) { + const val = optionalString(input.contactTelegram, 120); + if (val !== undefined) output.contactTelegram = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactEmail')) { + const val = optionalEmail(input.contactEmail); + if (val !== undefined) output.contactEmail = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactPhone')) { + const val = optionalPhone(input.contactPhone); + if (val !== undefined) output.contactPhone = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactMessage')) { + const val = optionalMultiline(input.contactMessage, 2000); + if (val !== undefined) output.contactMessage = val ?? null; + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'wallets')) { + const wallets = sanitizeWallets(input.wallets); + if (wallets !== undefined) output.wallets = wallets; + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'chain') || output.currency) { + const chain = sanitizeChain(input.chain, output.currency || sanitizeCurrency(input.currency)); + if (chain !== undefined) output.chain = chain; + } + + if (!partial) { + output.kind = 'wanted'; + } + + return removeUndefined(output); +} + +export function getOwnerId(record) { + if (!record) return null; + const owner = + record.ownerMemberId || + record.ownerId || + record.owner || + record._owner || + null; + return owner != null ? String(owner) : null; +} + +function sanitizeWalletEntrySafe(entry) { + try { + return sanitizeWalletEntry(entry); + } catch (_err) { + return null; + } +} + +export function shapeWantedRecord(record, { includeContact = false, viewerId } = {}) { + if (!record) return null; + const ownerId = getOwnerId(record); + const viewerIsOwner = viewerId && ownerId && String(ownerId) === String(viewerId); + const canViewContact = includeContact || viewerIsOwner; + + const currencyRaw = typeof record.currency === 'string' ? record.currency.trim() : ''; + const currency = currencyRaw ? currencyRaw.toUpperCase().slice(0, 12) : null; + const chain = sanitizeChain(record.chain, currency || 'ETH'); + + const budget = toNumberOrNull(record.budget != null ? record.budget : record.price); + const tokenId = toTokenIdOrNull(record.tokenId); + + const walletsRaw = Array.isArray(record.wallets) ? record.wallets : []; + const wallets = walletsRaw.map(sanitizeWalletEntrySafe).filter(Boolean); + + const shaped = { + _id: record._id, + id: record._id, + kind: 'wanted', + title: record.title || '', + traitQuery: record.traitQuery || null, + tokenId, + budget, + price: budget, + currency, + status: record.status ? String(record.status).toUpperCase().slice(0, MAX_STATUS_LENGTH) : 'OPEN', + description: record.description ?? record.notes ?? '', + descriptionRich: record.descriptionRich ?? record.description ?? '', + chain, + ownerId, + ownerMemberId: ownerId, + ownerDisplayName: record.ownerDisplayName || record.ownerName || null, + createdAt: record.createdAt || record._createdDate || null, + updatedAt: record.updatedAt || record._updatedDate || null, + wallets + }; + + shaped.contactLine = canViewContact ? record.contactLine ?? null : null; + shaped.contactWhatsApp = canViewContact ? record.contactWhatsApp ?? null : null; + shaped.contactTelegram = canViewContact ? record.contactTelegram ?? null : null; + shaped.contactEmail = canViewContact ? record.contactEmail ?? null : null; + shaped.contactPhone = canViewContact ? record.contactPhone ?? null : null; + shaped.contactMessage = canViewContact ? (record.contactMessage ?? record.message ?? null) : null; + + return shaped; +} diff --git a/bazaarcodexv1.html b/bazaarcodexv1.html new file mode 100644 index 000000000..cca6b8f96 --- /dev/null +++ b/bazaarcodexv1.html @@ -0,0 +1,4140 @@ + + + + + + + THE DIKPIK BAZAAR + + + + + + + + +
+
+
+
+ +
+
+
+

+ THE DIKPIK BAZAAR +

+
+ + + + + + +
+
+

Family Tree • Auctions • Wanted

+
+
+ + +
+
+
+ + + + + + +
+ +
+
+
Filter by Traits
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+ + +
+
+
+
+

+ DIKPIK Family Tree +

+

Loading…

+
+
+ The Family Tree auto-assigns each NFT to a classic mafia hierarchy (Boss → Underboss → Consigliere → Caporegime → Soldier → Associate) + per faction. Choose a faction above to see its crew arranged in a centered pyramid by rank. Rarity and trait percentages are shown + on each token; click any token for the full trait breakdown in neon boxes with collection-wide rarity % (to 2 decimals). +
+
+
+
+ +
+
+

+ All Tokens (Filtered View) +

+
+ + + Page 1 / 1 + + +
+ Per page + +
+
+
+ +
+ + +
+
+
+ + + Page 1 / 1 + + +
+
+
+
+ + + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codex/backend/auth.jsw b/codex/backend/auth.jsw new file mode 100644 index 000000000..2834883d2 --- /dev/null +++ b/codex/backend/auth.jsw @@ -0,0 +1,90 @@ +import wixData from 'wix-data'; +import wixUsersBackend from 'wix-users-backend'; +import { getSecret } from 'wix-secrets-backend'; +import { sign } from 'backend/jwt.jsw'; + +const NONCE_COLLECTION = 'AuthNonces'; +const WALLETS_COLLECTION = 'MembersWallets'; + +const domainPromise = getSecret('APP_DOMAIN'); +const chainPromise = getSecret('SUPPORTED_CHAIN_ID').then((id) => + Number(id || 1) +); + +export async function getWalletConfig() { + return { + chainId: await chainPromise, + domain: await domainPromise, + uri: await domainPromise + }; +} + +export async function getNonce(memberId) { + const user = wixUsersBackend.currentUser; + if (!user.loggedIn) throw new Error('LOGIN_REQUIRED'); + if (memberId && memberId !== user.id) + throw new Error('MEMBER_MISMATCH'); + const nonce = require('crypto').randomBytes(16).toString('hex'); + await wixData.save(NONCE_COLLECTION, { + memberId: user.id, + nonce, + createdAt: new Date() + }); + return nonce; +} + +export async function verifySiwe({ address, signature, nonce }) { + const user = wixUsersBackend.currentUser; + if (!user.loggedIn) throw new Error('LOGIN_REQUIRED'); + const chainId = await chainPromise; + const domain = await domainPromise; + const message = [ + `${domain} wants you to sign in with your Ethereum account:`, + address, + '', + 'Sign-in with Ethereum to DIKPIK Bazaar.', + '', + `URI: ${domain}`, + 'Version: 1', + `Chain ID: ${chainId}`, + `Nonce: ${nonce}` + ].join('\n'); + const ethers = require('ethers'); + const recovered = ethers.verifyMessage(message, signature).toLowerCase(); + if (recovered !== address.toLowerCase()) throw new Error('BAD_SIGNATURE'); + + const nonceResult = await wixData + .query(NONCE_COLLECTION) + .eq('memberId', user.id) + .eq('nonce', nonce) + .limit(1) + .find(); + if (!nonceResult.items.length) throw new Error('NONCE_NOT_FOUND'); + await wixData.remove(NONCE_COLLECTION, nonceResult.items[0]._id); + + await upsertWallet(user.id, address.toLowerCase(), chainId); + + return await sign( + { + sub: user.id, + address: address.toLowerCase(), + chainId + }, + 900 + ); +} + +async function upsertWallet(memberId, address, chainId) { + const exists = await wixData + .query(WALLETS_COLLECTION) + .eq('address', address) + .limit(1) + .find(); + if (exists.items.length) return exists.items[0]; + return wixData.insert(WALLETS_COLLECTION, { + memberId, + address, + chainId, + createdAt: new Date() + }); +} diff --git a/codex/backend/bids.jsw b/codex/backend/bids.jsw new file mode 100644 index 000000000..2e04c858c --- /dev/null +++ b/codex/backend/bids.jsw @@ -0,0 +1,63 @@ +import wixData from 'wix-data'; +import { verify } from 'backend/jwt.jsw'; + +const AUCTIONS = 'Auctions'; +const BIDS = 'Bids'; + +export async function listBids(auctionId) { + const items = await wixData + .query(BIDS) + .eq('auctionId', auctionId) + .descending('amount') + .find(); + return items.items; +} + +export async function placeBid({ auctionId, amount, jwt }) { + if (!jwt) throw new Error('AUTH_REQUIRED'); + const token = await verify(jwt); + if (!auctionId || !amount) throw new Error('INVALID_INPUT'); + + const auction = await wixData.get(AUCTIONS, auctionId); + if (!auction) throw new Error('AUCTION_NOT_FOUND'); + if (auction.status !== 'LIVE') throw new Error('AUCTION_CLOSED'); + + const minAmount = Math.max( + Number(auction.minPrice || 0), + Number(auction.highBidAmount || 0) + Number(auction.minIncrement || 0) + ); + if (Number(amount) < minAmount) throw new Error('BID_TOO_LOW'); + + const bid = await wixData.insert(BIDS, { + auctionId, + memberId: token.sub, + address: token.address, + amount: Number(amount), + createdAt: new Date() + }); + + const updatedAuction = { + ...auction, + highBidAmount: Number(amount), + highBidder: token.sub, + version: (auction.version || 0) + 1 + }; + + try { + await wixData.update(AUCTIONS, updatedAuction, { + condition: wixData + .filter() + .eq('_id', auction._id) + .eq('version', auction.version || 0) + }); + } catch (err) { + await wixData.remove(BIDS, bid._id); + throw new Error('AUCTION_CONFLICT'); + } + + return { + bid, + highBidAmount: updatedAuction.highBidAmount, + version: updatedAuction.version + }; +} diff --git a/codex/backend/http-functions.js b/codex/backend/http-functions.js new file mode 100644 index 000000000..f7b31e5e4 --- /dev/null +++ b/codex/backend/http-functions.js @@ -0,0 +1,517 @@ +import { response } from 'wix-http-functions'; +import wixData from 'wix-data'; +import { currentMember } from 'wix-members-backend'; +import { + WANTED_COLLECTION, + WantedError, + normalizeWantedPayload, + shapeWantedRecord, + getOwnerId +} from './wantedUtils.js'; + +const LISTINGS_COLLECTION = 'Import2'; +const WANTED_RESPONSES_COLLECTION = 'WantedResponses'; + +const ALLOWED_ORIGINS = [ + 'https://www.dikpik.io', + 'https://www-dikpik-io.filesusr.com', + 'https://editor.wix.com', + 'https://manage.wix.com', + 'https://create.editorx.com', + 'https://manage.editorx.com' +]; + +const DEFAULT_ORIGIN = ALLOWED_ORIGINS[0]; + +function pickOrigin(request) { + const headers = request?.headers || {}; + const rawOrigin = headers.origin || headers.Origin || ''; + const origin = String(rawOrigin).trim(); + if (ALLOWED_ORIGINS.includes(origin)) return origin; + return DEFAULT_ORIGIN; +} + +function corsHeaders(origin, request) { + const headers = request?.headers || {}; + const acrh = + headers['access-control-request-headers'] || + headers['Access-Control-Request-Headers'] || + 'Content-Type, Authorization'; + + return { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': origin, + 'Access-Control-Allow-Methods': 'GET,POST,PATCH,DELETE,OPTIONS', + 'Access-Control-Allow-Headers': acrh, + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Max-Age': '86400', + 'Access-Control-Expose-Headers': 'Content-Type, Vary', + Vary: 'Origin, Access-Control-Request-Headers' + }; +} + +function httpJson(status, data, origin, request, extraHeaders = {}) { + return response({ + status, + headers: { ...corsHeaders(origin, request), ...extraHeaders }, + body: JSON.stringify(data ?? null) + }); +} + +function optionsResponse(origin, request) { + return response({ + status: 204, + headers: corsHeaders(origin, request) + }); +} + +function compact(obj) { + return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)); +} + +async function parseJson(request) { + try { + const raw = await request.body?.text(); + if (!raw) return {}; + return JSON.parse(raw); + } catch (_err) { + throw new WantedError('INVALID_JSON', 400); + } +} + +async function fetchCurrentMember() { + try { + return await currentMember.getMember({ fieldsets: ['FULL'] }); + } catch (_err) { + return null; + } +} + +async function requireMember() { + const member = await fetchCurrentMember(); + if (!member) throw new WantedError('LOGIN_REQUIRED', 401); + return member; +} + +function memberDisplayName(member) { + if (!member) return 'Member'; + const nickname = member.profile?.nickname; + if (nickname) return nickname; + const slug = member.profile?.slug; + if (slug) return slug; + const name = member.contact?.name; + if (name) return name; + const email = member.contact?.emails?.[0]?.email; + if (email) return email; + return 'Member'; +} + +async function getListingById(id) { + try { + return await wixData.get(LISTINGS_COLLECTION, id); + } catch (_err) { + return null; + } +} + +function toNumber(value) { + const num = Number(value); + return Number.isFinite(num) ? num : undefined; +} + +function toText(value) { + if (value === undefined || value === null) return undefined; + const str = String(value).trim(); + return str || undefined; +} + +function toDate(value) { + if (!value) return undefined; + const date = new Date(value); + return Number.isNaN(date.getTime()) ? undefined : date; +} + +function shapeAuction(record) { + if (!record) return null; + const tokenNum = toNumber(record.tokenId); + const priceNum = toNumber(record.price); + const minNum = toNumber(record.min); + const expires = record.expiresAt ? new Date(record.expiresAt) : null; + return { + id: record._id, + kind: 'auction', + title: record.title || '', + tokenId: tokenNum != null ? tokenNum : null, + price: priceNum != null ? priceNum : null, + currency: record.currency || null, + min: minNum != null ? minNum : null, + min_ccy: record.min_ccy || record.currency || null, + traitQuery: record.traitQuery || null, + notes: record.notes || null, + ownerMemberId: record.ownerMemberId || getOwnerId(record) || null, + escrowed: Boolean(record.escrowed), + createdAt: record.createdAt || record._createdDate || null, + expiresAt: expires ? expires.toISOString() : null + }; +} + +function buildErrorBody(err) { + const message = err instanceof WantedError ? err.message : 'Internal error'; + const payload = { error: message }; + if (err instanceof WantedError && err.details) { + payload.details = err.details; + } + return payload; +} + +function handleError(err, origin, request) { + const status = err?.statusCode || err?.status || 500; + if (status >= 500) { + console.error('[bazaar] http-functions unhandled error', err); + } + return httpJson(status, buildErrorBody(err), origin, request); +} + +async function listWanted(origin, request) { + const member = await fetchCurrentMember(); + const viewerId = member?._id ? String(member._id) : null; + const results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .descending('_createdDate') + .limit(1000) + .find(); + const items = results.items.map((item) => + shapeWantedRecord(item, { includeContact: Boolean(viewerId), viewerId }) + ); + return httpJson(200, items, origin, request); +} + +async function listWantedMine(origin, request) { + const member = await requireMember(); + const memberId = String(member._id); + + let results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .eq('ownerMemberId', memberId) + .descending('_createdDate') + .limit(1000) + .find(); + + if (!results.items.length) { + results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .eq('_owner', memberId) + .descending('_createdDate') + .limit(1000) + .find(); + } + + const items = results.items.map((item) => + shapeWantedRecord(item, { includeContact: true, viewerId: memberId }) + ); + return httpJson(200, items, origin, request); +} + +async function createWanted(body, origin, request) { + const member = await requireMember(); + const memberId = String(member._id); + const normalized = normalizeWantedPayload(body || {}, { partial: false }); + const now = new Date(); + const doc = compact({ + ...normalized, + ownerMemberId: memberId, + ownerDisplayName: memberDisplayName(member), + createdAt: now, + updatedAt: now + }); + const saved = await wixData.insert(WANTED_COLLECTION, doc, { suppressAuth: true }); + const shaped = shapeWantedRecord(saved, { includeContact: true, viewerId: memberId }); + return httpJson(201, shaped, origin, request); +} + +async function updateWanted(id, payload, origin, request) { + const member = await requireMember(); + const memberId = String(member._id); + const existing = await getListingById(id); + if (!existing || existing.kind !== 'wanted') { + throw new WantedError('Wanted post not found.', 404); + } + const ownerId = getOwnerId(existing); + if (!ownerId || String(ownerId) !== memberId) { + throw new WantedError('You are not allowed to perform this action.', 403); + } + const normalized = normalizeWantedPayload(payload || {}, { partial: true }); + const updateDoc = compact({ + ...normalized, + _id: id, + ownerMemberId: ownerId, + updatedAt: new Date() + }); + const keys = Object.keys(updateDoc).filter((key) => key !== '_id'); + if (!keys.length) { + throw new WantedError('NO_ALLOWED_FIELDS', 400); + } + const updated = await wixData.update(WANTED_COLLECTION, updateDoc, { suppressAuth: true }); + const merged = { ...existing, ...updated }; + const shaped = shapeWantedRecord(merged, { includeContact: true, viewerId: memberId }); + return httpJson(200, shaped, origin, request); +} + +async function deleteWanted(id, origin, request) { + const member = await requireMember(); + const memberId = String(member._id); + const listing = await getListingById(id); + if (!listing || listing.kind !== 'wanted') { + throw new WantedError('Wanted post not found.', 404); + } + const ownerId = getOwnerId(listing); + if (!ownerId || String(ownerId) !== memberId) { + throw new WantedError('You are not allowed to perform this action.', 403); + } + await wixData.remove(WANTED_COLLECTION, id); + return httpJson(200, { ok: true }, origin, request); +} + +async function wantedContact(id, origin, request) { + const member = await requireMember(); + const listing = await getListingById(id); + if (!listing || listing.kind !== 'wanted') { + throw new WantedError('Wanted post not found.', 404); + } + const shaped = shapeWantedRecord(listing, { + includeContact: true, + viewerId: String(member._id) + }); + const contact = { + contactLine: shaped.contactLine, + contactWhatsApp: shaped.contactWhatsApp, + contactTelegram: shaped.contactTelegram, + contactEmail: shaped.contactEmail, + contactPhone: shaped.contactPhone, + contactMessage: shaped.contactMessage + }; + return httpJson(200, contact, origin, request); +} + +function sanitizeResponsePayload(payload) { + const messageRaw = typeof payload?.message === 'string' ? payload.message.trim() : ''; + if (!messageRaw) { + throw new WantedError('MESSAGE_REQUIRED', 400, { field: 'message' }); + } + let offer; + if (payload?.offer !== undefined && payload.offer !== null && payload.offer !== '') { + const num = Number(payload.offer); + if (!Number.isFinite(num)) { + throw new WantedError('OFFER_INVALID', 400, { field: 'offer' }); + } + offer = Number(num); + } + return { message: messageRaw, offer }; +} + +async function wantedRespond(id, payload, origin, request) { + const member = await requireMember(); + const listing = await getListingById(id); + if (!listing || listing.kind !== 'wanted') { + throw new WantedError('Wanted post not found.', 404); + } + const { message, offer } = sanitizeResponsePayload(payload || {}); + const record = compact({ + listingId: id, + responderMemberId: String(member._id), + message, + offer, + createdAt: new Date() + }); + const saved = await wixData.insert(WANTED_RESPONSES_COLLECTION, record, { suppressAuth: true }); + return httpJson(201, { ok: true, id: saved._id }, origin, request); +} + +export function get_ping(request) { + const origin = pickOrigin(request); + const headers = { + ...corsHeaders(origin, request), + 'Content-Type': 'text/plain' + }; + return response({ status: 200, headers, body: 'pong' }); +} + +export function options_listings(request) { + const origin = pickOrigin(request); + return optionsResponse(origin, request); +} + +export function options_wanted(request) { + const origin = pickOrigin(request); + return optionsResponse(origin, request); +} + +export function options_bids(request) { + const origin = pickOrigin(request); + return optionsResponse(origin, request); +} + +export async function get_listings(request) { + const origin = pickOrigin(request); + try { + const now = new Date(); + const results = await wixData + .query(LISTINGS_COLLECTION) + .eq('kind', 'auction') + .gt('expiresAt', now) + .descending('_createdDate') + .limit(1000) + .find(); + const items = results.items.map(shapeAuction).filter(Boolean); + return httpJson(200, items, origin, request); + } catch (err) { + return handleError(err, origin, request); + } +} + +function normalizeAuctionPayload(body, ownerMemberId) { + const title = toText(body.title); + const tokenId = toNumber(body.tokenId); + const expiresAt = toDate(body.expiresAt); + if (!title) throw new WantedError('Missing "title".', 400); + if (!Number.isFinite(tokenId)) throw new WantedError('"tokenId" is required for auctions.', 400); + if (!expiresAt) throw new WantedError('"expiresAt" is required for auctions.', 400); + const payload = compact({ + kind: 'auction', + title, + tokenId, + price: toNumber(body.price), + currency: toText(body.currency)?.toUpperCase() || null, + min: toNumber(body.min), + min_ccy: toText(body.min_ccy)?.toUpperCase() || null, + traitQuery: toText(body.traitQuery), + notes: toText(body.notes), + contact: toText(body.contact), + expiresAt, + ownerMemberId: ownerMemberId ? String(ownerMemberId) : undefined + }); + return payload; +} + +export async function post_listings(request) { + const origin = pickOrigin(request); + try { + const body = await parseJson(request); + const kindRaw = typeof body?.kind === 'string' ? body.kind.toLowerCase() : 'auction'; + if (kindRaw === 'wanted') { + return await createWanted(body, origin, request); + } + + const member = await fetchCurrentMember(); + const ownerId = member?._id ? String(member._id) : undefined; + const doc = normalizeAuctionPayload(body || {}, ownerId); + const saved = await wixData.insert(LISTINGS_COLLECTION, doc, { suppressAuth: true }); + return httpJson(201, shapeAuction(saved), origin, request); + } catch (err) { + return handleError(err, origin, request); + } +} + +export async function del_listings(request) { + const origin = pickOrigin(request); + const [id] = request.path || []; + if (!id) { + return httpJson(400, { error: 'Missing id.' }, origin, request); + } + try { + const member = await requireMember(); + const memberId = String(member._id); + const listing = await getListingById(id); + if (!listing) throw new WantedError('Listing not found.', 404); + const ownerId = listing.ownerMemberId || getOwnerId(listing); + if (!ownerId || String(ownerId) !== memberId) { + throw new WantedError('You are not allowed to perform this action.', 403); + } + await wixData.remove(LISTINGS_COLLECTION, id); + return httpJson(200, { ok: true }, origin, request); + } catch (err) { + return handleError(err, origin, request); + } +} + +export async function get_wanted(request) { + const origin = pickOrigin(request); + const [first, second] = request.path || []; + try { + if (!first) { + return await listWanted(origin, request); + } + if (first === 'mine' && !second) { + return await listWantedMine(origin, request); + } + + const id = first; + if (second === 'contact') { + return await wantedContact(id, origin, request); + } + if (!second) { + const member = await fetchCurrentMember(); + const viewerId = member?._id ? String(member._id) : null; + const listing = await getListingById(id); + if (!listing || listing.kind !== 'wanted') { + return httpJson(404, { error: 'Wanted post not found.' }, origin, request); + } + const shaped = shapeWantedRecord(listing, { + includeContact: Boolean(viewerId), + viewerId + }); + return httpJson(200, shaped, origin, request); + } + return httpJson(400, { error: 'Unsupported wanted endpoint.' }, origin, request); + } catch (err) { + return handleError(err, origin, request); + } +} + +export async function post_wanted(request) { + const origin = pickOrigin(request); + const [id, action] = request.path || []; + try { + if (!id) { + const body = await parseJson(request); + return await createWanted(body, origin, request); + } + if (action === 'respond') { + const body = await parseJson(request); + return await wantedRespond(id, body, origin, request); + } + return httpJson(400, { error: 'Unsupported wanted endpoint.' }, origin, request); + } catch (err) { + return handleError(err, origin, request); + } +} + +export async function patch_wanted(request) { + const origin = pickOrigin(request); + const [id] = request.path || []; + if (!id) { + return httpJson(400, { error: 'Missing id.' }, origin, request); + } + try { + const body = await parseJson(request); + return await updateWanted(id, body, origin, request); + } catch (err) { + return handleError(err, origin, request); + } +} + +export async function del_wanted(request) { + const origin = pickOrigin(request); + const [id] = request.path || []; + if (!id) { + return httpJson(400, { error: 'Missing id.' }, origin, request); + } + try { + return await deleteWanted(id, origin, request); + } catch (err) { + return handleError(err, origin, request); + } +} diff --git a/codex/backend/jwt.jsw b/codex/backend/jwt.jsw new file mode 100644 index 000000000..74cc76686 --- /dev/null +++ b/codex/backend/jwt.jsw @@ -0,0 +1,53 @@ +import { getSecret } from 'wix-secrets-backend'; + +const secretPromise = getSecret('WIX_SECRET_JWT_KEY'); + +function base64Url(input) { + return Buffer.from(JSON.stringify(input)) + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); +} + +function decode(str) { + const pad = str.length % 4 === 2 ? '==' : str.length % 4 === 3 ? '=' : ''; + const normalized = str.replace(/-/g, '+').replace(/_/g, '/') + pad; + return JSON.parse(Buffer.from(normalized, 'base64').toString('utf8')); +} + +export async function sign(payload, ttlSeconds = 900) { + const secret = await secretPromise; + const header = base64Url({ alg: 'HS256', typ: 'JWT' }); + const now = Math.floor(Date.now() / 1000); + const body = base64Url({ ...payload, iat: now, exp: now + ttlSeconds }); + const data = `${header}.${body}`; + const signature = Buffer.from( + require('crypto').createHmac('sha256', secret).update(data).digest('base64') + ) + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + return `${data}.${signature}`; +} + +export async function verify(token) { + const [headerPart, bodyPart, sigPart] = token.split('.'); + if (!headerPart || !bodyPart || !sigPart) throw new Error('INVALID_JWT'); + const secret = await secretPromise; + const data = `${headerPart}.${bodyPart}`; + const expected = Buffer.from( + require('crypto').createHmac('sha256', secret).update(data).digest('base64') + ) + .toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + if (expected !== sigPart) throw new Error('INVALID_SIGNATURE'); + const payload = decode(bodyPart); + if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) { + throw new Error('JWT_EXPIRED'); + } + return payload; +} diff --git a/codex/backend/wantedPosts.jsw b/codex/backend/wantedPosts.jsw new file mode 100644 index 000000000..99357f98a --- /dev/null +++ b/codex/backend/wantedPosts.jsw @@ -0,0 +1,151 @@ +import wixData from 'wix-data'; +import { currentMember } from 'wix-members-backend'; +import { + WANTED_COLLECTION, + WantedError, + normalizeWantedPayload, + shapeWantedRecord, + getOwnerId +} from './wantedUtils.js'; + +function compact(obj) { + return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)); +} + +async function fetchCurrentMember() { + try { + return await currentMember.getMember({ fieldsets: ['FULL'] }); + } catch (_err) { + return null; + } +} + +async function requireMember() { + const member = await fetchCurrentMember(); + if (!member) throw new WantedError('Login required.', 401); + return member; +} + +function resolveMemberName(member) { + if (!member) return 'Member'; + const nickname = member.profile?.nickname; + if (nickname) return nickname; + const slug = member.profile?.slug; + if (slug) return slug; + const name = member.contact?.name; + if (name) return name; + const email = member.contact?.emails?.[0]?.email; + if (email) return email; + return 'Member'; +} + +async function getWantedById(id) { + try { + return await wixData.get(WANTED_COLLECTION, id); + } catch (_err) { + return null; + } +} + +function mergeRecords(oldRec, newRec) { + return { ...(oldRec || {}), ...(newRec || {}) }; +} + +export async function listWantedPosts() { + const member = await fetchCurrentMember(); + const viewerId = member?._id ? String(member._id) : null; + + const results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .descending('_createdDate') + .limit(1000) + .find(); + + return results.items.map((item) => + shapeWantedRecord(item, { includeContact: Boolean(viewerId), viewerId }) + ); +} + +export async function getMyWantedPosts() { + const member = await requireMember(); + const memberId = String(member._id); + + let results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .eq('ownerMemberId', memberId) + .descending('_createdDate') + .limit(1000) + .find(); + + if (!results.items.length) { + results = await wixData + .query(WANTED_COLLECTION) + .eq('kind', 'wanted') + .eq('_owner', memberId) + .descending('_createdDate') + .limit(1000) + .find(); + } + + return results.items.map((item) => + shapeWantedRecord(item, { includeContact: true, viewerId: memberId }) + ); +} + +export async function createWantedPost(data) { + const member = await requireMember(); + const memberId = String(member._id); + const payload = normalizeWantedPayload(data || {}, { partial: false }); + const now = new Date(); + + const doc = compact({ + ...payload, + ownerMemberId: memberId, + ownerDisplayName: resolveMemberName(member), + createdAt: now, + updatedAt: now + }); + + const saved = await wixData.insert(WANTED_COLLECTION, doc, { suppressAuth: true }); + + return shapeWantedRecord(saved, { includeContact: true, viewerId: memberId }); +} + +export async function updateWantedPost(id, data) { + if (!id || typeof id !== 'string') { + throw new WantedError('ID is required.', 400, { field: 'id' }); + } + + const member = await requireMember(); + const memberId = String(member._id); + const existing = await getWantedById(id); + + if (!existing) { + throw new WantedError('Wanted post not found.', 404); + } + + const ownerId = getOwnerId(existing); + if (!ownerId || String(ownerId) !== memberId) { + throw new WantedError('You can only update your own wanted posts.', 403); + } + + const payload = normalizeWantedPayload(data || {}, { partial: true }); + const updateFields = compact({ + ...payload, + _id: id, + ownerMemberId: ownerId, + updatedAt: new Date() + }); + + const changedKeys = Object.keys(updateFields).filter((key) => key !== '_id'); + if (!changedKeys.length) { + throw new WantedError('No changes provided.', 400); + } + + const updated = await wixData.update(WANTED_COLLECTION, updateFields, { suppressAuth: true }); + const merged = mergeRecords(existing, updated); + + return shapeWantedRecord(merged, { includeContact: true, viewerId: memberId }); +} diff --git a/codex/backend/wantedUtils.js b/codex/backend/wantedUtils.js new file mode 100644 index 000000000..b0a95c1cd --- /dev/null +++ b/codex/backend/wantedUtils.js @@ -0,0 +1,361 @@ +// Utility helpers shared by backend wanted-post implementations. +// Handles validation, normalization, and serialization so that both HTTP +// functions and web modules can rely on the same logic. + +const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; +const MAX_STATUS_LENGTH = 24; +const DEFAULT_COLLECTION = 'Import2'; + +export const WANTED_COLLECTION = DEFAULT_COLLECTION; + +export class WantedError extends Error { + constructor(message, statusCode = 400, details) { + super(message); + this.name = 'WantedError'; + this.statusCode = statusCode; + if (details) this.details = details; + } +} + +function removeUndefined(obj) { + return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)); +} + +function requiredString(value, fieldLabel, maxLength = 255) { + if (value === undefined || value === null) { + throw new WantedError(`${fieldLabel} is required.`, 400, { field: fieldLabel }); + } + const str = String(value).trim(); + if (!str) { + throw new WantedError(`${fieldLabel} is required.`, 400, { field: fieldLabel }); + } + return maxLength && str.length > maxLength ? str.slice(0, maxLength) : str; +} + +function optionalString(value, maxLength = 255) { + if (value === undefined) return undefined; + if (value === null) return null; + const str = String(value).trim(); + if (!str) return null; + return maxLength && str.length > maxLength ? str.slice(0, maxLength) : str; +} + +function optionalMultiline(value, maxLength = 6000) { + if (value === undefined) return undefined; + if (value === null) return null; + let str = String(value).trim(); + if (!str) return null; + str = str.replace(/\r\n/g, '\n'); + return maxLength && str.length > maxLength ? str.slice(0, maxLength) : str; +} + +function optionalEmail(value) { + if (value === undefined) return undefined; + if (value === null) return null; + const str = String(value).trim(); + if (!str) return null; + if (!EMAIL_RE.test(str)) { + throw new WantedError('Invalid email address.', 400, { field: 'contactEmail' }); + } + const email = str.toLowerCase(); + return email.length > 254 ? email.slice(0, 254) : email; +} + +function optionalPhone(value) { + if (value === undefined) return undefined; + if (value === null) return null; + const str = String(value).trim(); + if (!str) return null; + const normalized = str.replace(/[^\d+().\- ]+/g, ''); + return normalized.length > 64 ? normalized.slice(0, 64) : normalized; +} + +function optionalBudget(value) { + if (value === undefined) return undefined; + if (value === null || value === '') return null; + const num = Number(value); + if (!Number.isFinite(num)) { + throw new WantedError('Budget must be a number.', 400, { field: 'budget' }); + } + if (num < 0) { + throw new WantedError('Budget must be zero or greater.', 400, { field: 'budget' }); + } + return Number(num.toFixed(8)); +} + +function optionalTokenId(value) { + if (value === undefined) return undefined; + if (value === null || value === '') return null; + const num = Number(value); + if (!Number.isFinite(num)) { + throw new WantedError('tokenId must be a number.', 400, { field: 'tokenId' }); + } + if (num < 0) { + throw new WantedError('tokenId must be zero or greater.', 400, { field: 'tokenId' }); + } + return Math.floor(num); +} + +function sanitizeCurrency(value) { + if (value === undefined || value === null || value === '') return 'ETH'; + const str = String(value).trim().toUpperCase(); + if (!str) return 'ETH'; + if (str.length > 12) { + throw new WantedError('Currency code is too long.', 400, { field: 'currency' }); + } + return str; +} + +function sanitizeStatus(value, defaultValue) { + if (value === undefined) return defaultValue; + if (value === null || value === '') return null; + const str = String(value).trim(); + if (!str) return null; + const upper = str.toUpperCase(); + if (upper.length > MAX_STATUS_LENGTH) { + throw new WantedError('Status value is too long.', 400, { field: 'status' }); + } + return upper; +} + +function sanitizeChain(value, currency) { + if (value === undefined || value === null || value === '') { + return currency === 'SOL' ? 'solana' : 'evm'; + } + const str = String(value).trim().toLowerCase(); + if (!str) return currency === 'SOL' ? 'solana' : 'evm'; + if (['sol', 'solana', 'sol-mainnet', 'solana-mainnet'].includes(str)) return 'solana'; + return 'evm'; +} + +function sanitizeWalletEntry(entry) { + if (!entry || typeof entry !== 'object') return null; + const address = optionalString(entry.address, 200); + if (address === undefined || address === null) return null; + const chain = sanitizeChain(entry.chain); + const wallet = { + address: chain === 'solana' ? address : address.toLowerCase(), + chain, + verified: Boolean(entry.verified) + }; + + const type = optionalString(entry.type, 32); + if (type) wallet.type = type; + + const signature = optionalMultiline(entry.signature, 4000); + if (signature) wallet.signature = signature; + + const message = optionalMultiline(entry.message, 4000); + if (message) wallet.message = message; + + if (entry.connectedAt) wallet.connectedAt = String(entry.connectedAt); + + return wallet; +} + +function sanitizeWallets(value) { + if (value === undefined) return undefined; + if (value === null) return []; + if (!Array.isArray(value)) return []; + return value.map(sanitizeWalletEntry).filter(Boolean); +} + +function toNumberOrNull(value) { + if (value === undefined || value === null || value === '') return null; + const num = Number(value); + return Number.isFinite(num) ? Number(num) : null; +} + +function toTokenIdOrNull(value) { + if (value === undefined || value === null || value === '') return null; + const num = Number(value); + return Number.isFinite(num) ? Math.floor(num) : null; +} + +export function normalizeWantedPayload(input = {}, { partial = false } = {}) { + if (!input || typeof input !== 'object') { + throw new WantedError('Invalid payload.', 400); + } + + const output = {}; + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'title')) { + output.title = requiredString(input.title, 'title', 140); + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'traitQuery')) { + if (Object.prototype.hasOwnProperty.call(input, 'traitQuery')) { + const trait = optionalString(input.traitQuery, 240); + if (trait !== undefined) output.traitQuery = trait; + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'tokenId')) { + if (Object.prototype.hasOwnProperty.call(input, 'tokenId')) { + const token = optionalTokenId(input.tokenId); + if (token !== undefined) output.tokenId = token; + } + } + + if (!partial) { + const hasToken = Object.prototype.hasOwnProperty.call(output, 'tokenId') && output.tokenId !== null && output.tokenId !== undefined; + const traitSeed = Object.prototype.hasOwnProperty.call(output, 'traitQuery') + ? output.traitQuery + : optionalString(input.traitQuery, 240); + const hasTrait = Boolean(traitSeed && traitSeed.length); + if (!hasToken && !hasTrait) { + throw new WantedError('Provide a tokenId or trait query.', 400, { field: 'tokenId' }); + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'budget') || Object.prototype.hasOwnProperty.call(input, 'price')) { + const budgetSource = Object.prototype.hasOwnProperty.call(input, 'budget') + ? input.budget + : input.price; + const budget = optionalBudget(budgetSource); + if (budget === undefined) { + if (!partial) throw new WantedError('Budget is required.', 400, { field: 'budget' }); + } else if (budget === null) { + output.budget = null; + output.price = null; + } else { + output.budget = budget; + output.price = budget; + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'currency')) { + output.currency = sanitizeCurrency(input.currency); + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'status')) { + output.status = sanitizeStatus( + Object.prototype.hasOwnProperty.call(input, 'status') ? input.status : undefined, + partial ? undefined : 'OPEN' + ); + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'description') || Object.prototype.hasOwnProperty.call(input, 'descriptionRich')) { + const descriptionSource = Object.prototype.hasOwnProperty.call(input, 'descriptionRich') + ? input.descriptionRich + : input.description; + const description = optionalMultiline(descriptionSource, 6000); + if (description === undefined) { + if (!partial) { + output.description = ''; + output.descriptionRich = ''; + } + } else { + output.description = description ?? ''; + output.descriptionRich = description ?? ''; + } + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactLine')) { + const val = optionalString(input.contactLine, 120); + if (val !== undefined) output.contactLine = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactWhatsApp')) { + const val = optionalString(input.contactWhatsApp, 120); + if (val !== undefined) output.contactWhatsApp = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactTelegram')) { + const val = optionalString(input.contactTelegram, 120); + if (val !== undefined) output.contactTelegram = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactEmail')) { + const val = optionalEmail(input.contactEmail); + if (val !== undefined) output.contactEmail = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactPhone')) { + const val = optionalPhone(input.contactPhone); + if (val !== undefined) output.contactPhone = val; + } + if (!partial || Object.prototype.hasOwnProperty.call(input, 'contactMessage')) { + const val = optionalMultiline(input.contactMessage, 2000); + if (val !== undefined) output.contactMessage = val ?? null; + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'wallets')) { + const wallets = sanitizeWallets(input.wallets); + if (wallets !== undefined) output.wallets = wallets; + } + + if (!partial || Object.prototype.hasOwnProperty.call(input, 'chain') || output.currency) { + const chain = sanitizeChain(input.chain, output.currency || sanitizeCurrency(input.currency)); + if (chain !== undefined) output.chain = chain; + } + + if (!partial) { + output.kind = 'wanted'; + } + + return removeUndefined(output); +} + +export function getOwnerId(record) { + if (!record) return null; + const owner = + record.ownerMemberId || + record.ownerId || + record.owner || + record._owner || + null; + return owner != null ? String(owner) : null; +} + +function sanitizeWalletEntrySafe(entry) { + try { + return sanitizeWalletEntry(entry); + } catch (_err) { + return null; + } +} + +export function shapeWantedRecord(record, { includeContact = false, viewerId } = {}) { + if (!record) return null; + const ownerId = getOwnerId(record); + const viewerIsOwner = viewerId && ownerId && String(ownerId) === String(viewerId); + const canViewContact = includeContact || viewerIsOwner; + + const currencyRaw = typeof record.currency === 'string' ? record.currency.trim() : ''; + const currency = currencyRaw ? currencyRaw.toUpperCase().slice(0, 12) : null; + const chain = sanitizeChain(record.chain, currency || 'ETH'); + + const budget = toNumberOrNull(record.budget != null ? record.budget : record.price); + const tokenId = toTokenIdOrNull(record.tokenId); + + const walletsRaw = Array.isArray(record.wallets) ? record.wallets : []; + const wallets = walletsRaw.map(sanitizeWalletEntrySafe).filter(Boolean); + + const shaped = { + _id: record._id, + id: record._id, + kind: 'wanted', + title: record.title || '', + traitQuery: record.traitQuery || null, + tokenId, + budget, + price: budget, + currency, + status: record.status ? String(record.status).toUpperCase().slice(0, MAX_STATUS_LENGTH) : 'OPEN', + description: record.description ?? record.notes ?? '', + descriptionRich: record.descriptionRich ?? record.description ?? '', + chain, + ownerId, + ownerMemberId: ownerId, + ownerDisplayName: record.ownerDisplayName || record.ownerName || null, + createdAt: record.createdAt || record._createdDate || null, + updatedAt: record.updatedAt || record._updatedDate || null, + wallets + }; + + shaped.contactLine = canViewContact ? record.contactLine ?? null : null; + shaped.contactWhatsApp = canViewContact ? record.contactWhatsApp ?? null : null; + shaped.contactTelegram = canViewContact ? record.contactTelegram ?? null : null; + shaped.contactEmail = canViewContact ? record.contactEmail ?? null : null; + shaped.contactPhone = canViewContact ? record.contactPhone ?? null : null; + shaped.contactMessage = canViewContact ? (record.contactMessage ?? record.message ?? null) : null; + + return shaped; +} diff --git a/codex/bazaarcodexv1.html b/codex/bazaarcodexv1.html new file mode 100644 index 000000000..9fa682f35 --- /dev/null +++ b/codex/bazaarcodexv1.html @@ -0,0 +1,4130 @@ + + + + + + + THE DIKPIK BAZAAR + + + + + + + + +
+
+
+
+ +
+
+
+

+ THE DIKPIK BAZAAR +

+
+ + + + + + +
+
+

Family Tree • Auctions • Wanted

+
+
+ + +
+
+
+ + + + + + +
+ +
+
+
Filter by Traits
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+ + +
+
+
+
+

+ DIKPIK Family Tree +

+

Loading…

+
+
+ The Family Tree auto-assigns each NFT to a classic mafia hierarchy (Boss → Underboss → Consigliere → Caporegime → Soldier → Associate) + per faction. Choose a faction above to see its crew arranged in a centered pyramid by rank. Rarity and trait percentages are shown + on each token; click any token for the full trait breakdown in neon boxes with collection-wide rarity % (to 2 decimals). +
+
+
+
+ +
+
+

+ All Tokens (Filtered View) +

+
+ + + Page 1 / 1 + + +
+ Per page + +
+
+
+ +
+ + +
+
+
+ + + Page 1 / 1 + + +
+
+
+
+ + + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codex/docs/SMOKE.md b/codex/docs/SMOKE.md new file mode 100644 index 000000000..b9ce4c3c0 --- /dev/null +++ b/codex/docs/SMOKE.md @@ -0,0 +1,18 @@ +## Wallet & Bids Smoke Checklist + +1. **Connect MetaMask** + - Load `/wanted-and-bazar`. + - Click Connect; MetaMask prompts; ensure address + chain display. +2. **JWT issuance** + - Capture Network tab → `auth/verifySiwe` returns 200, `sessionStorage.dikpik_jwt` populated. +3. **Wrong chain** + - Switch MetaMask to another network; Connect should show “wrong chain†toast and call `switchChain`. +4. **Place bid** + - Select live auction, enter amount > current high. + - Verify `/backend/bids.placeBid` returns updated amount and UI toast. +5. **Concurrency** + - Simulate stale version by placing two bids quickly; second should receive `AUCTION_CONFLICT` toast. +6. **JWT expiry** + - Clear session storage token, try bid → should force re-auth (getNonce + verify). +7. **Disconnect** + - Click Disconnect; address label resets; placing bid should fail until reconnection. diff --git a/codex/docs/WIX_WALLET_REDESIGN.md b/codex/docs/WIX_WALLET_REDESIGN.md new file mode 100644 index 000000000..8aeee1420 --- /dev/null +++ b/codex/docs/WIX_WALLET_REDESIGN.md @@ -0,0 +1,44 @@ +## Wix Wallet Flow Redesign + +We are removing the Auctions iframe dependency because it blocks first‑party cookies, causes MetaMask popups to fail, and makes `/ _functions` auth impossible to secure. Phase 1 moves all wallet UX directly into the Velo page so Wix owns the window context and can run SIWE without cross‑origin messages. + +### Event Flow +```mermaid +sequenceDiagram + participant U as User (Wix Page) + participant W as wallet.ts + participant MM as MetaMask / WC + participant A as backend/auth.jsw + participant B as backend/bids.jsw + participant D as Wix Collections + + U->>W: Click "Connect Wallet" + W->>MM: requestAccounts / WalletConnect handshake + MM-->>W: address, chainId + W->>A: getNonce(memberId) + A-->>W: nonce + W->>MM: SIWE message signature + MM-->>W: signature + W->>A: verifySiwe(address, signature, nonce) + A-->>W: JWT + W->>B: placeBid({auctionId, amount}, JWT) + B->>D: validate auction (optimistic version) + D-->>B: updated auction/bid + B-->>W: bid confirmation + W-->>U: Toast + UI update +``` + +### Phase 1 Scope + +- EVM wallets: MetaMask + WalletConnect v2 (covers Binance Wallet). +- Single chain id (config via secret `SUPPORTED_CHAIN_ID`). +- SIWE login, JWT stored in wix-storage session. +- Collections: `MembersWallets`, `Auctions` (add `version`), `Bids`, `AuthNonces`. +- Actions supported: connect wallet, sign SIWE, place bid, create/update auction, list bids. + +### Phase 2 Backlog + +- Additional chains (Solana/Phantom) with chain-agnostic signatures. +- Automatic escrow settlement + on-chain proofs. +- Scale beyond Wix collection limits with external DB + HTTP functions proxy. +- Push notifications / WebSocket live bids. diff --git a/codex/pages/WANTED AND BAZAR.ozhxc b/codex/pages/WANTED AND BAZAR.ozhxc new file mode 100644 index 000000000..d7feffae1 --- /dev/null +++ b/codex/pages/WANTED AND BAZAR.ozhxc @@ -0,0 +1,267 @@ +import { authentication } from 'wix-members-frontend'; +import { getMemberSnapshot, listenForAuthChanges } from 'public/dikpikBridge.js'; +import { connectWallet, shortAddress, getJwt } from 'public/wallet.js'; +import { + listWantedPosts, + getMyWantedPosts, + createWantedPost, + updateWantedPost +} from 'backend/wantedPosts.jsw'; + +const HTML_COMPONENT_ID = '#html1'; +const RELATIVE_FUNCTIONS_BASE = '/_functions'; +let bridgeWired = false; +let toastElement = null; +let toastTimer = null; +const TOAST_AUTO_CLOSE_MS = 5000; + +function postToIframe(htmlComponent, payload) { + try { + htmlComponent.postMessage(payload); + } catch (err) { + console.warn('[bazaar] postMessage failed', err); + } +} + +function buildApiConfig() { + const origin = + typeof window !== 'undefined' && window.location?.origin + ? window.location.origin.replace(/\/+$/, '') + : 'https://www.dikpik.io'; + const absoluteBase = `${origin}${RELATIVE_FUNCTIONS_BASE}`; + + return { + version: '2024-11-01', + origin, + apiBase: absoluteBase, + relativeBase: RELATIVE_FUNCTIONS_BASE, + endpoints: { + listings: `${absoluteBase}/listings`, + listing: `${absoluteBase}/listings/:id`, + wanted: `${absoluteBase}/wanted`, + wantedMine: `${absoluteBase}/wanted/mine`, + wantedItem: `${absoluteBase}/wanted/:id`, + wantedContact: `${absoluteBase}/wanted/:id/contact`, + wantedRespond: `${absoluteBase}/wanted/:id/respond`, + bids: `${absoluteBase}/bids`, + relay: `${absoluteBase}/relay` + } + }; +} + +async function pushMemberState(htmlComponent, { forceRefresh = false } = {}) { + const snapshot = await getMemberSnapshot({ forceRefresh }); + postToIframe(htmlComponent, { + type: 'DP:MEMBER:SET', + payload: snapshot + }); +} + +function sendConfig(htmlComponent) { + postToIframe(htmlComponent, { + type: 'DP:CONFIG', + payload: buildApiConfig() + }); +} + +function toErrorPayload(err) { + if (err instanceof Error) { + const payload = { error: err.message }; + if (err.status) payload.status = err.status; + if (err.payload?.details) payload.details = err.payload.details; + return payload; + } + if (typeof err === 'string') { + return { error: err }; + } + return { error: 'Unknown error' }; +} + +function ensureToastElement() { + if (typeof window === 'undefined' || typeof document === 'undefined') return null; + if (toastElement) return toastElement; + + const container = document.createElement('div'); + container.id = 'dp-legacy-toast'; + container.style.position = 'fixed'; + container.style.top = '32px'; + container.style.right = '32px'; + container.style.zIndex = '9999'; + container.style.padding = '16px 20px'; + container.style.borderRadius = '10px'; + container.style.background = 'rgba(16, 16, 16, 0.9)'; + container.style.color = '#fff'; + container.style.fontFamily = 'Montserrat, Arial, sans-serif'; + container.style.fontSize = '15px'; + container.style.fontWeight = '600'; + container.style.letterSpacing = '0.03em'; + container.style.boxShadow = '0 10px 40px rgba(0,0,0,0.45)'; + container.style.display = 'none'; + + document.body.appendChild(container); + toastElement = container; + return container; +} + +function showToast(message) { + if (!message) return; + const el = ensureToastElement(); + if (!el) { + console.log('[bazaar toast]', message); + return; + } + el.textContent = message; + el.style.display = 'block'; + + if (toastTimer) { + clearTimeout(toastTimer); + } + toastTimer = setTimeout(() => { + el.style.display = 'none'; + }, TOAST_AUTO_CLOSE_MS); +} + +function requireJwt() { + const jwt = getJwt(); + if (!jwt) throw new Error('AUTH_REQUIRED'); + return jwt; +} + +async function handleWalletConnectRequest() { + const address = await connectWallet(); + showToast('Wallet connected'); + return { + address, + shortAddress: shortAddress(address), + jwt: getJwt() + }; +} + +async function handleWantedCreate(payload) { + if (!payload || typeof payload !== 'object') { + throw new Error('Payload is required.'); + } + return createWantedPost(payload); +} + +async function handleWantedUpdate(input) { + const { id, data } = input || {}; + if (!id) throw new Error('ID is required.'); + if (!data || typeof data !== 'object') throw new Error('Update payload is required.'); + return updateWantedPost(id, data); +} + +function wireIframe(htmlComponent) { + if (bridgeWired) return; + bridgeWired = true; + + postToIframe(htmlComponent, { type: 'DP:PARENT_READY' }); + sendConfig(htmlComponent); + + listenForAuthChanges((snapshot) => { + postToIframe(htmlComponent, { + type: 'DP:MEMBER:SET', + payload: snapshot + }); + }); + + pushMemberState(htmlComponent).catch((err) => + console.warn('[bazaar] failed to send member snapshot', err) + ); + + const dispatchSuccess = (baseType, nonce, payload) => { + postToIframe(htmlComponent, { + type: `${baseType}:OK`, + payload, + nonce + }); + }; + + const dispatchError = (baseType, nonce, err) => { + postToIframe(htmlComponent, { + type: `${baseType}:ERR`, + ...toErrorPayload(err), + nonce + }); + }; + + htmlComponent.onMessage(async (event) => { + const message = event?.data; + if (!message || typeof message !== 'object') return; + + const { type, nonce, payload } = message; + + const exec = async (fn) => { + try { + const result = await fn(); + dispatchSuccess(type, nonce, result); + } catch (err) { + dispatchError(type, nonce, err); + } + }; + + switch (type) { + case 'DP:REQUEST_MEMBER': + await pushMemberState(htmlComponent); + break; + + case 'DP:REQUEST_CONFIG': + case 'DP:READY': + sendConfig(htmlComponent); + break; + + case 'DP:LOGIN': + try { + await authentication.promptLogin(); + dispatchSuccess(type, nonce, { ok: true }); + await pushMemberState(htmlComponent, { forceRefresh: true }); + } catch (err) { + dispatchError(type, nonce, err); + } + break; + + case 'DP:LOGOUT': + try { + await authentication.logout(); + dispatchSuccess(type, nonce, { ok: true }); + await pushMemberState(htmlComponent, { forceRefresh: true }); + } catch (err) { + dispatchError(type, nonce, err); + } + break; + + case 'DP:WALLET:CONNECT': + exec(() => handleWalletConnectRequest()); + break; + + case 'DP:WANTED:LIST': + exec(() => listWantedPosts()); + break; + + case 'DP:WANTED:MINE': + exec(() => getMyWantedPosts()); + break; + + case 'DP:WANTED:CREATE': + exec(() => handleWantedCreate(payload)); + break; + + case 'DP:WANTED:UPDATE': + exec(() => handleWantedUpdate(payload)); + break; + + default: + break; + } + }); +} + +$w.onReady(() => { + const htmlComponent = $w(HTML_COMPONENT_ID); + if (!htmlComponent || typeof htmlComponent.postMessage !== 'function') { + console.warn(`[bazaar] HTML component not found or unsupported (${HTML_COMPONENT_ID}).`); + return; + } + + wireIframe(htmlComponent); +}); diff --git a/codex/pages/auctions.js b/codex/pages/auctions.js new file mode 100644 index 000000000..ec9c94c41 --- /dev/null +++ b/codex/pages/auctions.js @@ -0,0 +1,106 @@ +import { initWalletUI, getJwt } from 'public/wallet.js'; +import { placeBid } from 'backend/bids.jsw'; + +const TREE_SECTION = '#treeSection'; +const BAZAAR_SECTION = '#bazaarSection'; + +const TAB_TREE = '#tabTree'; +const TAB_BAZAAR = '#tabBazaar'; + +const CONNECT_BUTTON = '#connectWalletButton'; +const DISCONNECT_BUTTON = '#disconnectWalletButton'; +const ADDRESS_LABEL = '#walletAddressText'; + +const BID_BUTTON = '#bidButton'; +const BID_VALUE = '#bidValue'; +const AUCTION_DATASET = '#auctionDataset'; +const TOAST_LABEL = '#toastText'; + +const CONTROLS = [CONNECT_BUTTON, DISCONNECT_BUTTON, ADDRESS_LABEL]; + +$w.onReady(() => { + collapseControls(); + bindTabs(); + initWalletUI({ + connectButton: $w(CONNECT_BUTTON), + disconnectButton: $w(DISCONNECT_BUTTON), + addressText: $w(ADDRESS_LABEL) + }); + + if ($w(BID_BUTTON)) { + $w(BID_BUTTON).onClick(handleBid); + } +}); + +function collapseControls() { + CONTROLS.forEach((selector) => { + const el = $w(selector); + if (el && typeof el.collapse === 'function') { + el.collapse(); + } + }); +} + +function expandControls() { + CONTROLS.forEach((selector) => { + const el = $w(selector); + if (el && typeof el.expand === 'function') { + el.expand(); + } + }); +} + +function bindTabs() { + if ($w(TAB_TREE)) { + $w(TAB_TREE).onClick(() => { + showTree(); + }); + } + if ($w(TAB_BAZAAR)) { + $w(TAB_BAZAAR).onClick(() => { + showBazaar(); + }); + } +} + +function showTree() { + if ($w(TREE_SECTION)) $w(TREE_SECTION).expand(); + if ($w(BAZAAR_SECTION)) $w(BAZAAR_SECTION).collapse(); + collapseControls(); +} + +function showBazaar() { + if ($w(TREE_SECTION)) $w(TREE_SECTION).collapse(); + if ($w(BAZAAR_SECTION)) $w(BAZAAR_SECTION).expand(); + expandControls(); +} + +async function handleBid() { + const amount = Number($w(BID_VALUE).value); + const current = $w(AUCTION_DATASET)?.getCurrentItem(); + const auctionId = current?._id; + if (!auctionId) { + showToast('Select an auction first.'); + return; + } + + try { + const jwt = getJwt(); + if (!jwt) { + showToast('Connect your wallet first.'); + return; + } + const result = await placeBid({ auctionId, amount, jwt }); + showToast(`Bid placed: ${result.highBidAmount}`); + } catch (err) { + showToast(err.message || 'Bid failed'); + } +} + +function showToast(message) { + if ($w(TOAST_LABEL)) { + $w(TOAST_LABEL).text = message; + } else { + console.log(message); + } +} diff --git a/codex/public/apiClient.js b/codex/public/apiClient.js new file mode 100644 index 000000000..5b8e1326e --- /dev/null +++ b/codex/public/apiClient.js @@ -0,0 +1,138 @@ +const COMMON_HEADERS = { + Accept: 'application/json' +}; + +let overrideBase = null; + +function trimTrailingSlash(value) { + return value.endsWith('/') ? value.replace(/\/+$/, '') : value; +} + +function resolveBase() { + if (overrideBase) return trimTrailingSlash(String(overrideBase).trim()); + if (typeof window !== 'undefined') { + if (window.DIKPIK_API_BASE) { + return trimTrailingSlash(String(window.DIKPIK_API_BASE).trim()); + } + if (window.DIKPIK_BAZAAR?.apiBase) { + return trimTrailingSlash(String(window.DIKPIK_BAZAAR.apiBase).trim()); + } + if (window.location?.origin) { + return `${window.location.origin.replace(/\/+$/, '')}/_functions`; + } + } + return 'https://www.dikpik.io/_functions'; +} + +function buildUrl(path) { + const base = resolveBase(); + const cleanBase = trimTrailingSlash(base); + const cleanPath = String(path || '').replace(/^\/+/, ''); + return cleanPath ? `${cleanBase}/${cleanPath}` : cleanBase; +} + +function createFetchOptions(method, body, init = {}) { + const headers = { ...COMMON_HEADERS, ...(init.headers || {}) }; + let payload; + if (body !== undefined) { + headers['Content-Type'] = headers['Content-Type'] || 'application/json'; + payload = headers['Content-Type'].includes('application/json') ? JSON.stringify(body) : body; + } + return { + mode: 'cors', + credentials: 'include', + ...init, + method, + headers, + body: payload + }; +} + +async function parseResponse(res) { + if (res.status === 204) return null; + const contentType = res.headers.get('content-type') || ''; + if (contentType.includes('application/json')) { + return res.json().catch(() => null); + } + return res.text().catch(() => null); +} + +async function handleResponse(res) { + const payload = await parseResponse(res); + if (!res.ok) { + const error = new Error( + (payload && (payload.error || payload.message)) || res.statusText || `HTTP ${res.status}` + ); + error.status = res.status; + error.payload = payload; + throw error; + } + return payload; +} + +async function request(method, path, body, init) { + const url = buildUrl(path); + const options = createFetchOptions(method, body, init); + const res = await fetch(url, options); + return handleResponse(res); +} + +export function configureApi({ base } = {}) { + overrideBase = base ? String(base).trim() : null; +} + +export function apiGet(path, init) { + return request('GET', path, undefined, init); +} + +export function apiPost(path, body, init) { + return request('POST', path, body, init); +} + +export function apiPatch(path, body, init) { + return request('PATCH', path, body, init); +} + +export function apiDelete(path, init) { + return request('DELETE', path, undefined, init); +} + +export function fetchListings(init) { + return apiGet('listings', init); +} + +export function createListing(payload, init) { + return apiPost('listings', payload, init); +} + +export function deleteListing(id, init) { + return apiDelete(`listings/${id}`, init); +} + +export function listWantedPosts(init) { + return apiGet('wanted', init); +} + +export function getMyWantedPosts(init) { + return apiGet('wanted/mine', init); +} + +export function createWantedPost(payload, init) { + return apiPost('wanted', payload, init); +} + +export function updateWantedPost(id, payload, init) { + return apiPatch(`wanted/${id}`, payload, init); +} + +export function deleteWantedPost(id, init) { + return apiDelete(`wanted/${id}`, init); +} + +export function fetchWantedContact(id, init) { + return apiGet(`wanted/${id}/contact`, init); +} + +export function respondToWanted(id, payload, init) { + return apiPost(`wanted/${id}/respond`, payload, init); +} diff --git a/codex/public/dikpikBridge.js b/codex/public/dikpikBridge.js new file mode 100644 index 000000000..82402bc3c --- /dev/null +++ b/codex/public/dikpikBridge.js @@ -0,0 +1,119 @@ +import { currentMember, authentication } from 'wix-members-frontend'; +import { session } from 'wix-storage'; + +const CACHE_KEY = 'dikpik-member-snapshot'; + +function persistSnapshot(snapshot) { + if (!snapshot) { + session.removeItem(CACHE_KEY); + return; + } + const sanitized = { ...snapshot }; + delete sanitized.authToken; + + try { + session.setItem(CACHE_KEY, JSON.stringify(sanitized)); + } catch (_err) { + session.removeItem(CACHE_KEY); + } +} + +function loadCachedSnapshot() { + const raw = session.getItem(CACHE_KEY); + if (!raw) return null; + try { + return JSON.parse(raw); + } catch (_err) { + session.removeItem(CACHE_KEY); + return null; + } +} + +function resolveRoles(member) { + const roles = Array.isArray(member?.roles) ? member.roles : []; + return roles + .map((role) => role?.name) + .filter((name) => typeof name === 'string' && name.trim().length > 0); +} + +function toSnapshot(member) { + if (!member) return null; + const contactEmail = member.contact?.emails?.[0]?.email; + return { + id: member._id || null, + slug: member.profile?.slug || null, + roles: resolveRoles(member), + nickname: + member.profile?.nickname || + member.profile?.slug || + member.contact?.name || + contactEmail || + 'Member', + email: contactEmail || null, + authToken: null + }; +} + +async function resolveAuthToken() { + try { + if (typeof authentication.getAuthToken === 'function') { + const token = await authentication.getAuthToken(); + if (token) return token; + } + } catch (err) { + console.warn('[dikpikBridge] getAuthToken failed', err); + } + try { + if (typeof authentication.getMemberTokens === 'function') { + const tokens = await authentication.getMemberTokens(); + const access = + tokens?.memberTokens?.accessToken || tokens?.accessToken || null; + if (access) return access; + } + } catch (err) { + console.warn('[dikpikBridge] getMemberTokens failed', err); + } + return null; +} + +export function getCachedMember() { + return loadCachedSnapshot(); +} + +export async function getMemberSnapshot({ forceRefresh = false } = {}) { + if (!forceRefresh) { + const cached = loadCachedSnapshot(); + if (cached) return cached; + } + + try { + const member = await currentMember.getMember({ fieldsets: ['FULL'] }); + if (!member) { + persistSnapshot(null); + return null; + } + const snapshot = toSnapshot(member); + snapshot.authToken = await resolveAuthToken(); + persistSnapshot(snapshot); + return snapshot; + } catch (err) { + console.warn('[dikpikBridge] currentMember.getMember failed', err); + return loadCachedSnapshot(); + } +} + +export function listenForAuthChanges(handler) { + if (typeof handler !== 'function') return () => {}; + + authentication.onLogin(async () => { + const snapshot = await getMemberSnapshot({ forceRefresh: true }); + handler(snapshot); + }); + + authentication.onLogout(() => { + persistSnapshot(null); + handler(null); + }); + + return () => {}; +} diff --git a/codex/public/lib/ethers.min.js b/codex/public/lib/ethers.min.js new file mode 100644 index 000000000..816d1ace9 --- /dev/null +++ b/codex/public/lib/ethers.min.js @@ -0,0 +1,22 @@ +// Lightweight loader that injects the real ethers.js build from a CDN. +// Usage: import { loadEthers } from 'public/lib/ethers.min.js'; + +import { loadScript } from 'wix-window'; + +const ETHERS_CDN = + 'https://cdn.jsdelivr.net/npm/ethers@6.11.1/dist/ethers.umd.min.js'; + +let ethersPromise = null; + +export function loadEthers() { + if (typeof window === 'undefined') { + return Promise.reject(new Error('Window is required.')); + } + if (window.ethers) { + return Promise.resolve(window.ethers); + } + if (!ethersPromise) { + ethersPromise = loadScript(ETHERS_CDN).then(() => window.ethers); + } + return ethersPromise; +} diff --git a/codex/public/lib/web3modal.js b/codex/public/lib/web3modal.js new file mode 100644 index 000000000..71d9087ac --- /dev/null +++ b/codex/public/lib/web3modal.js @@ -0,0 +1,21 @@ +import { loadScript } from 'wix-window'; + +const WEB3MODAL_CDN = + 'https://unpkg.com/@web3modal/standalone@4.0.10/dist/index.umd.js'; + +let web3ModalPromise = null; + +export function loadWeb3Modal() { + if (typeof window === 'undefined') { + return Promise.reject(new Error('Window is required.')); + } + if (window.Web3ModalStandalone) { + return Promise.resolve(window.Web3ModalStandalone); + } + if (!web3ModalPromise) { + web3ModalPromise = loadScript(WEB3MODAL_CDN).then( + () => window.Web3ModalStandalone + ); + } + return web3ModalPromise; +} diff --git a/codex/public/wallet.js b/codex/public/wallet.js new file mode 100644 index 000000000..34b5ed73f --- /dev/null +++ b/codex/public/wallet.js @@ -0,0 +1,173 @@ +import { session } from 'wix-storage'; +import { local } from 'wix-storage'; +import wixWindow from 'wix-window'; +import wixUsers from 'wix-users'; +import { loadEthers } from 'public/lib/ethers.min.js'; +import { loadWeb3Modal } from 'public/lib/web3modal.js'; +import { + getNonce, + verifySiwe, + getWalletConfig +} from 'backend/auth.jsw'; + +const JWT_KEY = 'dikpik_jwt'; +const ADDRESS_KEY = 'dikpik_wallet_address'; + +let ethersLib; +let web3ModalLib; +let walletProvider = null; +let currentAddress = null; +let currentChainId = null; +let supportedChainId = null; +let siweDomain = null; +let siweUri = null; + +async function ensureLibs() { + if (!ethersLib) { + ethersLib = await loadEthers(); + } + if (!web3ModalLib) { + web3ModalLib = await loadWeb3Modal(); + } + if (!supportedChainId || !siweDomain) { + const cfg = await getWalletConfig(); + supportedChainId = cfg.chainId; + siweDomain = cfg.domain; + siweUri = cfg.uri; + } +} + +export async function connectWallet(memberId) { + await ensureLibs(); + const injected = window.ethereum; + if (injected) { + walletProvider = new ethersLib.BrowserProvider(injected); + } else { + const modal = new web3ModalLib.Web3Modal({ + projectId: supportedChainId, + standaloneChains: [`eip155:${supportedChainId}`] + }); + const walletConnect = await modal.openModal(); + walletProvider = new ethersLib.BrowserProvider(walletConnect); + } + const signer = await walletProvider.getSigner(); + const network = await walletProvider.getNetwork(); + currentChainId = Number(network.chainId); + if (currentChainId !== supportedChainId) { + await switchChain(supportedChainId); + } + currentAddress = await signer.getAddress(); + local.setItem(ADDRESS_KEY, currentAddress); + await authenticate(memberId); + return currentAddress; +} + +export function getCurrentAddress() { + return currentAddress || local.getItem(ADDRESS_KEY); +} + +export function getJwt() { + return session.getItem(JWT_KEY) || null; +} + +export function disconnectWallet() { + currentAddress = null; + walletProvider = null; + session.removeItem(JWT_KEY); + local.removeItem(ADDRESS_KEY); +} + +export async function switchChain(chainId) { + if (!window.ethereum) return; + const hexChain = `0x${chainId.toString(16)}`; + try { + await window.ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: hexChain }] + }); + } catch (err) { + if (err.code === 4902) { + await window.ethereum.request({ + method: 'wallet_addEthereumChain', + params: [ + { + chainId: hexChain, + rpcUrls: ['https://rpc.ankr.com/eth'], + chainName: 'Configured Chain' + } + ] + }); + } else { + throw err; + } + } +} + +async function authenticate(memberId) { + const nonce = await getNonce(memberId); + const message = buildSiweMessage(nonce); + const signer = await walletProvider.getSigner(); + const signature = await signer.signMessage(message); + const jwt = await verifySiwe({ + address: currentAddress, + signature, + nonce + }); + session.setItem(JWT_KEY, jwt); +} + +function buildSiweMessage(nonce) { + const now = new Date().toISOString(); + return [ + `${siweDomain} wants you to sign in with your Ethereum account:`, + `${currentAddress}`, + '', + 'Sign-in with Ethereum to DIKPIK Bazaar.', + '', + `URI: ${siweUri}`, + `Version: 1`, + `Chain ID: ${supportedChainId}`, + `Nonce: ${nonce}`, + `Issued At: ${now}` + ].join('\n'); +} + +export function shortAddress(addr) { + if (!addr) return ''; + return `${addr.slice(0, 6)}...${addr.slice(-4)}`; +} + +export async function initWalletUI({ + connectButton, + disconnectButton, + addressText +}) { + if (connectButton) { + connectButton.onClick(async () => { + try { + await connectWallet(wixUsers.currentUser.id); + if (addressText) addressText.text = shortAddress(getCurrentAddress()); + } catch (err) { + showToast(err.message || 'Wallet connection failed'); + } + }); + } + if (disconnectButton) { + disconnectButton.onClick(() => { + disconnectWallet(); + if (addressText) addressText.text = 'Not connected'; + }); + } + const saved = getCurrentAddress(); + if (saved && addressText) { + addressText.text = shortAddress(saved); + } +} + +function showToast(message) { + if (typeof wixWindow.openLightbox === 'function') { + wixWindow.openLightbox('Toast', { message }); + } else { + console.error(message); + } +} diff --git a/codex/segments/segment_00.txt b/codex/segments/segment_00.txt new file mode 100644 index 000000000..79ed021e5 --- /dev/null +++ b/codex/segments/segment_00.txt @@ -0,0 +1,80 @@ + + + + + + + THE DIKPIK BAZAAR + + + + + + + +
+
+
+
+ +
+
+
+

+ THE DIKPIK BAZAAR +

+
+ diff --git a/codex/segments/segment_03.txt b/codex/segments/segment_03.txt new file mode 100644 index 000000000..c6c46d719 --- /dev/null +++ b/codex/segments/segment_03.txt @@ -0,0 +1,80 @@ + + + + + +
+
+

Family Tree • Auctions • Wanted

+
+
+ + +
+
+
+ + + + + + +
+ +
+
+
Filter by Traits
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+ + +
+
+
+
+

+ DIKPIK Family Tree +

+

Loading…

+
+
+ The Family Tree auto-assigns each NFT to a classic mafia hierarchy (Boss → Underboss → Consigliere → Caporegime → Soldier → Associate) + per faction. Choose a faction above to see its crew arranged in a centered pyramid by rank. Rarity and trait percentages are shown + on each token; click any token for the full trait breakdown in neon boxes with collection-wide rarity % (to 2 decimals). +
+
+
+
+ +
+
+

+ All Tokens (Filtered View) +

+
diff --git a/codex/segments/segment_04.txt b/codex/segments/segment_04.txt new file mode 100644 index 000000000..41cbb27b1 --- /dev/null +++ b/codex/segments/segment_04.txt @@ -0,0 +1,80 @@ + + + Page 1 / 1 + + +
+ Per page + +
+
+
+ +
+ + +
+
+
+ + + Page 1 / 1 + + +
+
+
+
+ + + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/codex/wixcms/BazaarBids.csv b/codex/wixcms/BazaarBids.csv new file mode 100644 index 000000000..ffc21e856 --- /dev/null +++ b/codex/wixcms/BazaarBids.csv @@ -0,0 +1 @@ +listingId,listingTitle,tokenId,wallet,amount,currency,depositTx,message,status,createdAt,updatedAt,bidderMemberId,bidderDisplayName,verified,createdByIp,createdByUserAgent diff --git a/codex/wixcms/RelaySettings.csv b/codex/wixcms/RelaySettings.csv new file mode 100644 index 000000000..f2c78e513 --- /dev/null +++ b/codex/wixcms/RelaySettings.csv @@ -0,0 +1 @@ +memberId,chain,contract,createdAt,updatedAt,lastUpdated diff --git a/commitlint.config.js b/commitlint.config.js new file mode 100644 index 000000000..c34aa79d0 --- /dev/null +++ b/commitlint.config.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['@commitlint/config-conventional'] +}; diff --git a/cybercity.html b/cybercity.html new file mode 100644 index 000000000..f8b9c0da8 --- /dev/null +++ b/cybercity.html @@ -0,0 +1,98 @@ + + + + + + + DIKPIK Cybercity + + + + +
+
+
+

DIKPIK // CYBERCITY

+

Goblin Verse Cybercity Dashboard

+

Step 1 scaffold - architecture & page shell. No 3D scene yet (gated).

+
+ +
+ +
+
+
+

Cybercity Preview Panel

+

Canvas reserved for the Neon Isometric Blocks scene. Interaction and data wiring land after Step 3-5.

+
+ Gated: waiting for STEP 2/3 +
+
+ Three.js scene placeholder. Will host districts, faction glow, billboards, and "Coming Soon" zones. +
+
+ +
+
+
+

Step 2: Wallet + NFT Verification

+

Connect MetaMask (WalletConnect placeholder) and verify Goblin NFTs (ERC-721/1155). Uses config from frontend/data/contracts.json.

+
+ Active: Step 2 +
+
+
+ + +
WalletConnect coming next iteration.
+
+
+
Waiting to connect...
+
No results yet.
+
+
+
+ +
+

Step-by-step gate

+
    +
  1. STEP 1 (now): Page scaffold, data/config placeholders, shared styling.
  2. +
  3. STEP 2: Wallet login + NFT verification wiring. (in progress)
  4. +
  5. STEP 3: District schema (names, areas, control, lore, expansion zones).
  6. +
  7. STEP 4: Three.js scene bootstrapping.
  8. +
  9. STEP 5: District visualization + sponsor billboards + "Coming Soon" zones.
  10. +
  11. STEP 6: Faction wars + ranking logic (tier multipliers, mythic bonuses).
  12. +
  13. STEP 7: DIKPIK Token presale integration.
  14. +
  15. STEP 8: DIKPIK Coin hooks.
  16. +
  17. STEP 9+: Bazaar/Auction, sponsorships, merch, AI lore, testing/deploy.
  18. +
+
+ +
+

Config & Data

+

Districts, factions, sponsors, and lore snippets live in separate JSON/config files for easy edits.

+
    +
  • frontend/data/districts.json - name, area, faction, tier, lore, comingSoon, sponsor slot.
  • +
  • frontend/data/factions.json - faction colors, badges, lore tags.
  • +
  • frontend/js/cybercity.js - scene controller (stubbed until Step 4/5).
  • +
+
+
+ + + + diff --git a/db/migrations/001_xero_timesheets_legal.sql b/db/migrations/001_xero_timesheets_legal.sql new file mode 100644 index 000000000..7df74c3d7 --- /dev/null +++ b/db/migrations/001_xero_timesheets_legal.sql @@ -0,0 +1,65 @@ +-- Xero connections and mappings +CREATE TABLE IF NOT EXISTS xero_connections ( + connection_id TEXT PRIMARY KEY, + tenant_id TEXT NOT NULL, + access_token TEXT NOT NULL, + refresh_token_encrypted TEXT NOT NULL, + expires_at INTEGER NOT NULL, + scope TEXT NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS xero_employee_map ( + worker_id TEXT PRIMARY KEY, + xero_employee_id TEXT NOT NULL, + payroll_calendar_id TEXT, + ordinary_earnings_rate_id TEXT, + super_fund_id TEXT, + last_synced_at TEXT, + sync_status TEXT, + sync_error TEXT +); + +-- Timesheets +CREATE TABLE IF NOT EXISTS timesheets ( + timesheet_id TEXT PRIMARY KEY, + worker_id TEXT NOT NULL, + client_id TEXT NOT NULL, + assignment_id TEXT, + period_start TEXT NOT NULL, + period_end TEXT NOT NULL, + status TEXT NOT NULL, + submitted_at TEXT, + approved_at TEXT, + approved_by TEXT, + approval_comment TEXT, + xero_timesheet_id TEXT, + xero_synced_at TEXT, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS timesheet_lines ( + line_id TEXT PRIMARY KEY, + timesheet_id TEXT NOT NULL, + work_date TEXT NOT NULL, + hours_decimal REAL NOT NULL, + earnings_code TEXT NOT NULL, + site_id TEXT, + job_id TEXT, + shift_id TEXT, + notes TEXT +); + +-- Legal acceptances +CREATE TABLE IF NOT EXISTS legal_acceptances ( + acceptance_id TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + role TEXT NOT NULL, + document_type TEXT NOT NULL, + document_version TEXT NOT NULL, + accepted_at TEXT NOT NULL, + ip_address TEXT, + user_agent TEXT +); diff --git a/db/migrations/002_xero_settings.sql b/db/migrations/002_xero_settings.sql new file mode 100644 index 000000000..da01e627f --- /dev/null +++ b/db/migrations/002_xero_settings.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS xero_settings ( + settings_id TEXT PRIMARY KEY, + payroll_calendar_id TEXT, + ordinary_earnings_rate_id TEXT, + super_fund_id TEXT, + updated_at INTEGER NOT NULL +); diff --git a/docs/SMOKE.md b/docs/SMOKE.md new file mode 100644 index 000000000..b9ce4c3c0 --- /dev/null +++ b/docs/SMOKE.md @@ -0,0 +1,18 @@ +## Wallet & Bids Smoke Checklist + +1. **Connect MetaMask** + - Load `/wanted-and-bazar`. + - Click Connect; MetaMask prompts; ensure address + chain display. +2. **JWT issuance** + - Capture Network tab → `auth/verifySiwe` returns 200, `sessionStorage.dikpik_jwt` populated. +3. **Wrong chain** + - Switch MetaMask to another network; Connect should show “wrong chain†toast and call `switchChain`. +4. **Place bid** + - Select live auction, enter amount > current high. + - Verify `/backend/bids.placeBid` returns updated amount and UI toast. +5. **Concurrency** + - Simulate stale version by placing two bids quickly; second should receive `AUCTION_CONFLICT` toast. +6. **JWT expiry** + - Clear session storage token, try bid → should force re-auth (getNonce + verify). +7. **Disconnect** + - Click Disconnect; address label resets; placing bid should fail until reconnection. diff --git a/docs/WIX_WALLET_REDESIGN.md b/docs/WIX_WALLET_REDESIGN.md new file mode 100644 index 000000000..8aeee1420 --- /dev/null +++ b/docs/WIX_WALLET_REDESIGN.md @@ -0,0 +1,44 @@ +## Wix Wallet Flow Redesign + +We are removing the Auctions iframe dependency because it blocks first‑party cookies, causes MetaMask popups to fail, and makes `/ _functions` auth impossible to secure. Phase 1 moves all wallet UX directly into the Velo page so Wix owns the window context and can run SIWE without cross‑origin messages. + +### Event Flow +```mermaid +sequenceDiagram + participant U as User (Wix Page) + participant W as wallet.ts + participant MM as MetaMask / WC + participant A as backend/auth.jsw + participant B as backend/bids.jsw + participant D as Wix Collections + + U->>W: Click "Connect Wallet" + W->>MM: requestAccounts / WalletConnect handshake + MM-->>W: address, chainId + W->>A: getNonce(memberId) + A-->>W: nonce + W->>MM: SIWE message signature + MM-->>W: signature + W->>A: verifySiwe(address, signature, nonce) + A-->>W: JWT + W->>B: placeBid({auctionId, amount}, JWT) + B->>D: validate auction (optimistic version) + D-->>B: updated auction/bid + B-->>W: bid confirmation + W-->>U: Toast + UI update +``` + +### Phase 1 Scope + +- EVM wallets: MetaMask + WalletConnect v2 (covers Binance Wallet). +- Single chain id (config via secret `SUPPORTED_CHAIN_ID`). +- SIWE login, JWT stored in wix-storage session. +- Collections: `MembersWallets`, `Auctions` (add `version`), `Bids`, `AuthNonces`. +- Actions supported: connect wallet, sign SIWE, place bid, create/update auction, list bids. + +### Phase 2 Backlog + +- Additional chains (Solana/Phantom) with chain-agnostic signatures. +- Automatic escrow settlement + on-chain proofs. +- Scale beyond Wix collection limits with external DB + HTTP functions proxy. +- Push notifications / WebSocket live bids. diff --git a/docs/cybercity_districts.gsheet b/docs/cybercity_districts.gsheet new file mode 100644 index 000000000..9a7c50252 --- /dev/null +++ b/docs/cybercity_districts.gsheet @@ -0,0 +1 @@ +{"":"WARNING! DO NOT EDIT THIS FILE! ANY CHANGES MADE WILL BE LOST!","doc_id":"14tVHbcDJYsaIx2EI-zco_GAXtIkpma9vlgeunSinHk8","resource_key":"","email":"info@dikpik.io"} diff --git a/docs/metamask-testing.md b/docs/metamask-testing.md new file mode 100644 index 000000000..b459f097d --- /dev/null +++ b/docs/metamask-testing.md @@ -0,0 +1,12 @@ +# MetaMask testing with fake crypto + +You can safely test wallet flows without real funds by pointing MetaMask at a test network or a local chain. + +## Quick options +- **Sepolia/Holesky testnets:** Add the network in MetaMask (often prelisted), then use a faucet to claim test ETH. Search “Sepolia faucet†or “Holesky faucet†and paste your address. +- **Local chain:** Run a dev chain (Hardhat/Ganache/Anvil). Add a custom RPC in MetaMask with the local RPC URL (e.g., `http://127.0.0.1:8545`) and chain ID the dev node reports. Use the pre-funded private keys from the dev chain. + +## Tips +- Never import a production private key into test or local MetaMask profiles. +- For cross‑origin dApp tests, keep the site using the same chain ID that MetaMask is connected to. +- Reset MetaMask nonce/caches via Settings → Advanced → “Reset Account†if txs get stuck.*** diff --git a/docs/spreadsheets/ads/ad_inventory.csv b/docs/spreadsheets/ads/ad_inventory.csv new file mode 100644 index 000000000..3f089e04c --- /dev/null +++ b/docs/spreadsheets/ads/ad_inventory.csv @@ -0,0 +1,78 @@ +SlotID,Format,CountAvailable,Term,BasePriceUSD,RateModel,CTA,RecommendedBrands,Notes +BILL-HOLO-CORE,Neon Holo Billboard (Core Nexus),6,1-week,400,Flat,Affiliate/Deep Link,"Razer; ASUS ROG; Cloudflare; Chainlink; Coinbase",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CORE,Neon Holo Billboard (Core Nexus),6,1-month,1400,Flat,Affiliate/Deep Link,"Razer; ASUS ROG; Cloudflare; Chainlink; Coinbase",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CORE,Neon Holo Billboard (Core Nexus),6,4-month,5000,Flat,Affiliate/Deep Link,"Razer; ASUS ROG; Cloudflare; Chainlink; Coinbase",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CORE,Neon Holo Billboard (Core Nexus),6,6-month,7200,Flat,Affiliate/Deep Link,"Razer; ASUS ROG; Cloudflare; Chainlink; Coinbase",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CORE,Neon Holo Billboard (Core Nexus),6,12-month,12800,Flat,Affiliate/Deep Link,"Razer; ASUS ROG; Cloudflare; Chainlink; Coinbase",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-SCRAP,Neon Holo Billboard (Scrap Bazaar),6,1-week,350,Flat,Affiliate/Deep Link,"Red Bull; Monster; Nike; Adidas; Polygon",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-SCRAP,Neon Holo Billboard (Scrap Bazaar),6,1-month,1225,Flat,Affiliate/Deep Link,"Red Bull; Monster; Nike; Adidas; Polygon",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-SCRAP,Neon Holo Billboard (Scrap Bazaar),6,4-month,4375,Flat,Affiliate/Deep Link,"Red Bull; Monster; Nike; Adidas; Polygon",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-SCRAP,Neon Holo Billboard (Scrap Bazaar),6,6-month,6300,Flat,Affiliate/Deep Link,"Red Bull; Monster; Nike; Adidas; Polygon",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-SCRAP,Neon Holo Billboard (Scrap Bazaar),6,12-month,11200,Flat,Affiliate/Deep Link,"Red Bull; Monster; Nike; Adidas; Polygon",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-GUT,Neon Holo Billboard (Gutter Spires),6,1-week,350,Flat,Affiliate/Deep Link,"Vans; Supreme; Pepsi; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-GUT,Neon Holo Billboard (Gutter Spires),6,1-month,1225,Flat,Affiliate/Deep Link,"Vans; Supreme; Pepsi; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-GUT,Neon Holo Billboard (Gutter Spires),6,4-month,4375,Flat,Affiliate/Deep Link,"Vans; Supreme; Pepsi; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-GUT,Neon Holo Billboard (Gutter Spires),6,6-month,6300,Flat,Affiliate/Deep Link,"Vans; Supreme; Pepsi; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-GUT,Neon Holo Billboard (Gutter Spires),6,12-month,11200,Flat,Affiliate/Deep Link,"Vans; Supreme; Pepsi; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-HYDRO,Neon Holo Billboard (Hydro Docks),6,1-week,360,Flat,Affiliate/Deep Link,"Monster; Gatorade; Prime; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-HYDRO,Neon Holo Billboard (Hydro Docks),6,1-month,1260,Flat,Affiliate/Deep Link,"Monster; Gatorade; Prime; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-HYDRO,Neon Holo Billboard (Hydro Docks),6,4-month,4500,Flat,Affiliate/Deep Link,"Monster; Gatorade; Prime; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-HYDRO,Neon Holo Billboard (Hydro Docks),6,6-month,6480,Flat,Affiliate/Deep Link,"Monster; Gatorade; Prime; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-HYDRO,Neon Holo Billboard (Hydro Docks),6,12-month,11520,Flat,Affiliate/Deep Link,"Monster; Gatorade; Prime; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CLOUD,Neon Holo Billboard (Cloud Spires),6,1-week,380,Flat,Affiliate/Deep Link,"Coinbase; Chainlink; Cloudflare; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CLOUD,Neon Holo Billboard (Cloud Spires),6,1-month,1330,Flat,Affiliate/Deep Link,"Coinbase; Chainlink; Cloudflare; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CLOUD,Neon Holo Billboard (Cloud Spires),6,4-month,4750,Flat,Affiliate/Deep Link,"Coinbase; Chainlink; Cloudflare; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CLOUD,Neon Holo Billboard (Cloud Spires),6,6-month,6840,Flat,Affiliate/Deep Link,"Coinbase; Chainlink; Cloudflare; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-CLOUD,Neon Holo Billboard (Cloud Spires),6,12-month,12160,Flat,Affiliate/Deep Link,"Coinbase; Chainlink; Cloudflare; Polygon; Base",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-BASS,Neon Holo Billboard (Bass Depths),6,1-week,340,Flat,Affiliate/Deep Link,"Adidas; Red Bull; Monster; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-BASS,Neon Holo Billboard (Bass Depths),6,1-month,1190,Flat,Affiliate/Deep Link,"Adidas; Red Bull; Monster; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-BASS,Neon Holo Billboard (Bass Depths),6,4-month,4250,Flat,Affiliate/Deep Link,"Adidas; Red Bull; Monster; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-BASS,Neon Holo Billboard (Bass Depths),6,6-month,6120,Flat,Affiliate/Deep Link,"Adidas; Red Bull; Monster; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +BILL-HOLO-BASS,Neon Holo Billboard (Bass Depths),6,12-month,10880,Flat,Affiliate/Deep Link,"Adidas; Red Bull; Monster; Base; Arbitrum",Rotating creative; 1080x1920 or 1920x1080, 15s loop +MARQ-CORE,District Marquee (Core Nexus),5,1-week,150,Flat,Landing/CTA,"Razer; ASUS ROG; Cloudflare; Chainlink; Coinbase",Entry header +MARQ-SCRAP,District Marquee (Scrap Bazaar),5,1-week,140,Flat,Landing/CTA,"Red Bull; Monster; Nike; Adidas; Polygon",Entry header +MARQ-GUT,District Marquee (Gutter Spires),5,1-week,140,Flat,Landing/CTA,"Vans; Supreme; Pepsi; Base; Arbitrum",Entry header +MARQ-HYDRO,District Marquee (Hydro Docks),5,1-week,140,Flat,Landing/CTA,"Monster; Gatorade; Prime; Polygon; Base",Entry header +MARQ-CLOUD,District Marquee (Cloud Spires),5,1-week,150,Flat,Landing/CTA,"Coinbase; Chainlink; Cloudflare; Polygon; Base",Entry header +MARQ-BASS,District Marquee (Bass Depths),5,1-week,140,Flat,Landing/CTA,"Adidas; Red Bull; Monster; Base; Arbitrum",Entry header +KIOSK-CORE,Branded Kiosk/Shop (Core Nexus),6,1-week,500,Flat,Affiliate/QR,"Razer; ASUS ROG; Cloudflare; Chainlink; Coinbase",Includes QR + product panel +KIOSK-SCRAP,Branded Kiosk/Shop (Scrap Bazaar),6,1-week,480,Flat,Affiliate/QR,"Red Bull; Monster; Nike; Adidas; Polygon",Includes QR + product panel +KIOSK-GUT,Branded Kiosk/Shop (Gutter Spires),6,1-week,480,Flat,Affiliate/QR,"Vans; Supreme; Pepsi; Base; Arbitrum",Includes QR + product panel +KIOSK-HYDRO,Branded Kiosk/Shop (Hydro Docks),6,1-week,480,Flat,Affiliate/QR,"Monster; Gatorade; Prime; Polygon; Base",Includes QR + product panel +KIOSK-CLOUD,Branded Kiosk/Shop (Cloud Spires),6,1-week,500,Flat,Affiliate/QR,"Coinbase; Chainlink; Cloudflare; Polygon; Base",Includes QR + product panel +KIOSK-BASS,Branded Kiosk/Shop (Bass Depths),6,1-week,470,Flat,Affiliate/QR,"Adidas; Red Bull; Monster; Base; Arbitrum",Includes QR + product panel +VEND-ENERGY-CORE,Vending Machine (CORE),5,1-month,1100,Flat,Affiliate/QR,"Red Bull; Monster; Gatorade; Prime; Ghost",Energy/on-ramp promo +VEND-ENERGY-SCRAP,Vending Machine (SCRAP),5,1-month,1100,Flat,Affiliate/QR,"Red Bull; Monster; Gatorade; Prime; Ghost",Energy/on-ramp promo +VEND-ENERGY-GUT,Vending Machine (GUT),5,1-month,1100,Flat,Affiliate/QR,"Red Bull; Monster; Gatorade; Prime; Ghost",Energy/on-ramp promo +VEND-ENERGY-HYDRO,Vending Machine (HYDRO),5,1-month,1100,Flat,Affiliate/QR,"Red Bull; Monster; Gatorade; Prime; Ghost",Energy/on-ramp promo +VEND-ENERGY-CLOUD,Vending Machine (CLOUD),5,1-month,1100,Flat,Affiliate/QR,"Red Bull; Monster; Gatorade; Prime; Ghost",Energy/on-ramp promo +VEND-ENERGY-BASS,Vending Machine (BASS),5,1-month,1100,Flat,Affiliate/QR,"Red Bull; Monster; Gatorade; Prime; Ghost",Energy/on-ramp promo +QUEST-SPONSOR-CORE,Quest/Event (Core Nexus),6,1-week,1500,Flat,Deep Link,"Cloudflare; Chainlink; Coinbase; Polygon; Base",Sponsor quest with cosmetic reward +QUEST-SPONSOR-SCRAP,Quest/Event (Scrap Bazaar),6,1-week,1400,Flat,Deep Link,"Nike; Monster; Adidas; Polygon; Immutable",Sponsor quest with cosmetic reward +QUEST-SPONSOR-GUT,Quest/Event (Gutter Spires),6,1-week,1400,Flat,Deep Link,"Vans; Pepsi; Base; Arbitrum; Supreme",Sponsor quest with cosmetic reward +QUEST-SPONSOR-HYDRO,Quest/Event (Hydro Docks),6,1-week,1400,Flat,Deep Link,"Monster; Gatorade; Base; Polygon; Circle",Sponsor quest with cosmetic reward +QUEST-SPONSOR-CLOUD,Quest/Event (Cloud Spires),6,1-week,1500,Flat,Deep Link,"Cloudflare; Coinbase; Chainlink; Polygon; Base",Sponsor quest with cosmetic reward +QUEST-SPONSOR-BASS,Quest/Event (Bass Depths),6,1-week,1400,Flat,Deep Link,"Adidas; Red Bull; Base; Arbitrum; Circle",Sponsor quest with cosmetic reward +LEADER-SKIN-GLB,Leaderboard Skin (Faction Wars),6,1-week,1700,Flat,Landing/CTA,"Cloudflare; Coinbase; Polygon; Base; Razer",Wraps leaderboard UI +LANDING-HERO-GLB,Cybercity Landing Hero,6,1-week,1800,Flat,Landing/CTA,"Cloudflare; Nike; Red Bull; Coinbase; Polygon",Top hero slot; 1920x1080 static/loop +FLOOR-DECAL-CORE,Floor/Street Decal (Core Nexus),6,1-week,180,Flat,Affiliate/QR,"Razer; Cloudflare; Chainlink; Coinbase; Ledger",Ground decals with QR +AR-POPUP-CORE,AR Popup (Core Nexus interactable),6,1-week,200,Flat,Deep Link,"Razer; Cloudflare; Chainlink; Coinbase; Ledger",Popup CTA over props +LOOT-SKIN-CORE,Loot/Drop Skin (Core Nexus theme),6,1-week,240,Flat,Landing/CTA,"Razer; Cloudflare; Chainlink; Coinbase; Ledger",Sponsor skin on loot +FLOOR-DECAL-SCRAP,Floor/Street Decal (Scrap Bazaar),6,1-week,180,Flat,Affiliate/QR,"Nike; Adidas; Red Bull; Polygon; Immutable",Ground decals with QR +AR-POPUP-SCRAP,AR Popup (Scrap Bazaar interactable),6,1-week,200,Flat,Deep Link,"Nike; Adidas; Red Bull; Polygon; Immutable",Popup CTA over props +LOOT-SKIN-SCRAP,Loot/Drop Skin (Scrap Bazaar theme),6,1-week,240,Flat,Landing/CTA,"Nike; Adidas; Red Bull; Polygon; Immutable",Sponsor skin on loot +FLOOR-DECAL-GUT,Floor/Street Decal (Gutter Spires),6,1-week,180,Flat,Affiliate/QR,"Vans; Supreme; Base; Arbitrum; Pepsi",Ground decals with QR +AR-POPUP-GUT,AR Popup (Gutter Spires interactable),6,1-week,200,Flat,Deep Link,"Vans; Supreme; Base; Arbitrum; Pepsi",Popup CTA over props +LOOT-SKIN-GUT,Loot/Drop Skin (Gutter Spires theme),6,1-week,240,Flat,Landing/CTA,"Vans; Supreme; Base; Arbitrum; Pepsi",Sponsor skin on loot +FLOOR-DECAL-HYDRO,Floor/Street Decal (Hydro Docks),6,1-week,180,Flat,Affiliate/QR,"Monster; Gatorade; Base; Polygon; Circle",Ground decals with QR +AR-POPUP-HYDRO,AR Popup (Hydro Docks interactable),6,1-week,200,Flat,Deep Link,"Monster; Gatorade; Base; Polygon; Circle",Popup CTA over props +LOOT-SKIN-HYDRO,Loot/Drop Skin (Hydro Docks theme),6,1-week,240,Flat,Landing/CTA,"Monster; Gatorade; Base; Polygon; Circle",Sponsor skin on loot +FLOOR-DECAL-CLOUD,Floor/Street Decal (Cloud Spires),6,1-week,180,Flat,Affiliate/QR,"Coinbase; Chainlink; Base; Cloudflare; Polygon",Ground decals with QR +AR-POPUP-CLOUD,AR Popup (Cloud Spires interactable),6,1-week,200,Flat,Deep Link,"Coinbase; Chainlink; Base; Cloudflare; Polygon",Popup CTA over props +LOOT-SKIN-CLOUD,Loot/Drop Skin (Cloud Spires theme),6,1-week,240,Flat,Landing/CTA,"Coinbase; Chainlink; Base; Cloudflare; Polygon",Sponsor skin on loot +FLOOR-DECAL-BASS,Floor/Street Decal (Bass Depths),6,1-week,180,Flat,Affiliate/QR,"Adidas; Red Bull; Base; Arbitrum; Monster",Ground decals with QR +AR-POPUP-BASS,AR Popup (Bass Depths interactable),6,1-week,200,Flat,Deep Link,"Adidas; Red Bull; Base; Arbitrum; Monster",Popup CTA over props +LOOT-SKIN-BASS,Loot/Drop Skin (Bass Depths theme),6,1-week,240,Flat,Landing/CTA,"Adidas; Red Bull; Base; Arbitrum; Monster",Sponsor skin on loot +AUDIO-STING-GLB,Audio Sting (scene transition),5,1-week,300,Flat,Landing/CTA,"Red Bull; Monster; Nike; Razer; Coinbase",1-2s audio logo +TELEPORT-GATE-GLB,Teleport Gate Wrap,5,1-week,320,Flat,Affiliate/Deep Link,"Cloudflare; Coinbase; Base; Polygon; Chainlink",Gate frame wrap + CTA +BEACON-WRAP-GLB,Beacon/Relay Wrap,5,1-week,210,Flat,Affiliate/Deep Link,"Cloudflare; Chainlink; Polygon; Base; Arbitrum",Small form factor on relays \ No newline at end of file diff --git a/docs/spreadsheets/districts/cybercity_districts.csv b/docs/spreadsheets/districts/cybercity_districts.csv new file mode 100644 index 000000000..4faa68942 --- /dev/null +++ b/docs/spreadsheets/districts/cybercity_districts.csv @@ -0,0 +1,52 @@ +City,ID,Name,AreaSqKm,Faction,Control,Tier,ComingSoon,Sponsor,Lore,SubdistrictOf +Neo Goblintown,corebreakers-root-spire,Corebreakers Root Spire,2,Corebreakers,Corebreakers,Boss,false,Cloudflare,"Root clearance command spire for Corebreakers, where high-tier clearances govern the grid.", +Neo Goblintown,root-clearance,Root Clearance Deck,0.45,Corebreakers,Corebreakers,Boss,false,Cloudflare,"Grid Clearance: Root. Boss-level overrides and uplinks.",corebreakers-root-spire +Neo Goblintown,admin-node,Admin Node,0.35,Corebreakers,Corebreakers,Underboss,false,Cloudflare,"Grid Clearance: Admin. Underboss crews manage control planes.",corebreakers-root-spire +Neo Goblintown,core-consig-relay,Consigliere Relay,0.3,Corebreakers,Corebreakers,Consigliere,false,Cloudflare,"Consiglieres broker clearances across the spire.",corebreakers-root-spire +Neo Goblintown,poweruser-tier,Power-User Tier,0.3,Corebreakers,Corebreakers,Caporegime,false,Cloudflare,"Grid Clearance: Power-User. Capos keep the edge nodes alive.",corebreakers-root-spire +Neo Goblintown,core-soldier-outpost,Soldier Outposts,0.3,Corebreakers,Corebreakers,Soldier,false,Cloudflare,"Soldiers guard node rooms and cabling.",corebreakers-root-spire +Neo Goblintown,user-commons,User Commons,0.3,Corebreakers,Corebreakers,Associate,false,Cloudflare,"Grid Clearance: User. Associates handle front-line support and routing.",corebreakers-root-spire +Neo Goblintown,scrapshamans-burn-yards,Scrapshamans Burn Yards,2,Scrapshamans,Scrapshamans,Boss,false,Red Bull,"Burn yards fueled by Trash-Burner and Battery-Siphon rigs, run by Scrapshaman bosses.", +Neo Goblintown,trash-burner-yard,Trash-Burner Yard,0.45,Scrapshamans,Scrapshamans,Boss,false,Red Bull,"Power Source: Trash-Burner. Bosses command the burn pits.",scrapshamans-burn-yards +Neo Goblintown,battery-siphon-row,Battery-Siphon Row,0.35,Scrapshamans,Scrapshamans,Underboss,false,Red Bull,"Power Source: Battery-Siphon. Underboss siphon crews.",scrapshamans-burn-yards +Neo Goblintown,scrap-consig-runes,Consigliere Runes,0.3,Scrapshamans,Scrapshamans,Consigliere,false,Red Bull,"Consiglieres adjudicate trade and rune disputes.",scrapshamans-burn-yards +Neo Goblintown,kinetic-looms,Kinetic Looms,0.3,Scrapshamans,Scrapshamans,Caporegime,false,Red Bull,"Power Source: Kinetic. Capos harvest kinetic lines.",scrapshamans-burn-yards +Neo Goblintown,scrap-soldier-lines,Soldier Lines,0.3,Scrapshamans,Scrapshamans,Soldier,false,Red Bull,"Soldiers haul scrap and defend stalls.",scrapshamans-burn-yards +Neo Goblintown,upcycle-commons,Upcycle Commons,0.3,Scrapshamans,Scrapshamans,Associate,false,Red Bull,"Associates sort scrap and keep the markets humming.",scrapshamans-burn-yards +Neo Goblintown,gutterborns-alignment-lanes,Gutterborns Alignment Lanes,2,Gutterborns,Gutterborns,Underboss,false,Pepsi,"Alignment-tagged lanes where Gutterborn crews flex Horny, Chaotic, and Greedy codes.", +Neo Goblintown,gutter-boss-tagspire,Boss Tagspire,0.45,Gutterborns,Gutterborns,Boss,false,Pepsi,"Boss perch above the tagged skyline.",gutterborns-alignment-lanes +Neo Goblintown,horny-lanes,Horny Lanes,0.35,Gutterborns,Gutterborns,Underboss,false,Pepsi,"Alignment: Horny. Underboss turf for hungry crews.",gutterborns-alignment-lanes +Neo Goblintown,gutter-consig-council,Consigliere Council,0.3,Gutterborns,Gutterborns,Consigliere,false,Pepsi,"Consiglieres settle turf disputes.",gutterborns-alignment-lanes +Neo Goblintown,gutter-capo-junction,Caporegime Junction,0.3,Gutterborns,Gutterborns,Caporegime,false,Pepsi,"Capos route runners through junctions.",gutterborns-alignment-lanes +Neo Goblintown,chaotic-runs,Chaotic Runs,0.3,Gutterborns,Gutterborns,Soldier,false,Pepsi,"Alignment: Chaotic. Soldiers sprint these backroads.",gutterborns-alignment-lanes +Neo Goblintown,greedy-corners,Greedy Corners,0.3,Gutterborns,Gutterborns,Associate,false,Pepsi,"Alignment: Greedy. Associates hustle the corners.",gutterborns-alignment-lanes +Neo Goblintown,hydrohackers-power-docks,Hydrohackers Power Docks,2,Hydrohackers,Hydrohackers,Boss,false,Monster,"Dockside rigs powered by battery-siphons, kinetic pumps, and solar arrays.", +Neo Goblintown,battery-docks,Battery-Siphon Docks,0.45,Hydrohackers,Hydrohackers,Boss,false,Monster,"Power Source: Battery-Siphon. Boss rigs dominate the docks.",hydrohackers-power-docks +Neo Goblintown,kinetic-pumps,Kinetic Pumps,0.35,Hydrohackers,Hydrohackers,Underboss,false,Monster,"Power Source: Kinetic. Underboss lines on pump control.",hydrohackers-power-docks +Neo Goblintown,hydro-consig-piers,Consigliere Piers,0.3,Hydrohackers,Hydrohackers,Consigliere,false,Monster,"Consiglieres arbitrate dock flows.",hydrohackers-power-docks +Neo Goblintown,solar-arrays,Solar Arrays,0.3,Hydrohackers,Hydrohackers,Caporegime,false,Monster,"Power Source: Solar. Capos keep arrays humming.",hydrohackers-power-docks +Neo Goblintown,hydro-soldier-bays,Soldier Bays,0.3,Hydrohackers,Hydrohackers,Soldier,false,Monster,"Soldiers secure pump bays.",hydrohackers-power-docks +Neo Goblintown,guest-jetties,Guest Jetties,0.3,Hydrohackers,Hydrohackers,Associate,false,Monster,"Grid Clearance: Guest. Associates handle day-pass traffic at the docks.",hydrohackers-power-docks +Neo Goblintown,cloudjackers-clearance-row,Cloudjackers Clearance Row,2,Cloudjackers,Cloudjackers,Boss,false,Coinbase,"Clearance-tier corridors where Cloudjackers staff Guest, User, and Power-User gates.", +Neo Goblintown,guest-gate,Guest Gate,0.45,Cloudjackers,Cloudjackers,Boss,false,Coinbase,"Grid Clearance: Guest. Bosses watch the entry gate.",cloudjackers-clearance-row +Neo Goblintown,user-hub,User Hub,0.35,Cloudjackers,Cloudjackers,Underboss,false,Coinbase,"Grid Clearance: User. Underboss crews manage user flow.",cloudjackers-clearance-row +Neo Goblintown,cloud-consig-bridge,Consigliere Bridge,0.3,Cloudjackers,Cloudjackers,Consigliere,false,Coinbase,"Consiglieres monitor clearance policies.",cloudjackers-clearance-row +Neo Goblintown,poweruser-bridge,Power-User Bridge,0.3,Cloudjackers,Cloudjackers,Caporegime,false,Coinbase,"Grid Clearance: Power-User. Capos keep the bridge stable.",cloudjackers-clearance-row +Neo Goblintown,cloud-soldier-gates,Soldier Gates,0.3,Cloudjackers,Cloudjackers,Soldier,false,Coinbase,"Soldiers man the access gates.",cloudjackers-clearance-row +Neo Goblintown,guest-commons,Guest Commons,0.3,Cloudjackers,Cloudjackers,Associate,false,Coinbase,"Grid Clearance: Guest/User. Associates onboard newcomers.",cloudjackers-clearance-row +Neo Goblintown,bassfiends-subsonic-depths,Bassfiends Subsonic Depths,2,Bassfiends,Bassfiends,Underboss,false,Adidas,"Deep channels where Bassfiends ride Subsonic and Infrared currents, pulling lightning when storms hit.", +Neo Goblintown,bass-boss-vault,Boss Vault,0.45,Bassfiends,Bassfiends,Boss,false,Adidas,"Bassfiend bosses rumble in the vault.",bassfiends-subsonic-depths +Neo Goblintown,subsonic-trenches,Subsonic Trenches,0.35,Bassfiends,Bassfiends,Underboss,false,Adidas,"Signal Affinity: Subsonic. Underboss trenches rumble.",bassfiends-subsonic-depths +Neo Goblintown,bass-consig-cavern,Consigliere Cavern,0.3,Bassfiends,Bassfiends,Consigliere,false,Adidas,"Consiglieres manage subsonic treaties.",bassfiends-subsonic-depths +Neo Goblintown,bass-capo-steps,Caporegime Steps,0.3,Bassfiends,Bassfiends,Caporegime,false,Adidas,"Capos control ascent to the depths.",bassfiends-subsonic-depths +Neo Goblintown,infrared-coves,Infrared Coves,0.3,Bassfiends,Bassfiends,Soldier,false,Adidas,"Signal Affinity: Infrared. Soldiers guard hot caches.",bassfiends-subsonic-depths +Neo Goblintown,lightning-rod-vault,Lightning-Rod Vault,0.3,Bassfiends,Bassfiends,Associate,false,Adidas,"Power Source: Lightning-Rod. Associates harness storms.",bassfiends-subsonic-depths +City 02,expansion-02,Coming Soon Zone 02,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 03,expansion-03,Coming Soon Zone 03,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 04,expansion-04,Coming Soon Zone 04,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 05,expansion-05,Coming Soon Zone 05,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 06,expansion-06,Coming Soon Zone 06,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 07,expansion-07,Coming Soon Zone 07,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 08,expansion-08,Coming Soon Zone 08,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 09,expansion-09,Coming Soon Zone 09,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", +City 10,expansion-10,Coming Soon Zone 10,3,,,,true,Coming Soon,"Reserved for future Goblin Verse city drop.", \ No newline at end of file diff --git a/docs/spreadsheets/districts/cybercity_districts.gsheet b/docs/spreadsheets/districts/cybercity_districts.gsheet new file mode 100644 index 000000000..b90460d14 --- /dev/null +++ b/docs/spreadsheets/districts/cybercity_districts.gsheet @@ -0,0 +1 @@ +{"":"WARNING! DO NOT EDIT THIS FILE! ANY CHANGES MADE WILL BE LOST!","doc_id":"1MYRTg7z4wRxayQoj9hqQ-vRNNbJjkDdgDiTvjilFA-E","resource_key":"","email":"info@dikpik.io"} diff --git a/docs/spreadsheets/monetization/monetize.csv b/docs/spreadsheets/monetization/monetize.csv new file mode 100644 index 000000000..8698499aa --- /dev/null +++ b/docs/spreadsheets/monetization/monetize.csv @@ -0,0 +1,14 @@ +Item,Description,HolderShare%,PlatformShare%,Dev/Marketing%,InfluencerPool%,Token/Coin Reserve% +Ownership Rights,Slot tied to NFT (by trait/ID); holder controls approved creatives,0,0,0,0,0 +Revenue Share (Baseline),Platform sells ads/affiliates; holder paid in DIKPIK Token,55,25,12,5,3 +Revenue Share (High-Tier Holder),Flagship slots; slightly better holder cut,60,22,11,4,3 +Revenue Share (Direct Deal Floor),Holder sets floor; platform approves creative,50,25,15,5,5 +Self-Serve Listings,Holder opts in; platform rotates sponsor ads,55,25,12,5,3 +Direct Deals,Holder uploads sponsor creative; content policy enforced,50,25,15,5,5 +Quest/Event Sponsorship,Holder slot shares quest revenue,55,25,12,5,3 +Cosmetic/Loot Sales,Branded skins in holder’s district,50,25,15,5,5 +Payout Asset (Token),DIKPIK Token used for payouts initially,0,0,0,0,0 +Payout Asset (Coin Future),Switch/bridge to DIKPIK Coin when launched,0,0,0,0,0 +Influencer/Creator Promo,Allocated from platform share for collabs,0,0,0,10,0 +Dev/Marketing Fund,Portion of platform share for ops,0,0,25,0,0 +Token/Coin Reserve,Long-term reserve for liquidity/rewards,0,0,0,0,100 \ No newline at end of file diff --git a/docs/spreadsheets/plots/revenue_from_plots.csv b/docs/spreadsheets/plots/revenue_from_plots.csv new file mode 100644 index 000000000..54acc363a --- /dev/null +++ b/docs/spreadsheets/plots/revenue_from_plots.csv @@ -0,0 +1,68 @@ +SlotID,SlotType,District,TotalShares,PricePerShare,SaleType,WeeklyRateOpt,WeeklyRateCon,MonthlyGrossOpt,MonthlyGrossCon,HolderPoolPct,HolderPoolMonthlyOpt,HolderPoolMonthlyCon,PayoutPerShareMonthlyOpt,PayoutPerShareMonthlyCon,ROI_Monthly_Opt,ROI_Monthly_Con,Notes +CORE-HERO-01,Landing Hero,Global,12,320,Auction,4400,3080,17600,12320,55%,9680.00,6776.00,806.67,564.67,2.52,1.76, +CORE-HERO-02,Landing Hero,Global,12,320,Auction,4400,3080,17600,12320,55%,9680.00,6776.00,806.67,564.67,2.52,1.76, +LEAD-SKIN-01,Leaderboard Skin,Global,12,280,Auction,4000,2800,16000,11200,55%,8800.00,6160.00,733.33,513.33,2.62,1.83, +LEAD-SKIN-02,Leaderboard Skin,Global,12,280,Auction,4000,2800,16000,11200,55%,8800.00,6160.00,733.33,513.33,2.62,1.83, +EVENT-ARENA-01,Event Arena,Global,15,260,Auction,5000,3500,20000,14000,55%,11000.00,7700.00,733.33,513.33,2.82,1.97, +GATE-TELEPORT-01,Teleport Gate,Global,15,220,Auction,3600,2520,14400,10080,55%,7920.00,5544.00,528.00,369.60,2.40,1.68, +CORE-BILL-01,Billboard,Core Nexus,10,260,Fixed,1000,700,4000,2800,55%,2200.00,1540.00,220.00,154.00,0.85,0.59, +CORE-BILL-02,Billboard,Core Nexus,10,280,Auction,1100,770,4400,3080,55%,2420.00,1694.00,242.00,169.40,0.86,0.61, +CORE-KIOSK-01,Kiosk,Core Nexus,20,140,Fixed,1240,868,4960,3472,55%,2728.00,1909.60,136.40,95.48,0.97,0.68, +CORE-QUEST-01,Quest Slot,Core Nexus,20,150,Fixed,3600,2520,14400,10080,55%,7920.00,5544.00,396.00,277.20,2.64,1.85, +CORE-VEND-01,Vending,Core Nexus,30,95,Fixed,760,532,3040,2128,55%,1672.00,1170.40,55.73,39.01,0.59,0.41, +CORE-DECAL-01,Floor Decal,Core Nexus,50,55,Fixed,380,266,1520,1064,55%,836.00,585.20,16.72,11.70,0.30,0.21, +CORE-AR-01,AR Popup,Core Nexus,50,60,Fixed,460,322,1840,1288,55%,1012.00,708.40,20.24,14.17,0.34,0.24, +CORE-LOOT-01,Loot Skin,Core Nexus,40,68,Fixed,560,392,2240,1568,55%,1232.00,862.40,30.80,21.56,0.45,0.32, +CORE-BEACON-01,Beacon Wrap,Core Nexus,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +CORE-BEACON-02,Beacon Wrap,Core Nexus,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +SCRAP-BILL-01,Billboard,Scrap Bazaar,10,260,Fixed,1000,700,4000,2800,55%,2200.00,1540.00,220.00,154.00,0.85,0.59, +SCRAP-BILL-02,Billboard,Scrap Bazaar,10,280,Auction,1100,770,4400,3080,55%,2420.00,1694.00,242.00,169.40,0.86,0.61, +SCRAP-KIOSK-01,Kiosk,Scrap Bazaar,20,140,Fixed,1240,868,4960,3472,55%,2728.00,1909.60,136.40,95.48,0.97,0.68, +SCRAP-QUEST-01,Quest Slot,Scrap Bazaar,20,150,Fixed,3600,2520,14400,10080,55%,7920.00,5544.00,396.00,277.20,2.64,1.85, +SCRAP-VEND-01,Vending,Scrap Bazaar,30,95,Fixed,760,532,3040,2128,55%,1672.00,1170.40,55.73,39.01,0.59,0.41, +SCRAP-DECAL-01,Floor Decal,Scrap Bazaar,50,55,Fixed,380,266,1520,1064,55%,836.00,585.20,16.72,11.70,0.30,0.21, +SCRAP-AR-01,AR Popup,Scrap Bazaar,50,60,Fixed,460,322,1840,1288,55%,1012.00,708.40,20.24,14.17,0.34,0.24, +SCRAP-LOOT-01,Loot Skin,Scrap Bazaar,40,68,Fixed,560,392,2240,1568,55%,1232.00,862.40,30.80,21.56,0.45,0.32, +SCRAP-BEACON-01,Beacon Wrap,Scrap Bazaar,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +SCRAP-BEACON-02,Beacon Wrap,Scrap Bazaar,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +GUT-BILL-01,Billboard,Gutter Spires,10,260,Fixed,1000,700,4000,2800,55%,2200.00,1540.00,220.00,154.00,0.85,0.59, +GUT-BILL-02,Billboard,Gutter Spires,10,280,Auction,1100,770,4400,3080,55%,2420.00,1694.00,242.00,169.40,0.86,0.61, +GUT-KIOSK-01,Kiosk,Gutter Spires,20,140,Fixed,1240,868,4960,3472,55%,2728.00,1909.60,136.40,95.48,0.97,0.68, +GUT-QUEST-01,Quest Slot,Gutter Spires,20,150,Fixed,3600,2520,14400,10080,55%,7920.00,5544.00,396.00,277.20,2.64,1.85, +GUT-VEND-01,Vending,Gutter Spires,30,95,Fixed,760,532,3040,2128,55%,1672.00,1170.40,55.73,39.01,0.59,0.41, +GUT-DECAL-01,Floor Decal,Gutter Spires,50,55,Fixed,380,266,1520,1064,55%,836.00,585.20,16.72,11.70,0.30,0.21, +GUT-AR-01,AR Popup,Gutter Spires,50,60,Fixed,460,322,1840,1288,55%,1012.00,708.40,20.24,14.17,0.34,0.24, +GUT-LOOT-01,Loot Skin,Gutter Spires,40,68,Fixed,560,392,2240,1568,55%,1232.00,862.40,30.80,21.56,0.45,0.32, +GUT-BEACON-01,Beacon Wrap,Gutter Spires,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +GUT-BEACON-02,Beacon Wrap,Gutter Spires,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +HYDRO-BILL-01,Billboard,Hydro Docks,10,260,Fixed,1000,700,4000,2800,55%,2200.00,1540.00,220.00,154.00,0.85,0.59, +HYDRO-BILL-02,Billboard,Hydro Docks,10,280,Auction,1100,770,4400,3080,55%,2420.00,1694.00,242.00,169.40,0.86,0.61, +HYDRO-KIOSK-01,Kiosk,Hydro Docks,20,140,Fixed,1240,868,4960,3472,55%,2728.00,1909.60,136.40,95.48,0.97,0.68, +HYDRO-QUEST-01,Quest Slot,Hydro Docks,20,150,Fixed,3600,2520,14400,10080,55%,7920.00,5544.00,396.00,277.20,2.64,1.85, +HYDRO-VEND-01,Vending,Hydro Docks,30,95,Fixed,760,532,3040,2128,55%,1672.00,1170.40,55.73,39.01,0.59,0.41, +HYDRO-DECAL-01,Floor Decal,Hydro Docks,50,55,Fixed,380,266,1520,1064,55%,836.00,585.20,16.72,11.70,0.30,0.21, +HYDRO-AR-01,AR Popup,Hydro Docks,50,60,Fixed,460,322,1840,1288,55%,1012.00,708.40,20.24,14.17,0.34,0.24, +HYDRO-LOOT-01,Loot Skin,Hydro Docks,40,68,Fixed,560,392,2240,1568,55%,1232.00,862.40,30.80,21.56,0.45,0.32, +HYDRO-BEACON-01,Beacon Wrap,Hydro Docks,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +HYDRO-BEACON-02,Beacon Wrap,Hydro Docks,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +CLOUD-BILL-01,Billboard,Cloud Spires,10,260,Fixed,1000,700,4000,2800,55%,2200.00,1540.00,220.00,154.00,0.85,0.59, +CLOUD-BILL-02,Billboard,Cloud Spires,10,280,Auction,1100,770,4400,3080,55%,2420.00,1694.00,242.00,169.40,0.86,0.61, +CLOUD-KIOSK-01,Kiosk,Cloud Spires,20,140,Fixed,1240,868,4960,3472,55%,2728.00,1909.60,136.40,95.48,0.97,0.68, +CLOUD-QUEST-01,Quest Slot,Cloud Spires,20,150,Fixed,3600,2520,14400,10080,55%,7920.00,5544.00,396.00,277.20,2.64,1.85, +CLOUD-VEND-01,Vending,Cloud Spires,30,95,Fixed,760,532,3040,2128,55%,1672.00,1170.40,55.73,39.01,0.59,0.41, +CLOUD-DECAL-01,Floor Decal,Cloud Spires,50,55,Fixed,380,266,1520,1064,55%,836.00,585.20,16.72,11.70,0.30,0.21, +CLOUD-AR-01,AR Popup,Cloud Spires,50,60,Fixed,460,322,1840,1288,55%,1012.00,708.40,20.24,14.17,0.34,0.24, +CLOUD-LOOT-01,Loot Skin,Cloud Spires,40,68,Fixed,560,392,2240,1568,55%,1232.00,862.40,30.80,21.56,0.45,0.32, +CLOUD-BEACON-01,Beacon Wrap,Cloud Spires,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +CLOUD-BEACON-02,Beacon Wrap,Cloud Spires,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +BASS-BILL-01,Billboard,Bass Depths,10,260,Fixed,1000,700,4000,2800,55%,2200.00,1540.00,220.00,154.00,0.85,0.59, +BASS-BILL-02,Billboard,Bass Depths,10,280,Auction,1100,770,4400,3080,55%,2420.00,1694.00,242.00,169.40,0.86,0.61, +BASS-KIOSK-01,Kiosk,Bass Depths,20,140,Fixed,1240,868,4960,3472,55%,2728.00,1909.60,136.40,95.48,0.97,0.68, +BASS-QUEST-01,Quest Slot,Bass Depths,20,150,Fixed,3600,2520,14400,10080,55%,7920.00,5544.00,396.00,277.20,2.64,1.85, +BASS-VEND-01,Vending,Bass Depths,30,95,Fixed,760,532,3040,2128,55%,1672.00,1170.40,55.73,39.01,0.59,0.41, +BASS-DECAL-01,Floor Decal,Bass Depths,50,55,Fixed,380,266,1520,1064,55%,836.00,585.20,16.72,11.70,0.30,0.21, +BASS-AR-01,AR Popup,Bass Depths,50,60,Fixed,460,322,1840,1288,55%,1012.00,708.40,20.24,14.17,0.34,0.24, +BASS-LOOT-01,Loot Skin,Bass Depths,40,68,Fixed,560,392,2240,1568,55%,1232.00,862.40,30.80,21.56,0.45,0.32, +BASS-BEACON-01,Beacon Wrap,Bass Depths,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +BASS-BEACON-02,Beacon Wrap,Bass Depths,60,48,Fixed,440,308,1760,1232,55%,968.00,677.60,16.13,11.29,0.34,0.24, +TOTAL,All Slots,All,2178,,,,,,,,,,,,,,Potential revenue if all shares sell: 191280 \ No newline at end of file diff --git a/docs/worker-setup.md b/docs/worker-setup.md new file mode 100644 index 000000000..132cc25d1 --- /dev/null +++ b/docs/worker-setup.md @@ -0,0 +1,43 @@ +## Cloudflare Worker Setup + +This repository now includes the scaffolding for the Dikpik Bazaar backend Worker. + +### 1. Requirements + +- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/) v4+ +- Cloudflare account with access to the `dikpik-bazaar` D1 database +- Postmark server/API token + +### 2. Configure Secrets + +After pulling the latest code, store the Postmark token inside Cloudflare: + +```bash +wrangler secret put POSTMARK_API_TOKEN --name dikpik-backend +``` + +Paste the token when prompted. Wrangler stores it securely and the Worker can reference it via `env.POSTMARK_API_TOKEN`. + +### 3. Initialize the D1 schema + +Run the schema file once to bootstrap tables: + +```bash +wrangler d1 execute dikpik-bazaar --file=worker/schema.sql +``` + +This creates `members`, `wallets`, `deposits`, `listings`, `bids`, and `sessions`. + +If you ran the schema previously, rerun the command to ensure the new `sessions` table exists (the statements are idempotent). + +### 4. Deploy / Test locally + +```bash +# local dev (uses wrangler dev, requires --remote for D1 access) +wrangler dev --name dikpik-backend --remote + +# deploy to Cloudflare +wrangler deploy +``` + +The Worker currently exposes a `/api/health` endpoint which returns wallet configuration and confirms bindings. Additional routes (auth, deposits, listings) will be layered on top of this foundation. diff --git a/docs/xero-payroll-au.md b/docs/xero-payroll-au.md new file mode 100644 index 000000000..cb10e46c8 --- /dev/null +++ b/docs/xero-payroll-au.md @@ -0,0 +1,28 @@ +# Xero Payroll AU Integration + +Draft – obtain legal review before production. + +This document describes the internal-only Xero Payroll AU integration for SiteReady Workforce. + +## Required environment variables + +Server-side only (Cloudflare secrets or local env): + +- XERO_CLIENT_ID +- XERO_CLIENT_SECRET +- XERO_REDIRECT_URI +- XERO_TOKEN_ENCRYPTION_KEY +- APP_URL + +## OAuth flow (high-level) + +1) Admin clicks Connect in `/dashboard/admin/xero` +2) OAuth consent redirects to `/api/integrations/xero/callback` +3) Tokens are stored encrypted at rest +4) Xero tenant is stored for future API calls + +## Security notes + +- Never expose client secrets or tokens to the browser +- Always use `Xero-Tenant-Id` and `Idempotency-Key` headers for POST requests +- Mask TFN/bank fields in all UI screens and logs diff --git a/frontend/data/contracts.json b/frontend/data/contracts.json new file mode 100644 index 000000000..fcf1c3063 --- /dev/null +++ b/frontend/data/contracts.json @@ -0,0 +1,18 @@ +[ + { + "id": "goblin721-main", + "name": "Goblin Verse (ERC721)", + "address": "0x0000000000000000000000000000000000000000", + "standard": "ERC721", + "tokenIds": [1, 2, 3], + "chainId": 1 + }, + { + "id": "goblin1155-main", + "name": "Goblin Items (ERC1155)", + "address": "0x0000000000000000000000000000000000000000", + "standard": "ERC1155", + "tokenIds": [1001, 1002], + "chainId": 1 + } +] diff --git a/frontend/data/districts.json b/frontend/data/districts.json new file mode 100644 index 000000000..3e02a6d20 --- /dev/null +++ b/frontend/data/districts.json @@ -0,0 +1,455 @@ +[ + { + "id": "corebreakers-root-spire", + "city": "Neo Goblintown", + "name": "Corebreakers Root Spire", + "areaSqKm": 2, + "faction": "Corebreakers", + "tier": "Boss", + "control": "Corebreakers", + "lore": "Root clearance command spire for Corebreakers, where high-tier clearances govern the grid.", + "sponsor": "Cloudflare", + "comingSoon": false, + "subDistricts": [ + { + "id": "root-clearance", + "name": "Root Clearance Deck", + "tier": "Boss", + "lore": "Grid Clearance: Root. Boss-level overrides and uplinks.", + "areaSqKm": 0.45 + }, + { + "id": "admin-node", + "name": "Admin Node", + "tier": "Underboss", + "lore": "Grid Clearance: Admin. Underboss crews manage control planes.", + "areaSqKm": 0.35 + }, + { + "id": "core-consig-relay", + "name": "Consigliere Relay", + "tier": "Consigliere", + "lore": "Consiglieres broker clearances across the spire.", + "areaSqKm": 0.3 + }, + { + "id": "poweruser-tier", + "name": "Power-User Tier", + "tier": "Caporegime", + "lore": "Grid Clearance: Power-User. Capos keep the edge nodes alive.", + "areaSqKm": 0.3 + }, + { + "id": "core-soldier-outpost", + "name": "Soldier Outposts", + "tier": "Soldier", + "lore": "Soldiers guard node rooms and cabling.", + "areaSqKm": 0.3 + }, + { + "id": "user-commons", + "name": "User Commons", + "tier": "Associate", + "lore": "Grid Clearance: User. Associates handle front-line support and routing.", + "areaSqKm": 0.3 + } + ] + }, + { + "id": "scrapshamans-burn-yards", + "city": "Neo Goblintown", + "name": "Scrapshamans Burn Yards", + "areaSqKm": 2, + "faction": "Scrapshamans", + "tier": "Boss", + "control": "Scrapshamans", + "lore": "Burn yards fueled by Trash-Burner and Battery-Siphon rigs, run by Scrapshaman bosses.", + "sponsor": "Red Bull", + "comingSoon": false, + "subDistricts": [ + { + "id": "trash-burner-yard", + "name": "Trash-Burner Yard", + "tier": "Boss", + "lore": "Power Source: Trash-Burner. Bosses command the burn pits.", + "areaSqKm": 0.45 + }, + { + "id": "battery-siphon-row", + "name": "Battery-Siphon Row", + "tier": "Underboss", + "lore": "Power Source: Battery-Siphon. Underboss siphon crews.", + "areaSqKm": 0.35 + }, + { + "id": "scrap-consig-runes", + "name": "Consigliere Runes", + "tier": "Consigliere", + "lore": "Consiglieres adjudicate trade and rune disputes.", + "areaSqKm": 0.3 + }, + { + "id": "kinetic-looms", + "name": "Kinetic Looms", + "tier": "Caporegime", + "lore": "Power Source: Kinetic. Capos harvest kinetic lines.", + "areaSqKm": 0.3 + }, + { + "id": "scrap-soldier-lines", + "name": "Soldier Lines", + "tier": "Soldier", + "lore": "Soldiers haul scrap and defend stalls.", + "areaSqKm": 0.3 + }, + { + "id": "upcycle-commons", + "name": "Upcycle Commons", + "tier": "Associate", + "lore": "Associates sort scrap and keep the markets humming.", + "areaSqKm": 0.3 + } + ] + }, + { + "id": "gutterborns-alignment-lanes", + "city": "Neo Goblintown", + "name": "Gutterborns Alignment Lanes", + "areaSqKm": 2, + "faction": "Gutterborns", + "tier": "Underboss", + "control": "Gutterborns", + "lore": "Alignment-tagged lanes where Gutterborn crews flex Horny, Chaotic, and Greedy codes.", + "sponsor": "Pepsi", + "comingSoon": false, + "subDistricts": [ + { + "id": "gutter-boss-tagspire", + "name": "Boss Tagspire", + "tier": "Boss", + "lore": "Boss perch above the tagged skyline.", + "areaSqKm": 0.45 + }, + { + "id": "horny-lanes", + "name": "Horny Lanes", + "tier": "Underboss", + "lore": "Alignment: Horny. Underboss turf for hungry crews.", + "areaSqKm": 0.35 + }, + { + "id": "gutter-consig-council", + "name": "Consigliere Council", + "tier": "Consigliere", + "lore": "Consiglieres settle turf disputes.", + "areaSqKm": 0.3 + }, + { + "id": "gutter-capo-junction", + "name": "Caporegime Junction", + "tier": "Caporegime", + "lore": "Capos route runners through junctions.", + "areaSqKm": 0.3 + }, + { + "id": "chaotic-runs", + "name": "Chaotic Runs", + "tier": "Soldier", + "lore": "Alignment: Chaotic. Soldiers sprint these backroads.", + "areaSqKm": 0.3 + }, + { + "id": "greedy-corners", + "name": "Greedy Corners", + "tier": "Associate", + "lore": "Alignment: Greedy. Associates hustle the corners.", + "areaSqKm": 0.3 + } + ] + }, + { + "id": "hydrohackers-power-docks", + "city": "Neo Goblintown", + "name": "Hydrohackers Power Docks", + "areaSqKm": 2, + "faction": "Hydrohackers", + "tier": "Boss", + "control": "Hydrohackers", + "lore": "Dockside rigs powered by battery-siphons, kinetic pumps, and solar arrays.", + "sponsor": "Monster", + "comingSoon": false, + "subDistricts": [ + { + "id": "battery-docks", + "name": "Battery-Siphon Docks", + "tier": "Boss", + "lore": "Power Source: Battery-Siphon. Boss rigs dominate the docks.", + "areaSqKm": 0.45 + }, + { + "id": "kinetic-pumps", + "name": "Kinetic Pumps", + "tier": "Underboss", + "lore": "Power Source: Kinetic. Underboss lines on pump control.", + "areaSqKm": 0.35 + }, + { + "id": "hydro-consig-piers", + "name": "Consigliere Piers", + "tier": "Consigliere", + "lore": "Consiglieres arbitrate dock flows.", + "areaSqKm": 0.3 + }, + { + "id": "solar-arrays", + "name": "Solar Arrays", + "tier": "Caporegime", + "lore": "Power Source: Solar. Capos keep arrays humming.", + "areaSqKm": 0.3 + }, + { + "id": "hydro-soldier-bays", + "name": "Soldier Bays", + "tier": "Soldier", + "lore": "Soldiers secure pump bays.", + "areaSqKm": 0.3 + }, + { + "id": "guest-jetties", + "name": "Guest Jetties", + "tier": "Associate", + "lore": "Grid Clearance: Guest. Associates handle day-pass traffic at the docks.", + "areaSqKm": 0.3 + } + ] + }, + { + "id": "cloudjackers-clearance-row", + "city": "Neo Goblintown", + "name": "Cloudjackers Clearance Row", + "areaSqKm": 2, + "faction": "Cloudjackers", + "tier": "Boss", + "control": "Cloudjackers", + "lore": "Clearance-tier corridors where Cloudjackers staff Guest, User, and Power-User gates.", + "sponsor": "Coinbase", + "comingSoon": false, + "subDistricts": [ + { + "id": "guest-gate", + "name": "Guest Gate", + "tier": "Boss", + "lore": "Grid Clearance: Guest. Bosses watch the entry gate.", + "areaSqKm": 0.45 + }, + { + "id": "user-hub", + "name": "User Hub", + "tier": "Underboss", + "lore": "Grid Clearance: User. Underboss crews manage user flow.", + "areaSqKm": 0.35 + }, + { + "id": "cloud-consig-bridge", + "name": "Consigliere Bridge", + "tier": "Consigliere", + "lore": "Consiglieres monitor clearance policies.", + "areaSqKm": 0.3 + }, + { + "id": "poweruser-bridge", + "name": "Power-User Bridge", + "tier": "Caporegime", + "lore": "Grid Clearance: Power-User. Capos keep the bridge stable.", + "areaSqKm": 0.3 + }, + { + "id": "cloud-soldier-gates", + "name": "Soldier Gates", + "tier": "Soldier", + "lore": "Soldiers man the access gates.", + "areaSqKm": 0.3 + }, + { + "id": "guest-commons", + "name": "Guest Commons", + "tier": "Associate", + "lore": "Grid Clearance: Guest/User. Associates onboard newcomers.", + "areaSqKm": 0.3 + } + ] + }, + { + "id": "bassfiends-subsonic-depths", + "city": "Neo Goblintown", + "name": "Bassfiends Subsonic Depths", + "areaSqKm": 2, + "faction": "Bassfiends", + "tier": "Underboss", + "control": "Bassfiends", + "lore": "Deep channels where Bassfiends ride Subsonic and Infrared currents, pulling lightning when storms hit.", + "sponsor": "Adidas", + "comingSoon": false, + "subDistricts": [ + { + "id": "bass-boss-vault", + "name": "Boss Vault", + "tier": "Boss", + "lore": "Bassfiend bosses rumble in the vault.", + "areaSqKm": 0.45 + }, + { + "id": "subsonic-trenches", + "name": "Subsonic Trenches", + "tier": "Underboss", + "lore": "Signal Affinity: Subsonic. Underboss trenches rumble.", + "areaSqKm": 0.35 + }, + { + "id": "bass-consig-cavern", + "name": "Consigliere Cavern", + "tier": "Consigliere", + "lore": "Consiglieres manage subsonic treaties.", + "areaSqKm": 0.3 + }, + { + "id": "bass-capo-steps", + "name": "Caporegime Steps", + "tier": "Caporegime", + "lore": "Capos control ascent to the depths.", + "areaSqKm": 0.3 + }, + { + "id": "infrared-coves", + "name": "Infrared Coves", + "tier": "Soldier", + "lore": "Signal Affinity: Infrared. Soldiers guard hot caches.", + "areaSqKm": 0.3 + }, + { + "id": "lightning-rod-vault", + "name": "Lightning-Rod Vault", + "tier": "Associate", + "lore": "Power Source: Lightning-Rod. Associates harness storms.", + "areaSqKm": 0.3 + } + ] + }, + { + "id": "expansion-02", + "city": "City 02", + "name": "Coming Soon Zone 02", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-03", + "city": "City 03", + "name": "Coming Soon Zone 03", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-04", + "city": "City 04", + "name": "Coming Soon Zone 04", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-05", + "city": "City 05", + "name": "Coming Soon Zone 05", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-06", + "city": "City 06", + "name": "Coming Soon Zone 06", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-07", + "city": "City 07", + "name": "Coming Soon Zone 07", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-08", + "city": "City 08", + "name": "Coming Soon Zone 08", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-09", + "city": "City 09", + "name": "Coming Soon Zone 09", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + }, + { + "id": "expansion-10", + "city": "City 10", + "name": "Coming Soon Zone 10", + "areaSqKm": 3, + "faction": null, + "tier": null, + "control": null, + "lore": "Reserved for future Goblin Verse city drop.", + "sponsor": "Coming Soon", + "comingSoon": true, + "subDistricts": [] + } +] \ No newline at end of file diff --git a/frontend/data/factions.json b/frontend/data/factions.json new file mode 100644 index 000000000..7b7884355 --- /dev/null +++ b/frontend/data/factions.json @@ -0,0 +1,20 @@ +{ + "Corebreakers": { + "color": "#00ffff", + "accent": "#7af5ff", + "badge": "CB", + "lore": "Mesh hijackers with reactor-grade rigs." + }, + "Scrapshamans": { + "color": "#ff00ff", + "accent": "#ff9cff", + "badge": "SS", + "lore": "Salvage mages who bend junk into signal totems." + }, + "Gutterborn": { + "color": "#ff5500", + "accent": "#ffb380", + "badge": "GB", + "lore": "Rooftop runners and neon taggers." + } +} diff --git a/frontend/data/plots.json b/frontend/data/plots.json new file mode 100644 index 000000000..5b7be2790 --- /dev/null +++ b/frontend/data/plots.json @@ -0,0 +1,10 @@ +[ + { + "slotId": "root-clearance", + "owner": null, + "nickname": null, + "sharesOwned": 0, + "totalShares": 10, + "price": null + } +] diff --git a/frontend/js/cybercity.js b/frontend/js/cybercity.js new file mode 100644 index 000000000..20d5c176e --- /dev/null +++ b/frontend/js/cybercity.js @@ -0,0 +1,90 @@ +// Step 2: wallet + NFT verification wiring. Three.js scene lands in Steps 4/5 after district schema (Step 3). + +import { connectMetaMask, verifyOwnership, summarizeHoldings, WALLET_STATE } from "./wallet.js"; + +async function loadJson(path){ + const res = await fetch(path); + if (!res.ok) throw new Error(`Failed to load ${path}`); + return res.json(); +} + +async function bootstrapCybercity(){ + const statusEl = document.createElement("div"); + statusEl.className = "fixed bottom-3 right-3 text-xs bg-white/10 border border-white/15 rounded px-3 py-2 text-white/70"; + statusEl.textContent = "Cybercity scaffold ready. Wallet verification active. Scene waits for Steps 4-5."; + document.body.appendChild(statusEl); + + const datasets = {}; + try{ + const [districts, factions, contracts] = await Promise.all([ + loadJson("./frontend/data/districts.json"), + loadJson("./frontend/data/factions.json"), + loadJson("./frontend/data/contracts.json") + ]); + datasets.districts = districts; + datasets.factions = factions; + datasets.contracts = contracts; + console.info("[cybercity] configs loaded", { districtsCount: districts.length, factions: Object.keys(factions || {}), contracts: contracts?.length || 0 }); + statusEl.textContent = `Datasets loaded: ${districts.length} districts (${districts.filter(d => d.comingSoon).length} coming soon). Wallet verification active.`; + } catch (err){ + console.warn("[cybercity] config load warning", err); + statusEl.textContent = "Cybercity scaffold loaded, but config files are missing/invalid. See console."; + } + + bindWalletUI(datasets); +} + +function bindWalletUI(datasets){ + const connectBtn = document.getElementById("cc_connect"); + const verifyBtn = document.getElementById("cc_verify"); + const statusEl = document.getElementById("cc_status"); + const resultsEl = document.getElementById("cc_results"); + if (!connectBtn || !verifyBtn || !statusEl || !resultsEl){ + console.warn("[cybercity] wallet UI elements missing"); + return; + } + + connectBtn.addEventListener("click", async () => { + statusEl.textContent = "Connecting to MetaMask..."; + try{ + const { provider, signer, address } = await connectMetaMask(); + WALLET_STATE.provider = provider; + WALLET_STATE.signer = signer; + WALLET_STATE.address = address; + statusEl.textContent = `Connected: ${address}`; + } catch (err){ + statusEl.textContent = `Connect error: ${err?.message || err}`; + } + }); + + verifyBtn.addEventListener("click", async () => { + if (!WALLET_STATE.provider || !WALLET_STATE.address){ + statusEl.textContent = "Connect a wallet first."; + return; + } + const contracts = datasets.contracts || []; + if (!contracts.length){ + statusEl.textContent = "No contracts configured (frontend/data/contracts.json)."; + return; + } + statusEl.textContent = "Verifying NFT ownership..."; + try{ + const results = await verifyOwnership({ + provider: WALLET_STATE.provider, + address: WALLET_STATE.address, + contracts + }); + const summary = summarizeHoldings(results); + resultsEl.textContent = summary; + statusEl.textContent = "Verification complete."; + } catch (err){ + statusEl.textContent = `Verify error: ${err?.message || err}`; + } + }); +} + +if (document.readyState === "complete" || document.readyState === "interactive"){ + bootstrapCybercity(); +} else { + document.addEventListener("DOMContentLoaded", bootstrapCybercity); +} diff --git a/frontend/js/plots.js b/frontend/js/plots.js new file mode 100644 index 000000000..9a82c5faf --- /dev/null +++ b/frontend/js/plots.js @@ -0,0 +1,134 @@ +const factionColors = { + Corebreakers: "#00ffff", + Scrapshamans: "#ff00ff", + Gutterborns: "#ff5500", + Hydrohackers: "#00ff99", + Cloudjackers: "#66a3ff", + Bassfiends: "#ffcc33", + default: "#7dd3fc" +}; + +function maskWallet(w) { + if (!w) return "Available"; + return w.length > 10 ? `${w.slice(0, 6)}…${w.slice(-4)}` : w; +} + +async function loadJson(url) { + const res = await fetch(url); + if (!res.ok) throw new Error(`Failed to load ${url}`); + return res.json(); +} + +async function buildGrid() { + const grid = document.getElementById("plotsGrid"); + if (!grid) return; + grid.innerHTML = ""; + + let plots = []; + let slots = []; + try { + plots = await loadJson("./frontend/data/districts.json"); + slots = await loadJson("./frontend/data/plots.json"); + } catch (err) { + console.error("[plots] failed to load data", err); + grid.innerHTML = `
Failed to load plot data.
`; + return; + } + + const byFaction = new Map(); + plots.forEach(d => { + const key = d.faction || d.name || "Unknown"; + if (!byFaction.has(key)) byFaction.set(key, []); + (d.subDistricts || []).forEach(sd => byFaction.get(key).push({ district: d, sub: sd })); + }); + byFaction.forEach((items, faction) => { + const wrap = document.createElement("div"); + wrap.className = "rounded-xl border border-white/10 bg-black/40 p-4"; + const title = document.createElement("div"); + title.className = "text-lg font-semibold flex items-center gap-2"; + const color = factionColors[faction] || factionColors.default; + title.innerHTML = `${faction}`; + wrap.appendChild(title); + + const mapWrap = document.createElement("div"); + mapWrap.className = "mt-3 relative overflow-hidden rounded-lg border border-white/10 bg-gradient-to-br from-slate-900/60 via-slate-900/30 to-slate-900/60 p-3"; + mapWrap.style.minHeight = "260px"; + const cloud = document.createElement("div"); + cloud.className = "grid gap-2 sm:grid-cols-6 auto-rows-[60px]"; + + items.forEach(({ district, sub }, idx) => { + const slotId = sub.id; + const slot = slots.find(s => s.slotId === slotId) || {}; + const owned = Boolean(slot.owner); + const size = clampSize(sub.areaSqKm || 0.3); + const tile = document.createElement("div"); + const shapeClass = `plot-tile-shape-${(idx % 3) + 1}`; + tile.className = `p-2 plot-iso ${shapeClass} border shadow-[0_0_20px_-10px_rgba(0,255,255,0.4)] ${owned ? "border-white/15 bg-white/5" : "border-cyan-500/30 bg-cyan-500/5"} hover:border-cyan-400/60 transition`; + tile.style.gridColumn = `span ${size.w}`; + tile.style.gridRow = `span ${size.h}`; + tile.innerHTML = ` +
+
+ ${sub.name} + ${sub.tier} +
+
${district.name}
+
Area wt: ${sub.areaSqKm}
+
${owned ? maskWallet(slot.owner) : "Available"}
+
+ `; + tile.addEventListener("click", () => openModal({ district, sub, slot })); + cloud.appendChild(tile); + }); + mapWrap.appendChild(cloud); + wrap.appendChild(mapWrap); + grid.appendChild(wrap); + }); +} + +function clampSize(area) { + // Map area weights to grid spans (min 1x1, max 3x3) + if (area >= 0.45) return { w: 3, h: 2 }; + if (area >= 0.35) return { w: 2, h: 2 }; + if (area >= 0.3) return { w: 2, h: 1 }; + return { w: 1, h: 1 }; +} + +function openModal({ district, sub, slot }) { + const modal = document.getElementById("plotModal"); + if (!modal) return; + const owned = Boolean(slot.owner); + document.getElementById("modalTier").textContent = sub.tier; + document.getElementById("modalName").textContent = sub.name; + document.getElementById("modalDistrict").textContent = district.name; + document.getElementById("modalLore").textContent = sub.lore || district.lore || ""; + document.getElementById("modalFaction").textContent = district.faction || "Unknown"; + document.getElementById("modalArea").textContent = sub.areaSqKm || "n/a"; + document.getElementById("modalStatus").textContent = owned ? "Owned" : "Available"; + document.getElementById("modalOwner").textContent = owned ? maskWallet(slot.owner) : "—"; + + const offerBtn = document.getElementById("modalOffer"); + const wantedBtn = document.getElementById("modalWanted"); + const buyBtn = document.getElementById("modalBuy"); + offerBtn.classList.toggle("hidden", !owned); + wantedBtn.classList.toggle("hidden", !owned); + buyBtn.classList.toggle("hidden", owned); + document.getElementById("modalInfo").textContent = owned ? "You can post an offer or a wanted message to the owner." : "Place an offer to buy this plot."; + + modal.classList.remove("hidden"); +} + +function closeModal() { + const modal = document.getElementById("plotModal"); + if (modal) modal.classList.add("hidden"); +} + +document.addEventListener("DOMContentLoaded", () => { + buildGrid(); + const modalClose = document.getElementById("modalClose"); + if (modalClose) modalClose.addEventListener("click", closeModal); + document.getElementById("modalOffer")?.addEventListener("click", () => alert("Offer flow stub")); + document.getElementById("modalWanted")?.addEventListener("click", () => alert("Wanted flow stub")); + document.getElementById("modalBuy")?.addEventListener("click", () => alert("Buy/Offer flow stub")); + document.addEventListener("keydown", (e) => { if (e.key === "Escape") closeModal(); }); +}); diff --git a/frontend/js/wallet.js b/frontend/js/wallet.js new file mode 100644 index 000000000..10ce98323 --- /dev/null +++ b/frontend/js/wallet.js @@ -0,0 +1,174 @@ +// Step 2: Wallet + NFT verification scaffold. +// Uses ethers via ESM CDN; MetaMask only (WalletConnect placeholder). + +import { ethers, BrowserProvider, Contract } from "https://esm.sh/ethers@6.13.0"; + +const ABI_721 = [ + "function balanceOf(address) view returns (uint256)", + "function ownerOf(uint256) view returns (address)", + "function tokenOfOwnerByIndex(address,uint256) view returns (uint256)", + "function tokenURI(uint256) view returns (string)" +]; +const ABI_1155 = [ + "function balanceOf(address,uint256) view returns (uint256)", + "function uri(uint256) view returns (string)" +]; + +function ipfsToHttp(uri){ + if (!uri) return null; + return uri.startsWith("ipfs://") ? uri.replace("ipfs://", "https://ipfs.io/ipfs/") : uri; +} + +async function fetchJsonMaybe(uri){ + if (!uri) return null; + const url = ipfsToHttp(uri); + const res = await fetch(url); + if (!res.ok) throw new Error(`Metadata fetch failed for ${url}`); + return res.json(); +} + +export async function connectMetaMask(){ + if (!window.ethereum) throw new Error("MetaMask not found in this browser"); + const provider = new BrowserProvider(window.ethereum); + const [addr] = await provider.send("eth_requestAccounts", []); + const address = ethers.getAddress(addr); + const signer = await provider.getSigner(); + return { provider, signer, address }; +} + +export async function verifyOwnership({ provider, address, contracts }){ + if (!provider) throw new Error("Provider missing"); + const out = []; + for (const cfg of contracts || []){ + const standard = (cfg.standard || "").toUpperCase(); + const addr = cfg.address; + if (!addr || /your/i.test(addr) || /^0x0{40}$/i.test(addr)){ + out.push({ contract: addr || "unset", standard, status: "skipped", reason: "placeholder address" }); + continue; + } + try{ + if (standard === "ERC721"){ + out.push(await verifyErc721(provider, address, cfg)); + } else if (standard === "ERC1155"){ + out.push(await verifyErc1155(provider, address, cfg)); + } else { + out.push({ contract: addr, standard, status: "skipped", reason: "unsupported standard" }); + } + } catch (err){ + out.push({ contract: addr, standard, status: "error", error: err?.message || String(err) }); + } + } + return out; +} + +async function verifyErc721(provider, owner, cfg){ + const contract = new Contract(cfg.address, ABI_721, provider); + let ownedIds = []; + if (Array.isArray(cfg.tokenIds) && cfg.tokenIds.length){ + for (const id of cfg.tokenIds){ + try{ + const o = await contract.ownerOf(id); + if (ethers.getAddress(o) === owner) ownedIds.push(String(id)); + } catch (_err){ + // ignore missing token + } + } + } else { + try{ + const bal = Number(await contract.balanceOf(owner)); + if (Number.isFinite(bal) && bal > 0){ + // attempt enumeration; if fails, we only report count + for (let i = 0; i < Math.min(bal, 10); i++){ + try{ + const id = await contract.tokenOfOwnerByIndex(owner, i); + ownedIds.push(String(id)); + } catch (_err){ + break; + } + } + } + } catch (err){ + return { contract: cfg.address, standard: "ERC721", status: "error", error: err?.message || String(err) }; + } + } + const items = []; + for (const id of ownedIds){ + try{ + const uri = await contract.tokenURI(id); + const meta = await fetchJsonMaybe(uri); + items.push({ tokenId: id, uri, metadata: meta }); + } catch (_err){ + items.push({ tokenId: id, uri: null, metadata: null }); + } + } + return { + contract: cfg.address, + name: cfg.name || "ERC721", + standard: "ERC721", + status: "ok", + ownedIds, + items + }; +} + +async function verifyErc1155(provider, owner, cfg){ + const contract = new Contract(cfg.address, ABI_1155, provider); + const tokenIds = Array.isArray(cfg.tokenIds) ? cfg.tokenIds : []; + const items = []; + for (const id of tokenIds){ + try{ + const bal = await contract.balanceOf(owner, id); + if (bal && bal > 0n){ + let meta = null; + try{ + const uri = await contract.uri(id); + meta = await fetchJsonMaybe(uri); + } catch (_err){} + items.push({ tokenId: String(id), balance: bal.toString(), metadata: meta }); + } + } catch (_err){ + // ignore missing + } + } + return { + contract: cfg.address, + name: cfg.name || "ERC1155", + standard: "ERC1155", + status: "ok", + items + }; +} + +export function summarizeHoldings(results){ + const lines = []; + results.forEach(r => { + if (r.status === "ok"){ + const count = r.items?.length || 0; + lines.push(`${r.name || r.contract} (${r.standard}): ${count} item${count === 1 ? "" : "s"}`); + r.items?.forEach(item => { + const attrs = extractTraits(item.metadata); + lines.push(`- #${item.tokenId}${attrs ? ` | ${attrs}` : ""}`); + }); + } else if (r.status === "skipped"){ + lines.push(`${r.contract || "contract"}: skipped (${r.reason})`); + } else if (r.status === "error"){ + lines.push(`${r.contract || "contract"}: error ${r.error}`); + } + }); + return lines.join("\n") || "No holdings detected."; +} + +export function extractTraits(meta){ + if (!meta || !meta.attributes) return ""; + const pairs = []; + for (const attr of meta.attributes){ + if (attr?.trait_type && attr?.value) pairs.push(`${attr.trait_type}: ${attr.value}`); + } + return pairs.join(" | "); +} + +export const WALLET_STATE = { + provider: null, + address: null, + signer: null +}; diff --git a/index.html b/index.html new file mode 100644 index 000000000..dada09eca --- /dev/null +++ b/index.html @@ -0,0 +1,2814 @@ + + + + + + + + THE DIKPIK BAZAAR + + + + + + + + +
+
+
+
+ +
+
+
+

+ THE DIKPIK BAZAAR +

+
+ Plots + Cybercity + + + + + + +
+
+

Family Tree • Auctions • Wanted

+
+
+ + +
+
+
+ + + + + + +
+ +
+
+
Filter by Traits
+
+ + +
+
+
+
+
+
+ +
+
+ + +
+ + +
+
+
+
+

+ DIKPIK Family Tree +

+

Loading...

+
+
+ The Family Tree auto-assigns each NFT to a classic mafia hierarchy (Boss -> Underboss -> Consigliere -> Caporegime -> Soldier -> Associate) + per faction. Choose a faction above to see its crew arranged in a centered pyramid by rank. Rarity and trait percentages are shown + on each token; click any token for the full trait breakdown in neon boxes with collection-wide rarity % (to 2 decimals). +
+
+
+
+ +
+
+

+ All Tokens (Filtered View) +

+
+ + + Page 1 / 1 + + +
+ Per page + +
+
+
+ +
+ + +
+
+
+ + + Page 1 / 1 + + +
+
+
+
+ + + +
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mock/book-labour/index.html b/mock/book-labour/index.html new file mode 100644 index 000000000..f3011a806 --- /dev/null +++ b/mock/book-labour/index.html @@ -0,0 +1,180 @@ + + + + + + Book labour now – Mock + + + + + +
+
+

Book labour now

+

Sydney / NSW labour hire request mock. All data stays in the browser. Use the JSON preview or CSV export to inspect payloads.

+ +
+ + +
+
+ +
+
+

Company details

+
+ + +
+ +
+ + + + +
+ +
+ + + + +
+ +
+ + + +
+
+ +
+

Registered address (company registered address)

+
+ + + +
+
+ +
+

Site address (Location of site that workers need to go to)

+ +
+ +
+
+
+
+

Selected address

+

None

+

Lat/Lng: -

+

Open in Google Maps

+ +
+
+ + + + +
+ +
+
+
+

Workers needed

+

(If need different types of workers please click Next role needed to add more)

+
+ +
+
+
+ +
+

Notes (optional)

+ +
+ +
+

Attachments (optional)

+

Upload scope, induction, or any other docs you want to send us (PDFs preferred, max ~10MB total).

+ +

If files are bigger than 10MB, paste a link instead:

+ +
    +
    + +
    + +
    +
    +
    + + + + + + diff --git a/mock/book-labour/script.js b/mock/book-labour/script.js new file mode 100644 index 000000000..f7eaac78d --- /dev/null +++ b/mock/book-labour/script.js @@ -0,0 +1,467 @@ +const datasets = { + companyStructures: ["Pty Ltd", "Ltd", "Pty", "NL"], + titles: ["Mr", "Mrs", "Miss", "Ms", "Mx"], + states: ["ACT", "NSW", "NT", "QLD", "SA", "TAS", "VIC", "WA"], + roles: [ + "General Labourer", "Skilled Labourer", "Site Cleaner", "Traffic Controller", + "Carpenter", "Formworker", "Concreter", "Steel Fixer", "Scaffolder", + "Painter", "Plumber", "Electrician", "HVAC Tech", + "Operator—Forklift", "Operator—EWP", "Operator—Excavator", "Operator—Skid Steer", + "Operator—Crane", "Leading Hand", "Site Supervisor" + ], + tickets: [ + "White Card (WHS)", "First Aid (HLTAID011+)", "Working at Heights", "Confined Space", + "Asbestos Awareness", "Traffic Control (TC/TMI)", "Forklift (LF)", "EWP (WP)", + "Scaffolding (SB/SA/SI)", "Rigging (RB/RI)", "Dogging (DG)", "Crane Operation (CO)", + "Excavator (LE)", "Skid Steer (LS)", "Telehandler (LT)", "Electrical Licence", + "HVAC Cert", "Fire Safety Induction", "Manual Handling", "Silica Awareness" + ], + otOptions: [ + "No OT","Weekday OT (1.5x)","Weekday OT (2.0x)","Saturday OT (1.5x)","Saturday OT (2.0x)", + "Sunday OT (2.0x)","Public Holiday (2.5x)","Night Shift Loading","Call-out","Standby" + ], + experience: ["Newly ticketed","1","2","3","4","5","6","7","8","9","10+"], + durationUnits: ["days","weeks"] +}; + +let map; +let marker; +let autocomplete; +let selectedPlace = null; +let mapReady = false; + +const form = document.getElementById("booking-form"); +const mapsWarning = document.getElementById("maps-warning"); +const modal = document.getElementById("modal"); +const jsonOutput = document.getElementById("json-output"); +const closeModalBtn = document.getElementById("close-modal"); +const openJsonBtn = document.getElementById("open-json"); +const exportCsvBtn = document.getElementById("export-csv"); +const attachmentsInput = document.getElementById("attachments"); +const attachmentsList = document.getElementById("attachments-list"); + +const groupsEl = document.getElementById("groups"); +const addGroupBtn = document.getElementById("add-group"); + +let groups = []; + +function populateSelect(name, values) { + const el = form.querySelector(`[name='${name}']`); + values.forEach((v) => { + const opt = document.createElement("option"); + opt.value = v; + opt.textContent = v; + el.appendChild(opt); + }); +} + +populateSelect("companyStructure", datasets.companyStructures); +populateSelect("contactTitle", datasets.titles); +populateSelect("registeredState", datasets.states); + +function defaultGroup() { + return { + count: 1, + role: "", + tickets: [], + experience: "", + startDate: "", + startTime: "", + hoursPerDay: "", + expectedDurationValue: "", + expectedDurationUnit: "", + otOptions: [], + nextRole: false + }; +} + +function renderGroups() { + groupsEl.innerHTML = ""; + groups.forEach((group, idx) => { + const wrap = document.createElement("div"); + wrap.className = "group"; + wrap.innerHTML = ` +
    +

    Role group ${idx + 1}

    + ${groups.length > 1 ? `` : ""} +
    +
    + + + + +
    +
    + + + + + +
    +
    + + +
    + `; + groupsEl.appendChild(wrap); + }); +} + +function syncGroupValue(idx, name, value) { + groups[idx][name] = value; + if (name === "nextRole" && value) { + groups[idx][name] = false; + groups.push(defaultGroup()); + renderGroups(); + attachGroupListeners(); + } +} + +function attachGroupListeners() { + groupsEl.querySelectorAll("input, select").forEach((el) => { + el.onchange = (e) => { + const target = e.target; + const idx = Number(target.dataset.idx); + if (target.name === "tickets" || target.name === "otOptions") { + const vals = Array.from(target.selectedOptions).map((o) => o.value); + syncGroupValue(idx, target.name, vals); + } else if (target.name === "nextRole") { + syncGroupValue(idx, target.name, target.checked); + } else { + syncGroupValue(idx, target.name, target.value); + } + }; + }); + groupsEl.querySelectorAll("[data-remove]").forEach((btn) => { + btn.onclick = () => { + const idx = Number(btn.dataset.remove); + groups.splice(idx, 1); + if (groups.length === 0) groups.push(defaultGroup()); + renderGroups(); + attachGroupListeners(); + }; + }); +} + +addGroupBtn.addEventListener("click", () => { + groups.push(defaultGroup()); + renderGroups(); + attachGroupListeners(); +}); + +// initial group +groups = [defaultGroup()]; +renderGroups(); +attachGroupListeners(); + +function showError(name, msg) { + const el = document.querySelector(`[data-error-for='${name}']`); + if (el) el.textContent = msg || ""; +} + +function clearErrors() { + document.querySelectorAll(".error").forEach((el) => (el.textContent = "")); +} + +function validate() { + clearErrors(); + const data = new FormData(form); + let ok = true; + const required = [ + "companyName","companyStructure","contactTitle","contactName","contactJobTitle", + "contactMobile","contactEmail","acn","abn","registeredStreet","registeredState","registeredPostcode", + "siteSearch" + ]; + required.forEach((field) => { + if (!data.get(field)) { + ok = false; + showError(field, "Required"); + } + }); + const acn = (data.get("acn") || "").toString().replace(/\D/g, ""); + const abn = (data.get("abn") || "").toString().replace(/\D/g, ""); + if (acn.length !== 9) { ok = false; showError("acn", "Must be 9 digits"); } + if (abn.length !== 11) { ok = false; showError("abn", "Must be 11 digits"); } + if (acn && abn && abn.endsWith(acn)) document.getElementById("abn-hint").hidden = false; + else document.getElementById("abn-hint").hidden = true; + + const email = data.get("contactEmail"); + if (email && !/^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(email)) { ok = false; showError("contactEmail", "Invalid email"); } + const website = data.get("website"); + if (website && !/^https?:\\/\\//i.test(website)) { ok = false; showError("website", "Must start with http/https"); } + const pc = (data.get("registeredPostcode") || "").toString().replace(/\D/g, ""); + if (pc.length !== 4) { ok = false; showError("registeredPostcode", "4 digits"); } + + groups.forEach((g, idx) => { + const prefix = (field) => `${field}-${idx}`; + if (!g.count || Number(g.count) < 1) { ok = false; showError(prefix("count"), "Required"); } + if (!g.role) { ok = false; showError(prefix("role"), "Required"); } + if (!g.experience) { ok = false; showError(prefix("experience"), "Required"); } + if (!g.startDate) { ok = false; showError(prefix("startDate"), "Required"); } + if (!g.startTime) { ok = false; showError(prefix("startTime"), "Required"); } + if (!g.hoursPerDay) { ok = false; showError(prefix("hoursPerDay"), "Required"); } + if (!g.expectedDurationValue) { ok = false; showError(prefix("expectedDurationValue"), "Required"); } + if (!g.expectedDurationUnit) { ok = false; showError(prefix("expectedDurationUnit"), "Required"); } + }); + + return ok; +} + +function gatherPayload() { + const data = new FormData(form); + return { + company: { + name: data.get("companyName") || "", + structure: data.get("companyStructure") || "", + contactTitle: data.get("contactTitle") || "", + contactName: data.get("contactName") || "", + contactJobTitle: data.get("contactJobTitle") || "", + contactEmail: data.get("contactEmail") || "", + contactMobile: data.get("contactMobile") || "", + contactLandline: data.get("contactLandline") || "", + preferredContactTime: data.get("preferredContactTime") || "", + contactOnBehalf: data.get("contactOnBehalf") === "on", + acn: (data.get("acn") || "").toString().replace(/\D/g, ""), + abn: (data.get("abn") || "").toString().replace(/\D/g, ""), + website: data.get("website") || "" + }, + registeredAddress: { + street: data.get("registeredStreet") || "", + state: data.get("registeredState") || "", + postcode: (data.get("registeredPostcode") || "").toString().replace(/\D/g, "") + }, + siteAddress: { + siteName: data.get("siteName") || "", + formattedAddress: data.get("siteFormattedAddress") || data.get("siteSearch") || "", + lat: data.get("siteLat") ? Number(data.get("siteLat")) : null, + lng: data.get("siteLng") ? Number(data.get("siteLng")) : null, + placeId: data.get("sitePlaceId") || null, + mapUrl: document.getElementById("map-link").href || "" + }, + roles: groups.map((g) => ({ + count: Number(g.count) || 0, + role: g.role, + tickets: g.tickets, + experience: g.experience, + startDate: g.startDate, + startTime: g.startTime, + hoursPerDay: Number(g.hoursPerDay) || 0, + expectedDurationValue: Number(g.expectedDurationValue) || 0, + expectedDurationUnit: g.expectedDurationUnit, + otOptions: g.otOptions + })), + notes: data.get("notes") || "", + attachments: window.__attachments || [], + attachmentsLink: data.get("attachmentsLink") || "", + submittedAt: new Date().toISOString() + }; +} + +function openModal(json) { + jsonOutput.textContent = JSON.stringify(json, null, 2); + modal.hidden = false; +} +function closeModal() { + modal.hidden = true; +} +closeModalBtn.onclick = closeModal; +modal.addEventListener("click", (e) => { + if (e.target === modal) closeModal(); +}); +window.addEventListener("keydown", (e) => { + if (e.key === "Escape" && !modal.hidden) closeModal(); +}); + +openJsonBtn.addEventListener("click", () => { + if (!validate()) { + alert("Fix errors before previewing JSON."); + return; + } + const payload = gatherPayload(); + openModal(payload); +}); + +exportCsvBtn.addEventListener("click", () => { + if (!validate()) { + alert("Fix errors before exporting CSV."); + return; + } + const payload = gatherPayload(); + const rows = []; + payload.roles.forEach((role, idx) => { + rows.push({ + "Company Structure": payload.company.structure, + "ACN": payload.company.acn, + "ABN": payload.company.abn, + "Website": payload.company.website, + "Registered Street": payload.registeredAddress.street, + "Registered State": payload.registeredAddress.state, + "Registered Postcode": payload.registeredAddress.postcode, + "Site Name": payload.siteAddress.siteName, + "Site Address": payload.siteAddress.formattedAddress, + "Site Map Link": payload.siteAddress.mapUrl, + "Role Group #": idx + 1, + "Number of Workers": role.count, + "Role": role.role, + "Tickets": role.tickets.join("; "), + "Experience": role.experience, + "Start Date": role.startDate, + "Start Time": role.startTime, + "Hours/Day": role.hoursPerDay, + "Expected Duration": `${role.expectedDurationValue} ${role.expectedDurationUnit}`, + "OT Options": role.otOptions.join("; "), + "Notes": payload.notes, + "Attachments Link": payload.attachmentsLink, + "Submitted At": payload.submittedAt + }); + }); + const header = Object.keys(rows[0] || {}); + const csv = [header.join(",")].concat( + rows.map((r) => + header + .map((h) => `"${String(r[h] ?? "").replace(/"/g, '""')}"`) + .join(","), + ), + ).join("\\n"); + const blob = new Blob([csv], { type: "text/csv" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "booking.csv"; + a.click(); + URL.revokeObjectURL(url); +}); + +form.addEventListener("submit", (e) => { + e.preventDefault(); + if (!validate()) return; + const payload = gatherPayload(); + openModal(payload); +}); + +attachmentsInput?.addEventListener("change", async (e) => { + const files = e.target.files; + if (!files) return; + let total = 0; + const arr = []; + for (const file of Array.from(files)) { + total += file.size; + if (total > 10 * 1024 * 1024) { + alert("Attachments exceed 10MB total. Please reduce file sizes."); + return; + } + const base64 = await file.arrayBuffer().then((buf) => btoa(String.fromCharCode(...new Uint8Array(buf)))); + arr.push({ name: file.name, type: file.type, size: file.size, content: base64 }); + } + window.__attachments = arr; + attachmentsList.innerHTML = arr.map((f) => `
  • ${f.name} (${Math.round(f.size / 1024)} KB)
  • `).join(""); +}); + +// MAPS +window.initMap = function initMap() { + try { + const mapEl = document.getElementById("map"); + if (!mapEl) return; + map = new google.maps.Map(mapEl, { + center: { lat: -33.8688, lng: 151.2093 }, + zoom: 12, + streetViewControl: false, + mapTypeControl: false, + }); + marker = new google.maps.Marker({ map, draggable: true, position: map.getCenter() }); + google.maps.event.addListener(marker, "dragend", () => { + const pos = marker.getPosition(); + updateSelected(null, pos); + }); + + const input = document.getElementById("site-search"); + autocomplete = new google.maps.places.Autocomplete(input, { fields: ["formatted_address", "geometry", "place_id"] }); + autocomplete.addListener("place_changed", () => { + const place = autocomplete.getPlace(); + if (!place.geometry) return; + updateSelected(place, place.geometry.location); + }); + + document.getElementById("recenter").onclick = () => { + if (selectedPlace && selectedPlace.location) { + map.panTo(selectedPlace.location); + marker.setPosition(selectedPlace.location); + } + }; + mapReady = true; + } catch (e) { + mapsWarning.hidden = false; + mapReady = false; + } +}; + +function updateSelected(place, latLng) { + const formatted = place?.formatted_address || ""; + const loc = latLng || place?.geometry?.location; + const lat = loc?.lat?.() ?? loc?.lat; + const lng = loc?.lng?.() ?? loc?.lng; + selectedPlace = { formatted, placeId: place?.place_id || null, location: loc }; + document.querySelector("[name='siteFormattedAddress']").value = formatted; + document.querySelector("[name='siteLat']").value = lat ?? ""; + document.querySelector("[name='siteLng']").value = lng ?? ""; + document.querySelector("[name='sitePlaceId']").value = place?.place_id || ""; + document.getElementById("selected-address").textContent = formatted || "None"; + document.getElementById("coords").textContent = lat ? `Lat/Lng: ${lat.toFixed(5)}, ${lng.toFixed(5)}` : "Lat/Lng: -"; + const link = lat ? `https://www.google.com/maps/search/?api=1&query=${lat},${lng}${place?.place_id ? `&query_place_id=${place.place_id}` : ""}` : "#"; + document.getElementById("map-link").href = link; + if (mapReady && lat && lng) { + map.panTo({ lat, lng }); + marker.setPosition({ lat, lng }); + } +} + +// Fallback if Maps fails to load +setTimeout(() => { + if (!mapReady && !window.google) { + mapsWarning.hidden = false; + } +}, 2000); diff --git a/mock/book-labour/styles.css b/mock/book-labour/styles.css new file mode 100644 index 000000000..aa1ec0a0e --- /dev/null +++ b/mock/book-labour/styles.css @@ -0,0 +1,211 @@ +:root { + --bg: #f5f7fa; + --card: #fff; + --border: #e5e7eb; + --text: #212529; + --muted: #6b7280; + --primary: #0b1f3b; + --accent: #ff7a1a; + --shadow: 0 8px 24px rgba(15, 23, 42, 0.08); +} + +* { box-sizing: border-box; } +body { + margin: 0; + font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + background: var(--bg); + color: var(--text); +} +a { color: var(--primary); } + +.page { + max-width: 1040px; + margin: 0 auto 48px; + padding: 32px 16px 48px; +} +.top h1 { margin: 0 0 4px; font-size: 28px; } +.lede { margin: 0 0 12px; color: var(--muted); } +.actions { display: flex; gap: 8px; margin: 12px 0 16px; } +.actions button { + background: var(--primary); + color: #fff; + border: none; + border-radius: 20px; + padding: 10px 14px; + cursor: pointer; + font-weight: 600; +} +.actions button:nth-child(2) { background: var(--accent); } + +.warning { + background: #fff3cd; + border: 1px solid #facc15; + color: #8a6d3b; + padding: 10px 12px; + border-radius: 8px; + margin: 0 0 10px; +} + +form { display: flex; flex-direction: column; gap: 18px; } +.section { + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + box-shadow: var(--shadow); +} +.section h2 { margin: 0 0 12px; font-size: 18px; } +.grid { display: grid; gap: 12px; } +.grid.two { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); } +.grid.three { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } +.grid.four { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); } + +label { display: flex; flex-direction: column; gap: 6px; font-weight: 600; } +label.inline { flex-direction: row; align-items: center; gap: 8px; font-weight: 500; } +input, select, textarea { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 8px; + font-size: 14px; +} +textarea { resize: vertical; } + +.error { + color: #b91c1c; + font-size: 12px; + min-height: 16px; +} +.hint { color: var(--muted); font-size: 12px; margin: 4px 0 0; } + +.map-row { display: grid; grid-template-columns: 2fr 1fr; gap: 12px; margin-top: 10px; } +#map { + min-height: 220px; + background: #eceff4; + border: 1px dashed var(--border); + border-radius: 10px; +} +.map-meta { + padding: 10px; + border: 1px solid var(--border); + border-radius: 10px; + background: #f9fafb; + font-size: 14px; +} +.map-meta button { + margin-top: 8px; + padding: 8px 10px; + border: 1px solid var(--primary); + background: #fff; + color: var(--primary); + border-radius: 8px; + cursor: pointer; +} + +#workers-section.highlight { + border: 2px solid var(--accent); + background: #fff7f1; +} +.section-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} +.section-head button { + border: 1px solid var(--primary); + background: #fff; + color: var(--primary); + border-radius: 8px; + padding: 8px 12px; + cursor: pointer; +} + +.group { + border: 1px solid var(--border); + border-radius: 12px; + padding: 12px; + background: #fff; + margin-top: 10px; + box-shadow: inset 0 1px 0 rgba(0,0,0,0.02); +} +.group-head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} +.group-head h3 { margin: 0; font-size: 16px; } +.group-head button { + border: 1px solid #d1d5db; + background: #fff; + border-radius: 8px; + padding: 6px 10px; + cursor: pointer; +} + +.inline-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 10px; +} + +.actions button#submit { + background: var(--primary); + color: #fff; + border-radius: 10px; + padding: 12px 16px; + border: none; + cursor: pointer; + min-width: 140px; +} + +.modal { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.55); + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + z-index: 20; +} +.modal[hidden] { display: none; } +.modal-content { + background: #111827; + color: #e5e7eb; + padding: 16px; + border-radius: 12px; + width: min(880px, 100%); + max-height: 80vh; + overflow: auto; + box-shadow: var(--shadow); +} +.modal-head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} +.modal-head button { + border: none; + background: #f87171; + color: #fff; + border-radius: 6px; + padding: 6px 10px; + cursor: pointer; +} +pre { + background: #0b1220; + padding: 12px; + border-radius: 8px; + overflow: auto; + font-size: 13px; +} + +.attach-list { margin: 8px 0 0; padding-left: 18px; color: var(--muted); } + +@media (max-width: 720px) { + .map-row { grid-template-columns: 1fr; } + .section { padding: 14px; } +} diff --git a/monetize.gsheet b/monetize.gsheet new file mode 100644 index 000000000..d5bf6d1ff --- /dev/null +++ b/monetize.gsheet @@ -0,0 +1 @@ +{"":"WARNING! DO NOT EDIT THIS FILE! ANY CHANGES MADE WILL BE LOST!","doc_id":"1JzlnDYc-58czHR8NyL-Mg5YlBPfSA90hzVGIPzE1Ae8","resource_key":"","email":"info@dikpik.io"} diff --git a/notes_legacyHomeHttpFunctions.txt b/notes_legacyHomeHttpFunctions.txt new file mode 100644 index 000000000..5749258e4 --- /dev/null +++ b/notes_legacyHomeHttpFunctions.txt @@ -0,0 +1,184 @@ +// Legacy HOME page HTTP functions preserved for reference. +// This file is NOT wired into the current build; keep here for future review. + +import { ok, created, badRequest, unauthorized, forbidden, notFound, serverError } from 'wix-http-functions'; +import wixData from 'wix-data'; +import { currentMember } from 'wix-members-backend'; +import { v4 as uuidv4 } from 'uuid'; + +const REQUIRED_DEPOSIT_ETH = 0.0078; +const COLLECTION_LISTINGS = 'Listings'; +const COLLECTION_WANTED = 'Wanted'; +const COLLECTION_RELAY = 'RelaySettings'; + +async function getMember(ctx) { + try { + const member = await currentMember.getMember({ fields: ['contactId', 'profile.nickname'] }); + if (!member) return null; + return { id: member._id, nickname: member.profile.nickname || 'Member' }; + } catch (err) { + console.error('Member lookup failed', err); + return null; + } +} + +export async function get_listings(request) { + try { + const member = await getMember(request); + const isOwnerQuery = request.query.owner === 'me' && member; + const filter = isOwnerQuery + ? wixData.query(COLLECTION_LISTINGS).eq('ownerId', member.id) + : wixData.query(COLLECTION_LISTINGS).ne('status', 'draft'); + const result = await filter.find(); + return ok({ items: result.items }); + } catch (err) { + console.error('GET listings failed', err); + return serverError({ message: 'Failed to load listings' }); + } +} + +export async function post_listings(request) { + const member = await getMember(request); + if (!member) return unauthorized({ message: 'Login required' }); + + let payload; + try { + payload = await request.body.json(); + } catch (_) { + return badRequest({ message: 'Invalid JSON' }); + } + + if (!payload.tokenId || !payload.minPrice) return badRequest({ message: 'tokenId and minPrice required' }); + if (payload.depositEth && payload.depositEth < REQUIRED_DEPOSIT_ETH) { + return badRequest({ message: `depositEth must be ≥ ${REQUIRED_DEPOSIT_ETH}` }); + } + + const now = new Date(); + const doc = { + _id: uuidv4(), + tokenId: Number(payload.tokenId), + title: payload.title?.trim() || `Auction #${payload.tokenId}`, + minPrice: Number(payload.minPrice), + buyNowPrice: payload.buyNowPrice ? Number(payload.buyNowPrice) : null, + depositEth: payload.depositEth ? Number(payload.depositEth) : REQUIRED_DEPOSIT_ETH, + currency: payload.currency || 'ETH', + expiresAt: payload.expiresAt ? new Date(payload.expiresAt) : null, + status: 'live', + ownerId: member.id, + ownerName: member.nickname, + relayChain: payload.relayChain || '', + relayContract: payload.relayContract || '', + openseaUrl: payload.openseaUrl || '', + maskDescription: payload.maskDescription || '', + createdAt: now, + updatedAt: now + }; + + try { + await wixData.insert(COLLECTION_LISTINGS, doc); + return created(doc); + } catch (err) { + console.error('Create listing failed', err); + return serverError({ message: 'Unable to create listing' }); + } +} + +export async function patch_listings(request) { + const member = await getMember(request); + if (!member) return unauthorized({ message: 'Login required' }); + + const id = request.path[0]; + if (!id) return badRequest({ message: 'Missing id' }); + + let payload; + try { + payload = await request.body.json(); + } catch (_) { + return badRequest({ message: 'Invalid JSON' }); + } + + try { + const existing = await wixData.get(COLLECTION_LISTINGS, id); + if (!existing) return notFound({ message: 'Listing not found' }); + const isOwner = existing.ownerId === member.id; + const isModerator = member.roles?.includes('Moderator'); + if (!isOwner && !isModerator) return forbidden({ message: 'Not allowed' }); + + Object.assign(existing, payload, { updatedAt: new Date() }); + + if (existing.expiresAt && new Date(existing.expiresAt) < new Date()) { + existing.status = 'expired'; + } + + const updated = await wixData.update(COLLECTION_LISTINGS, existing); + return ok(updated); + } catch (err) { + console.error('Update listing failed', err); + return serverError({ message: 'Unable to update listing' }); + } +} + +export async function delete_listings(request) { + const member = await getMember(request); + if (!member) return unauthorized({ message: 'Login required' }); + + const id = request.path[0]; + if (!id) return badRequest({ message: 'Missing id' }); + + try { + const existing = await wixData.get(COLLECTION_LISTINGS, id); + if (!existing) return notFound({ message: 'Listing not found' }); + const isOwner = existing.ownerId === member.id; + const isModerator = member.roles?.includes('Moderator'); + if (!isOwner && !isModerator) return forbidden({ message: 'Not allowed' }); + + await wixData.remove(COLLECTION_LISTINGS, id); + return ok({ removed: id }); + } catch (err) { + console.error('Delete listing failed', err); + return serverError({ message: 'Unable to delete listing' }); + } +} + +// Mirror the same CRUD structure for Wanted posts (stubbed for reference). +export async function get_wanted(request) { + return serverError({ message: 'Not implemented in legacy snippet.' }); +} +export async function post_wanted(request) { + return serverError({ message: 'Not implemented in legacy snippet.' }); +} +export async function patch_wanted(request) { + return serverError({ message: 'Not implemented in legacy snippet.' }); +} +export async function delete_wanted(request) { + return serverError({ message: 'Not implemented in legacy snippet.' }); +} + +// Relay settings handlers +export async function get_relay(request) { + const member = await getMember(request); + if (!member) return unauthorized({ message: 'Login required' }); + const result = await wixData.query(COLLECTION_RELAY).eq('memberId', member.id).find(); + return ok(result.items[0] || null); +} + +export async function post_relay(request) { + const member = await getMember(request); + if (!member) return unauthorized({ message: 'Login required' }); + const body = await request.body.json(); + const doc = { + memberId: member.id, + chain: body.chain || '', + contract: body.contract || '', + lastUpdated: new Date() + }; + const existing = await wixData.query(COLLECTION_RELAY).eq('memberId', member.id).find(); + if (existing.items.length) { + doc._id = existing.items[0]._id; + await wixData.update(COLLECTION_RELAY, doc); + } else { + doc._id = uuidv4(); + await wixData.insert(COLLECTION_RELAY, doc); + } + return ok(doc); +} diff --git a/package-lock.json b/package-lock.json index 8cd526b9b..a1de7846b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,474 +1,6771 @@ { "name": "hashlips_art_engine", "version": "1.1.1", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@mapbox/node-pre-gyp": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.6.tgz", - "integrity": "sha512-qK1ECws8UxuPqOA8F5LFD90vyVU33W7N3hGfgsOVfrJaRVc8McC3JClTDHpeSbL9CBrOHly/4GsNPAvIgNZE+g==", - "requires": { - "detect-libc": "^1.0.3", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.5", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" + "packages": { + "": { + "name": "hashlips_art_engine", + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "canvas": "^2.8.0", + "gif-encoder-2": "^1.0.5", + "sha1": "^1.1.1" + }, + "bin": { + "hashlips_art_engine": "index.js" + }, + "devDependencies": { + "@commitlint/cli": "^19.4.1", + "@commitlint/config-conventional": "^19.4.1", + "@types/node": "^20.16.5", + "@vitest/coverage-v8": "^0.34.6", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^17.9.0", + "eslint-plugin-promise": "^6.1.1", + "prettier": "^3.3.3", + "typescript": "^5.5.4", + "vitest": "^0.34.6" } }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" }, - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "node_modules/@commitlint/cli": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", + "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^19.8.1", + "@commitlint/lint": "^19.8.1", + "@commitlint/load": "^19.8.1", + "@commitlint/read": "^19.8.1", + "@commitlint/types": "^19.8.1", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" } }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "node_modules/@commitlint/config-conventional": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", + "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/@commitlint/config-validator": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", + "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" } }, - "canvas": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.8.0.tgz", - "integrity": "sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q==", - "requires": { - "@mapbox/node-pre-gyp": "^1.0.0", - "nan": "^2.14.0", - "simple-get": "^3.0.3" + "node_modules/@commitlint/ensure": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", + "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" } }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + "node_modules/@commitlint/execute-rule": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", + "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + "node_modules/@commitlint/format": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", + "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + "node_modules/@commitlint/is-ignored": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", + "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "node_modules/@commitlint/lint": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", + "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^19.8.1", + "@commitlint/parse": "^19.8.1", + "@commitlint/rules": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "node_modules/@commitlint/load": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", + "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/execute-rule": "^19.8.1", + "@commitlint/resolve-extends": "^19.8.1", + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + "node_modules/@commitlint/message": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", + "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "requires": { - "ms": "2.1.2" + "node_modules/@commitlint/parse": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", + "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" } }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" + "node_modules/@commitlint/read": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", + "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^19.8.1", + "@commitlint/types": "^19.8.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" } }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "node_modules/@commitlint/resolve-extends": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", + "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/types": "^19.8.1", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + "node_modules/@commitlint/rules": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", + "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^19.8.1", + "@commitlint/message": "^19.8.1", + "@commitlint/to-lines": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { - "minipass": "^3.0.0" + "node_modules/@commitlint/to-lines": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", + "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "node_modules/@commitlint/top-level": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", + "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } }, - "gauge": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.1.tgz", - "integrity": "sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ==", - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1 || ^2.0.0", - "strip-ansi": "^3.0.1 || ^4.0.0", - "wide-align": "^1.1.2" + "node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" } }, - "gif-encoder-2": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/gif-encoder-2/-/gif-encoder-2-1.0.5.tgz", - "integrity": "sha512-fsRAKbZuUoZ7FYGjpFElmflTkKwsn/CzAmL/xDl4558aTAgysIDCUF6AXWO8dmai/ApfZACbPVAM+vPezJXlFg==" + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", - "debug": "4" + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", - "requires": { - "yallist": "^4.0.0" + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } }, - "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", - "requires": { - "whatwg-url": "^5.0.0" + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "requires": { - "abbrev": "1" + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" } }, - "npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" } }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "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==" + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } }, - "sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=", - "requires": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "signal-exit": { + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.6.tgz", + "integrity": "sha512-qK1ECws8UxuPqOA8F5LFD90vyVU33W7N3hGfgsOVfrJaRVc8McC3JClTDHpeSbL9CBrOHly/4GsNPAvIgNZE+g==", + "dependencies": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.5", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", + "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", + "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", + "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", + "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", + "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", + "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", + "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", + "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", + "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", + "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", + "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", + "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", + "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", + "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", + "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", + "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", + "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", + "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", + "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", + "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", + "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", + "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "4.3.20", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz", + "integrity": "sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai-subset": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/chai-subset/-/chai-subset-1.3.6.tgz", + "integrity": "sha512-m8lERkkQj+uek18hXOZuec3W/fCRTrU4hrnXjH3qhHy96ytuPaPiWGgu7sJb7tZxZonO75vYAjCvpe/e4VUwRw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/chai": "<5.2.0" + } + }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.2.tgz", + "integrity": "sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitest/coverage-v8": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-0.34.6.tgz", + "integrity": "sha512-fivy/OK2d/EsJFoEoxHFEnNGTg+MmdZBAVK9Ka4qhXR2K3J0DS08vcGVwzDtXSuUMabLv4KtPcpSKkcMXFDViw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@bcoe/v8-coverage": "^0.2.3", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.5", + "magic-string": "^0.30.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": ">=0.32.0 <1" + } + }, + "node_modules/@vitest/expect": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz", + "integrity": "sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.34.6.tgz", + "integrity": "sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "0.34.6", + "p-limit": "^4.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.34.6.tgz", + "integrity": "sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.34.6.tgz", + "integrity": "sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", + "integrity": "sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.4.3", + "loupe": "^2.3.6", + "pretty-format": "^29.5.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/canvas": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.8.0.tgz", + "integrity": "sha512-gLTi17X8WY9Cf5GZ2Yns8T5lfBOcGgFehDFb+JQwDqdOoBOcECS9ZWMEAqMSVcMYwXD659J8NyzjRY/2aE+C2Q==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.14.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=", + "engines": { + "node": "*" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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==", + "dev": true, + "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/cliui/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "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/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", + "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "^2.6.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=", + "engines": { + "node": "*" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "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/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", + "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", + "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", + "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/ota-meshi", + "https://opencollective.com/eslint" + ], + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.11.0", + "eslint-compat-utils": "^0.5.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/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/eslint-plugin-n": { + "version": "17.23.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.23.1.tgz", + "integrity": "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.5.0", + "enhanced-resolve": "^5.17.1", + "eslint-plugin-es-x": "^7.8.0", + "get-tsconfig": "^4.8.1", + "globals": "^15.11.0", + "globrex": "^0.1.2", + "ignore": "^5.3.2", + "semver": "^7.6.3", + "ts-declaration-location": "^1.0.6" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": ">=8.23.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz", + "integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "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/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "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==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.1.tgz", + "integrity": "sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1 || ^2.0.0", + "strip-ansi": "^3.0.1 || ^4.0.0", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gif-encoder-2": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/gif-encoder-2/-/gif-encoder-2-1.0.5.tgz", + "integrity": "sha512-fsRAKbZuUoZ7FYGjpFElmflTkKwsn/CzAmL/xDl4558aTAgysIDCUF6AXWO8dmai/ApfZACbPVAM+vPezJXlFg==" + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "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/import-fresh/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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/nan": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "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/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "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": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", + "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + } + ] + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha1": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", + "integrity": "sha1-rdqnqTFo85PxnrKxUJFhjicA+Eg=", + "dependencies": { + "charenc": ">= 0.0.1", + "crypt": ">= 0.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "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.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz", "integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==" }, - "simple-concat": { + "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "simple-get": { + "node_modules/simple-get": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", - "requires": { + "dependencies": { "decompress-response": "^4.2.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, - "string-width": { + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "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/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { + "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "strip-ansi": { + "node_modules/strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { + "dependencies": { "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", + "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "tar": { + "node_modules/tar": { "version": "6.1.11", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "requires": { + "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinypool": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz", + "integrity": "sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, - "tr46": { + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" }, - "util-deprecate": { + "node_modules/ts-declaration-location": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", + "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", + "dev": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "picomatch": "^4.0.2" + }, + "peerDependencies": { + "typescript": ">=4.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "webidl-conversions": { + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz", + "integrity": "sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "mlly": "^1.4.0", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.6.tgz", + "integrity": "sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^4.3.5", + "@types/chai-subset": "^1.3.3", + "@types/node": "*", + "@vitest/expect": "0.34.6", + "@vitest/runner": "0.34.6", + "@vitest/snapshot": "0.34.6", + "@vitest/spy": "0.34.6", + "@vitest/utils": "0.34.6", + "acorn": "^8.9.0", + "acorn-walk": "^8.2.0", + "cac": "^6.7.14", + "chai": "^4.3.10", + "debug": "^4.3.4", + "local-pkg": "^0.4.3", + "magic-string": "^0.30.1", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.3.3", + "strip-literal": "^1.0.1", + "tinybench": "^2.5.0", + "tinypool": "^0.7.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0", + "vite-node": "0.34.6", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": ">=v14.18.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@vitest/browser": "*", + "@vitest/ui": "*", + "happy-dom": "*", + "jsdom": "*", + "playwright": "*", + "safaridriver": "*", + "webdriverio": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright": { + "optional": true + }, + "safaridriver": { + "optional": true + }, + "webdriverio": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" }, - "whatwg-url": { + "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { + "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, - "wide-align": { + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "requires": { + "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "wrappy": { + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "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/wrap-ansi/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/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==", + "dev": true, + "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/wrap-ansi/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "yallist": { + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/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==", + "dev": true, + "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/yargs/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 141abf44e..6c4078692 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,22 @@ }, "scripts": { "build": "node index.js", + "dev": "node index.js", "generate": "node index.js", - "rarity": "node utils/rarity.js", + "lint": "eslint \"src/healthcheck.js\" \"__tests__\" \"vitest.config.mjs\" \"commitlint.config.js\" --max-warnings=0", + "typecheck": "tsc --noEmit", + "test": "vitest run --coverage", + "test:watch": "vitest watch", + "format": "prettier --check .", + "format:fix": "prettier --write .", + "smoke:worker": "node worker/tools/smoke.js", "preview": "node utils/preview.js", "pixelate": "node utils/pixelate.js", + "rarity": "node utils/rarity.js", "update_info": "node utils/update_info.js", "preview_gif": "node utils/preview_gif.js", - "generate_metadata": "node utils/generate_metadata.js" + "generate_metadata": "node utils/generate_metadata.js", + "check": "npm run lint && npm run typecheck && npm test" }, "author": "Daniel Eugene Botha (HashLips)", "license": "MIT", @@ -27,5 +36,19 @@ "canvas": "^2.8.0", "gif-encoder-2": "^1.0.5", "sha1": "^1.1.1" + }, + "devDependencies": { + "@commitlint/cli": "^19.4.1", + "@commitlint/config-conventional": "^19.4.1", + "@types/node": "^20.16.5", + "@vitest/coverage-v8": "^0.34.6", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-n": "^17.9.0", + "eslint-plugin-promise": "^6.1.1", + "prettier": "^3.3.3", + "typescript": "^5.5.4", + "vitest": "^0.34.6" } } diff --git a/pages/auctions.js b/pages/auctions.js new file mode 100644 index 000000000..01c8978c4 --- /dev/null +++ b/pages/auctions.js @@ -0,0 +1,96 @@ +import { initWalletUI, getJwt } from 'public/wallet.js'; +import { placeBid } from 'backend/bids.jsw'; + +const TREE_SECTION = '#treeSection'; +const BAZAAR_SECTION = '#bazaarSection'; + +const TAB_TREE = '#tabTree'; +const TAB_BAZAAR = '#tabBazaar'; + +const CONNECT_BUTTON = '#connectWalletButton'; +const DISCONNECT_BUTTON = '#disconnectWalletButton'; +const ADDRESS_LABEL = '#walletAddressText'; + +const BID_BUTTON = '#bidButton'; +const BID_VALUE = '#bidValue'; +const AUCTION_DATASET = '#auctionDataset'; +const TOAST_LABEL = '#toastText'; + +const CONTROLS = [CONNECT_BUTTON, DISCONNECT_BUTTON, ADDRESS_LABEL]; + +$w.onReady(() => { + expandControls(); + bindTabs(); + initWalletUI({ + connectButton: $w(CONNECT_BUTTON), + disconnectButton: $w(DISCONNECT_BUTTON), + addressText: $w(ADDRESS_LABEL) + }); + + if ($w(BID_BUTTON)) { + $w(BID_BUTTON).onClick(handleBid); + } +}); + +function expandControls() { + CONTROLS.forEach((selector) => { + const el = $w(selector); + if (el && typeof el.expand === 'function') { + el.expand(); + } + }); +} + +function bindTabs() { + if ($w(TAB_TREE)) { + $w(TAB_TREE).onClick(() => { + showTree(); + }); + } + if ($w(TAB_BAZAAR)) { + $w(TAB_BAZAAR).onClick(() => { + showBazaar(); + }); + } +} + +function showTree() { + if ($w(TREE_SECTION)) $w(TREE_SECTION).expand(); + if ($w(BAZAAR_SECTION)) $w(BAZAAR_SECTION).collapse(); +} + +function showBazaar() { + if ($w(TREE_SECTION)) $w(TREE_SECTION).collapse(); + if ($w(BAZAAR_SECTION)) $w(BAZAAR_SECTION).expand(); + expandControls(); +} + +async function handleBid() { + const amount = Number($w(BID_VALUE).value); + const current = $w(AUCTION_DATASET)?.getCurrentItem(); + const auctionId = current?._id; + if (!auctionId) { + showToast('Select an auction first.'); + return; + } + + try { + const jwt = getJwt(); + if (!jwt) { + showToast('Connect your wallet first.'); + return; + } + const result = await placeBid({ auctionId, amount, jwt }); + showToast(`Bid placed: ${result.highBidAmount}`); + } catch (err) { + showToast(err.message || 'Bid failed'); + } +} + +function showToast(message) { + if ($w(TOAST_LABEL)) { + $w(TOAST_LABEL).text = message; + } else { + console.log(message); + } +} diff --git a/pipeline/tools/export_plot_cards.py b/pipeline/tools/export_plot_cards.py new file mode 100644 index 000000000..f4b991a10 --- /dev/null +++ b/pipeline/tools/export_plot_cards.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +""" +Export per-plot "plot cards" (cropped images + manifest) from existing manifests and renders. +Outputs: + pipeline/exports/plots/cards/.png (512x512) + pipeline/exports/plots/cards/_labeled.png (if labeled source exists) + pipeline/exports/plots/plots_cards_manifest.json + pipeline/exports/plots/plots_cards_manifest.csv + Logs: pipeline/logs/export_plot_cards_log.txt + +Uses: + - pipeline/exports/plots_master.json + - pipeline/exports/renders/meta/.json + - pipeline/exports/renders/final/_styled_4k.png (and labeled version if present) +""" + +from __future__ import annotations + +import csv +import json +import math +from pathlib import Path + +from PIL import Image, ImageDraw + + +def load_json(path: Path): + try: + return json.loads(path.read_text(encoding="utf-8")) + except Exception: + return None + + +def ensure_dir(path: Path): + path.mkdir(parents=True, exist_ok=True) + + +def world_to_px(x, y, meta): + minx, miny = meta["world_bounds_min"] + maxx, maxy = meta["world_bounds_max"] + w = meta["image_w"] + h = meta["image_h"] + u = (x - minx) / (maxx - minx + 1e-6) + v = (y - miny) / (maxy - miny + 1e-6) + px = int(u * w) + py = int(v * h) + if meta.get("axis", {}).get("v_flipped", False): + py = h - py + return px, py + + +def crop_plot(img: Image.Image, bbox_px, out_size=512): + x0, y0, x1, y1 = bbox_px + width = x1 - x0 + height = y1 - y0 + cx = (x0 + x1) / 2 + cy = (y0 + y1) / 2 + size = max(width, height) + # margin 15% + size *= 1.15 + half = size / 2 + box = ( + int(cx - half), + int(cy - half), + int(cx + half), + int(cy + half), + ) + cropped = img.crop(box) + return cropped.resize((out_size, out_size), Image.LANCZOS) + + +def main(): + root = Path.cwd() + exports = root / "pipeline" / "exports" + cards_dir = exports / "plots" / "cards" + ensure_dir(cards_dir) + log_dir = root / "pipeline" / "logs" + ensure_dir(log_dir) + log_file = log_dir / "export_plot_cards_log.txt" + + plots_master = exports / "plots_master.json" + data = load_json(plots_master) + if not data: + print("[plot_cards] ERROR: plots_master.json missing or invalid") + return 1 + plots = data.get("plots", data if isinstance(data, list) else []) + + meta_dir = exports / "renders" / "meta" + final_dir = exports / "renders" / "final" + + manifest = [] + lines = [] + + for p in plots: + pid = p.get("plot_id") + src = p.get("_source", "") + district = p.get("district", "") + faction = p.get("faction", "") + loc = p.get("world_location", [0, 0, 0]) + # Infer map name: expected to be like L__ + map_name = Path(src.replace("plot_manifest_", "")).stem.replace(".json", "") + map_name = map_name.replace("plot_manifest_", "") + if map_name.lower().startswith("plot_manifest_"): + map_name = map_name[len("plot_manifest_") :] + if map_name.startswith("L_"): + map_name = map_name + else: + # fallback: build from faction/district + slug = f"{faction}_{district}".replace(" ", "").replace("-", "") + map_name = f"L_{slug}" + + meta_path = meta_dir / f"{map_name}.json" + meta = load_json(meta_path) + if not meta: + lines.append(f"SKIP {pid}: meta missing {meta_path}") + continue + + styled = final_dir / f"{map_name}_styled_4k.png" + styled_labeled = final_dir / f"{map_name}_styled_4k_labeled.png" + if not styled.exists(): + lines.append(f"SKIP {pid}: styled missing {styled}") + continue + + x, y = loc[0], loc[1] + px, py = world_to_px(x, y, meta) + tile_cm = meta.get("tile_cm", 1000) + # Convert tile_cm to pixels using bounds + px_per_cm_x = meta["image_w"] / (meta["world_bounds_max"][0] - meta["world_bounds_min"][0] + 1e-6) + px_per_cm_y = meta["image_h"] / (meta["world_bounds_max"][1] - meta["world_bounds_min"][1] + 1e-6) + half_w = int((tile_cm * px_per_cm_x) / 2) + half_h = int((tile_cm * px_per_cm_y) / 2) + bbox_px = (px - half_w, py - half_h, px + half_w, py + half_h) + + img = Image.open(styled).convert("RGB") + card = crop_plot(img, bbox_px, out_size=512) + card_path = cards_dir / f"{pid}.png" + card.save(card_path) + + if styled_labeled.exists(): + img_l = Image.open(styled_labeled).convert("RGB") + card_l = crop_plot(img_l, bbox_px, out_size=512) + card_l.save(cards_dir / f"{pid}_labeled.png") + + manifest.append({ + "plot_id": pid, + "faction": faction, + "district": district, + "map": map_name, + "image": str(card_path.relative_to(root)), + "image_labeled": str((cards_dir / f"{pid}_labeled.png").relative_to(root)) if (cards_dir / f"{pid}_labeled.png").exists() else "", + "world_location": loc, + "bbox_px": bbox_px, + }) + lines.append(f"OK {pid}") + + # Write manifests + cards_manifest_json = exports / "plots" / "plots_cards_manifest.json" + cards_manifest_csv = exports / "plots" / "plots_cards_manifest.csv" + ensure_dir(cards_manifest_json.parent) + cards_manifest_json.write_text(json.dumps(manifest, indent=2), encoding="utf-8") + + with cards_manifest_csv.open("w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=["plot_id", "faction", "district", "map", "image", "image_labeled", "world_location", "bbox_px"]) + writer.writeheader() + for row in manifest: + writer.writerow(row) + + log_file.write_text("\n".join(lines), encoding="utf-8") + print(f"[plot_cards] done, {len(manifest)} cards") + return 0 + + +if __name__ == "__main__": + import sys + sys.exit(main()) diff --git a/pipeline/unreal/add_return_gate_to_districts.py b/pipeline/unreal/add_return_gate_to_districts.py new file mode 100644 index 000000000..ef9207242 --- /dev/null +++ b/pipeline/unreal/add_return_gate_to_districts.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# Add a Return-to-Hub gate to every district map (if missing). +# Run via: +# UnrealEditor-Cmd.exe "YourProject.uproject" -ExecutePythonScript="...\pipeline\unreal\add_return_gate_to_districts.py" -unattended -nop4 + +import json +from pathlib import Path + +import unreal + +BP_RETURN = "/Game/Goblinverse/Blueprints/BP_ReturnGate" +RETURN_TAG = "ReturnGate" +HUB_MAP = "/Game/Goblinverse/Maps/L_NeoGoblintown_Hub" + + +def maps_list(project_root: Path): + maps = [] + idx = project_root / "pipeline" / "district_specs" / "generated" / "_index.json" + if idx.exists(): + try: + data = json.loads(idx.read_text(encoding="utf-8")) + for spec in data.get("generated", []): + name = Path(spec).stem + maps.append(f"/Game/Goblinverse/Maps/Districts/L_{name}") + except Exception: + pass + if not maps: + assets = unreal.EditorAssetLibrary.list_assets("/Game/Goblinverse/Maps/Districts", recursive=True, include_folder=False) + for a in assets: + if a.endswith(".umap"): + maps.append(a[:-5]) + else: + maps.append(a) + seen = set() + out = [] + for m in maps: + if m not in seen: + seen.add(m) + out.append(m) + return out + + +def has_return_gate(): + for a in unreal.EditorLevelLibrary.get_all_level_actors(): + if RETURN_TAG in [str(t) for t in a.tags]: + return True + return False + + +def add_gate(): + if not unreal.EditorAssetLibrary.does_asset_exist(BP_RETURN): + unreal.log_error(f"[ReturnGate] Missing BP_ReturnGate: {BP_RETURN}") + return False + bp_cls = unreal.EditorAssetLibrary.load_blueprint_class(BP_RETURN) + actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_cls, unreal.Vector(0, 0, 150), unreal.Rotator(0, 180, 0)) + if actor: + actor.tags = [RETURN_TAG] + try: + actor.set_editor_property("target_map", HUB_MAP) + except Exception: + pass + return True + return False + + +def main(): + project_root = Path(unreal.Paths.project_dir()).resolve() + log_dir = project_root / "pipeline" / "logs" + log_dir.mkdir(parents=True, exist_ok=True) + log_file = log_dir / "add_return_gates_log.txt" + lines = [] + + for m in maps_list(project_root): + try: + unreal.log(f"[ReturnGate] Loading {m}") + unreal.EditorLoadingAndSavingUtils.load_map(m) + if has_return_gate(): + lines.append(f"SKIP {m} (exists)") + else: + if add_gate(): + lines.append(f"OK {m}") + else: + lines.append(f"FAIL {m}") + unreal.EditorLevelLibrary.save_current_level() + except Exception as e: + lines.append(f"FAIL {m}: {e}") + + log_file.write_text("\n".join(lines), encoding="utf-8") + unreal.log(f"[ReturnGate] Done. Log: {log_file}") + print(f"[ReturnGate] Done. Log: {log_file}") + + +if __name__ == "__main__": + main() diff --git a/pipeline/unreal/build_hub_map.py b/pipeline/unreal/build_hub_map.py new file mode 100644 index 000000000..0da8fa2b4 --- /dev/null +++ b/pipeline/unreal/build_hub_map.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# Build /Game/Goblinverse/Maps/L_NeoGoblintown_Hub with faction teleport gates. +# Run via: +# UnrealEditor-Cmd.exe "YourProject.uproject" -ExecutePythonScript="...\pipeline\unreal\build_hub_map.py" -unattended -nop4 + +import unreal + +HUB_MAP_PATH = "/Game/Goblinverse/Maps/L_NeoGoblintown_Hub" +HUB_DIR = "/Game/Goblinverse/Maps" +BP_GATE = "/Game/Goblinverse/Blueprints/BP_TeleportGate" +BP_RETURN = "/Game/Goblinverse/Blueprints/BP_ReturnGate" + +GATES = [ + ("CORE", "RootSpire", "/Game/Goblinverse/Maps/Districts/L_CORE_RootSpire", 0), + ("SCRAP", "TrashBurnerYard", "/Game/Goblinverse/Maps/Districts/L_SCRAP_TrashBurnerYard", 60), + ("GUT", "BossTagspire", "/Game/Goblinverse/Maps/Districts/L_GUT_BossTagspire", 120), + ("HYDRO", "BatterySiphonDocks", "/Game/Goblinverse/Maps/Districts/L_HYDRO_BatterySiphonDocks", 180), + ("CLOUD", "GuestGate", "/Game/Goblinverse/Maps/Districts/L_CLOUD_GuestGate", 240), + ("BASS", "BossVault", "/Game/Goblinverse/Maps/Districts/L_BASS_BossVault", 300), +] + +FACTION_MATS = { + "CORE": "/Game/Goblinverse/Materials/MI_Corebreakers", + "SCRAP": "/Game/Goblinverse/Materials/MI_Scrapshamans", + "GUT": "/Game/Goblinverse/Materials/MI_Gutterborns", + "HYDRO": "/Game/Goblinverse/Materials/MI_Hydrohackers", + "CLOUD": "/Game/Goblinverse/Materials/MI_Cloudjackers", + "BASS": "/Game/Goblinverse/Materials/MI_Bassfiends", +} + + +def ensure_gate_bp(bp_path, is_return=False): + if unreal.EditorAssetLibrary.does_asset_exist(bp_path): + return bp_path + # Minimal BP creation with Python is non-trivial; assume assets exist or the user will place a mesh manually. + unreal.log_error(f"[Hub] Missing Blueprint: {bp_path}. Please create it manually (BP_TeleportGate / BP_ReturnGate).") + return None + + +def ensure_map(): + if not unreal.EditorAssetLibrary.does_directory_exist(HUB_DIR): + unreal.EditorAssetLibrary.make_directory(HUB_DIR) + if unreal.EditorAssetLibrary.does_asset_exist(HUB_MAP_PATH): + unreal.EditorLevelLibrary.load_level(HUB_MAP_PATH) + unreal.log("[Hub] Loaded existing hub map") + else: + unreal.EditorLevelLibrary.new_level(HUB_MAP_PATH) + unreal.log("[Hub] Created new hub map") + + +def ensure_atmosphere(): + # fog + has_fog = any(isinstance(a, unreal.ExponentialHeightFog) for a in unreal.EditorLevelLibrary.get_all_level_actors()) + if not has_fog: + unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.ExponentialHeightFog, unreal.Vector(0, 0, 0)) + # post process + ppv = None + for a in unreal.EditorLevelLibrary.get_all_level_actors(): + if isinstance(a, unreal.PostProcessVolume): + ppv = a + break + if not ppv: + ppv = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.PostProcessVolume, unreal.Vector(0, 0, 0)) + ppv.set_editor_property("b_unbound", True) + settings = ppv.get_editor_property("settings") + settings.bloom_intensity = 0.7 + settings.vignette_intensity = 0.4 + settings.film_grain_intensity = 0.35 + settings.scene_fringe_intensity = 1.4 + settings.auto_exposure_min_brightness = 1.0 + settings.auto_exposure_max_brightness = 1.0 + ppv.set_editor_property("settings", settings) + + +def place_gates(): + gate_bp = ensure_gate_bp(BP_GATE) + if not gate_bp: + return + radius = 1500.0 + for faction, label, target, angle_deg in GATES: + angle_rad = unreal.MathLibrary.deg_to_rad(angle_deg) + x = radius * unreal.MathLibrary.cos(angle_rad) + y = radius * unreal.MathLibrary.sin(angle_rad) + loc = unreal.Vector(x, y, 100) + rot = unreal.Rotator(0, angle_deg + 180, 0) + actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.EditorAssetLibrary.load_blueprint_class(gate_bp), loc, rot) + if not actor: + continue + actor.set_actor_label(f"Gate_{faction}") + try: + actor.set_editor_property("target_map", target) + actor.set_editor_property("gate_label", f"{faction} :: {label}") + except Exception: + pass + # apply faction material if available + mat_path = FACTION_MATS.get(faction) + if mat_path and unreal.EditorAssetLibrary.does_asset_exist(mat_path): + mat = unreal.EditorAssetLibrary.load_asset(mat_path) + smc = actor.get_component_by_class(unreal.StaticMeshComponent) + if smc and mat: + smc.set_material(0, mat) + + +def ensure_player_start(): + exists = [a for a in unreal.EditorLevelLibrary.get_all_level_actors() if isinstance(a, unreal.PlayerStart)] + if not exists: + unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.PlayerStart, unreal.Vector(0, -2000, 150), unreal.Rotator(0, 0, 0)) + + +def main(): + log_file = Path(unreal.Paths.project_dir()) / "pipeline" / "logs" / "build_hub_map_log.txt" + lines = [] + try: + ensure_map() + ensure_atmosphere() + place_gates() + ensure_player_start() + unreal.EditorLevelLibrary.save_current_level() + lines.append("OK hub built") + except Exception as e: + lines.append(f"FAIL: {e}") + log_file.write_text("\n".join(lines), encoding="utf-8") + unreal.log(f"[Hub] Done. Log: {log_file}") + print(f"[Hub] Done. Log: {log_file}") + + +if __name__ == "__main__": + main() diff --git a/plots.html b/plots.html new file mode 100644 index 000000000..a2ee7f702 --- /dev/null +++ b/plots.html @@ -0,0 +1,95 @@ + + + + + + + DIKPIK Plots + + + + + +
    +
    +
    +

    DIKPIK // PLOTS

    +

    2D Plot Grid

    +

    Click a tile to view owner (masked), nickname, and make an offer. Available plots show a buy/offer CTA.

    +
    + +
    + +
    +
    +

    Factions & Districts

    +
    + Owned + Available +
    +
    +
    +
    +
    + + + + + + diff --git a/revenue_from_plots (1).gsheet b/revenue_from_plots (1).gsheet new file mode 100644 index 000000000..5480cdc1c --- /dev/null +++ b/revenue_from_plots (1).gsheet @@ -0,0 +1 @@ +{"":"WARNING! DO NOT EDIT THIS FILE! ANY CHANGES MADE WILL BE LOST!","doc_id":"1X1J7V3dRZpnbTLu96lcs-4GRCUSe7FSJ25iieUxFQYc","resource_key":"","email":"info@dikpik.io"} diff --git a/revenue_from_plots (2).gsheet b/revenue_from_plots (2).gsheet new file mode 100644 index 000000000..86d18b766 --- /dev/null +++ b/revenue_from_plots (2).gsheet @@ -0,0 +1 @@ +{"":"WARNING! DO NOT EDIT THIS FILE! ANY CHANGES MADE WILL BE LOST!","doc_id":"1DRsFRrKPefUH5_5sFagz-nsM-QmAXTH38ITZyBAkt14","resource_key":"","email":"info@dikpik.io"} diff --git a/revenue_from_plots.gsheet b/revenue_from_plots.gsheet new file mode 100644 index 000000000..543ea4ac5 --- /dev/null +++ b/revenue_from_plots.gsheet @@ -0,0 +1 @@ +{"":"WARNING! DO NOT EDIT THIS FILE! ANY CHANGES MADE WILL BE LOST!","doc_id":"1gQjO8eCtntDkzQOMRHxMGlfVh0CQy2MgODanc8DyThc","resource_key":"","email":"info@dikpik.io"} diff --git a/scripts/verify-bundle.js b/scripts/verify-bundle.js new file mode 100644 index 000000000..591eddac2 --- /dev/null +++ b/scripts/verify-bundle.js @@ -0,0 +1,39 @@ +// Simple guard script to fail CI if the OpenNext bundle contains +// Windows-style absolute paths or references to @vercel/og/resvg.wasm. +// Run after opennextjs-cloudflare build (and patch-resvg) when verifying a build. + +import fs from 'fs'; +import path from 'path'; + +const targets = [ + path.join('.open-next', 'worker.js'), + path.join('.open-next', 'server-functions', 'default', 'handler.mjs'), +]; + +const disallowedPatterns = [ + /@vercel\/og/i, + /resvg\.wasm/i, + /[A-Z]:[\\/]/, // Windows drive paths +]; + +let hadError = false; + +for (const target of targets) { + if (!fs.existsSync(target)) { + console.warn(`[verify-bundle] Skipping missing file: ${target}`); + continue; + } + const content = fs.readFileSync(target, 'utf8'); + for (const pat of disallowedPatterns) { + if (pat.test(content)) { + console.error(`[verify-bundle] Disallowed pattern ${pat} found in ${target}`); + hadError = true; + } + } +} + +if (hadError) { + process.exit(1); +} else { + console.log('[verify-bundle] Bundle check passed (no og/resvg/Windows paths).'); +} diff --git a/siteready-website/.github/workflows/cloudflare-workers-deploy.yml b/siteready-website/.github/workflows/cloudflare-workers-deploy.yml new file mode 100644 index 000000000..1d075351c --- /dev/null +++ b/siteready-website/.github/workflows/cloudflare-workers-deploy.yml @@ -0,0 +1,40 @@ +name: Deploy to Cloudflare Workers (OpenNext) + +on: + push: + branches: ["main"] + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + BASIC_AUTH_ENABLED: ${{ secrets.BASIC_AUTH_ENABLED }} + BASIC_AUTH_USER: ${{ secrets.BASIC_AUTH_USER }} + BASIC_AUTH_PASS: ${{ secrets.BASIC_AUTH_PASS }} + NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: ${{ secrets.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install + run: npm ci + + - name: Apply D1 safe migrations (prod) + run: npx wrangler d1 execute DB --remote --file db/migrations/001_safe.sql + + - name: Lint + run: npm run lint + + - name: Build + Deploy (keep dashboard vars) + run: npm run cf:deploy:keep-all diff --git a/siteready-website/.gitignore b/siteready-website/.gitignore new file mode 100644 index 000000000..f6b03dc3d --- /dev/null +++ b/siteready-website/.gitignore @@ -0,0 +1,52 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# OpenNext / Cloudflare Workers build output +.open-next/ +.wrangler/ + +# Cloudflare local dev vars (do not commit secrets) +.dev.vars* +# Local env/secrets (do not commit) +.env.* +.env.local +.env.*.local diff --git a/siteready-website/DEPLOY-CLOUDFLARE.md b/siteready-website/DEPLOY-CLOUDFLARE.md new file mode 100644 index 000000000..c103d714c --- /dev/null +++ b/siteready-website/DEPLOY-CLOUDFLARE.md @@ -0,0 +1,70 @@ +# Cloudflare Workers Deploy (OpenNext) + +This project deploys Next.js to Cloudflare Workers using OpenNext (`@opennextjs/cloudflare`). + +## One-time setup (this machine) +1. Install deps + - `npm install` +2. Login to the correct Cloudflare account + - `npm run cf:whoami` + - If not logged in or wrong account: `npm run cf:login` + - Then re-check: `npm run cf:whoami` + +## Local Workers-runtime preview (recommended before deploying) +1. Ensure `.dev.vars` exists: + - `.dev.vars` should contain: + - `NEXTJS_ENV=development` +2. Keep local secrets in `.env.local` (NOT committed). Example keys used by the app: + - `SMTP_HOST` + - `SMTP_PORT` + - `SMTP_USER` + - `SMTP_PASS` (local only) + - `SMTP_FROM` + - `NOTIFY_TO` +3. Run preview: + - `npm run cf:preview` +4. Test: + - Visit the local preview URL + - Submit forms: /labour-hire, /for-workers, /contact +5. Stop preview with Ctrl+C. + +## First deploy (staging *.workers.dev) +1. Deploy: + - `npm run cf:deploy` +2. Set the SMTP password as a Cloudflare Secret (interactive prompt): + - `npm run cf:secret:smtp-pass` + - Paste the Google Workspace App Password when prompted (never commit it). +3. Tail logs (optional): + - `npm run cf:tail` + +## Deploy “from now on†+After code changes: +- `npm run cf:deploy:keep-vars` + +Notes: +- Non-secret runtime vars are in `wrangler.jsonc` under `vars`. +- Secrets like `SMTP_PASS` must be set via `wrangler secret put` (or dashboard secrets). + +--- + +## Windows deploy workaround (no-bundle) + +If `cf:deploy` fails on Windows with a Wrangler bundling error involving `resvg.wasm?module`, use: + +- `npm run cf:deploy:no-bundle` + +If you already set vars/secrets in the Cloudflare dashboard and want to preserve them across deploys: + +- `npm run cf:deploy:keep-vars:no-bundle` + +## Recommended “from now onâ€: GitHub Actions deploy (avoids Windows bundling issues) + +This repo includes a workflow: `.github/workflows/cloudflare-workers-deploy.yml`. + +### One-time GitHub setup +In your GitHub repo → Settings → Secrets and variables → Actions → New repository secret: + +- `CLOUDFLARE_API_TOKEN` = (create in Cloudflare dashboard; token with Workers deploy permissions) +- `CLOUDFLARE_ACCOUNT_ID` = `e78998f06cca1c169ce8aa6975355767` + +After that, every push to `main` will deploy from a Linux runner. diff --git a/siteready-website/KISS_AUDIT_PACK.md b/siteready-website/KISS_AUDIT_PACK.md new file mode 100644 index 000000000..93ccc9325 --- /dev/null +++ b/siteready-website/KISS_AUDIT_PACK.md @@ -0,0 +1,199 @@ +# KISS Audit Pack — SiteReady Workforce (Staging) + +## Step 1 — Stack + Structure +- **Framework**: Next.js App Router (source at `src/app/*/page.tsx`). +- **Styling**: Tailwind (via `@import "tailwindcss";` in `src/app/globals.css`). +- **Layout**: Global site shell in `src/components/SiteLayout.tsx` with `SiteHeader` + `SiteFooter`. +- **Deploy target**: Cloudflare Workers via OpenNext (`wrangler.jsonc`, `open-next.config.ts`, `custom-worker.ts`). +- **DB**: Cloudflare D1 (SQLite) bound as `env.DB` (see `wrangler.jsonc`). + +**Local commands (from `package.json`):** +- `npm run dev` — local dev server +- `npm run build` — production build +- `npm run lint` — lint +- `npm run cf:deploy:keep-all` — OpenNext build + deploy + +## Step 2 — Sitemap + Templates + CTA +Routes are derived from `src/app/**/page.tsx` (dynamic routes included). + +**Sitemap (full):** +- `/` (Home) +- `/labour-hire` +- `/for-workers` +- `/technology` +- `/safety-compliance` +- `/contact` +- `/login` +- `/register` +- `/forgot-password` +- `/reset-password` +- `/logout` +- `/admin-login` +- `/admin-setup` +- `/platform-not-configured` +- `/privacy` +- `/terms` +- `/legal/privacy` +- `/legal/collection-notice` +- `/legal/terms` +- `/legal/xero-consent` +- `/dashboard` +- `/dashboard/requests` +- `/dashboard/requests/status` +- `/dashboard/requests/proposed` +- `/dashboard/requests/[id]` +- `/dashboard/timesheets` +- `/dashboard/worker` +- `/dashboard/worker/assignments` +- `/dashboard/worker/proposed` +- `/dashboard/worker/timesheets` +- `/dashboard/worker/working-now` +- `/dashboard/admin` +- `/dashboard/admin/requests` +- `/dashboard/admin/clients` +- `/dashboard/admin/contractors` +- `/dashboard/admin/workers` +- `/dashboard/admin/access` +- `/dashboard/admin/xero` +- `/dashboard/admin/xero/accounting` +- `/dashboard/admin/xero/bill-failures` + +**Templates/components + primary CTA (selected examples):** +- `/` — `SectionWrapper`, `SectionHeading`, `FeatureCard`, `StepCard`, `PrimaryButton`, `SecondaryButton`. + **Primary CTA**: “Book labour†→ `/labour-hire`. + **Secondary CTA**: “Find site work†→ `/for-workers`. + **Competing CTAs**: Yes (book labour + find work + request workers). +- `/labour-hire` — `RoleCard`, `BookLabourNowForm`, `StepCard`. + **Primary CTA**: “Log in to request workers†→ `/login?next=/dashboard/requests`. + **Competing CTAs**: Yes (log in + create account + form). +- `/for-workers` — `FeatureCard`, `RoleCard`, `StepCard` + Application form. + **Primary CTA**: “Apply now†(anchor to form). +- `/contact` — Contact form. + **Primary CTA**: “Send messageâ€. +- `/technology` — Info only. + **Primary CTA**: None. +- `/safety-compliance` — Info + 2 CTAs. + **Primary CTA**: “Book labour†→ `/labour-hire`. + **Competing CTAs**: Yes (“For workersâ€). +- `/dashboard` — role-based action buttons (admin/worker/client). + **Primary CTA**: role-dependent. +- `/dashboard/worker` — worker profile + availability + submit profile. + **Primary CTA**: “Submit profileâ€. + +## Step 3 — Navigation + Funnel +**Header nav (`src/components/SiteHeader.tsx`):** +- Home, Labour Hire, For Workers, Technology, Safety & Compliance, Contact +- Admin login, Login, Create account (CTA) + +**Footer nav (`src/components/SiteFooter.tsx`):** +- Home, Labour Hire, For Workers, Technology, Safety & Compliance, Privacy (`#`), Terms (`#`) + +**Navigation issues:** +- Privacy/Terms in footer link to `#`, while `/privacy`, `/terms`, and `/legal/*` exist. +- Admin login is in primary header; may compete with employer/worker flows. + +**Primary journeys inferred:** +- **Employer (Hire)**: Home → Labour Hire → Log in/Create account → Dashboard → Requests. +- **Worker (Apply)**: Home → For Workers → Apply form → Login/Register → Worker dashboard. +- **Contact**: Home → Contact → Submit form. + +## Step 4 — Homepage + Key Pages Content Inventory +**Home (`/`)** +- H1: “Construction labour & cleaning, without the chaos†+- Sections: Problems we solve, What we supply, How it works, Tech & reporting, Who we’re for, CTA strip +- CTAs: Book labour, Find site work, Request workers +- Notes: CTA density is high; repeated “portal mockup†blocks. + +**Labour Hire (`/labour-hire`)** +- H1: “Construction labour hire – Sydney / NSW†+- Sections: Why hosts use us, Roles we supply, How bookings work, Compliance & insurance, Book labour +- CTAs: Log in to request workers, Create account, Log in (repeat), Create account (repeat) +- Form: `BookLabourNowForm` (multi-step booking flow) + +**For Workers (`/for-workers`)** +- H1: “Find steady construction work in Sydney / NSW†+- Sections: Benefits, Types of work, Requirements, How it works, Apply form +- Form fields: name, mobile, email, suburb, travel radius, right to work, visa type, roles, tickets, experience years +- CTA: Apply now (anchor) + +**Safety & Compliance (`/safety-compliance`)** +- H1: “Safety & Compliance†+- Sections: Employment model, WHS duties, Vetting, Operations, Insurance, Portal support, Risks +- CTAs: Book labour, For workers +- Notes: Very long page; could be condensed to essentials. + +**Contact (`/contact`)** +- H1: “Talk to SiteReady Workforce†+- Form: name, company, email, phone, message +- CTA: Send message + +## Step 5 — UI Consistency Audit (KISS) +**Typography** +- System UI stack in `globals.css` +- Headings use Tailwind classes: `text-4xl`, `text-5xl`, `font-bold` +- Body text varies (`text-lg`, `text-sm`, `text-xs`) + +**Spacing** +- Frequent `gap-6`, `py-6`, `py-10`, `mt-4`, `space-y-6` +- No explicit spacing scale defined; relies on Tailwind defaults + +**Colors (CSS variables)** +- `--color-srPrimary #0b1f3b` +- `--color-srAccent #ff7a1a` +- `--color-srTeal #11b6a8` +- `--color-srText #212529` +- `--color-srMuted #6b7280` +- `--color-srBg #f5f7fa` +- `--color-srCard #ffffff` +- `--color-srBorder #e5e7eb` + +**Buttons / cards** +- Button variants: `PrimaryButton`, `SecondaryButton`, dashboard `pillButton` +- Card variants: `FeatureCard`, `RoleCard`, `StepCard`, plus “portal mockup†panels +- Inconsistency: button styles and card styles vary between marketing and dashboard + +## Step 6 — Performance + Clutter Triggers +- No heavy media assets found in main pages; “portal mockup†is text-only. +- Multiple CTAs on key pages create visual competition. +- Safety & Compliance page is long and dense → high cognitive load. + +## Step 7 — KISS Recommendations +### Quick wins (1–2 days) +1) **Reduce CTA duplication on `/labour-hire`** + - Keep one primary CTA (Log in) + one secondary (Create account) and remove repeated CTA block. + - Why: reduce decision fatigue. +2) **Fix footer legal links** + - Replace `#` with real pages (`/legal/privacy`, `/legal/terms`). + - Why: trust/clarity. +3) **Merge CTA messaging on `/`** + - Use one main CTA (Book labour) and one secondary (Find site work); remove “Request workers†strip or make it consistent. + +### Medium (3–7 days) +1) **Condense Safety & Compliance** + - Convert multiple sections into 2–3 grouped sections with short bullets. + - Why: reduce scroll and repetition. +2) **Unify button styles** + - Align dashboard “pillButton†styling with Primary/Secondary buttons or create a single shared pattern. +3) **Clarify For Workers vs Register** + - Add a single CTA on `/for-workers` to create account or apply, not both. + +### Deep refactor (1–3 weeks) +1) **Simplify top-level nav** + - Reduce to 5 items + 1 CTA (see below). +2) **Consolidate legal pages** + - Keep `/legal/*` and remove `/privacy` + `/terms` duplicates, or vice versa. +3) **Create a unified “How it worksâ€** + - Replace separate steps across Home/Labour Hire/For Workers with a shared component + shorter copy. + +**Suggested simplified navigation (max 5 + 1 CTA)** +- Home +- Hire +- For Workers +- Technology (or Safety & Compliance, not both) +- Contact +- CTA: Book labour + +## Step 8 — Files Created +- `KISS_AUDIT_PACK.md` +- `SITE_STRUCTURE.json` + diff --git a/siteready-website/PROGRESS.md b/siteready-website/PROGRESS.md new file mode 100644 index 000000000..11cf79ac0 --- /dev/null +++ b/siteready-website/PROGRESS.md @@ -0,0 +1,88 @@ +# PROGRESS + +## Done +- Staging-first workflow confirmed in config: `wrangler.jsonc` uses `env.staging.name = "siteready-website-staging"` and Workers Dev deployment. +- Last committed checkpoint identified: + - `1a02fdf` (2026-02-15 12:32:35 +07:00) future-service interest intake + admin list. + - `00d7224` (2026-02-15 12:26:16 +07:00) staging WIP checkpoint. +- Latest uncommitted work anchor identified (Bangkok time): + - `2026-02-18 17:45:33` on `src/components/booking/WorkersGroupFields.tsx`. + - Nearby edits around `2026-02-18 17:34:53` on `src/components/booking/BookLabourNowForm.tsx`. +- Requests-page credit validation UX now mirrors onboarding pattern: + - Top missing-section summary in credit panel. + - `Take me to highlighted area` pulse button. + - Scroll/focus to first missing role field. + - Per-field arrow hints for credit-estimate-required fields. +- Client policy flow update: + - Added back button on `/dashboard/policies/client-terms`. + - Added `I do not accept` flow with confirmation modal and return to requests. + - Requests now support `Save and return later` (draft save) when policies are not yet accepted. + - Draft requests are saved for admin visibility with pending policy acceptance status. +- Automated emails management (admin): + - Added new admin page: `/dashboard/admin/automated-emails`. + - Added search/filter by audience (`client`, `worker`, `admin`, `mixed`). + - Added editable fields per template: subject/message/button label/button path/logo/footer toggles. + - Added backend API: `GET/POST /api/admin/email-templates`. + - Added D1-backed overrides table creation-on-demand: `email_template_overrides`. + - Seeded registry with current automated email flows across client/worker/admin. +- Staging/production link correctness: + - Replaced hardcoded production worker portal URLs with dynamic origin (`req.nextUrl.origin`) in: + - `src/app/api/admin/workers/[id]/approve/route.ts` + - `src/app/api/admin/workers/[id]/clarification-email/route.ts` +- Build status: + - `npm run build` passes. +- Automated emails UX fine-tuning: + - Added clickable resolved destination URL for each template button path. + - Added email-style visual preview showing subject, body, CTA button, logo placement, and footer/legal content. + - Added logo image endpoint for preview rendering: `/api/email/logo`. +- Live sender wiring (template overrides now applied): + - Draft reminder email (`request_draft_resume`) now uses override subject/message/CTA path/CTA label. + - Worker approved email (`worker_profile_approved`) now uses override subject/message/CTA path/CTA label. + - Worker clarification email (`worker_clarification`) now applies template overrides (supports `{{message}}`, `{{subject}}`, `{{worker_name}}`, `{{cta_url}}`, `{{cta_label}}`). + - Policy broadcast emails now use override templates: + - `policy_worker_terms_updated` + - `policy_client_terms_updated` + - `policy_privacy_updated` + - `policy_frontend_terms_updated` +- Policy recipients update: + - Added `listClientPolicyRecipientEmails()` to include: + - client contact email + - timesheet approver primary email + - timesheet approver backup email + - client user email(s) + - Policy sends now use this audience for: + - client terms + - privacy policy (plus workers) + - website terms (plus workers) + - Worker terms remains workers only. +- Website terms login-stage gating: + - Added `/dashboard/policies/website-terms` acceptance page. + - Middleware now redirects signed-in users to website terms page if latest `frontend_terms` is not accepted. + - Acceptance panel now auto-returns to requested `next` path after website terms acceptance. + - `frontend_terms` added to policy-required checks in `/api/policies/status` and request submit path. +- Admin clients policy panel cleanup: + - Removed `Worker Terms` from client policy acceptance display. + +## Next +- Continue from the 2026-02-18 booking/admin/worker WIP set and finish one vertical slice at a time. +- Wire selected high-traffic email senders to consume overrides at send time (subject/message/CTA), starting with: + - draft/incomplete request reminder + - worker approved + - worker clarification + - policy update broadcasts +- Make small checkpoint commits after each coherent change: + - `git add -A` + - `git commit -m "wip: "` +- Keep this file current at each checkpoint. + +## Blockers +- No hard blocker identified yet; main risk is large uncommitted surface area across UI + API + DB changes. +- Overrides are currently editable and stored, but not yet fully wired into every email sender template path. + +## Session Handoff Rule +- When chat context gets long, create a short session summary with: + - done + - next + - blockers + - exact files touched + - last command/error diff --git a/siteready-website/README.md b/siteready-website/README.md new file mode 100644 index 000000000..e215bc4cc --- /dev/null +++ b/siteready-website/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/siteready-website/SITE_STRUCTURE.json b/siteready-website/SITE_STRUCTURE.json new file mode 100644 index 000000000..229f5afb9 --- /dev/null +++ b/siteready-website/SITE_STRUCTURE.json @@ -0,0 +1,360 @@ +{ + "pages": [ + { + "route": "/", + "title": "Construction labour & cleaning, without the chaos", + "primaryCTA": "Book labour", + "secondaryCTAs": ["Find site work", "Request workers"], + "templates": ["SectionWrapper"], + "components": ["SectionHeading", "FeatureCard", "StepCard", "PrimaryButton", "SecondaryButton"] + }, + { + "route": "/labour-hire", + "title": "Construction labour hire – Sydney / NSW", + "primaryCTA": "Log in to request workers", + "secondaryCTAs": ["Create an account", "Log in", "Create account"], + "templates": ["SectionWrapper"], + "components": ["SectionHeading", "RoleCard", "StepCard", "FeatureCard", "BookLabourNowForm"] + }, + { + "route": "/for-workers", + "title": "Find steady construction work in Sydney / NSW", + "primaryCTA": "Apply now", + "secondaryCTAs": [], + "templates": ["SectionWrapper"], + "components": ["SectionHeading", "FeatureCard", "RoleCard", "StepCard", "ApplicationForm"] + }, + { + "route": "/technology", + "title": "Our labour & cleaning portal", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["SectionWrapper"], + "components": ["SectionHeading", "FeatureCard"] + }, + { + "route": "/safety-compliance", + "title": "Safety & Compliance", + "primaryCTA": "Book labour", + "secondaryCTAs": ["For workers"], + "templates": ["SectionWrapper"], + "components": ["SectionHeading", "FeatureCard", "PrimaryButton", "SecondaryButton"] + }, + { + "route": "/contact", + "title": "Talk to SiteReady Workforce", + "primaryCTA": "Send message", + "secondaryCTAs": [], + "templates": ["SectionWrapper"], + "components": ["SectionHeading", "PrimaryButton", "ContactForm"] + }, + { + "route": "/login", + "title": "Login", + "primaryCTA": "Login", + "secondaryCTAs": ["Continue with Google", "Continue with Apple", "Forgot password"], + "templates": ["Auth"], + "components": ["LoginForm"] + }, + { + "route": "/register", + "title": "Create account", + "primaryCTA": "Create account", + "secondaryCTAs": ["Continue with Google", "Continue with Apple", "Login"], + "templates": ["Auth"], + "components": ["RegisterForm"] + }, + { + "route": "/forgot-password", + "title": "Forgot password", + "primaryCTA": "Send reset email", + "secondaryCTAs": ["Back to login"], + "templates": ["Auth"], + "components": ["ForgotPasswordForm"] + }, + { + "route": "/reset-password", + "title": "Reset password", + "primaryCTA": "Reset password", + "secondaryCTAs": [], + "templates": ["Auth"], + "components": ["ResetPasswordForm"] + }, + { + "route": "/logout", + "title": "Logout", + "primaryCTA": "Logout", + "secondaryCTAs": [], + "templates": ["Auth"], + "components": [] + }, + { + "route": "/admin-login", + "title": "Admin login", + "primaryCTA": "Login", + "secondaryCTAs": [], + "templates": ["Auth"], + "components": ["AdminLoginForm"] + }, + { + "route": "/admin-setup", + "title": "Admin setup", + "primaryCTA": "Create admin", + "secondaryCTAs": [], + "templates": ["Auth"], + "components": ["AdminSetupForm"] + }, + { + "route": "/platform-not-configured", + "title": "Platform not configured", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["System"], + "components": [] + }, + { + "route": "/privacy", + "title": "Privacy", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Legal"], + "components": ["LegalContent"] + }, + { + "route": "/terms", + "title": "Terms", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Legal"], + "components": ["LegalContent"] + }, + { + "route": "/legal/privacy", + "title": "Privacy", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Legal"], + "components": ["LegalContent"] + }, + { + "route": "/legal/collection-notice", + "title": "Collection notice", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Legal"], + "components": ["LegalContent"] + }, + { + "route": "/legal/terms", + "title": "Terms", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Legal"], + "components": ["LegalContent"] + }, + { + "route": "/legal/xero-consent", + "title": "Xero consent", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Legal"], + "components": ["LegalContent"] + }, + { + "route": "/dashboard", + "title": "Dashboard", + "primaryCTA": "Role-based actions", + "secondaryCTAs": ["Logout"], + "templates": ["Dashboard"], + "components": ["DashboardLinks"] + }, + { + "route": "/dashboard/requests", + "title": "Requests", + "primaryCTA": "Submit booking", + "secondaryCTAs": ["Go back to main area"], + "templates": ["Dashboard"], + "components": ["RequestForm", "CompanyDetailsForm"] + }, + { + "route": "/dashboard/requests/status", + "title": "Request status", + "primaryCTA": "View request", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["StatusList"] + }, + { + "route": "/dashboard/requests/proposed", + "title": "Proposed workers", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["ProposedWorkers"] + }, + { + "route": "/dashboard/requests/[id]", + "title": "Request detail", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["RequestDetail"] + }, + { + "route": "/dashboard/timesheets", + "title": "Timesheets", + "primaryCTA": "Approve timesheets", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["TimesheetList"] + }, + { + "route": "/dashboard/worker", + "title": "Worker portal", + "primaryCTA": "Submit profile", + "secondaryCTAs": ["Save profile", "Save tickets", "Availability status"], + "templates": ["Dashboard"], + "components": ["WorkerProfileForm", "TicketsForm", "WorkExperienceForm"] + }, + { + "route": "/dashboard/worker/assignments", + "title": "Worker assignments", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["AssignmentsList"] + }, + { + "route": "/dashboard/worker/proposed", + "title": "Proposed positions", + "primaryCTA": "", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["ProposedPositions"] + }, + { + "route": "/dashboard/worker/timesheets", + "title": "Worker timesheets", + "primaryCTA": "Submit timesheet", + "secondaryCTAs": ["Save draft"], + "templates": ["Dashboard"], + "components": ["WorkerTimesheetForm"] + }, + { + "route": "/dashboard/worker/working-now", + "title": "Availability status", + "primaryCTA": "Save working status", + "secondaryCTAs": ["Back to worker portal"], + "templates": ["Dashboard"], + "components": ["AvailabilityForm"] + }, + { + "route": "/dashboard/admin", + "title": "Admin dashboard", + "primaryCTA": "Manage workers", + "secondaryCTAs": ["Manage clients", "View requests", "Access & roles"], + "templates": ["Dashboard"], + "components": ["AdminNav"] + }, + { + "route": "/dashboard/admin/requests", + "title": "Admin requests", + "primaryCTA": "View request detail", + "secondaryCTAs": ["Delete request"], + "templates": ["Dashboard"], + "components": ["AdminRequestList"] + }, + { + "route": "/dashboard/admin/clients", + "title": "Admin clients", + "primaryCTA": "View client", + "secondaryCTAs": ["Delete client"], + "templates": ["Dashboard"], + "components": ["AdminClientList"] + }, + { + "route": "/dashboard/admin/contractors", + "title": "Admin contractors", + "primaryCTA": "Update pay rate", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["ContractorTable"] + }, + { + "route": "/dashboard/admin/workers", + "title": "Admin workers", + "primaryCTA": "Approve worker", + "secondaryCTAs": ["Delete worker"], + "templates": ["Dashboard"], + "components": ["AdminWorkerList"] + }, + { + "route": "/dashboard/admin/access", + "title": "Admin access", + "primaryCTA": "Manage roles", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["AdminAccess"] + }, + { + "route": "/dashboard/admin/xero", + "title": "Xero integration", + "primaryCTA": "Connect Xero", + "secondaryCTAs": ["Test connection", "Disconnect"], + "templates": ["Dashboard"], + "components": ["XeroStatus"] + }, + { + "route": "/dashboard/admin/xero/accounting", + "title": "Xero accounting settings", + "primaryCTA": "Save settings", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["XeroAccountingSettings"] + }, + { + "route": "/dashboard/admin/xero/bill-failures", + "title": "Bill sync failures", + "primaryCTA": "Retry", + "secondaryCTAs": [], + "templates": ["Dashboard"], + "components": ["BillFailuresTable"] + } + ], + "navigation": { + "header": ["Home", "Labour Hire", "For Workers", "Technology", "Safety & Compliance", "Contact", "Admin login", "Login", "Create account"], + "footer": ["Home", "Labour Hire", "For Workers", "Technology", "Safety & Compliance", "Privacy", "Terms"] + }, + "journeys": [ + { + "name": "Employer / Hire flow", + "steps": ["/", "/labour-hire", "/login?next=/dashboard/requests", "/dashboard/requests"] + }, + { + "name": "Worker / Apply flow", + "steps": ["/", "/for-workers", "/register", "/dashboard/worker"] + }, + { + "name": "Contact flow", + "steps": ["/", "/contact", "/api/contact"] + } + ], + "uiInventory": { + "colors": [ + "#0b1f3b", + "#ff7a1a", + "#11b6a8", + "#212529", + "#6b7280", + "#f5f7fa", + "#ffffff", + "#e5e7eb" + ], + "typography": [ + "system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif", + "text-5xl, text-4xl, text-lg, text-sm, text-xs" + ], + "buttons": ["PrimaryButton", "SecondaryButton", "pillButton"], + "spacingNotes": "Tailwind spacing used (gap-6, space-y-6, py-6/py-10, mt-4). No explicit design tokens." + } +} diff --git a/siteready-website/THEME_COVERAGE_REPORT.md b/siteready-website/THEME_COVERAGE_REPORT.md new file mode 100644 index 000000000..795920eab --- /dev/null +++ b/siteready-website/THEME_COVERAGE_REPORT.md @@ -0,0 +1,114 @@ +# Theme Coverage & Mapping Report + +## 1) Theme sources & tokens +**Tailwind config**: `tailwind.config.js` +- Tokens: `srPrimary`, `srAccent`, `srTeal`, `srText`, `srMuted`, `srBg`, `srCard`, `srBorder` + +**CSS variables**: `src/app/globals.css` +- `--color-srPrimary`, `--color-srAccent`, `--color-srTeal`, `--color-srText`, `--color-srMuted`, `--color-srBg`, `--color-srCard`, `--color-srBorder` +- Body applies `bg-srBg text-srText` + +**Layout split** +- Marketing: `src/app/(marketing)/layout.tsx` -> `SiteLayout` +- Dashboard: `src/app/(dashboard)/layout.tsx` -> `DashboardLayout` + +**Gap** +- Only one token palette exists. Dual theme requires theme-scoped token overrides (e.g. `data-theme` on layout root) so marketing vs dashboard can swap palettes without touching component styles. + +## 2) UI element inventory (counts + components) +**Buttons** +- PrimaryButton usage: 21 +- SecondaryButton usage: 137 +- Legacy pillButton class (CSS): 15 +- Shared CTA button: `src/components/shared/cta-button.tsx` + +**Cards / containers** +- Marketing: `FeatureCard`, `RoleCard`, `StepCard`, `SectionWrapper` +- Dashboard: inline-styled cards in multiple pages (see non-token list below) + +**Badges / status chips** +- Many inline status colors in dashboard pages (see non-token list below) + +**Inputs** +- Marketing forms mostly tokenized, but dashboard forms still use non-token colors in `src/components/booking/BookLabourNowForm.module.css`. + +## 3) Non-tokenized color usage (file + line) +These will not update when dual theme is applied. Replace with semantic tokens or status tokens. + +See `non_token_colors.json` for the full list. Highest-impact areas: +- `src/app/(dashboard)/dashboard/admin/workers/page.tsx` (large block of inline status/card colors) +- `src/app/(dashboard)/dashboard/worker/page.tsx` (status chips + card colors) +- `src/components/booking/BookLabourNowForm.module.css` (many hard-coded colors) +- `src/components/header.tsx` and `src/components/footer.tsx` (non-token tailwind colors) + +## 4) Proposed semantic token mapping (dual-theme) +**Marketing theme (Option A)** +- `--sr-primary`: `#111827` (Charcoal) +- `--sr-accent`: `#FBBF24` (Yellow) +- `--sr-bg`: `#F5F7FA` +- `--sr-text`: `#111827` +- `--sr-muted`: `#6B7280` +- `--sr-border`: `#E5E7EB` + +**Dashboard theme (Option B)** +- `--sr-primary`: `#0B2A4A` (Navy) +- `--sr-accent`: `#38BDF8` (Sky) +- `--sr-bg`: `#F5F7FA` (or a slightly cooler `#F3F6FA`) +- `--sr-text`: `#0B2A4A` +- `--sr-muted`: `#64748B` +- `--sr-border`: `#E2E8F0` + +**Status tokens (global)** +- `--sr-success`, `--sr-warning`, `--sr-danger`, `--sr-neutral` +- Map inline success/warn/error colors (e.g., `#16a34a`, `#f59e0b`, `#ef4444`, `#64748b`) to these tokens. + +**Implementation recommendation** +- Add `data-theme="marketing"` and `data-theme="dashboard"` on layout root. +- Define theme overrides in `globals.css`: + - `[data-theme="marketing"] { --color-srPrimary: ... }` + - `[data-theme="dashboard"] { --color-srPrimary: ... }` +- Replace hard-coded hex and non-semantic Tailwind colors with CSS variables or `sr*` tokens. + +## 5) Copy / CTA consistency (key routes) +**/** +- H1: “Construction labour & cleaning, without the chaos†+- CTAs: “Book labour†(primary), “Find site work†(secondary) + +**/labour-hire** +- H1: “Construction labour hire – Sydney / NSW†+- Primary: “Submit request†(form submit) +- Secondary: “Log in / Create an account†(single block) + +**/for-workers** +- H1: “Find steady construction work in Sydney / NSW†+- Primary: “Apply now†+ +**/safety-compliance** +- H1: “Safety & Compliance†+- CTAs: “Book labour†(primary), “For workers†(secondary) + +**/contact** +- Primary: contact form submit + +**/dashboard*** +- Multiple equal-weight buttons on admin/worker pages. Recommend a single primary action per view. + +## 6) Recommendations (KISS-aligned) +**Quick wins (1–2 days)** +- Replace inline hex colors in dashboard pages with status tokens and `sr*` tokens. +- Tokenize `BookLabourNowForm.module.css` with CSS variables. +- Remove or retokenize legacy `src/components/header.tsx` and `src/components/footer.tsx` if unused. + +**Medium (3–7 days)** +- Introduce `data-theme` and dual token sets in `globals.css`. +- Create shared `StatusBadge` and `Card` components that consume tokens only. + +**Deep refactor (1–3 weeks)** +- Centralize all color usage into tokens and eliminate inline styles in dashboard pages. +- Standardize buttons to `PrimaryButton` / `SecondaryButton` / `TextLink` and remove legacy CTA components. + +## 7) Simplified navigation proposal +- Header: Hire, For Workers, Compliance, Contact +- CTA: Book labour +- Login as text link +- Admin login only in footer diff --git a/siteready-website/UI_INVENTORY.json b/siteready-website/UI_INVENTORY.json new file mode 100644 index 000000000..d860231b3 --- /dev/null +++ b/siteready-website/UI_INVENTORY.json @@ -0,0 +1,232 @@ +{ + "navigation": { + "header": [ + "Hire", + "For Workers", + "Compliance", + "Contact", + "Login", + "Book labour" + ], + "footer": [ + "Home", + "Labour Hire", + "For Workers", + "Technology", + "Safety & Compliance", + "Privacy", + "Terms", + "Admin login" + ] + }, + "uiInventory": [ + { + "filePath": "src/components/PrimaryButton.tsx", + "colorsUsed": [ + "bg-orange-400", + "text-slate-900" + ], + "tokenized": false, + "component": "PrimaryButton" + }, + { + "filePath": "src/components/SecondaryButton.tsx", + "colorsUsed": [ + "border-srBorder", + "text-srPrimary" + ], + "tokenized": true, + "component": "SecondaryButton" + }, + { + "filePath": "src/components/SiteHeader.tsx", + "colorsUsed": [ + "srPrimary", + "srAccent", + "srBorder" + ], + "tokenized": true, + "component": "SiteHeader" + }, + { + "filePath": "src/components/SiteFooter.tsx", + "colorsUsed": [ + "bg-srPrimary", + "text-white" + ], + "tokenized": true, + "component": "SiteFooter" + }, + { + "filePath": "src/components/booking/BookLabourNowForm.module.css", + "colorsUsed": [ + "#0b1f3b", + "#ff7a1a", + "#fef3c7" + ], + "tokenized": false, + "component": "BookLabourNowForm" + }, + { + "filePath": "src/app/(dashboard)/dashboard/**", + "colorsUsed": [ + "#e2e8f0", + "#f8fafc", + "#0f172a", + "#f87171" + ], + "tokenized": false, + "component": "Dashboard inline styles" + } + ], + "journeys": [ + { + "name": "Employer / Hire", + "steps": [ + "/", + "/labour-hire", + "/register", + "/dashboard/requests" + ] + }, + { + "name": "Worker / Apply", + "steps": [ + "/for-workers", + "/register", + "/dashboard/worker" + ] + }, + { + "name": "Contact / Booking", + "steps": [ + "/contact" + ] + } + ], + "pages": [ + { + "secondaryCTAs": [ + "Find site work" + ], + "primaryCTA": "Book labour", + "templates": [ + "SectionWrapper", + "SectionHeading" + ], + "components": [ + "PrimaryButton", + "SecondaryButton", + "FeatureCard", + "StepCard" + ], + "route": "/", + "title": "Construction labour & cleaning, without the chaos" + }, + { + "secondaryCTAs": [ + "Log in", + "Create an account" + ], + "primaryCTA": "Submit request", + "templates": [ + "SectionWrapper", + "SectionHeading" + ], + "components": [ + "BookLabourNowForm", + "FeatureCard", + "RoleCard", + "StepCard" + ], + "route": "/labour-hire", + "title": "Construction labour hire – Sydney / NSW" + }, + { + "secondaryCTAs": [], + "primaryCTA": "Apply now", + "templates": [ + "SectionWrapper", + "SectionHeading" + ], + "components": [ + "PrimaryButton", + "FeatureCard", + "RoleCard", + "StepCard" + ], + "route": "/for-workers", + "title": "Find steady construction work in Sydney / NSW" + }, + { + "secondaryCTAs": [ + "For workers" + ], + "primaryCTA": "Book labour", + "templates": [ + "SectionWrapper", + "SectionHeading" + ], + "components": [ + "PrimaryButton", + "SecondaryButton", + "FeatureCard" + ], + "route": "/safety-compliance", + "title": "Safety & Compliance" + }, + { + "secondaryCTAs": [], + "primaryCTA": "Submit", + "templates": [ + "SectionWrapper" + ], + "components": [ + "Contact form" + ], + "route": "/contact", + "title": "Contact" + }, + { + "secondaryCTAs": [], + "primaryCTA": "View requests", + "templates": [ + "DashboardLayout" + ], + "components": [ + "SecondaryButton" + ], + "route": "/dashboard", + "title": "Client dashboard" + }, + { + "secondaryCTAs": [ + "Save profile" + ], + "primaryCTA": "Submit profile", + "templates": [ + "DashboardLayout" + ], + "components": [ + "SecondaryButton" + ], + "route": "/dashboard/worker", + "title": "Worker portal" + }, + { + "secondaryCTAs": [ + "Manage clients", + "View requests" + ], + "primaryCTA": "Manage workers", + "templates": [ + "DashboardLayout" + ], + "components": [ + "SecondaryButton" + ], + "route": "/dashboard/admin", + "title": "Admin dashboard" + } + ] +} diff --git a/siteready-website/custom-worker.ts b/siteready-website/custom-worker.ts new file mode 100644 index 000000000..4ba9aceb3 --- /dev/null +++ b/siteready-website/custom-worker.ts @@ -0,0 +1,72 @@ +// Custom OpenNext worker entrypoint to add scheduled ingestion. +import { ingestUsiList } from "./src/lib/superfund/ingest"; +import { retryFailedContractorBills } from "./src/lib/jobs/retryFailedBills"; + +type WorkerEnv = { + DB?: unknown; +}; +type ScheduledEventLike = { cron?: string }; +type ExecutionContextLike = { waitUntil: (promise: Promise) => void }; + +const loadOpenNextWorker = async () => { + // @ts-expect-error: generated at build time + const mod = await import("./.open-next/worker.js"); + return (mod.default ?? mod) as unknown as { + fetch: (req: Request, env: WorkerEnv, ctx: ExecutionContextLike) => Promise; + scheduled?: (event: ScheduledEventLike, env: WorkerEnv, ctx: ExecutionContextLike) => Promise; + DOQueueHandler?: (...args: unknown[]) => unknown; + DOShardedTagCache?: (...args: unknown[]) => unknown; + BucketCachePurge?: (...args: unknown[]) => unknown; + }; +}; + +export const DOQueueHandler = (...args: unknown[]) => + loadOpenNextWorker().then((worker) => worker.DOQueueHandler?.(...args)); +export const DOShardedTagCache = (...args: unknown[]) => + loadOpenNextWorker().then((worker) => worker.DOShardedTagCache?.(...args)); +export const BucketCachePurge = (...args: unknown[]) => + loadOpenNextWorker().then((worker) => worker.BucketCachePurge?.(...args)); + +const customWorker = { + async fetch(req: Request, env: WorkerEnv, ctx: ExecutionContextLike) { + const worker = await loadOpenNextWorker(); + return worker.fetch(req, env, ctx); + }, + async scheduled(event: ScheduledEventLike, env: WorkerEnv, ctx: ExecutionContextLike) { + const worker = await loadOpenNextWorker(); + if (worker.scheduled) { + try { + await worker.scheduled(event, env, ctx); + } catch (error) { + console.error("OpenNext scheduled failed", error instanceof Error ? error.message : error); + } + } + if (!env.DB) return; + ctx.waitUntil( + (async () => { + try { + const result = await ingestUsiList(env.DB as never); + console.log("Super fund ingest complete", { + rowsParsed: result.rowsParsed, + rowsUpserted: result.rowsUpserted, + durationMs: result.durationMs, + }); + } catch (error) { + console.error("Super fund ingest failed", error instanceof Error ? error.message : error); + } + })(), + ); + ctx.waitUntil( + (async () => { + try { + const result = await retryFailedContractorBills({ db: env.DB as never, limit: 10 }); + console.log("Contractor bill retries complete", result); + } catch (error) { + console.error("Contractor bill retries failed", error instanceof Error ? error.message : error); + } + })(), + ); + }, +}; + +export default customWorker; diff --git a/siteready-website/db/migrations/000_init.sql b/siteready-website/db/migrations/000_init.sql new file mode 100644 index 000000000..0ac87adbf --- /dev/null +++ b/siteready-website/db/migrations/000_init.sql @@ -0,0 +1,174 @@ +-- D1 schema for labour-hire platform MVP +-- Tables are intentionally compact; adjust indexes once real traffic patterns are known. + +PRAGMA foreign_keys = ON; + +CREATE TABLE IF NOT EXISTS users ( + user_id TEXT PRIMARY KEY, + email TEXT UNIQUE, + password_hash TEXT, + role TEXT CHECK (role IN ('client','worker','staff')) NOT NULL, + next_path TEXT, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) +); + +CREATE TABLE IF NOT EXISTS clients ( + client_id TEXT PRIMARY KEY, + user_id TEXT REFERENCES users(user_id) ON DELETE SET NULL, + org_name TEXT, + contact_name TEXT, + contact_mobile TEXT, + contact_email TEXT, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) +); + +CREATE TABLE IF NOT EXISTS requests ( + request_id TEXT PRIMARY KEY, + client_id TEXT REFERENCES clients(client_id) ON DELETE CASCADE, + status TEXT CHECK (status IN ('draft','submitted','candidates_proposed','contract_signed','released','active','completed')) NOT NULL, + role_id TEXT NOT NULL, + required_tickets TEXT, -- JSON array + min_years_experience INTEGER NOT NULL DEFAULT 0, + site_address TEXT, + site_lat REAL, + site_lng REAL, + site_place_id TEXT, + site_map_url TEXT, + shift_start_time TEXT, + shift_end_time TEXT, + headcount INTEGER NOT NULL DEFAULT 1, + start_date TEXT, + end_date TEXT, + notes TEXT, + urgency_level TEXT, + contract_signed INTEGER NOT NULL DEFAULT 0, + release_to_client INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + created_by TEXT, + updated_by TEXT +); + +CREATE TABLE IF NOT EXISTS workers ( + worker_id TEXT PRIMARY KEY, + user_id TEXT REFERENCES users(user_id) ON DELETE SET NULL, + company_number TEXT UNIQUE, + name TEXT, + mobile TEXT, + role_id TEXT NOT NULL, + years_experience INTEGER NOT NULL DEFAULT 0, + verified_status TEXT CHECK (verified_status IN ('unverified','pending','verified')) NOT NULL DEFAULT 'unverified', + last_verification_date TEXT, + base_lat REAL, + base_lng REAL, + preferred_radius_km INTEGER, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) +); + +CREATE TABLE IF NOT EXISTS worker_tickets ( + ticket_id TEXT PRIMARY KEY, + worker_id TEXT REFERENCES workers(worker_id) ON DELETE CASCADE, + type TEXT NOT NULL, + front_image_key TEXT, + back_image_key TEXT, + verified INTEGER NOT NULL DEFAULT 0, + visible_to_clients INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) +); + +CREATE TABLE IF NOT EXISTS worker_availability ( + availability_id TEXT PRIMARY KEY, + worker_id TEXT REFERENCES workers(worker_id) ON DELETE CASCADE, + start_datetime TEXT NOT NULL, + end_datetime TEXT NOT NULL, + notes TEXT, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) +); + +CREATE TABLE IF NOT EXISTS assignments ( + assignment_id TEXT PRIMARY KEY, + request_id TEXT REFERENCES requests(request_id) ON DELETE CASCADE, + worker_id TEXT REFERENCES workers(worker_id) ON DELETE CASCADE, + client_id TEXT REFERENCES clients(client_id) ON DELETE CASCADE, + status TEXT CHECK (status IN ('proposed','contracted','released','active','completed')) NOT NULL, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + created_by TEXT, + updated_by TEXT +); + +CREATE TABLE IF NOT EXISTS timesheets ( + timesheet_id TEXT PRIMARY KEY, + assignment_id TEXT REFERENCES assignments(assignment_id) ON DELETE CASCADE, + date TEXT NOT NULL, + hours REAL NOT NULL, + status TEXT CHECK (status IN ('pending','confirmed','queried')) NOT NULL DEFAULT 'pending', + note TEXT, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + confirmed_at TEXT, + queried_at TEXT +); + +CREATE TABLE IF NOT EXISTS incidents ( + incident_id TEXT PRIMARY KEY, + assignment_id TEXT REFERENCES assignments(assignment_id) ON DELETE CASCADE, + type TEXT, + severity TEXT, + date TEXT, + description TEXT, + evidence_urls TEXT, -- JSON array + status TEXT CHECK (status IN ('logged','under_review','resolved')) NOT NULL DEFAULT 'logged', + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + created_by TEXT +); + +CREATE TABLE IF NOT EXISTS praise ( + praise_id TEXT PRIMARY KEY, + assignment_id TEXT REFERENCES assignments(assignment_id) ON DELETE CASCADE, + tag TEXT, + note TEXT, + date TEXT, + created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), + created_by TEXT +); + +CREATE TABLE IF NOT EXISTS performance_metrics ( + worker_id TEXT PRIMARY KEY REFERENCES workers(worker_id) ON DELETE CASCADE, + on_time_rate REAL, + safety_score REAL, + teamwork_score REAL, + incident_rate REAL, + reliability_index REAL, + updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')) +); + +CREATE TABLE IF NOT EXISTS performance_history ( + metric_id TEXT PRIMARY KEY, + worker_id TEXT REFERENCES workers(worker_id) ON DELETE CASCADE, + period_start TEXT, + period_end TEXT, + values_json TEXT, + narrative_highlights TEXT +); + +CREATE TABLE IF NOT EXISTS audit_logs ( + audit_id TEXT PRIMARY KEY, + actor_id TEXT, + actor_role TEXT, + action TEXT, + entity_type TEXT, + entity_id TEXT, + timestamp INTEGER NOT NULL DEFAULT (strftime('%s','now')), + diff_json TEXT +); + +-- Simple indexes +CREATE INDEX IF NOT EXISTS idx_requests_client ON requests(client_id); +CREATE INDEX IF NOT EXISTS idx_assignments_request ON assignments(request_id); +CREATE INDEX IF NOT EXISTS idx_assignments_worker ON assignments(worker_id); +CREATE INDEX IF NOT EXISTS idx_timesheets_assignment ON timesheets(assignment_id); diff --git a/siteready-website/db/migrations/001_safe.sql b/siteready-website/db/migrations/001_safe.sql new file mode 100644 index 000000000..affbd48aa --- /dev/null +++ b/siteready-website/db/migrations/001_safe.sql @@ -0,0 +1,89 @@ +-- Safe, idempotent migration for new tables/columns. +-- Uses IF NOT EXISTS to avoid failures on repeated runs. + +PRAGMA foreign_keys = ON; + +-- Legal acceptances +CREATE TABLE IF NOT EXISTS legal_acceptances ( + acceptance_id TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + role TEXT NOT NULL, + document_type TEXT NOT NULL, + document_version TEXT NOT NULL, + accepted_at TEXT NOT NULL, + ip_address TEXT, + user_agent TEXT +); + +-- Xero integration +CREATE TABLE IF NOT EXISTS xero_connections ( + connection_id TEXT PRIMARY KEY, + tenant_id TEXT NOT NULL, + access_token TEXT NOT NULL, + refresh_token_encrypted TEXT NOT NULL, + expires_at INTEGER NOT NULL, + scope TEXT NOT NULL, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS xero_employee_map ( + worker_id TEXT PRIMARY KEY, + xero_employee_id TEXT NOT NULL, + payroll_calendar_id TEXT, + ordinary_earnings_rate_id TEXT, + super_fund_id TEXT, + last_synced_at TEXT, + sync_status TEXT, + sync_error TEXT +); + +CREATE TABLE IF NOT EXISTS xero_settings ( + settings_id TEXT PRIMARY KEY, + payroll_calendar_id TEXT, + ordinary_earnings_rate_id TEXT, + super_fund_id TEXT, + updated_at INTEGER NOT NULL +); + +-- Timesheet lines (new table) +CREATE TABLE IF NOT EXISTS timesheet_lines ( + line_id TEXT PRIMARY KEY, + timesheet_id TEXT NOT NULL, + work_date TEXT NOT NULL, + hours_decimal REAL NOT NULL, + earnings_code TEXT NOT NULL, + site_id TEXT, + job_id TEXT, + shift_id TEXT, + notes TEXT +); + +-- Super fund products (USI extract) +CREATE TABLE IF NOT EXISTS super_fund_products ( + usi TEXT PRIMARY KEY, + abn TEXT NOT NULL, + fund_name TEXT NOT NULL, + product_name TEXT NOT NULL, + contribution_restrictions INTEGER NOT NULL DEFAULT 0, + from_date TEXT, + to_date TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) +); + +CREATE INDEX IF NOT EXISTS idx_super_fund_products_fund_name ON super_fund_products(fund_name); +CREATE INDEX IF NOT EXISTS idx_super_fund_products_abn ON super_fund_products(abn); + +-- Super fund ingestion status (single row) +CREATE TABLE IF NOT EXISTS super_fund_ingestion_status ( + id INTEGER PRIMARY KEY CHECK (id = 1), + last_success_at TEXT, + last_run_at TEXT, + last_row_count INTEGER, + last_error TEXT, + source_url TEXT, + source_version TEXT +); +-- Note: existing tables (timesheets/workers/worker_tickets) are evolved at runtime +-- via application-level ALTER TABLE calls to avoid D1 syntax limitations here. diff --git a/siteready-website/db/migrations/002_tax_declaration.sql b/siteready-website/db/migrations/002_tax_declaration.sql new file mode 100644 index 000000000..0258ae02e --- /dev/null +++ b/siteready-website/db/migrations/002_tax_declaration.sql @@ -0,0 +1,29 @@ +-- Worker tax declaration (NAT 3092 aligned) +PRAGMA foreign_keys = ON; + +CREATE TABLE IF NOT EXISTS worker_tax_declarations ( + worker_id TEXT PRIMARY KEY REFERENCES workers(worker_id) ON DELETE CASCADE, + tax_residency_status TEXT NOT NULL, + employment_basis TEXT NOT NULL, + claim_tax_free_threshold INTEGER NOT NULL, + has_study_training_loan INTEGER NOT NULL, + has_sfss_debt INTEGER NOT NULL, + tfn_status TEXT NOT NULL, + tfn_encrypted TEXT, + tfn_last4 TEXT, + tfn_updated_at TEXT, + tfn_declaration_full_name TEXT, + tfn_declaration_signed INTEGER NOT NULL DEFAULT 0, + tfn_declaration_signed_at TEXT, + tfn_declaration_signed_ip TEXT, + tfn_declaration_signed_user_agent TEXT, + changed_surname_since_ato INTEGER NOT NULL DEFAULT 0, + previous_family_name TEXT, + eligible_to_receive_leave_loading INTEGER NOT NULL DEFAULT 0, + has_student_startup_loan INTEGER NOT NULL DEFAULT 0, + has_trade_support_loan_debt INTEGER NOT NULL DEFAULT 0, + withholding_additional_amount_cents INTEGER, + withholding_variation_percent INTEGER +); + +CREATE INDEX IF NOT EXISTS idx_worker_tax_declarations_worker ON worker_tax_declarations(worker_id); diff --git a/siteready-website/db/migrations/003_dual_engagement.sql b/siteready-website/db/migrations/003_dual_engagement.sql new file mode 100644 index 000000000..907061c27 --- /dev/null +++ b/siteready-website/db/migrations/003_dual_engagement.sql @@ -0,0 +1,56 @@ +-- Dual engagement (employee vs contractor) + Xero accounting mappings +PRAGMA foreign_keys = ON; + +ALTER TABLE workers ADD COLUMN engagement_type TEXT NOT NULL DEFAULT 'EMPLOYEE_CASUAL'; + +ALTER TABLE worker_tax_declarations ADD COLUMN tfn_not_quoted_ack INTEGER NOT NULL DEFAULT 0; + +CREATE TABLE IF NOT EXISTS contractor_company ( + worker_id TEXT PRIMARY KEY, + company_name TEXT NOT NULL, + company_structure TEXT NOT NULL, + abn TEXT NOT NULL, + acn TEXT, + website TEXT, + company_tfn_encrypted TEXT, + address_json TEXT NOT NULL, + bank_account_name TEXT NOT NULL, + bank_bsb TEXT NOT NULL, + bank_account_number_encrypted TEXT NOT NULL, + bank_account_last4 TEXT NOT NULL, + contractor_pay_rate_cents INTEGER, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')) +); + +CREATE TABLE IF NOT EXISTS xero_contact_map ( + id TEXT PRIMARY KEY, + entity_type TEXT NOT NULL, + local_entity_id TEXT NOT NULL, + xero_contact_id TEXT NOT NULL, + abn TEXT, + last_synced_at TEXT, + sync_status TEXT, + sync_error TEXT, + UNIQUE(entity_type, local_entity_id) +); + +CREATE TABLE IF NOT EXISTS xero_transaction_map ( + id TEXT PRIMARY KEY, + timesheet_id TEXT NOT NULL UNIQUE, + xero_payroll_timesheet_id TEXT, + xero_bill_invoice_id TEXT, + last_synced_at TEXT, + sync_status TEXT, + sync_error TEXT +); + +CREATE TABLE IF NOT EXISTS xero_accounting_settings ( + id INTEGER PRIMARY KEY CHECK (id = 1), + default_purchases_account_code TEXT NOT NULL, + bill_status_mode TEXT NOT NULL DEFAULT 'DRAFT' +); + +INSERT INTO xero_accounting_settings (id, default_purchases_account_code, bill_status_mode) +SELECT 1, '000', 'DRAFT' +WHERE NOT EXISTS (SELECT 1 FROM xero_accounting_settings WHERE id = 1); diff --git a/siteready-website/db/migrations/004_xero_accounting_hardening.sql b/siteready-website/db/migrations/004_xero_accounting_hardening.sql new file mode 100644 index 000000000..c12832396 --- /dev/null +++ b/siteready-website/db/migrations/004_xero_accounting_hardening.sql @@ -0,0 +1,6 @@ +-- Harden Xero Accounting sync (idempotency + reservation state) +PRAGMA foreign_keys = ON; + +ALTER TABLE xero_transaction_map ADD COLUMN bill_idempotency_key TEXT; +ALTER TABLE xero_transaction_map ADD COLUMN sync_state TEXT; +ALTER TABLE xero_transaction_map ADD COLUMN attempts INTEGER DEFAULT 0; diff --git a/siteready-website/db/migrations/005_tax_declaration_documents.sql b/siteready-website/db/migrations/005_tax_declaration_documents.sql new file mode 100644 index 000000000..d0e760fe9 --- /dev/null +++ b/siteready-website/db/migrations/005_tax_declaration_documents.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS worker_tax_declaration_documents ( + doc_id TEXT PRIMARY KEY, + worker_id TEXT NOT NULL, + doc_type TEXT NOT NULL, + file_key TEXT NOT NULL, + file_name TEXT NOT NULL, + file_type TEXT, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')), + UNIQUE(worker_id, doc_type) +); + +CREATE INDEX IF NOT EXISTS idx_worker_tax_declaration_documents_worker + ON worker_tax_declaration_documents (worker_id); diff --git a/siteready-website/db/migrations/006_client_signups.sql b/siteready-website/db/migrations/006_client_signups.sql new file mode 100644 index 000000000..901939fa8 --- /dev/null +++ b/siteready-website/db/migrations/006_client_signups.sql @@ -0,0 +1,18 @@ +CREATE TABLE IF NOT EXISTS client_signups ( + id TEXT PRIMARY KEY, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + email TEXT NOT NULL, + mobile TEXT NOT NULL, + whatsapp TEXT, + status TEXT NOT NULL DEFAULT 'email_sent', + onboarding_token_hash TEXT NOT NULL, + onboarding_token_expires_at INTEGER NOT NULL, + email_sent_at INTEGER NOT NULL, + email_clicked_at INTEGER, + converted_user_id TEXT, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS client_signups_email_unique ON client_signups(email); diff --git a/siteready-website/deploy-run-20390253334.log.txt b/siteready-website/deploy-run-20390253334.log.txt new file mode 100644 index 000000000..061cb92c7 --- /dev/null +++ b/siteready-website/deploy-run-20390253334.log.txt @@ -0,0 +1,298 @@ +deploy Set up job ∩╗â”2025-12-20T07:00:44.4668495Z Current runner version: '2.330.0' +deploy Set up job 2025-12-20T07:00:44.4723819Z ##[group]Runner Image Provisioner +deploy Set up job 2025-12-20T07:00:44.4725093Z Hosted Compute Agent +deploy Set up job 2025-12-20T07:00:44.4726237Z Version: 20251211.462 +deploy Set up job 2025-12-20T07:00:44.4727646Z Commit: 6cbad8c2bb55d58165063d031ccabf57e2d2db61 +deploy Set up job 2025-12-20T07:00:44.4728815Z Build Date: 2025-12-11T16:28:49Z +deploy Set up job 2025-12-20T07:00:44.4729868Z Worker ID: {d93a4cd1-c0d9-4808-aad3-99eba3b6cdf1} +deploy Set up job 2025-12-20T07:00:44.4731082Z ##[endgroup] +deploy Set up job 2025-12-20T07:00:44.4731874Z ##[group]Operating System +deploy Set up job 2025-12-20T07:00:44.4732723Z Ubuntu +deploy Set up job 2025-12-20T07:00:44.4733597Z 24.04.3 +deploy Set up job 2025-12-20T07:00:44.4734296Z LTS +deploy Set up job 2025-12-20T07:00:44.4735003Z ##[endgroup] +deploy Set up job 2025-12-20T07:00:44.4735892Z ##[group]Runner Image +deploy Set up job 2025-12-20T07:00:44.4736992Z Image: ubuntu-24.04 +deploy Set up job 2025-12-20T07:00:44.4737835Z Version: 20251215.174.1 +deploy Set up job 2025-12-20T07:00:44.4739733Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20251215.174/images/ubuntu/Ubuntu2404-Readme.md +deploy Set up job 2025-12-20T07:00:44.4742084Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20251215.174 +deploy Set up job 2025-12-20T07:00:44.4744047Z ##[endgroup] +deploy Set up job 2025-12-20T07:00:44.4745756Z ##[group]GITHUB_TOKEN Permissions +deploy Set up job 2025-12-20T07:00:44.4748502Z Contents: read +deploy Set up job 2025-12-20T07:00:44.4749315Z Metadata: read +deploy Set up job 2025-12-20T07:00:44.4750177Z ##[endgroup] +deploy Set up job 2025-12-20T07:00:44.4753507Z Secret source: Actions +deploy Set up job 2025-12-20T07:00:44.4754617Z Prepare workflow directory +deploy Set up job 2025-12-20T07:00:44.5713206Z Prepare all required actions +deploy Set up job 2025-12-20T07:00:44.5825581Z Getting action download info +deploy Set up job 2025-12-20T07:00:44.8650799Z Download action repository 'actions/checkout@v4' (SHA:34e114876b0b11c390a56381ad16ebd13914f8d5) +deploy Set up job 2025-12-20T07:00:45.0091140Z Download action repository 'actions/setup-node@v4' (SHA:49933ea5288caeca8642d1e84afbd3f7d6820020) +deploy Set up job 2025-12-20T07:00:45.1992347Z Complete job name: deploy +deploy Checkout ∩╗â”2025-12-20T07:00:45.2625636Z ##[group]Run actions/checkout@v4 +deploy Checkout 2025-12-20T07:00:45.2626888Z with: +deploy Checkout 2025-12-20T07:00:45.2627362Z repository: info936/siteready-website +deploy Checkout 2025-12-20T07:00:45.2628124Z token: *** +deploy Checkout 2025-12-20T07:00:45.2628543Z ssh-strict: true +deploy Checkout 2025-12-20T07:00:45.2628966Z ssh-user: git +deploy Checkout 2025-12-20T07:00:45.2629397Z persist-credentials: true +deploy Checkout 2025-12-20T07:00:45.2629873Z clean: true +deploy Checkout 2025-12-20T07:00:45.2630304Z sparse-checkout-cone-mode: true +deploy Checkout 2025-12-20T07:00:45.2630816Z fetch-depth: 1 +deploy Checkout 2025-12-20T07:00:45.2631241Z fetch-tags: false +deploy Checkout 2025-12-20T07:00:45.2631671Z show-progress: true +deploy Checkout 2025-12-20T07:00:45.2632120Z lfs: false +deploy Checkout 2025-12-20T07:00:45.2632523Z submodules: false +deploy Checkout 2025-12-20T07:00:45.2632977Z set-safe-directory: true +deploy Checkout 2025-12-20T07:00:45.2633687Z env: +deploy Checkout 2025-12-20T07:00:45.2634308Z CLOUDFLARE_API_TOKEN: *** +deploy Checkout 2025-12-20T07:00:45.2634906Z CLOUDFLARE_ACCOUNT_ID: *** +deploy Checkout 2025-12-20T07:00:45.2635382Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.3794372Z Syncing repository: info936/siteready-website +deploy Checkout 2025-12-20T07:00:45.3797142Z ##[group]Getting Git version info +deploy Checkout 2025-12-20T07:00:45.3798732Z Working directory is '/home/runner/work/siteready-website/siteready-website' +deploy Checkout 2025-12-20T07:00:45.3800719Z [command]/usr/bin/git version +deploy Checkout 2025-12-20T07:00:45.3999554Z git version 2.52.0 +deploy Checkout 2025-12-20T07:00:45.4003564Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.4010949Z Temporarily overriding HOME='/home/runner/work/_temp/a3966cab-512b-4a58-8ff7-16ffbe6babca' before making global git config changes +deploy Checkout 2025-12-20T07:00:45.4013746Z Adding repository directory to the temporary git global config as a safe directory +deploy Checkout 2025-12-20T07:00:45.4016418Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/siteready-website/siteready-website +deploy Checkout 2025-12-20T07:00:45.4021878Z Deleting the contents of '/home/runner/work/siteready-website/siteready-website' +deploy Checkout 2025-12-20T07:00:45.4023894Z ##[group]Initializing the repository +deploy Checkout 2025-12-20T07:00:45.4025910Z [command]/usr/bin/git init /home/runner/work/siteready-website/siteready-website +deploy Checkout 2025-12-20T07:00:45.4949285Z hint: Using 'master' as the name for the initial branch. This default branch name +deploy Checkout 2025-12-20T07:00:45.4952952Z hint: will change to "main" in Git 3.0. To configure the initial branch name +deploy Checkout 2025-12-20T07:00:45.4955099Z hint: to use in all of your new repositories, which will suppress this warning, +deploy Checkout 2025-12-20T07:00:45.4956786Z hint: call: +deploy Checkout 2025-12-20T07:00:45.4957517Z hint: +deploy Checkout 2025-12-20T07:00:45.4958400Z hint: git config --global init.defaultBranch +deploy Checkout 2025-12-20T07:00:45.4959497Z hint: +deploy Checkout 2025-12-20T07:00:45.4960566Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and +deploy Checkout 2025-12-20T07:00:45.4962281Z hint: 'development'. The just-created branch can be renamed via this command: +deploy Checkout 2025-12-20T07:00:45.4963653Z hint: +deploy Checkout 2025-12-20T07:00:45.4964370Z hint: git branch -m +deploy Checkout 2025-12-20T07:00:45.4965192Z hint: +deploy Checkout 2025-12-20T07:00:45.4966534Z hint: Disable this message with "git config set advice.defaultBranchName false" +deploy Checkout 2025-12-20T07:00:45.4968768Z Initialized empty Git repository in /home/runner/work/siteready-website/siteready-website/.git/ +deploy Checkout 2025-12-20T07:00:45.4972162Z [command]/usr/bin/git remote add origin https://github.com/info936/siteready-website +deploy Checkout 2025-12-20T07:00:45.4975277Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.4976951Z ##[group]Disabling automatic garbage collection +deploy Checkout 2025-12-20T07:00:45.4978221Z [command]/usr/bin/git config --local gc.auto 0 +deploy Checkout 2025-12-20T07:00:45.4980911Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.4982208Z ##[group]Setting up auth +deploy Checkout 2025-12-20T07:00:45.4983491Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand +deploy Checkout 2025-12-20T07:00:45.4987678Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" +deploy Checkout 2025-12-20T07:00:45.4992146Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader +deploy Checkout 2025-12-20T07:00:45.4997291Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" +deploy Checkout 2025-12-20T07:00:45.5252543Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: +deploy Checkout 2025-12-20T07:00:45.5256327Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url +deploy Checkout 2025-12-20T07:00:45.5429551Z [command]/usr/bin/git config --local http.https://github.com/.extraheader AUTHORIZATION: basic *** +deploy Checkout 2025-12-20T07:00:45.5555686Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.5557119Z ##[group]Fetching the repository +deploy Checkout 2025-12-20T07:00:45.5559694Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +e76583bf6c476593ecce0b7e2d6a18032bf3779c:refs/remotes/origin/main +deploy Checkout 2025-12-20T07:00:45.7618282Z From https://github.com/info936/siteready-website +deploy Checkout 2025-12-20T07:00:45.7620351Z * [new ref] e76583bf6c476593ecce0b7e2d6a18032bf3779c -> origin/main +deploy Checkout 2025-12-20T07:00:45.7626585Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.7627925Z ##[group]Determining the checkout info +deploy Checkout 2025-12-20T07:00:45.7629433Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.7647730Z [command]/usr/bin/git sparse-checkout disable +deploy Checkout 2025-12-20T07:00:45.7700298Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig +deploy Checkout 2025-12-20T07:00:45.7727379Z ##[group]Checking out the ref +deploy Checkout 2025-12-20T07:00:45.7730991Z [command]/usr/bin/git checkout --progress --force -B main refs/remotes/origin/main +deploy Checkout 2025-12-20T07:00:45.7842255Z Switched to a new branch 'main' +deploy Checkout 2025-12-20T07:00:45.7846533Z branch 'main' set up to track 'origin/main'. +deploy Checkout 2025-12-20T07:00:45.7855185Z ##[endgroup] +deploy Checkout 2025-12-20T07:00:45.7891980Z [command]/usr/bin/git log -1 --format=%H +deploy Checkout 2025-12-20T07:00:45.7914369Z e76583bf6c476593ecce0b7e2d6a18032bf3779c +deploy Setup Node ∩╗â”2025-12-20T07:00:45.8167424Z ##[group]Run actions/setup-node@v4 +deploy Setup Node 2025-12-20T07:00:45.8168098Z with: +deploy Setup Node 2025-12-20T07:00:45.8168526Z node-version: 20 +deploy Setup Node 2025-12-20T07:00:45.8169006Z cache: npm +deploy Setup Node 2025-12-20T07:00:45.8169448Z always-auth: false +deploy Setup Node 2025-12-20T07:00:45.8169951Z check-latest: false +deploy Setup Node 2025-12-20T07:00:45.8170651Z token: *** +deploy Setup Node 2025-12-20T07:00:45.8171082Z env: +deploy Setup Node 2025-12-20T07:00:45.8171957Z CLOUDFLARE_API_TOKEN: *** +deploy Setup Node 2025-12-20T07:00:45.8172669Z CLOUDFLARE_ACCOUNT_ID: *** +deploy Setup Node 2025-12-20T07:00:45.8173231Z ##[endgroup] +deploy Setup Node 2025-12-20T07:00:46.0057137Z Found in cache @ /opt/hostedtoolcache/node/20.19.6/x64 +deploy Setup Node 2025-12-20T07:00:46.0059967Z ##[group]Environment details +deploy Setup Node 2025-12-20T07:00:46.4793877Z node: v20.19.6 +deploy Setup Node 2025-12-20T07:00:46.4797583Z npm: 10.8.2 +deploy Setup Node 2025-12-20T07:00:46.4798682Z yarn: 1.22.22 +deploy Setup Node 2025-12-20T07:00:46.4801214Z ##[endgroup] +deploy Setup Node 2025-12-20T07:00:46.4822608Z [command]/opt/hostedtoolcache/node/20.19.6/x64/bin/npm config get cache +deploy Setup Node 2025-12-20T07:00:46.6116177Z /home/runner/.npm +deploy Setup Node 2025-12-20T07:00:46.7482629Z npm cache is not found +deploy Install ∩╗â”2025-12-20T07:00:46.7655215Z ##[group]Run npm ci +deploy Install 2025-12-20T07:00:46.7656503Z npm ci +deploy Install 2025-12-20T07:00:46.7701620Z shell: /usr/bin/bash -e {0} +deploy Install 2025-12-20T07:00:46.7702704Z env: +deploy Install 2025-12-20T07:00:46.7704181Z CLOUDFLARE_API_TOKEN: *** +deploy Install 2025-12-20T07:00:46.7705494Z CLOUDFLARE_ACCOUNT_ID: *** +deploy Install 2025-12-20T07:00:46.7706884Z ##[endgroup] +deploy Install 2025-12-20T07:00:52.5353484Z npm warn deprecated node-domexception@1.0.0: Use your platform's native DOMException instead +deploy Install 2025-12-20T07:01:09.5914627Z +deploy Install 2025-12-20T07:01:09.5937403Z added 919 packages, and audited 920 packages in 23s +deploy Install 2025-12-20T07:01:09.5938324Z +deploy Install 2025-12-20T07:01:09.5938849Z 204 packages are looking for funding +deploy Install 2025-12-20T07:01:09.5939847Z run `npm fund` for details +deploy Install 2025-12-20T07:01:09.5940420Z +deploy Install 2025-12-20T07:01:09.5940870Z found 0 vulnerabilities +deploy Lint ∩╗â”2025-12-20T07:01:09.6445105Z ##[group]Run npm run lint +deploy Lint 2025-12-20T07:01:09.6445435Z npm run lint +deploy Lint 2025-12-20T07:01:09.6482418Z shell: /usr/bin/bash -e {0} +deploy Lint 2025-12-20T07:01:09.6482704Z env: +deploy Lint 2025-12-20T07:01:09.6483251Z CLOUDFLARE_API_TOKEN: *** +deploy Lint 2025-12-20T07:01:09.6483602Z CLOUDFLARE_ACCOUNT_ID: *** +deploy Lint 2025-12-20T07:01:09.6483853Z ##[endgroup] +deploy Lint 2025-12-20T07:01:09.7612844Z +deploy Lint 2025-12-20T07:01:09.7615741Z > siteready-website@0.1.0 lint +deploy Lint 2025-12-20T07:01:09.7622473Z > eslint +deploy Lint 2025-12-20T07:01:09.7622761Z +deploy Build + Deploy (keep vars) ∩╗â”2025-12-20T07:01:14.1408699Z ##[group]Run npm run cf:deploy:keep-vars +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.1409074Z npm run cf:deploy:keep-vars +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.1441662Z shell: /usr/bin/bash -e {0} +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.1441905Z env: +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.1442245Z CLOUDFLARE_API_TOKEN: *** +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.1442554Z CLOUDFLARE_ACCOUNT_ID: *** +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.1442779Z ##[endgroup] +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.2583599Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.2587288Z > siteready-website@0.1.0 cf:deploy:keep-vars +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.2588505Z > opennextjs-cloudflare build && opennextjs-cloudflare deploy -- --keep-vars +deploy Build + Deploy (keep vars) 2025-12-20T07:01:14.2590661Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.0298722Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.0300260Z ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.0317288Z Γöé OpenNext ΓÇö Cloudflare build Γöé +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.0318506Z ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.0318966Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1255664Z App directory: /home/runner/work/siteready-website/siteready-website +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1260577Z WARN Next.js 16 is not fully supported yet! Some features may not work as expected. +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1284661Z Next.js version : 16.0.10 +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1285789Z @opennextjs/cloudflare version: 1.14.6 +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1286803Z @opennextjs/aws version: 3.9.6 +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1297052Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1298040Z ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1298984Z Γöé OpenNext ΓÇö Building Next.js app Γöé +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1300210Z ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.1300766Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.2553712Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.2554286Z > siteready-website@0.1.0 build +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.2554780Z > next build +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.2554995Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:16.9902635Z ΓÜá No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0079608Z Attention: Next.js now collects completely anonymous telemetry regarding usage. +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0086274Z This information is used to shape Next.js' roadmap and prioritize features. +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0091833Z You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL: +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0096916Z https://nextjs.org/telemetry +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0099415Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0220107Z Γû▓ Next.js 16.0.10 (Turbopack) +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0221491Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:17.0841778Z Creating an optimized production build ... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:25.9097657Z Γ£ô Compiled successfully in 8.3s +deploy Build + Deploy (keep vars) 2025-12-20T07:01:25.9163579Z Running TypeScript ... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:31.8508934Z Collecting page data using 1 worker ... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:32.2481059Z Generating static pages using 1 worker (0/12) ... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:32.6247503Z Generating static pages using 1 worker (3/12) +deploy Build + Deploy (keep vars) 2025-12-20T07:01:32.7739382Z Generating static pages using 1 worker (6/12) +deploy Build + Deploy (keep vars) 2025-12-20T07:01:32.8602411Z Generating static pages using 1 worker (9/12) +deploy Build + Deploy (keep vars) 2025-12-20T07:01:32.8608340Z Γ£ô Generating static pages using 1 worker (12/12) in 612.8ms +deploy Build + Deploy (keep vars) 2025-12-20T07:01:32.8725189Z Finalizing page optimization ... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2152179Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2170649Z Route (app) +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2172468Z Γöî Γùï / +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2172906Z Γö£ Γùï /_not-found +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2173393Z Γö£ ƒ /api/applications +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2173889Z Γö£ ƒ /api/bookings +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2174333Z Γö£ ƒ /api/contact +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2174754Z Γö£ Γùï /contact +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2175165Z Γö£ Γùï /for-workers +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2175597Z Γö£ Γùï /labour-hire +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2176306Z Γö£ Γùï /safety-compliance +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2176800Z Γöö Γùï /technology +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2177026Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2177039Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2177565Z Γùï (Static) prerendered as static content +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2178155Z ƒ (Dynamic) server-rendered on demand +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2178453Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2535314Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2545895Z ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2549109Z Γöé OpenNext ΓÇö Generating bundle Γöé +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2550049Z ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2551302Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.2862279Z Bundling middleware function... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.3191609Z Bundling static assets... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.3248048Z Bundling cache assets... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:33.3446786Z Building server function: default... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:35.9776270Z Applying code patches: 2.377s +deploy Build + Deploy (keep vars) 2025-12-20T07:01:36.3136887Z # copyPackageTemplateFiles +deploy Build + Deploy (keep vars) 2025-12-20T07:01:36.3168944Z ⚙️ Bundling the OpenNext server... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:36.3170743Z  +deploy Build + Deploy (keep vars) 2025-12-20T07:01:38.6177807Z Worker saved in `.open-next/worker.js` 🚀 +deploy Build + Deploy (keep vars) 2025-12-20T07:01:38.6178421Z  +deploy Build + Deploy (keep vars) 2025-12-20T07:01:38.6182509Z OpenNext build complete. +deploy Build + Deploy (keep vars) 2025-12-20T07:01:40.3868339Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:40.3869957Z ΓöîΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÉ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:40.3870954Z Γöé OpenNext ΓÇö Cloudflare deploy Γöé +deploy Build + Deploy (keep vars) 2025-12-20T07:01:40.3874142Z ΓööΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÿ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:40.3878636Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:40.9940777Z Incremental cache does not need populating +deploy Build + Deploy (keep vars) 2025-12-20T07:01:40.9943635Z Tag cache does not need populating +deploy Build + Deploy (keep vars) 2025-12-20T07:01:42.3394277Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:42.3399312Z ⛅️ wrangler 4.56.0 +deploy Build + Deploy (keep vars) 2025-12-20T07:01:42.3403605Z ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ +deploy Build + Deploy (keep vars) 2025-12-20T07:01:43.8118016Z 🌀 Building list of assets... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:43.8176628Z ✨ Read 29 files from the assets directory /home/runner/work/siteready-website/siteready-website/.open-next/assets +deploy Build + Deploy (keep vars) 2025-12-20T07:01:43.8467111Z 🌀 Starting asset upload... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:45.7098555Z 🌀 Found 1 new or modified static asset to upload. Proceeding with upload... +deploy Build + Deploy (keep vars) 2025-12-20T07:01:45.7100676Z + /BUILD_ID +deploy Build + Deploy (keep vars) 2025-12-20T07:01:46.9839849Z Uploaded 1 of 1 asset +deploy Build + Deploy (keep vars) 2025-12-20T07:01:46.9842875Z ✨ Success! Uploaded 1 file (23 already uploaded) (1.27 sec) +deploy Build + Deploy (keep vars) 2025-12-20T07:01:46.9843628Z +deploy Build + Deploy (keep vars) 2025-12-20T07:01:47.1798677Z Total Upload: 9319.65 KiB / gzip: 2185.19 KiB +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2424015Z Worker Startup Time: 34 ms +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2436181Z Your Worker has access to the following bindings: +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2437421Z Binding Resource +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2440714Z env.WORKER_SELF_REFERENCE (siteready-website) Worker +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2442205Z env.ASSETS Assets +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2443221Z env.SMTP_HOST ("smtp.gmail.com") Environment Variable +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2444251Z env.SMTP_PORT ("587") Environment Variable +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2445301Z env.SMTP_USER ("info@sitereadyworkforce.com") Environment Variable +deploy Build + Deploy (keep vars) 2025-12-20T07:01:51.2446695Z env.SMTP_FROM ("SiteReady Workforce + SMTP_FROM="SiteReady Workforce (Dev) " + NOTIFY_TO="yourtempaddress@gmail.com" + ``` +Notes: +- Suitable only for development/testing, not high-volume production. +- Google enforces daily limits and may flag suspicious activity. + +## Phase 2 – Production with Google Workspace (Recommended) +Once the name is final, the domain is purchased, and Workspace is set up (e.g. `info@yourfinalbrand.com.au`): +- Use the same Nodemailer setup with updated credentials: + ``` + SMTP_HOST=smtp.gmail.com + SMTP_PORT=587 + SMTP_USER=info@yourfinalbrand.com.au + SMTP_PASS= + SMTP_FROM="Your Final Brand Name " + NOTIFY_TO="bookings@yourfinalbrand.com.au" + ``` +- Update `siteConfig.ts` to the final company name and contact details. +- No code changes are needed for forms/API routes; only environment values change. + +## How the Environment Variables Interact with `sendNotificationEmail` +- `src/lib/email.ts` reads `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `SMTP_FROM`, `NOTIFY_TO`. +- Creates a Nodemailer transport. +- If `SMTP_FROM` or `NOTIFY_TO` is missing, it logs a warning and returns early (no email sent), allowing the site to run without SMTP during early development. +- Once `.env.local` is filled, live emails will be sent on form submission. +- Migrating dev → prod only requires updating `.env.local` (or host env vars). + +## Deploying on Cloudflare – Static vs API Routes +### Static (next export) +- Great for marketing pages only. +- Does **not** support Next.js API routes (`/api/bookings`, `/api/applications`, `/api/contact`). +- Local dev can test APIs; production static deploy will serve only the pages. + +### Next.js on Cloudflare Functions (Next on Pages) +- Runs the full Next.js app with API routes on the edge. +- Set the same SMTP env vars in Cloudflare project settings. +- Forms will send emails in production without code changes. + +### Suggested path +- **Option A (now):** Stay static while pre-launch; marketing pages live, APIs only used locally. +- **Option B (later):** Switch to Functions mode when name/domain/email are final; configure env vars in Cloudflare; live forms will send emails. + +## Checklist – What the Owner Needs to Do Later +- Decide final trading name (avoid conflicts). +- Confirm domain availability and purchase domain. +- Set up Google Workspace mailbox (e.g. `info@...`). +- Update `siteConfig.ts` with final name, email, phone. +- Update `.env.local` (and Cloudflare env) with Workspace SMTP details. +- Reconfigure Cloudflare deployment from static to Next.js/Functions mode when ready for live forms. +- Add Privacy/Terms pages before going fully live. diff --git a/siteready-website/docs/database-and-storage.md b/siteready-website/docs/database-and-storage.md new file mode 100644 index 000000000..986811903 --- /dev/null +++ b/siteready-website/docs/database-and-storage.md @@ -0,0 +1,36 @@ +# Database & Storage (Platform Layer) + +## Targets +- **Cloudflare D1 (SQLite)** for platform tables: + - users, clients, requests, workers, worker_tickets, worker_availability, assignments, timesheets, incidents, praise, performance_metrics/history, audit_logs. + - SQL migration: `db/migrations/000_init.sql`. + - Binding name expected: `DB`. +- **Cloudflare R2** for ticket image storage. + - Binding name expected: `TICKETS_BUCKET`. + - Ticket images must only be revealed to clients when `contract_signed` AND `release_to_client` are true; staff can always view. + +## Configuration +Set bindings in Cloudflare (Workers): +- D1 binding: `DB` +- R2 binding: `TICKETS_BUCKET` + +Env vars: +- `APP_SECRET` – required for app session signing. +- Seeding (optional): + - `SEED_STAFF_EMAIL`, `SEED_STAFF_PASSWORD` + - `SEED_CLIENT_EMAIL`, `SEED_CLIENT_PASSWORD` + - `SEED_WORKER_EMAIL`, `SEED_WORKER_PASSWORD` +- Existing SMTP vars for notifications remain unchanged. + +## Migrations +- Migration SQL lives in `db/migrations/000_init.sql`. +- Apply to your D1 database before enabling the platform features. +- If D1 is not bound, protected platform pages will show a “Platform not configured†notice (see `src/app/platform-not-configured/page.tsx`). + +## Graceful degradation +- Public pages remain available. +- Protected routes (labour-hire/dashboard/client/worker/staff) still require login; if D1 is missing, platform UI should render a clear “not configured†message instead of crashing. + +## Next steps (not yet wired) +- Drizzle/D1 query helpers and R2 upload utilities will be added when wiring persistence and ticket uploads. +- Seed data (roles/tickets/demo users) will rely on env-provided credentials (no secrets in code). diff --git a/siteready-website/docs/google-sheets-integration.md b/siteready-website/docs/google-sheets-integration.md new file mode 100644 index 000000000..4731d27e3 --- /dev/null +++ b/siteready-website/docs/google-sheets-integration.md @@ -0,0 +1,46 @@ +# Google Sheets Integration Options + +This project ships two optional patterns for pushing booking data to Google Sheets. Neither runs by default; wire them only if you need them. + +## 1) Apps Script Web App (simplest) +Files: `integrations/google-sheets/apps-script/Code.gs` + +Steps: +1. Create a Google Sheet; note its ID. +2. Open Extensions → Apps Script; paste `Code.gs`. +3. Set Script Property `WEBHOOK_TOKEN` (optional shared secret). +4. Deploy → New deployment → Web app → Execute as: Me; Who has access: Anyone with the link (or restricted if you add auth). Copy the Web App URL. +5. In `.env` for Next.js (Cloudflare Worker): + - `SHEETS_WEBAPP_URL=` + - `SHEETS_WEBAPP_TOKEN=` (optional) +6. The Web App appends one row per role group with the schema below. + +## 2) Node/Express Service Account Webhook +Files: `integrations/google-sheets/node-express/server.ts` + +Steps: +1. Create a Google Cloud project; enable Sheets API. +2. Create a Service Account; generate a JSON key. +3. Share the target Sheet with the Service Account email. +4. Env vars for the Node service: + - `PORT=3001` + - `SHEETS_SPREADSHEET_ID=` + - `SHEETS_SERVICE_ACCOUNT_EMAIL=` + - `SHEETS_SERVICE_ACCOUNT_KEY=` (use `\n` escapes) + - `SHEETS_WEBHOOK_TOKEN=` (optional) +5. Run `npm install express googleapis` in that folder; deploy where you like. +6. Set your Next.js env to call this webhook URL instead of Apps Script. + +## Next.js optional push +In `/api/bookings`, after validation/email, if `SHEETS_WEBAPP_URL` exists it POSTs the full booking payload plus `bookingId`/`submittedAt`. If `SHEETS_WEBAPP_TOKEN` is set, it’s sent as `X-Webhook-Token`. Sheets push failures do **not** block the booking. + +## Spreadsheet columns (one row per role group) +``` +Company Structure, ACN, ABN, Website, Registered Street, Registered State, Registered Postcode, +Site Address, Site Map Link, Role Group #, Number of Workers, Role, Tickets (semicolon), +Experience, Start Date, Start Time, Hours/Day, Expected Duration (value + unit), +OT Options (semicolon), Notes, Submitted At, Booking ID +``` + +## Maps API key +Set `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` with Maps JavaScript API + Places API enabled. Restrict the key to your domains in production. diff --git a/siteready-website/docs/legal-and-privacy.md b/siteready-website/docs/legal-and-privacy.md new file mode 100644 index 000000000..543fc0fc1 --- /dev/null +++ b/siteready-website/docs/legal-and-privacy.md @@ -0,0 +1,39 @@ +# Legal and Privacy Consent Flows + +Draft – obtain legal review before production. + +## Where consents appear + +- Worker dashboard: consent modal appears before saving sensitive profile data and before submitting the first timesheet. +- Client timesheets: consent modal appears before viewing or approving timesheets. +- Admin Xero connection: consent modal appears before connecting Xero. + +## Updating versions + +Legal documents live in `src/lib/legal/documents.ts`. + +When a legal document changes: + +1) Update the `version` string. +2) Update the document `body` text. +3) Communicate changes to users (existing acceptances are version-specific). + +## API enforcement + +- `/api/workers/me` blocks sensitive profile updates without `APP5_COLLECTION_NOTICE`. +- `/api/timesheets` blocks submissions without `PLATFORM_TERMS`. +- `/api/timesheets` and `/api/timesheets/[id]` block client access without `CLIENT_CONFIDENTIALITY_TERMS`. +- `/api/integrations/xero/connect` blocks without `ADMIN_XERO_ACK`. +- Xero sync checks for `XERO_DATA_SHARING_CONSENT` on the worker. + +## Stored fields + +Acceptances are stored in `legal_acceptances` with: + +- document_type +- document_version +- user_id +- role +- accepted_at +- ip_address +- user_agent diff --git a/siteready-website/docs/platform-auth-and-permissions.md b/siteready-website/docs/platform-auth-and-permissions.md new file mode 100644 index 000000000..6cb79f6b8 --- /dev/null +++ b/siteready-website/docs/platform-auth-and-permissions.md @@ -0,0 +1,42 @@ +# Platform Auth & Permissions (MVP Layer) + +## Overview +- Site still wrapped by Cloudflare Worker Basic Auth and www→apex redirect. +- App-level auth (sessions) added on top: + - Protected routes: `/labour-hire`, `/dashboard`, `/client*`, `/worker*`, `/staff*` (via middleware). + - Unauthenticated users are redirected to `/login?next=...`. +- Sessions use an HTTP-only cookie (`sr_session`) signed with `APP_SECRET`. +- Auth roles: `client`, `worker`, `staff`. Current login is a lightweight demo form; real user provisioning will be added alongside the DB layer. +- Credential login (PBKDF2) via `/api/auth/login`. Registration seed endpoint `/api/auth/register` exists for bootstrapping; requires D1 binding. +- Seed helper `/api/seed` (POST) will create users if env vars are present: + - `SEED_STAFF_EMAIL/SEED_STAFF_PASSWORD` + - `SEED_CLIENT_EMAIL/SEED_CLIENT_PASSWORD` + - `SEED_WORKER_EMAIL/SEED_WORKER_PASSWORD` + +## Session details +- Cookie contains a signed payload: `{ sessionId, role, email?, userId?, issuedAt, expiresAt }`. +- Signed with HMAC SHA-256 using `APP_SECRET`. +- 7-day expiry. If invalid/expired, user is redirected to `/login`. + +## Routes +- `/login`: simple form to pick a role (client/worker/staff) and optional email. On submit -> `/api/auth/login` -> sets cookie -> redirects to `next` param or `/dashboard`. +- `/logout`: clears session and redirects home. +- `/api/auth/login`: accepts `{ email?, role }`, sets session cookie. +- `/api/auth/logout`: clears session cookie. +- `/api/auth/session`: returns current session JSON or 401. +- `/api/auth/register`: accepts `{ email, password, role }` (PBKDF2) to seed users in D1. Skip if seeding externally. +- Protected routes: middleware checks session; if missing, redirects to `/login`. + +## Basic Auth +- Existing Cloudflare Worker Basic Auth remains unchanged. If enabled, it wraps the entire site before the app-level auth. + +## Next steps (planned) +- Replace demo login with real user records in D1 (clients/workers/staff). +- Add password hashing (PBKDF2) and registration/seed paths (staff-provisioned). +- Role-based dashboards and PII redaction per spec. +- Matching, timesheets, incidents, praise, audit logging, and DB schema will follow in subsequent steps. + +## Env vars (auth-related) +- `APP_SECRET` – required to sign app-level session cookies. +- (Optional legacy) `AUTH0_*` – currently unused; app auth uses `APP_SECRET`. +- Basic Auth envs remain as before (`BASIC_AUTH_*`) for outer gate. diff --git a/siteready-website/docs/tax-declaration.md b/siteready-website/docs/tax-declaration.md new file mode 100644 index 000000000..8a0417e5b --- /dev/null +++ b/siteready-website/docs/tax-declaration.md @@ -0,0 +1,41 @@ +# Tax Declaration (Worker) + +This feature implements the ATO TFN Declaration (NAT 3092 style) flow for workers and maps the data into Xero Payroll AU employee TaxDeclaration payloads. + +## Storage +- Table: `worker_tax_declarations` +- Key: `worker_id` +- TFN is encrypted at rest using `DATA_ENCRYPTION_KEY`. +- Only `tfn_last4` is stored in clear text for UI display. + +## API +- `GET /api/worker/tax-declaration` + - Worker reads their own declaration. + - Admin may pass `?worker_id=...` to view summary. +- `POST /api/worker/tax-declaration` + - Saves/updates the declaration. + - Requires required fields; enforces TFN rules and WHM threshold guardrail. + - When signed, stores IP and user agent. + +## Required fields +- `tax_residency_status` (AUSTRALIAN_RESIDENT | FOREIGN_RESIDENT | WORKING_HOLIDAY_MAKER) +- `employment_basis` (FULL_TIME | PART_TIME | CASUAL | LABOUR_HIRE | SUPER_INCOME_STREAM) +- `claim_tax_free_threshold` (yes/no) +- `has_study_training_loan` (yes/no) +- `has_sfss_debt` (yes/no) +- `tfn_status` (PROVIDED | APPLIED_OR_ENQUIRED | EXEMPT_UNDER18 | EXEMPT_PENSION | NOT_QUOTED) +- If `tfn_status=PROVIDED`, TFN must be 8 or 9 digits and is encrypted at rest. + +## Signature +- Worker signs using a checkbox. +- Once signed, the declaration is locked on the worker UI. +- Admin can still view the summary; Xero sync requires signature. + +## Xero mapping +- `TaxDeclaration.ResidencyStatus` maps from `tax_residency_status`. +- `TaxFreeThresholdClaimed`, `HasHELPDebt`, `HasSFSSDebt`, `EligibleToReceiveLeaveLoading` +- TFN is sent only if `tfn_status=PROVIDED` and declaration is signed. + +## RBAC +- Clients cannot access the tax declaration endpoints. +- Admin can view summaries only. diff --git a/siteready-website/docs/xero-accounting-bills.md b/siteready-website/docs/xero-accounting-bills.md new file mode 100644 index 000000000..433ad1358 --- /dev/null +++ b/siteready-website/docs/xero-accounting-bills.md @@ -0,0 +1,43 @@ +# Xero Accounting Bills (Contractor) + +This integration creates **ACCPAY** bills in Xero Accounting for workers engaged as contractors (engagement type `CONTRACTOR_ABN`). + +## Flow +1) Contractor completes company + bank details. +2) Admin sets a contractor pay rate (AUD/hr). +3) Client approves a timesheet. +4) System upserts a Xero contact for the contractor. +5) System creates an **ACCPAY** invoice (bill) in Xero. + +## Required admin configuration +- **Purchases account code** + Set in `/dashboard/admin/xero/accounting`. + Bills will **not** sync while this is blank or `000`. + +- **Contractor pay rate (AUD/hr)** + Set in `/dashboard/admin/contractors`. + Bills will **fail** if no pay rate is configured. + +## Retry behavior +- Failed bill syncs are listed at `/dashboard/admin/xero/bill-failures`. +- Admin can retry manually. +- Automated retry runs on cron: + - max 5 attempts + - 15‑minute backoff between attempts + - deterministic idempotency key per timesheet + +## Idempotency and duplicate prevention +Bill creation uses a deterministic idempotency key: +``` +bill:: +``` +This prevents duplicates when retries occur. + +## Common issues +- **Missing purchases account code** → Configure `default_purchases_account_code`. +- **Missing contractor pay rate** → Set pay rate on contractor. +- **Xero not connected** → Reconnect in `/dashboard/admin/xero`. + +## Security +- Bank account numbers are encrypted at rest. +- Sensitive values are never logged. diff --git a/siteready-website/docs/xero-payroll-au.md b/siteready-website/docs/xero-payroll-au.md new file mode 100644 index 000000000..61a9b46d5 --- /dev/null +++ b/siteready-website/docs/xero-payroll-au.md @@ -0,0 +1,25 @@ +# Xero Payroll AU Integration (Internal) + +This integration connects SiteReady Workforce to Xero Payroll AU for employee and timesheet sync. + +## Required environment variables + +Server-side only (Cloudflare secrets / vars): +- `XERO_CLIENT_ID` +- `XERO_CLIENT_SECRET` +- `XERO_REDIRECT_URI` +- `XERO_TOKEN_ENCRYPTION_KEY` (base64 32-byte key) +- `APP_URL` (used for building redirect URLs) + +## OAuth flow + +1) Admin visits `/api/integrations/xero/connect` +2) User completes Xero OAuth +3) Callback hits `/api/integrations/xero/callback` +4) Tokens + tenant ID are stored encrypted + +## Notes + +- Tokens must be stored encrypted at rest. +- Client secrets must never be exposed to the browser. +- All Xero requests include `Xero-Tenant-Id` + `Authorization` headers. diff --git a/siteready-website/eslint.config.mjs b/siteready-website/eslint.config.mjs new file mode 100644 index 000000000..6ef658e6b --- /dev/null +++ b/siteready-website/eslint.config.mjs @@ -0,0 +1,32 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import { FlatCompat } from "@eslint/eslintrc"; +import { dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const compat = new FlatCompat({ baseDirectory: __dirname }); + +const eslintConfig = defineConfig([ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + rules: { + "@next/next/no-img-element": "off", + }, + }, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ".open-next/**", + ".wrangler/**", + "node_modules/**", + "stubs/**", + "scripts/**", + "temp-check.js", + ]), +]); + +export default eslintConfig; diff --git a/siteready-website/integrations/google-sheets/apps-script/Code.gs b/siteready-website/integrations/google-sheets/apps-script/Code.gs new file mode 100644 index 000000000..df936014b --- /dev/null +++ b/siteready-website/integrations/google-sheets/apps-script/Code.gs @@ -0,0 +1,104 @@ +/** + * Google Apps Script Web App for receiving booking payloads. + * Deploy as: New Deployment -> Web App -> Execute as: Me, Who has access: Anyone with the link (or restricted). + * Set SHEETS_WEBAPP_TOKEN in your Next.js env and match it here. + */ + +const SHEET_NAME = "Bookings"; +const HEADER = [ + "Company Structure", + "ACN", + "ABN", + "Website", + "Registered Street", + "Registered State", + "Registered Postcode", + "Site Address", + "Site Map Link", + "Role Group #", + "Number of Workers", + "Role", + "Tickets", + "Experience", + "Start Date", + "Start Time", + "Hours/Day", + "Expected Duration", + "OT Options", + "Notes", + "Submitted At", + "Booking ID", +]; + +function doPost(e) { + try { + const token = e?.postData?.contents ? JSON.parse(e.postData.contents)?.token : e.parameter.token; + const body = e?.postData?.contents ? JSON.parse(e.postData.contents) : {}; + const headerToken = e?.parameter?.token || e?.postData?.type === "application/json" ? e?.postData?.token : null; + const requestToken = e?.postData?.contents ? e.postData.contents["X-Webhook-Token"] : null; + const suppliedToken = e?.postData?.headers?.["X-Webhook-Token"] || body?.["X-Webhook-Token"] || requestToken || token || headerToken; + const expectedToken = PropertiesService.getScriptProperties().getProperty("WEBHOOK_TOKEN"); + if (expectedToken && suppliedToken !== expectedToken) { + return ContentService.createTextOutput("Unauthorized").setMimeType(ContentService.MimeType.TEXT); + } + + const sheet = getSheet(); + ensureHeader(sheet); + + const payload = body; + if (!payload || !payload.roles) { + return ContentService.createTextOutput("Bad request").setMimeType(ContentService.MimeType.TEXT); + } + + const rows = buildRows(payload); + sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, HEADER.length).setValues(rows); + return ContentService.createTextOutput("OK").setMimeType(ContentService.MimeType.TEXT); + } catch (err) { + return ContentService.createTextOutput("Error").setMimeType(ContentService.MimeType.TEXT); + } +} + +function getSheet() { + const ss = SpreadsheetApp.getActiveSpreadsheet(); + const sheet = ss.getSheetByName(SHEET_NAME) || ss.insertSheet(SHEET_NAME); + return sheet; +} + +function ensureHeader(sheet) { + const firstRow = sheet.getRange(1, 1, 1, HEADER.length).getValues()[0]; + if (firstRow.join("") === "") { + sheet.getRange(1, 1, 1, HEADER.length).setValues([HEADER]); + } +} + +function buildRows(payload) { + const rows = []; + const roles = payload.roles || []; + roles.forEach(function (r, idx) { + rows.push([ + payload.company.structure, + payload.company.acn, + payload.company.abn, + payload.company.website || "", + payload.registeredAddress.street, + payload.registeredAddress.state, + payload.registeredAddress.postcode, + payload.siteAddress.formattedAddress, + payload.siteAddress.mapUrl || "", + idx + 1, + r.count, + r.role, + (r.tickets || []).join("; "), + r.experience, + r.startDate, + r.startTime, + r.hoursPerDay, + r.expectedDurationValue + " " + r.expectedDurationUnit, + (r.otOptions || []).join("; "), + payload.notes || "", + payload.submittedAt || "", + payload.bookingId || "", + ]); + }); + return rows; +} diff --git a/siteready-website/integrations/google-sheets/node-express/server.ts b/siteready-website/integrations/google-sheets/node-express/server.ts new file mode 100644 index 000000000..98fb2c793 --- /dev/null +++ b/siteready-website/integrations/google-sheets/node-express/server.ts @@ -0,0 +1,126 @@ +import express from "express"; +import { google } from "googleapis"; + +/** + * Minimal Express server to accept booking payloads and push to Google Sheets via Service Account. + * Not used in production by default; provided as a template. + * + * Env vars: + * PORT=3001 + * SHEETS_SPREADSHEET_ID=your_sheet_id + * SHEETS_SERVICE_ACCOUNT_EMAIL=service-account@project.iam.gserviceaccount.com + * SHEETS_SERVICE_ACCOUNT_KEY=-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY----- + * SHEETS_WEBHOOK_TOKEN=optional shared secret; if set, require header X-Webhook-Token + */ + +const app = express(); +app.use(express.json({ limit: "1mb" })); + +function getSheetsClient() { + const auth = new google.auth.JWT({ + email: process.env.SHEETS_SERVICE_ACCOUNT_EMAIL, + key: process.env.SHEETS_SERVICE_ACCOUNT_KEY?.split(String.raw`\n`).join("\n"), + scopes: ["https://www.googleapis.com/auth/spreadsheets"], + }); + return google.sheets({ version: "v4", auth }); +} + +type BookingRole = { + count?: number; + role?: string; + tickets?: string[]; + experience?: string; + startDate?: string; + startTime?: string; + hoursPerDay?: number; + expectedDurationValue?: number; + expectedDurationUnit?: string; + otOptions?: string[]; +}; + +type BookingPayload = { + company?: { + structure?: string; + acn?: string; + abn?: string; + website?: string; + }; + registeredAddress?: { + street?: string; + state?: string; + postcode?: string; + }; + siteAddress?: { + formattedAddress?: string; + mapUrl?: string; + }; + roles?: BookingRole[]; + notes?: string; + submittedAt?: string; + bookingId?: string; +}; + +function buildRows(payload: BookingPayload): string[][] { + const rows: string[][] = []; + (payload.roles || []).forEach((r: BookingRole, idx: number) => { + rows.push([ + payload.company?.structure ?? "", + payload.company?.acn ?? "", + payload.company?.abn ?? "", + payload.company?.website ?? "", + payload.registeredAddress?.street ?? "", + payload.registeredAddress?.state ?? "", + payload.registeredAddress?.postcode ?? "", + payload.siteAddress?.formattedAddress ?? "", + payload.siteAddress?.mapUrl ?? "", + String(idx + 1), + String(r.count ?? ""), + r.role ?? "", + (r.tickets || []).join("; "), + r.experience ?? "", + r.startDate ?? "", + r.startTime ?? "", + String(r.hoursPerDay ?? ""), + `${r.expectedDurationValue ?? ""} ${r.expectedDurationUnit ?? ""}`.trim(), + (r.otOptions || []).join("; "), + payload.notes ?? "", + payload.submittedAt ?? "", + payload.bookingId ?? "", + ]); + }); + return rows; +} + +app.post("/webhook", async (req, res) => { + try { + if (process.env.SHEETS_WEBHOOK_TOKEN) { + const token = req.header("X-Webhook-Token"); + if (token !== process.env.SHEETS_WEBHOOK_TOKEN) { + return res.status(401).json({ ok: false, error: "Unauthorized" }); + } + } + + const sheets = getSheetsClient(); + const spreadsheetId = process.env.SHEETS_SPREADSHEET_ID!; + const rows = buildRows(req.body); + + await sheets.spreadsheets.values.append({ + spreadsheetId, + range: "Bookings!A1", + valueInputOption: "RAW", + requestBody: { + values: rows, + }, + }); + + res.json({ ok: true }); + } catch (err) { + console.error(err); + res.status(500).json({ ok: false, error: "Sheet append failed" }); + } +}); + +const port = process.env.PORT || 3001; +app.listen(port, () => { + console.log(`Sheets webhook listening on ${port}`); +}); diff --git a/siteready-website/middleware.ts b/siteready-website/middleware.ts new file mode 100644 index 000000000..f9b404de5 --- /dev/null +++ b/siteready-website/middleware.ts @@ -0,0 +1,135 @@ +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; +import { parseSessionFromRequest } from "./src/lib/auth/session"; +import { hasAcceptedLatestPolicies } from "./src/lib/policies/acceptance"; +import { hasD1Binding } from "./src/lib/db/config"; +import { getDb } from "./src/lib/db/client"; + +const PROTECTED_PREFIXES = ["/dashboard", "/client", "/worker", "/staff"]; +const BASIC_AUTH_BYPASS_PREFIXES = ["/api/future-service-interest/reply-open"]; +const WEBSITE_TERMS_PATH = "/dashboard/policies/website-terms"; +const WEBSITE_TERMS_BYPASS = [ + WEBSITE_TERMS_PATH, + "/logout", +]; + +export async function middleware(req: NextRequest) { + try { + const url = req.nextUrl.clone(); + const host = url.hostname; + + // Redirect www.sitereadyworkforce.com -> sitereadyworkforce.com + if (host === "www.sitereadyworkforce.com") { + url.hostname = "sitereadyworkforce.com"; + return NextResponse.redirect(url, 308); + } + + const user = + process.env.BASIC_AUTH_USER || + process.env.BASIC_AUTH_USERNAME || + process.env.BASIC_AUTH_LOGIN || + ""; + const pass = + process.env.BASIC_AUTH_PASS || + process.env.BASIC_AUTH_PASSWORD || + process.env.BASIC_AUTH_SECRET || + ""; + + const basicAuthEnabledExplicit = + (process.env.BASIC_AUTH_ENABLED || "").toLowerCase() === "true" || + (process.env.BASIC_AUTH_ENABLE || "").toLowerCase() === "true"; + + // Fallback: if user/pass are present, enforce auth even if the enable flag is missing + const basicAuthEnabled = basicAuthEnabledExplicit || (user && pass); + + if (basicAuthEnabled) { + const bypassBasicAuth = BASIC_AUTH_BYPASS_PREFIXES.some( + (p) => url.pathname === p || url.pathname.startsWith(`${p}/`), + ); + if (bypassBasicAuth) { + return NextResponse.next(); + } + if (!user || !pass) { + return new NextResponse("Unauthorized", { + status: 401, + headers: { "WWW-Authenticate": 'Basic realm="SiteReady"' }, + }); + } + const authHeader = req.headers.get("authorization"); + + if (!authHeader || !authHeader.startsWith("Basic ")) { + return new NextResponse("Unauthorized", { + status: 401, + headers: { "WWW-Authenticate": 'Basic realm="SiteReady"' }, + }); + } + + const encoded = authHeader.replace("Basic ", ""); + let decoded = ""; + try { + if (typeof globalThis.atob === "function") { + decoded = globalThis.atob(encoded); + } + } catch { + decoded = ""; + } + if (!decoded) { + return new NextResponse("Unauthorized", { + status: 401, + headers: { "WWW-Authenticate": 'Basic realm="SiteReady"' }, + }); + } + const credentials = decoded.split(":"); + const [providedUser, providedPass] = credentials; + + if (providedUser !== user || providedPass !== pass) { + return new NextResponse("Unauthorized", { + status: 401, + headers: { "WWW-Authenticate": 'Basic realm="SiteReady"' }, + }); + } + } + + const isProtected = PROTECTED_PREFIXES.some((p) => url.pathname === p || url.pathname.startsWith(`${p}/`)); + if (!isProtected) { + return NextResponse.next(); + } + + const session = await parseSessionFromRequest(req, { allowUnverified: true }); + + if (!session) { + const returnTo = `${url.pathname}${url.search}`; + const loginUrl = new URL("/login", url.origin); + loginUrl.searchParams.set("next", returnTo); + return NextResponse.redirect(loginUrl); + } + + const skipWebsiteTerms = + WEBSITE_TERMS_BYPASS.some((p) => url.pathname === p || url.pathname.startsWith(`${p}/`)) || + url.pathname.startsWith("/dashboard/policies/website-terms"); + if (!skipWebsiteTerms && hasD1Binding()) { + const db = getDb(); + const userId = session.userId || session.email || ""; + if (db && userId) { + const websiteTermsAccepted = await hasAcceptedLatestPolicies(db, userId, ["frontend_terms"]); + if (!websiteTermsAccepted.ok) { + const returnTo = `${url.pathname}${url.search}`; + const termsUrl = new URL(WEBSITE_TERMS_PATH, url.origin); + termsUrl.searchParams.set("next", returnTo); + return NextResponse.redirect(termsUrl); + } + } + } + + return NextResponse.next(); + } catch { + return new NextResponse("Unauthorized", { + status: 401, + headers: { "WWW-Authenticate": 'Basic realm="SiteReady"' }, + }); + } +} + +export const config = { + matcher: "/:path*", +}; diff --git a/siteready-website/mock/book-labour/index.html b/siteready-website/mock/book-labour/index.html new file mode 100644 index 000000000..595903e2e --- /dev/null +++ b/siteready-website/mock/book-labour/index.html @@ -0,0 +1,150 @@ + + + + + + Book labour now — Mock + + + + + +
    +
    +

    Book labour now

    +

    Sydney / NSW labour hire request mock. All data stays in the browser. Use the JSON preview or CSV export to inspect payloads.

    + +
    + + +
    +
    + +
    +
    +

    Company details

    +
    + + +
    +
    + + + +
    +
    + + Contact is acting for / on behalf of the company listed above. +
    +
    + + + +
    +
    + +
    +

    Registered address (company registered address)

    +
    + + + +
    +
    + +
    +

    Site address (Location of site that workers need to go to)

    +
    + +
    +
    +
    +
    +

    Selected address

    +

    None

    +

    Lat/Lng: —

    +

    Open in Google Maps

    + +
    +
    + + + + +
    + +
    +
    +

    Workers needed

    + +
    +
    +
    + +
    +

    Notes (optional)

    + +
    + +
    + +
    +
    +
    + + + + + + + diff --git a/siteready-website/mock/book-labour/script.js b/siteready-website/mock/book-labour/script.js new file mode 100644 index 000000000..7f63167c1 --- /dev/null +++ b/siteready-website/mock/book-labour/script.js @@ -0,0 +1,605 @@ +// Datasets +const STATES = ["ACT", "NSW", "NT", "QLD", "SA", "TAS", "VIC", "WA"]; +const COMPANY_STRUCTURES = ["Pty Ltd", "Ltd", "Pty", "NL"]; +const ROLES = [ + "General Labourer", + "Skilled Labourer", + "Site Cleaner", + "Traffic Controller", + "Carpenter", + "Formworker", + "Concreter", + "Steel Fixer", + "Scaffolder", + "Painter", + "Plumber", + "Electrician", + "HVAC Tech", + "Operator—Forklift", + "Operator—EWP", + "Operator—Excavator", + "Operator—Skid Steer", + "Operator—Crane", + "Leading Hand", + "Site Supervisor", +]; +const TICKETS = [ + "White Card (WHS)", + "First Aid (HLTAID011+)", + "Working at Heights", + "Confined Space", + "Asbestos Awareness", + "Traffic Control (TC/TMI)", + "Forklift (LF)", + "EWP (WP)", + "Scaffolding (SB/SA/SI)", + "Rigging (RB/RI)", + "Dogging (DG)", + "Crane Operation (CO)", + "Excavator (LE)", + "Skid Steer (LS)", + "Telehandler (LT)", + "Electrical Licence", + "HVAC Cert", + "Fire Safety Induction", + "Manual Handling", + "Silica Awareness", +]; +const OT_OPTIONS = [ + "No OT", + "Weekday OT (1.5x)", + "Weekday OT (2.0x)", + "Saturday OT (1.5x)", + "Saturday OT (2.0x)", + "Sunday OT (2.0x)", + "Public Holiday (2.5x)", + "Night Shift Loading", + "Call-out", + "Standby", +]; +const EXPERIENCE = ["Newly ticketed", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10+"]; +const DURATION_UNITS = ["days", "weeks"]; + +let groups = []; +let map; +let marker; +let autocomplete; +let selectedPlace = null; +let mapSupported = false; + +function $(selector) { + return document.querySelector(selector); +} + +function createEl(tag, attrs = {}) { + const el = document.createElement(tag); + Object.entries(attrs).forEach(([k, v]) => { + if (k === "className") el.className = v; + else if (k === "text") el.textContent = v; + else el.setAttribute(k, v); + }); + return el; +} + +function populateSelect(select, options) { + select.innerHTML = ""; + const defaultOpt = createEl("option", { value: "", text: "Select..." }); + select.appendChild(defaultOpt); + options.forEach((opt) => { + const o = createEl("option", { value: opt, text: opt }); + select.appendChild(o); + }); +} + +function numberOnly(value) { + return value.replace(/\D/g, ""); +} + +function setError(name, message) { + const span = document.querySelector(`[data-error-for="${name}"]`); + if (span) span.textContent = message || ""; +} + +function clearAllErrors() { + document.querySelectorAll(".error").forEach((el) => (el.textContent = "")); +} + +function addGroup(initial = {}) { + const id = crypto.randomUUID ? crypto.randomUUID() : String(Date.now() + Math.random()); + const defaults = { + id, + count: "", + role: "", + tickets: [], + experience: "", + startDate: "", + startTime: "", + hoursPerDay: "", + expectedDurationValue: "", + expectedDurationUnit: "", + otOptions: [], + nextRole: false, + }; + groups.push({ ...defaults, ...initial, id }); + renderGroups(); +} + +function removeGroup(id) { + if (groups.length === 1) return; + groups = groups.filter((g) => g.id !== id); + renderGroups(); +} + +function updateGroup(id, key, value) { + groups = groups.map((g) => (g.id === id ? { ...g, [key]: value } : g)); +} + +function toggleInArray(arr, value, checked) { + if (checked) { + return arr.includes(value) ? arr : [...arr, value]; + } + return arr.filter((v) => v !== value); +} + +function renderGroups() { + const container = $("#groups"); + container.innerHTML = ""; + groups.forEach((group, idx) => { + const wrap = createEl("div", { className: "group" }); + const head = createEl("div", { className: "group-head" }); + head.appendChild(createEl("h3", { text: `Role group ${idx + 1}` })); + const removeBtn = createEl("button", { type: "button", className: "secondary", text: "Remove" }); + removeBtn.onclick = () => removeGroup(group.id); + head.appendChild(removeBtn); + wrap.appendChild(head); + + const grid1 = createEl("div", { className: "grid three" }); + const countLabel = createEl("label"); + countLabel.textContent = "Number of workers"; + const countInput = createEl("input", { type: "number", min: "1", value: group.count }); + countInput.oninput = (e) => updateGroup(group.id, "count", e.target.value); + countInput.onblur = () => validate(); + countLabel.appendChild(countInput); + countLabel.appendChild(createEl("span", { className: "error", "data-error-for": `count-${group.id}` })); + + const roleLabel = createEl("label"); + roleLabel.textContent = "Role needed"; + const roleSelect = createEl("select"); + populateSelect(roleSelect, ROLES); + roleSelect.value = group.role; + roleSelect.onchange = (e) => updateGroup(group.id, "role", e.target.value); + roleSelect.onblur = () => validate(); + roleLabel.appendChild(roleSelect); + roleLabel.appendChild(createEl("span", { className: "error", "data-error-for": `role-${group.id}` })); + + const expLabel = createEl("label"); + expLabel.textContent = "Minimum years’ experience"; + const expSelect = createEl("select"); + populateSelect(expSelect, EXPERIENCE); + expSelect.value = group.experience; + expSelect.onchange = (e) => updateGroup(group.id, "experience", e.target.value); + expSelect.onblur = () => validate(); + expLabel.appendChild(expSelect); + expLabel.appendChild(createEl("span", { className: "error", "data-error-for": `experience-${group.id}` })); + + grid1.append(countLabel, roleLabel, expLabel); + wrap.appendChild(grid1); + + const grid2 = createEl("div", { className: "grid three" }); + const startDateLabel = createEl("label"); + startDateLabel.textContent = "Start date"; + const startDateInput = createEl("input", { type: "date", value: group.startDate }); + startDateInput.onchange = (e) => updateGroup(group.id, "startDate", e.target.value); + startDateInput.onblur = () => validate(); + startDateLabel.appendChild(startDateInput); + startDateLabel.appendChild(createEl("span", { className: "error", "data-error-for": `startDate-${group.id}` })); + + const startTimeLabel = createEl("label"); + startTimeLabel.textContent = "Daily start time"; + const startTimeInput = createEl("input", { type: "time", value: group.startTime }); + startTimeInput.onchange = (e) => updateGroup(group.id, "startTime", e.target.value); + startTimeInput.onblur = () => validate(); + startTimeLabel.appendChild(startTimeInput); + startTimeLabel.appendChild(createEl("span", { className: "error", "data-error-for": `startTime-${group.id}` })); + + const hoursLabel = createEl("label"); + hoursLabel.textContent = "Estimated hours per day"; + const hoursInput = createEl("input", { type: "number", step: "0.1", min: "0", value: group.hoursPerDay }); + hoursInput.oninput = (e) => updateGroup(group.id, "hoursPerDay", e.target.value); + hoursInput.onblur = () => validate(); + hoursLabel.appendChild(hoursInput); + hoursLabel.appendChild(createEl("span", { className: "error", "data-error-for": `hoursPerDay-${group.id}` })); + + grid2.append(startDateLabel, startTimeLabel, hoursLabel); + wrap.appendChild(grid2); + + const grid3 = createEl("div", { className: "grid three" }); + const durationValueLabel = createEl("label"); + durationValueLabel.textContent = "Expected duration (value)"; + const durationValueInput = createEl("input", { type: "number", min: "1", value: group.expectedDurationValue }); + durationValueInput.oninput = (e) => updateGroup(group.id, "expectedDurationValue", e.target.value); + durationValueInput.onblur = () => validate(); + durationValueLabel.appendChild(durationValueInput); + durationValueLabel.appendChild(createEl("span", { className: "error", "data-error-for": `expectedDurationValue-${group.id}` })); + + const durationUnitLabel = createEl("label"); + durationUnitLabel.textContent = "Expected duration (unit)"; + const durationUnitSelect = createEl("select"); + populateSelect(durationUnitSelect, DURATION_UNITS); + durationUnitSelect.value = group.expectedDurationUnit; + durationUnitSelect.onchange = (e) => updateGroup(group.id, "expectedDurationUnit", e.target.value); + durationUnitSelect.onblur = () => validate(); + durationUnitLabel.appendChild(durationUnitSelect); + durationUnitLabel.appendChild(createEl("span", { className: "error", "data-error-for": `expectedDurationUnit-${group.id}` })); + + const nextRoleLabel = createEl("label"); + nextRoleLabel.className = "inline"; + const nextRoleInput = createEl("input", { type: "checkbox" }); + nextRoleInput.onchange = (e) => { + if (e.target.checked) { + addGroup(); + e.target.checked = false; + } + }; + nextRoleLabel.append(nextRoleInput, createEl("span", { text: "Next role needed (auto add another group)" })); + + grid3.append(durationValueLabel, durationUnitLabel, nextRoleLabel); + wrap.appendChild(grid3); + + // Tickets and OT multi-select + const grid4 = createEl("div", { className: "grid two" }); + const ticketsLabel = createEl("label"); + ticketsLabel.textContent = "Tickets (optional)"; + const ticketsDetails = createEl("details"); + const ticketsSummary = createEl("summary", { text: group.tickets.length ? `${group.tickets.length} selected` : "Select tickets" }); + ticketsDetails.appendChild(ticketsSummary); + const ticketsList = createEl("div"); + TICKETS.forEach((ticket) => { + const cbLabel = createEl("label", { className: "inline" }); + const cb = createEl("input", { type: "checkbox", value: ticket }); + cb.checked = group.tickets.includes(ticket); + cb.onchange = (e) => { + const updated = toggleInArray(group.tickets, ticket, e.target.checked); + updateGroup(group.id, "tickets", updated); + ticketsSummary.textContent = updated.length ? `${updated.length} selected` : "Select tickets"; + }; + cbLabel.append(cb, createEl("span", { text: ticket })); + ticketsList.appendChild(cbLabel); + }); + ticketsDetails.appendChild(ticketsList); + ticketsLabel.appendChild(ticketsDetails); + grid4.appendChild(ticketsLabel); + + const otLabel = createEl("label"); + otLabel.textContent = "OT/weekend expected? (optional)"; + const otDetails = createEl("details"); + const otSummary = createEl("summary", { text: group.otOptions.length ? `${group.otOptions.length} selected` : "Select OT" }); + const otList = createEl("div"); + OT_OPTIONS.forEach((ot) => { + const cbLabel = createEl("label", { className: "inline" }); + const cb = createEl("input", { type: "checkbox", value: ot }); + cb.checked = group.otOptions.includes(ot); + cb.onchange = (e) => { + const updated = toggleInArray(group.otOptions, ot, e.target.checked); + updateGroup(group.id, "otOptions", updated); + otSummary.textContent = updated.length ? `${updated.length} selected` : "Select OT"; + }; + cbLabel.append(cb, createEl("span", { text: ot })); + otList.appendChild(cbLabel); + }); + otDetails.append(otSummary, otList); + otLabel.appendChild(otDetails); + grid4.appendChild(otLabel); + + wrap.appendChild(grid4); + container.appendChild(wrap); + }); +} + +function buildMapLink(lat, lng, placeId) { + if (lat && lng) { + return `https://www.google.com/maps/search/?api=1&query=${lat},${lng}` + (placeId ? `&query_place_id=${placeId}` : ""); + } + return "#"; +} + +function updateMapMeta() { + const addr = selectedPlace?.formatted_address || $("#site-search").value || "None"; + $("#selected-address").textContent = addr; + const lat = selectedPlace?.geometry?.location?.lat() ?? selectedPlace?.lat ?? ""; + const lng = selectedPlace?.geometry?.location?.lng() ?? selectedPlace?.lng ?? ""; + $("#coords").textContent = lat && lng ? `Lat/Lng: ${lat.toFixed(6)}, ${lng.toFixed(6)}` : "Lat/Lng: —"; + const link = buildMapLink(lat, lng, selectedPlace?.place_id); + const mapLink = $("#map-link"); + mapLink.href = link; + mapLink.textContent = lat && lng ? "Open in Google Maps" : "Map link unavailable"; + + document.querySelector('input[name="siteFormattedAddress"]').value = addr !== "None" ? addr : ""; + document.querySelector('input[name="siteLat"]').value = lat || ""; + document.querySelector('input[name="siteLng"]').value = lng || ""; + document.querySelector('input[name="sitePlaceId"]').value = selectedPlace?.place_id || ""; +} + +function initMap() { + mapSupported = typeof google !== "undefined" && !!google.maps; + if (!mapSupported) { + $("#maps-warning").hidden = false; + return; + } + $("#maps-warning").hidden = true; + map = new google.maps.Map(document.getElementById("map"), { + center: { lat: -33.8688, lng: 151.2093 }, + zoom: 12, + }); + marker = new google.maps.Marker({ + map, + draggable: true, + }); + marker.addListener("dragend", () => { + const pos = marker.getPosition(); + if (pos) { + selectedPlace = { + formatted_address: selectedPlace?.formatted_address || $("#site-search").value, + lat: pos.lat(), + lng: pos.lng(), + place_id: selectedPlace?.place_id, + geometry: { location: pos }, + }; + updateMapMeta(); + } + }); + + const input = document.getElementById("site-search"); + autocomplete = new google.maps.places.Autocomplete(input, { componentRestrictions: { country: "au" } }); + autocomplete.addListener("place_changed", () => { + const place = autocomplete.getPlace(); + if (!place.geometry) { + setError("siteSearch", "Please select a suggested address."); + return; + } + selectedPlace = place; + map.panTo(place.geometry.location); + map.setZoom(16); + marker.setPosition(place.geometry.location); + updateMapMeta(); + }); + + $("#recenter").onclick = () => { + if (selectedPlace?.geometry?.location) { + map.panTo(selectedPlace.geometry.location); + marker.setPosition(selectedPlace.geometry.location); + map.setZoom(16); + } + }; +} + +// Called by Google script +window.initMap = initMap; + +function validate() { + clearAllErrors(); + let valid = true; + const form = $("#booking-form"); + const values = Object.fromEntries(new FormData(form).entries()); + values.acn = numberOnly(values.acn || ""); + values.abn = numberOnly(values.abn || ""); + values.registeredPostcode = numberOnly(values.registeredPostcode || ""); + + if (!values.companyName?.trim()) { + setError("companyName", "Company name is required"); valid = false; + } + if (!values.companyStructure) { setError("companyStructure", "Select structure"); valid = false; } + if (values.acn.length !== 9) { setError("acn", "ACN must be 9 digits"); valid = false; } + if (values.abn.length !== 11) { setError("abn", "ABN must be 11 digits"); valid = false; } + if (!values.contactName?.trim()) { setError("contactName", "Contact name required"); valid = false; } + if (!values.contactEmail?.trim()) { + setError("contactEmail", "Contact email required"); valid = false; + } else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(values.contactEmail)) { + setError("contactEmail", "Enter a valid email"); valid = false; + } + if (!values.contactMobile?.trim()) { setError("contactMobile", "Mobile required"); valid = false; } + const website = values.website?.trim(); + if (website) { + try { + const url = new URL(website); + if (!/^https?:/.test(url.protocol)) throw new Error("bad"); + } catch { + setError("website", "Enter a valid http/https URL"); + valid = false; + } + } + const searchVal = $("#site-search").value.trim(); + if (!searchVal) { setError("siteSearch", "Site address is required"); valid = false; } + + if (!values.registeredStreet?.trim()) { setError("registeredStreet", "Required"); valid = false; } + if (!values.registeredState) { setError("registeredState", "Select state"); valid = false; } + if (values.registeredPostcode.length !== 4) { setError("registeredPostcode", "4 digits"); valid = false; } + + if (!groups.length) { addGroup(); valid = false; } + groups.forEach((g) => { + if (!g.count || Number(g.count) < 1) { setError(`count-${g.id}`, "Enter a number ≥ 1"); valid = false; } + if (!g.role) { setError(`role-${g.id}`, "Select role"); valid = false; } + if (!g.experience) { setError(`experience-${g.id}`, "Select experience"); valid = false; } + if (!g.startDate) { setError(`startDate-${g.id}`, "Start date required"); valid = false; } + if (!g.startTime) { setError(`startTime-${g.id}`, "Start time required"); valid = false; } + if (!g.hoursPerDay || Number(g.hoursPerDay) <= 0) { setError(`hoursPerDay-${g.id}`, "Enter hours"); valid = false; } + if (!g.expectedDurationValue || Number(g.expectedDurationValue) <= 0) { setError(`expectedDurationValue-${g.id}`, "Enter duration"); valid = false; } + if (!g.expectedDurationUnit) { setError(`expectedDurationUnit-${g.id}`, "Select unit"); valid = false; } + }); + + const payload = { + company: { + name: values.companyName?.trim(), + structure: values.companyStructure, + acn: values.acn, + abn: values.abn, + website: website || undefined, + contactName: values.contactName?.trim(), + contactEmail: values.contactEmail?.trim(), + contactMobile: values.contactMobile?.trim(), + contactLandline: values.contactLandline?.trim(), + }, + registeredAddress: { + street: values.registeredStreet?.trim(), + state: values.registeredState, + postcode: values.registeredPostcode, + }, + siteAddress: { + formattedAddress: selectedPlace?.formatted_address || searchVal, + lat: document.querySelector('input[name="siteLat"]').value || null, + lng: document.querySelector('input[name="siteLng"]').value || null, + placeId: document.querySelector('input[name="sitePlaceId"]').value || null, + mapUrl: buildMapLink( + document.querySelector('input[name="siteLat"]').value, + document.querySelector('input[name="siteLng"]').value, + document.querySelector('input[name="sitePlaceId"]').value + ), + }, + roles: groups.map((g, idx) => ({ + count: Number(g.count), + role: g.role, + tickets: g.tickets, + experience: g.experience, + startDate: g.startDate, + startTime: g.startTime, + hoursPerDay: Number(g.hoursPerDay), + expectedDurationValue: Number(g.expectedDurationValue), + expectedDurationUnit: g.expectedDurationUnit, + otOptions: g.otOptions, + groupNumber: idx + 1, + })), + notes: values.notes?.trim() || "", + submittedAt: new Date().toISOString(), + }; + + const acn = values.acn; + const abn = values.abn; + const hint = $("#abn-hint"); + if (abn && acn && abn.endsWith(acn)) hint.hidden = false; + else hint.hidden = true; + + return { valid, payload }; +} + +function exportCsv(payload) { + const rows = []; + const header = [ + "Company Structure", + "ACN", + "ABN", + "Website", + "Registered Street", + "Registered State", + "Registered Postcode", + "Site Address", + "Site Map Link", + "Role Group #", + "Number of Workers", + "Role", + "Tickets", + "Experience", + "Start Date", + "Start Time", + "Hours/Day", + "Expected Duration", + "OT Options", + "Notes", + "Submitted At", + ]; + rows.push(header); + payload.roles.forEach((r, idx) => { + rows.push([ + payload.company.structure, + payload.company.acn, + payload.company.abn, + payload.company.website || "", + payload.registeredAddress.street, + payload.registeredAddress.state, + payload.registeredAddress.postcode, + payload.siteAddress.formattedAddress, + payload.siteAddress.mapUrl, + idx + 1, + r.count, + r.role, + r.tickets.join("; "), + r.experience, + r.startDate, + r.startTime, + r.hoursPerDay, + `${r.expectedDurationValue} ${r.expectedDurationUnit}`, + r.otOptions.join("; "), + payload.notes, + payload.submittedAt, + ]); + }); + const csvContent = rows.map((row) => row.map((cell) => `"${String(cell ?? "").replace(/"/g, '""')}"`).join(",")).join("\n"); + const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "booking.csv"; + a.click(); + URL.revokeObjectURL(url); +} + +document.addEventListener("DOMContentLoaded", () => { + populateSelect(document.querySelector('select[name="companyStructure"]'), COMPANY_STRUCTURES); + populateSelect(document.querySelector('select[name="registeredState"]'), STATES); + + const form = $("#booking-form"); + + ["acn", "abn", "registeredPostcode"].forEach((name) => { + const input = form.elements.namedItem(name); + input?.addEventListener("input", (e) => { + e.target.value = numberOnly(e.target.value); + }); + input?.addEventListener("blur", validate); + }); + + addGroup(); + + $("#add-group").onclick = () => addGroup(); + + form.addEventListener("submit", (e) => { + e.preventDefault(); + const { valid } = validate(); + if (!valid) return; + alert("Form valid. See JSON preview or CSV export for payload."); + }); + + document.querySelectorAll("input, select, textarea").forEach((el) => { + el.addEventListener("blur", validate); + }); + + $("#open-json").onclick = () => { + const { valid, payload } = validate(); + if (!valid) { + alert("Fix errors before previewing JSON."); + return; + } + $("#json-output").textContent = JSON.stringify(payload, null, 2); + $("#modal").hidden = false; + }; + $("#close-modal").onclick = () => { $("#modal").hidden = true; }; + $("#modal").addEventListener("click", (e) => { + if (e.target.id === "modal") $("#modal").hidden = true; + }); + + $("#export-csv").onclick = () => { + const { valid, payload } = validate(); + if (!valid) { + alert("Fix errors before exporting CSV."); + return; + } + exportCsv(payload); + }; + + // Fallback map warning if Google never calls initMap + setTimeout(() => { + if (!mapSupported) { + const warning = $("#maps-warning"); + warning.hidden = false; + warning.textContent = "Google Maps did not load (API key missing?). You can still submit the form, but map search/pin are unavailable."; + } + }, 2000); +}); diff --git a/siteready-website/mock/book-labour/styles.css b/siteready-website/mock/book-labour/styles.css new file mode 100644 index 000000000..7caa01df8 --- /dev/null +++ b/siteready-website/mock/book-labour/styles.css @@ -0,0 +1,205 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: system-ui, -apple-system, "Segoe UI", sans-serif; + background: #f5f7fa; + color: #0b1f3b; +} + +.page { + max-width: 1100px; + margin: 0 auto; + padding: 1.5rem; +} + +h1 { + margin-bottom: 0.5rem; +} + +.lede { + margin: 0 0 0.75rem; + color: #4b5563; +} + +.section { + background: #ffffff; + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 1rem; + margin: 1rem 0; +} + +.section h2 { + margin: 0 0 0.75rem; +} + +label { + display: block; + font-weight: 600; + color: #111827; + margin-bottom: 0.6rem; +} + +label input, +label select, +label textarea { + width: 100%; + margin-top: 0.35rem; + padding: 0.65rem 0.75rem; + border: 1px solid #d1d5db; + border-radius: 8px; + font-size: 0.95rem; +} + +textarea { + resize: vertical; +} + +.grid { + display: grid; + gap: 0.85rem; +} + +.grid.two { + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); +} + +.grid.three { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); +} + +.map-row { + display: grid; + grid-template-columns: minmax(260px, 2fr) minmax(200px, 1fr); + gap: 1rem; + align-items: start; +} + +#map { + min-height: 260px; + background: #e5e7eb; + border: 1px solid #d1d5db; + border-radius: 12px; +} + +.map-meta { + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 0.75rem; + font-size: 0.95rem; +} + +.map-meta p { + margin: 0.35rem 0; +} + +.actions { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + margin: 0.75rem 0 0.25rem; +} + +button { + border: none; + background: #0b1f3b; + color: #fff; + padding: 0.65rem 1rem; + border-radius: 10px; + font-weight: 600; + cursor: pointer; +} + +button.secondary { + background: #ffffff; + color: #0b1f3b; + border: 1px solid #0b1f3b; +} + +.warning { + background: #fff7ed; + border: 1px solid #fdba74; + color: #9a3412; + padding: 0.75rem; + border-radius: 10px; +} + +.error { + display: block; + color: #b91c1c; + font-size: 0.85rem; + min-height: 1.1em; +} + +.hint { + display: block; + color: #065f46; + font-size: 0.85rem; +} + +.group { + border: 1px solid #e5e7eb; + border-radius: 12px; + padding: 0.75rem; + margin-bottom: 0.75rem; + background: #f9fafb; +} + +.group-head { + display: flex; + align-items: center; + justify-content: space-between; +} + +.inline { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.modal { + position: fixed; + inset: 0; + background: rgba(0,0,0,0.45); + display: grid; + place-items: center; +} + +.modal-content { + background: #fff; + border-radius: 12px; + max-width: 800px; + width: 90%; + max-height: 80vh; + padding: 1rem; + display: grid; + grid-template-rows: auto 1fr; +} + +.modal-head { + display: flex; + align-items: center; + justify-content: space-between; +} + +.modal pre { + overflow: auto; + background: #0b1f3b; + color: #f9fafb; + padding: 0.75rem; + border-radius: 8px; + margin: 0.75rem 0 0; + font-size: 0.9rem; +} + +@media (max-width: 768px) { + .map-row { + grid-template-columns: 1fr; + } +} diff --git a/siteready-website/next.config.ts b/siteready-website/next.config.ts new file mode 100644 index 000000000..92003998c --- /dev/null +++ b/siteready-website/next.config.ts @@ -0,0 +1,40 @@ +import type { NextConfig } from "next"; +import path from "path"; + +const nextConfig: NextConfig = { + output: "standalone", + images: { + unoptimized: true, + }, + env: { + BASIC_AUTH_ENABLED: process.env.BASIC_AUTH_ENABLED, + BASIC_AUTH_USER: process.env.BASIC_AUTH_USER, + BASIC_AUTH_PASS: process.env.BASIC_AUTH_PASS, + }, + async headers() { + return [ + { + source: "/(.*)", + headers: [ + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, + { key: "X-Frame-Options", value: "DENY" }, + { key: "Permissions-Policy", value: "geolocation=(), microphone=(), camera=()" }, + { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains; preload" }, + ], + }, + ]; + }, + webpack: (config) => { + config.resolve = config.resolve || {}; + config.resolve.alias = { + ...(config.resolve.alias || {}), + "@vercel/og": path.resolve(__dirname, "stubs/empty.js"), + "@vercel/og/wasm": path.resolve(__dirname, "stubs/empty.js"), + "@vercel/og/image-response": path.resolve(__dirname, "stubs/empty.js"), + }; + return config; + }, +}; + +export default nextConfig; diff --git a/siteready-website/non_token_colors.json b/siteready-website/non_token_colors.json new file mode 100644 index 000000000..59b0b9ced --- /dev/null +++ b/siteready-website/non_token_colors.json @@ -0,0 +1,1542 @@ +[ + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\requests\\page.tsx", + "LineNumber": 344, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 221, + "Line": "border: \"1px solid #e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 224, + "Line": "background: \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 232, + "Line": "color: \"#0f172a\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 238, + "Line": "color: \"#0f172a\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 243, + "Line": "verified: \"#bbf7d0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 244, + "Line": "pending: \"#fde68a\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 245, + "Line": "needs_clearer: \"#fdba74\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 246, + "Line": "out_of_date: \"#fecaca\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 247, + "Line": "rejected: \"#fecaca\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 248, + "Line": "unverified: \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 255, + "Line": "background: colors[value] || \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 258, + "Line": "color: \"#0f172a\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 641, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 644, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 647, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 650, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 653, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 673, + "Line": "{item.missing ?
    Missing or unsigned declaration
    : null}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 675, + "Line": "
    Working holiday maker with tax-free threshold set
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 692, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 700, + "Line": "
    {w.role_id || \"Unassigned\"}
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 732, + "Line": "
    Worker profile
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 733, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 779, + "Line": "? \"#f87171\"" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 782, + "Line": "? \"#f87171\"" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 794, + "Line": "
    Company details
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 828, + "Line": "borderColor: needsSuper ? \"#f87171\" : cardStyle.border," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 833, + "Line": "
    Superannuation (payroll)
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 838, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 847, + "Line": "background: superChoice === \"REGULATED\" ? \"#dcfce7\" : \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 848, + "Line": "borderColor: \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 872, + "Line": "background: superChoice === \"SMSF\" ? \"#e0f2fe\" : \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 873, + "Line": "borderColor: \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 894, + "Line": "background: superChoice === \"STAPLED\" ? \"#fef3c7\" : \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 895, + "Line": "borderColor: \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 930, + "Line": "{superLoading[w.worker_id] ?
    Searching…
    : null}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 933, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 953, + "Line": "
    ABN {result.abn}
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 995, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1032, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1051, + "Line": "borderColor: needsApproval(details[w.worker_id]) ? \"#f87171\" : cardStyle.border," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1057, + "Line": "
    Identification documents
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1186, + "Line": "
    Banking
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1206, + "Line": "
    Tax declaration
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1229, + "Line": "
    Tickets
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1255, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1261, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1344, + "Line": "
    Work experience
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1404, + "Line": "
    No media uploaded.
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1407, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1425, + "Line": "
    Work media (additional)
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1483, + "Line": "
    Emergency contact
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1494, + "Line": "
    Consents
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1501, + "Line": "
    Delete request
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1518, + "Line": "style={{ background: \"#e0f2fe\", borderColor: \"#38bdf8\", color: \"#075985\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1525, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1530, + "Line": "{syncMessages[w.worker_id]}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\admin\\workers\\page.tsx", + "LineNumber": 1562, + "Line": "background: \"#fff\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\proposed\\page.tsx", + "LineNumber": 78, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 17, + "Line": "submitted: { label: \"Submitted\", color: \"#2563eb\", next: \"Under review\" }," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 18, + "Line": "under_review: { label: \"Under review\", color: \"#f59e0b\", next: \"Approved or declined\" }," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 19, + "Line": "approved: { label: \"Approved\", color: \"#10b981\", next: \"Assigned\" }," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 20, + "Line": "declined: { label: \"Declined\", color: \"#ef4444\", next: \"Closed\" }," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 21, + "Line": "assigned: { label: \"Assigned\", color: \"#6366f1\", next: \"Active\" }," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 22, + "Line": "active: { label: \"Active\", color: \"#0ea5e9\", next: \"Completed\" }," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 23, + "Line": "completed: { label: \"Completed\", color: \"#64748b\", next: \"Closed\" }," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 72, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 83, + "Line": "color: \"#fff\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\requests\\status\\page.tsx", + "LineNumber": 122, + "Line": "color: \"#fff\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\assignments\\page.tsx", + "LineNumber": 33, + "Line": "

    Active assignments

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 35, + "Line": "border: \"1px solid #e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 37, + "Line": "background: \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 49, + "Line": "border: \"1px solid #e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 51, + "Line": "background: \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1581, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1586, + "Line": "style={{ background: \"#ecfdf3\", borderColor: \"#22c55e\", color: \"#166534\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1593, + "Line": "style={{ background: \"#fee2e2\", borderColor: \"#ef4444\", color: \"#991b1b\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1620, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1625, + "Line": "style={{ background: \"#ecfdf3\", borderColor: \"#22c55e\", color: \"#166534\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1633, + "Line": "style={{ background: \"#fee2e2\", borderColor: \"#ef4444\", color: \"#991b1b\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1763, + "Line": "const sectionTitleStyle = { fontWeight: 800, color: \"#0f172a\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1768, + "Line": "return { label: \"Proposed position - waiting your approval\", bg: \"#fef3c7\", color: \"#92400e\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1771, + "Line": "return { label: \"Assigned to job\", bg: \"#dcfce7\", color: \"#166534\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1774, + "Line": "return { label: \"Working elsewhere - offers paused\", bg: \"#fee2e2\", color: \"#991b1b\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1777, + "Line": "return { label: \"Working elsewhere - still open to offers\", bg: \"#e0f2fe\", color: \"#075985\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1781, + "Line": "return { label: `Working now${from}`, bg: \"#fef3c7\", color: \"#92400e\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1785, + "Line": "return { label: `Fully available${from}`, bg: \"#dcfce7\", color: \"#166534\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1788, + "Line": "return { label: \"Approved\", bg: \"#dcfce7\", color: \"#166534\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1791, + "Line": "return { label: \"Pending approval\", bg: \"#e0f2fe\", color: \"#075985\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1793, + "Line": "return { label: \"Unverified - complete your profile\", bg: \"#fee2e2\", color: \"#991b1b\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1797, + "Line": "return { label: \"Approved profile\", bg: \"#dcfce7\", color: \"#166534\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1800, + "Line": "return { label: \"Pending approval\", bg: \"#e0f2fe\", color: \"#075985\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1802, + "Line": "return { label: \"Unverified - complete your profile\", bg: \"#fee2e2\", color: \"#991b1b\" };" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1808, + "Line": "

    Worker portal

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1812, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1824, + "Line": "style={{ background: \"#eff6ff\", borderColor: \"#3b82f6\", color: \"#1d4ed8\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1830, + "Line": "style={{ background: \"#ecfdf3\", borderColor: \"#22c55e\", color: \"#166534\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1836, + "Line": "style={{ background: \"#eef2ff\", borderColor: \"#6366f1\", color: \"#3730a3\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1842, + "Line": "style={{ background: \"#fff7ed\", borderColor: \"#f97316\", color: \"#c2410c\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1848, + "Line": "style={{ background: \"#fef2f2\", borderColor: \"#ef4444\", color: \"#991b1b\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 1990, + "Line": "border: \"1px solid #d4d4d4\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2035, + "Line": "border: profileErrors.super_choice ? \"2px solid #ef4444\" : undefined," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2046, + "Line": "background: superChoice === \"REGULATED\" ? \"#dcfce7\" : \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2047, + "Line": "borderColor: superChoice === \"REGULATED\" ? \"#22c55e\" : \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2049, + "Line": "color: \"#0f172a\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2066, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2075, + "Line": "background: superChoice === \"SMSF\" ? \"#e0f2fe\" : \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2076, + "Line": "borderColor: superChoice === \"SMSF\" ? \"#38bdf8\" : \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2078, + "Line": "color: \"#0f172a\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2097, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2106, + "Line": "background: superChoice === \"STAPLED\" ? \"#fef3c7\" : \"#f8fafc\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2107, + "Line": "borderColor: superChoice === \"STAPLED\" ? \"#f59e0b\" : \"#e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2109, + "Line": "color: \"#0f172a\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2129, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2135, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2150, + "Line": "Super member number (If registering as a company leave blank)" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2167, + "Line": "{fundLoading ?
    Searching…
    : null}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2172, + "Line": "border: \"1px solid #e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2175, + "Line": "background: \"#fff\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2190, + "Line": "borderBottom: \"1px solid #e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2194, + "Line": "
    ABN {result.abn}
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2227, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2267, + "Line": "border: Object.keys(taxDeclarationErrors).length ? \"2px solid #ef4444\" : undefined," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2272, + "Line": "

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2276, + "Line": "

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2319, + "Line": "
    We store this securely.
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2391, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2606, + "Line": "

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2645, + "Line": "style={{ background: \"#16a34a\", borderColor: \"#16a34a\", color: \"#fff\", fontWeight: 800 }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2657, + "Line": "

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 2660, + "Line": "

    Company details (if operating under your ABN)

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3051, + "Line": "style={{ background: \"#ecfdf3\", borderColor: \"#22c55e\", color: \"#166534\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3057, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3062, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3081, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3245, + "Line": "style={{ background: \"#fff7ed\", borderColor: \"#f97316\", color: \"#c2410c\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3258, + "Line": "style={{ background: \"#eff6ff\", borderColor: \"#3b82f6\", color: \"#1d4ed8\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3310, + "Line": "color: expiryCountdown.startsWith(\"Expires in\") ? \"#0f172a\" : \"#b91c1c\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3323, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3353, + "Line": "style={{ background: \"#ecfdf3\", borderColor: \"#22c55e\", color: \"#166534\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3361, + "Line": "style={{ background: \"#fee2e2\", borderColor: \"#ef4444\", color: \"#991b1b\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3371, + "Line": "style={{ background: \"#eff6ff\", borderColor: \"#3b82f6\", color: \"#1d4ed8\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3633, + "Line": "style={{ background: \"#fee2e2\", borderColor: \"#ef4444\", color: \"#991b1b\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3685, + "Line": "style={{ background: \"#ecfdf3\", borderColor: \"#22c55e\", color: \"#166534\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3692, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3822, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3829, + "Line": "color: submitStatus === \"success\" ? \"#166534\" : submitStatus === \"loading\" ? \"#1d4ed8\" : \"#b91c1c\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3862, + "Line": "style={{ background: \"#fee2e2\", borderColor: \"#ef4444\", color: \"#991b1b\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3885, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3886, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3890, + "Line": "style={{ background: \"#fee2e2\", borderColor: \"#ef4444\", color: \"#991b1b\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3896, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3917, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\page.tsx", + "LineNumber": 3959, + "Line": "style={{ background: \"#16a34a\", borderColor: \"#16a34a\", color: \"#fff\", fontWeight: 800 }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\proposed\\page.tsx", + "LineNumber": 27, + "Line": "

    Proposed positions

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\timesheets\\page.tsx", + "LineNumber": 202, + "Line": "border: \"1px solid #e2e8f0\"," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\timesheets\\page.tsx", + "LineNumber": 203, + "Line": "background: [\"#fff7ed\", \"#eff6ff\", \"#ecfdf3\", \"#fdf2f8\", \"#fef9c3\", \"#f3f4f6\", \"#ede9fe\"][index % 7]," + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\working-now\\page.tsx", + "LineNumber": 102, + "Line": "

    Current status / Working now / Holidays

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\working-now\\page.tsx", + "LineNumber": 109, + "Line": "

    Your current status

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\working-now\\page.tsx", + "LineNumber": 195, + "Line": "

    Planned holidays

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(dashboard)\\dashboard\\worker\\working-now\\page.tsx", + "LineNumber": 238, + "Line": "style={{ background: \"#dcfce7\", borderColor: \"#22c55e\", color: \"#166534\" }}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\admin-setup\\page.tsx", + "LineNumber": 41, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\contact\\page.tsx", + "LineNumber": 133, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\contact\\page.tsx", + "LineNumber": 138, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\for-workers\\page.tsx", + "LineNumber": 370, + "Line": "

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\for-workers\\page.tsx", + "LineNumber": 375, + "Line": "

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\login\\page.tsx", + "LineNumber": 105, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\login\\page.tsx", + "LineNumber": 106, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\login\\page.tsx", + "LineNumber": 107, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\login\\page.tsx", + "LineNumber": 108, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\page.tsx", + "LineNumber": 126, + "Line": "

    Booking #1023 - Confirmed
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\platform-not-configured\\page.tsx", + "LineNumber": 3, + "Line": "
    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\privacy\\page.tsx", + "LineNumber": 12, + "Line": "

    " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\privacy\\page.tsx", + "LineNumber": 18, + "Line": "

      " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\privacy\\page.tsx", + "LineNumber": 26, + "Line": "
        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\privacy\\page.tsx", + "LineNumber": 34, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\privacy\\page.tsx", + "LineNumber": 40, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\privacy\\page.tsx", + "LineNumber": 46, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\privacy\\page.tsx", + "LineNumber": 50, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\register\\page.tsx", + "LineNumber": 8, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\register\\page.tsx", + "LineNumber": 9, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\register\\page.tsx", + "LineNumber": 10, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\register\\page.tsx", + "LineNumber": 11, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\register\\page.tsx", + "LineNumber": 77, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\technology\\page.tsx", + "LineNumber": 30, + "Line": "
        Booking #1402 • Awaiting approval
        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\terms\\page.tsx", + "LineNumber": 12, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\terms\\page.tsx", + "LineNumber": 17, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\terms\\page.tsx", + "LineNumber": 23, + "Line": "

        " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\terms\\page.tsx", + "LineNumber": 29, + "Line": "

          " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\terms\\page.tsx", + "LineNumber": 35, + "Line": "

          " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\terms\\page.tsx", + "LineNumber": 41, + "Line": "

          " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\(marketing)\\terms\\page.tsx", + "LineNumber": 45, + "Line": "

          " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\api\\auth\\register\\route.ts", + "LineNumber": 73, + "Line": "

          " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\api\\auth\\register\\route.ts", + "LineNumber": 77, + "Line": "" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\api\\auth\\register\\route.ts", + "LineNumber": 82, + "Line": "

          This email is for your account access and updates.

          " + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 4, + "Line": "--color-srPrimary: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 5, + "Line": "--color-srAccent: #ff7a1a;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 6, + "Line": "--color-srTeal: #11b6a8;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 7, + "Line": "--color-srText: #212529;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 8, + "Line": "--color-srMuted: #6b7280;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 9, + "Line": "--color-srBg: #f5f7fa;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 10, + "Line": "--color-srCard: #ffffff;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\app\\globals.css", + "LineNumber": 11, + "Line": "--color-srBorder: #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 8, + "Line": "background: #fff4eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 9, + "Line": "border: 1px solid #ffd9b8;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 20, + "Line": "color: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 25, + "Line": "color: #c2410c;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 57, + "Line": "color: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 64, + "Line": "border: 1px solid #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 68, + "Line": "background: #fff;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 77, + "Line": "color: #b91c1c;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 82, + "Line": "border-color: #b91c1c !important;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 103, + "Line": "border-color: #b91c1c;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 109, + "Line": "border: 1px solid #fca5a5;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 110, + "Line": "background: #fef2f2;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 111, + "Line": "color: #991b1b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 119, + "Line": "border: 1px solid #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 122, + "Line": "background: #f9fafb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 139, + "Line": "background: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 140, + "Line": "color: #fff;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 156, + "Line": "background: #fff4eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 157, + "Line": "color: #b45309;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 158, + "Line": "border: 1px solid #fed7aa;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 164, + "Line": "background: #ecfdf3;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 165, + "Line": "color: #166534;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 166, + "Line": "border: 1px solid #bbf7d0;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 169, + "Line": "background: #fef2f2;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 170, + "Line": "color: #991b1b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 171, + "Line": "border: 1px solid #fecaca;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 174, + "Line": "background: #f3f4f6;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 175, + "Line": "color: #6b7280;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 176, + "Line": "border: 1px solid #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 187, + "Line": "color: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 193, + "Line": "border: 1px solid #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 206, + "Line": "background: #f3f4f6;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 211, + "Line": "background: #fff7ed;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 212, + "Line": "border: 1px solid #fdba74;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 213, + "Line": "color: #9a3412;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 239, + "Line": "border-bottom: 1px solid #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 247, + "Line": "color: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 248, + "Line": "background: #fff7ed;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 252, + "Line": "color: #6b7280;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 260, + "Line": "background: #fef3c7;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 261, + "Line": "border: 1px solid #fcd34d;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 262, + "Line": "color: #92400e;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 269, + "Line": "color: #065f46;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 280, + "Line": "border: 1px solid #fdba74;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 281, + "Line": "background: #fff7ed;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 282, + "Line": "color: #9a3412;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 297, + "Line": "color: #065f46;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 310, + "Line": "border: 1px solid #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 314, + "Line": "background: #fff;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 329, + "Line": "color: #4b5563;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 340, + "Line": "border: 1px solid #e5e7eb;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 342, + "Line": "background: #fff;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 361, + "Line": ".section { background: #ffffff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 1rem; }" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 363, + "Line": ".sectionTitleStrong { font-size: 1.1rem; color: #0b1f3b; font-weight: 700; }" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 364, + "Line": ".highlightSection { border: 2px solid #ff7a1a; background: #fff4eb; }" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 365, + "Line": ".workersTitle { font-size: 1.15rem; color: #0b1f3b; font-weight: 800; }" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 366, + "Line": ".note { font-weight: 600; color: #0b1f3b; }" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 367, + "Line": ".emphasis { font-size: 0.95rem; color: #d97706; font-weight: 700; }" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 369, + "Line": "border: 1px solid #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 370, + "Line": "background: #ffffff;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 371, + "Line": "color: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 380, + "Line": "background: #0b1f3b;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 381, + "Line": "color: #ffffff;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.module.css", + "LineNumber": 389, + "Line": "color: #b45309;" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.tsx", + "LineNumber": 591, + "Line": "{status === \"success\" && {message}}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\booking\\BookLabourNowForm.tsx", + "LineNumber": 592, + "Line": "{status === \"error\" && {message || \"Submission failed.\"}}" + }, + { + "Path": "C:\\Users\\info\\Documents\\GitHub\\hashlips_art_engine\\siteready-website\\src\\components\\footer.tsx", + "LineNumber": 11, + "Line": "