diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 24d3815..cb9b9a3 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,17 +1,16 @@
---
name: Bug report
about: Create a report to help us improve
-title: ''
+title: ""
labels: awaiting triage, bug
assignees: mtbitcr
-
---
**Internal Bug Number:** [Internal tracking code according to the bug tracking table]
-**Priority:** [Low | Medium | High] *(Please select one)*
+**Priority:** [Low | Medium | High] _(Please select one)_
-**Bug Type:** [Functional | Design | Both] *(Please select one)*
+**Bug Type:** [Functional | Design | Both] _(Please select one)_
**Affected Component:** [Specific UI component name, e.g., "Accept Quote button"]
@@ -21,20 +20,21 @@ assignees: mtbitcr
**Expected Behavior:** [Describe what should be happening. E.g., "Clicking the Quote button should redirect the user to the "View Quotes" screen"]
-**Screenshots/Videos:** [Attach screenshots or videos clearly demonstrating the bug. Annotate the screenshots to highlight the affected area. If possible, include screen recordings to show the steps to reproduce the bug.]
+**Screenshots/Videos:** [Attach screenshots or videos clearly demonstrating the bug. Annotate the screenshots to highlight the affected area. If possible, include screen recordings to show the steps to reproduce the bug.]
**Environment:**
-* **Device:** [Desktop | Mobile | Tablet] *(Please select one)*
-* **OS:** [e.g., macOS Ventura, Windows 11, iOS 16, Android 13]
-* **Browser:** [e.g., Chrome 114, Safari 16, Firefox 110, Edge 114]
-* **Browser Version (if applicable):** [e.g., 114.0.5735.199]
-* **Resolution (if applicable):** [e.g., 1920x1080]
-* **App Version (if applicable):** [e.g., v1.2.3]
+- **Device:** [Desktop | Mobile | Tablet] _(Please select one)_
+- **OS:** [e.g., macOS Ventura, Windows 11, iOS 16, Android 13]
+- **Browser:** [e.g., Chrome 114, Safari 16, Firefox 110, Edge 114]
+- **Browser Version (if applicable):** [e.g., 114.0.5735.199]
+- **Resolution (if applicable):** [e.g., 1920x1080]
+- **App Version (if applicable):** [e.g., v1.2.3]
**Additional Context:** [Add any other relevant information, such as:
-* Impact of the bug
-* Workarounds (if any)
-* Frequency of occurrence (e.g., always, sometimes, rarely)
-* Related issues (if any)
-* User feedback (if any)]
+
+- Impact of the bug
+- Workarounds (if any)
+- Frequency of occurrence (e.g., always, sometimes, rarely)
+- Related issues (if any)
+- User feedback (if any)]
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 4233e2c..f2bb2f4 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,10 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
-title: ''
+title: ""
labels: awaiting triage, enhancement, new feature
-assignees: ''
-
+assignees: ""
---
**Is your feature request related to a problem? Please describe.**
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 692c1be..8c9818d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,7 @@ jobs:
name: Validate (style, lint, test, build)
runs-on: ubuntu-latest
env:
- NODE_VERSION: 22
+ NODE_VERSION: 25
steps:
- name: Checkout code
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 46eec18..9e8b4b0 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -3,35 +3,33 @@ name: Deploy wildcat-dashboard-ui (Cloudflare Pages)
on:
push:
tags:
- - 'v*'
+ - "v*"
branches:
- - 'master'
+ - "master"
workflow_dispatch:
inputs:
- environment:
- description: 'target'
- required: true
- default: 'production'
- type: choice
- options:
- - dev
- - staging
- - production
-
+ environment:
+ description: "target"
+ required: true
+ default: "production"
+ type: choice
+ options:
+ - dev
+ - staging
+ - production
jobs:
-
-######################################################################
-# ENV: dev
-# CF project: wildcat-dashboard
-######################################################################
+ ######################################################################
+ # ENV: dev
+ # CF project: wildcat-dashboard
+ ######################################################################
deploy-dev:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
concurrency:
- group: 'deploy-dev-${{ github.ref_name }}'
+ group: "deploy-dev-${{ github.ref_name }}"
cancel-in-progress: true
# SET ENVIRONMENT
@@ -56,7 +54,7 @@ jobs:
with:
ref: ${{ github.ref }}
fetch-depth: 0
-
+
# for dev: only allow dev branch
- name: Validate correct branch on manual dispatch
run: |
@@ -71,7 +69,7 @@ jobs:
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
+ cache: "npm"
- name: Install dependencies
run: npm ci
@@ -87,22 +85,21 @@ jobs:
id: deploy-dev-dispatch
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
- apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=${{ env.CLOUDFLARE_PROJECT }} --branch=dev --commit-message="MANUAL DISPATCH ${{ github.sha }}" --commit-hash=${{ github.sha }} --commit-dirty=true
-
-######################################################################
-# ENV: staging
-# CF project: wildcat-dashboard-staging
-######################################################################
+ ######################################################################
+ # ENV: staging
+ # CF project: wildcat-dashboard-staging
+ ######################################################################
deploy-staging:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
concurrency:
- group: 'deploy-staging-${{ github.ref_name }}'
+ group: "deploy-staging-${{ github.ref_name }}"
cancel-in-progress: true
# SET ENVIRONMENT
@@ -144,7 +141,7 @@ jobs:
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
+ cache: "npm"
- name: Install dependencies
run: npm ci
@@ -154,15 +151,15 @@ jobs:
##############################################################################
# Customize deployment triggers
-
+
# STAGING PREVIEW: on push, branch master
- name: Deploy ${{ github.ref_name }} to STAGING PREVIEW of ${{ env.CLOUDFLARE_PROJECT }} project
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
id: deploy-staging-preview-push-master
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
- apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=${{ env.CLOUDFLARE_PROJECT }} --branch=preview --commit-message="PREVIEW ${{ github.ref_name }}" --commit-hash=${{ github.sha }} --commit-dirty=true
# STAGING PROD: on push, with tag
@@ -171,8 +168,8 @@ jobs:
id: deploy-staging-push-tag
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
- apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=${{ env.CLOUDFLARE_PROJECT }} --branch=master --commit-message="${{ github.ref_name }} (TAG PUSH)" --commit-hash=${{ github.sha }} --commit-dirty=true
# STAGING PROD: on manual dispatch
@@ -181,22 +178,21 @@ jobs:
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'staging'
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
- apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=${{ env.CLOUDFLARE_PROJECT }} --branch=master --commit-message="${{ github.ref_name }} (DISPATCH)" --commit-hash=${{ github.sha }} --commit-dirty=true
-
-######################################################################
-# ENV: production
-# CF project: wildcat-dashboard-production
-######################################################################
+ ######################################################################
+ # ENV: production
+ # CF project: wildcat-dashboard-production
+ ######################################################################
deploy-production:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
concurrency:
- group: 'deploy-production-${{ github.ref_name }}'
+ group: "deploy-production-${{ github.ref_name }}"
cancel-in-progress: true
# SET ENVIRONMENT
@@ -238,7 +234,7 @@ jobs:
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
+ cache: "npm"
- name: Install dependencies
run: npm ci
@@ -246,18 +242,17 @@ jobs:
- name: Build app
run: npm run build
-
##############################################################################
# Customize deployment triggers
-
+
# PRODUCTION PREVIEW: on push, with tag
- name: Deploy ${{ github.ref_name }} to PRODUCTION PREVIEW of ${{ env.CLOUDFLARE_PROJECT }} project
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
id: deploy-production-preview-push-tag
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
- apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=${{ env.CLOUDFLARE_PROJECT }} --branch=preview --commit-message="PREVIEW ${{ github.ref_name }}" --commit-hash=${{ github.sha }} --commit-dirty=true
# PRODUCTION PROD: manual dispatch, with tag
@@ -266,6 +261,6 @@ jobs:
if: github.event_name == 'workflow_dispatch' && github.event.inputs.environment == 'production'
uses: cloudflare/wrangler-action@da0e0dfe58b7a431659754fdf3f186c529afbe65 # v3.14.1
with:
- apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
- accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- command: pages deploy dist --project-name=${{ env.CLOUDFLARE_PROJECT }} --branch=master --commit-message="${{ github.ref_name }}" --commit-hash=${{ github.sha }} --commit-dirty=true
\ No newline at end of file
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ command: pages deploy dist --project-name=${{ env.CLOUDFLARE_PROJECT }} --branch=master --commit-message="${{ github.ref_name }}" --commit-hash=${{ github.sha }} --commit-dirty=true
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 0d43069..8ac131c 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: true
env:
- IMAGE_NAME: "bcr-wdc-dashboard-ui"
+ IMAGE_NAME: "bcr-wdc-dashboard-ui"
jobs:
build:
@@ -20,37 +20,37 @@ jobs:
packages: write
steps:
- - name: Checkout code
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
-
- - name: Login to GHCR
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up Docker Buildx
- id: buildx
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
-
- - id: meta
- name: Metadata
- uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
- with:
- images: |
- ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
- tags: |
- type=raw,value=nightly
-
- - id: push
- name: Build & push ${{ env.IMAGE_NAME }}
- uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
- with:
- context: .
- file: ./Dockerfile
- push: true
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
- cache-from: type=gha,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
- cache-to: type=gha,mode=max,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
+ - name: Checkout code
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Login to GHCR
+ uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
+
+ - id: meta
+ name: Metadata
+ uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
+ with:
+ images: |
+ ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=raw,value=nightly
+
+ - id: push
+ name: Build & push ${{ env.IMAGE_NAME }}
+ uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
+ cache-to: type=gha,mode=max,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2c99fd1..5b93e34 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,7 +10,7 @@ concurrency:
cancel-in-progress: true
env:
- IMAGE_NAME: "bcr-wdc-dashboard-ui"
+ IMAGE_NAME: "bcr-wdc-dashboard-ui"
jobs:
build:
@@ -20,37 +20,37 @@ jobs:
packages: write
steps:
- - name: Checkout code
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
-
- - name: Login to GHCR
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up Docker Buildx
- id: buildx
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
-
- - id: meta
- name: Metadata
- uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
- with:
- images: |
- ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
- tags: |
- type=semver,pattern={{version}}
-
- - id: push
- name: Build & push ${{ env.IMAGE_NAME }}
- uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
- with:
- context: .
- file: ./Dockerfile
- push: true
- tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
- cache-from: type=gha,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
- cache-to: type=gha,mode=max,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
\ No newline at end of file
+ - name: Checkout code
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - name: Login to GHCR
+ uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
+
+ - id: meta
+ name: Metadata
+ uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
+ with:
+ images: |
+ ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=semver,pattern={{version}}
+
+ - id: push
+ name: Build & push ${{ env.IMAGE_NAME }}
+ uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
+ cache-to: type=gha,mode=max,scope=${{ github.repository }}/${{ env.IMAGE_NAME }}
diff --git a/.prettierrc b/.prettierrc
index 37dedb1..e69d8aa 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,9 +1,12 @@
{
+ "printWidth": 80,
"tabWidth": 2,
- "semi": false,
+ "useTabs": false,
+ "semi": true,
"singleQuote": false,
"trailingComma": "all",
- "printWidth": 120,
- "useTabs": false,
- "endOfLine":"auto"
+ "arrowParens": "always",
+ "singleAttributePerLine": true,
+ "bracketSpacing": true,
+ "endOfLine": "lf"
}
diff --git a/Dockerfile b/Dockerfile
index 78b8322..98b5b51 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
ARG NODE_ENV
ARG VITE_MODE
-FROM node:24.2.0-slim@sha256:b30c143a092c7dced8e17ad67a8783c03234d4844ee84c39090c9780491aaf89 AS builder
+FROM node:25-slim AS builder
ARG NODE_ENV
ARG VITE_MODE
ENV NODE_ENV=${NODE_ENV:-production}
diff --git a/README.md b/README.md
index 497b774..b96456a 100644
--- a/README.md
+++ b/README.md
@@ -2,30 +2,39 @@
## Development
+Prerequisite: Node.js 25.x (see `.nvmrc`).
+
### Install
+
```
npm install
```
### Run Development
+
```
npm run dev
```
### Run Docker
+
Copy `.env.example` to `.env` and adjust
+
```
just build
just run
```
### shadcn
+
Add a shadcn component:
+
```shell
npx shadcn@canary add button
```
## Configuration
+
`VITE_API_BASE_URL="http://localhost:4242"` needs to point to the Wildcat BFF Dashboard Envoy Service
```
diff --git a/components.json b/components.json
index 640eed6..d72dbcd 100644
--- a/components.json
+++ b/components.json
@@ -18,4 +18,4 @@
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
-}
\ No newline at end of file
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 3c7d97f..a45555c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -16,7 +16,15 @@ services:
- VITE_API_MOCKING_ENABLED=${VITE_API_MOCKING_ENABLED}
- VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING=${VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING}
healthcheck:
- test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80"]
+ test:
+ [
+ "CMD",
+ "wget",
+ "--no-verbose",
+ "--tries=1",
+ "--spider",
+ "http://localhost:80",
+ ]
interval: 30s
timeout: 10s
retries: 3
diff --git a/eslint.config.js b/eslint.config.js
index c36899f..da2da9c 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,54 +1,62 @@
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import tseslint from 'typescript-eslint'
-import react from 'eslint-plugin-react'
+import js from "@eslint/js";
+import globals from "globals";
+import reactHooks from "eslint-plugin-react-hooks";
+import reactRefresh from "eslint-plugin-react-refresh";
+import tseslint from "typescript-eslint";
+import react from "eslint-plugin-react";
-import eslintConfigPrettier from 'eslint-config-prettier'
+import eslintConfigPrettier from "eslint-config-prettier";
export default tseslint.config(
{
languageOptions: {
parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ project: ["./tsconfig.node.json", "./tsconfig.app.json"],
tsconfigRootDir: import.meta.dirname,
},
},
- }, {
+ },
+ {
ignores: [
- 'dist',
- '.storybook',
- 'public/mockServiceWorker.js',
- 'src/generated',
- ]
- }, {
+ "dist",
+ ".storybook",
+ "coverage",
+ "opt",
+ "public/mockServiceWorker.js",
+ "src/generated",
+ ],
+ },
+ {
extends: [
- js.configs.recommended,
+ js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
],
- ignores: ['src/components/ui/*'],
- files: ['**/*.{ts,tsx}'],
+ ignores: ["src/components/ui/*"],
+ files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
- settings: { react: { version: '19.0.0' } },
+ settings: { react: { version: "19.0.0" } },
plugins: {
react,
- 'react-hooks': reactHooks,
- 'react-refresh': reactRefresh,
+ "react-hooks": reactHooks,
+ "react-refresh": reactRefresh,
},
rules: {
...react.configs.recommended.rules,
- ...react.configs['jsx-runtime'].rules,
+ ...react.configs["jsx-runtime"].rules,
...reactHooks.configs.recommended.rules,
- 'react-refresh/only-export-components': [
- 'warn',
+ "react-hooks/immutability": "off",
+ "react-hooks/purity": "off",
+ "react-hooks/set-state-in-effect": "off",
+ "react-hooks/incompatible-library": "off",
+ "react-refresh/only-export-components": [
+ "warn",
{ allowConstantExport: true },
],
},
},
eslintConfigPrettier,
-)
+);
diff --git a/index.html b/index.html
index c5f713b..a0efdd2 100644
--- a/index.html
+++ b/index.html
@@ -2,15 +2,35 @@
-
-
-
-
+
+
+
+
Wildcat Dashboard
-
+
diff --git a/openapi-ts.config.ts b/openapi-ts.config.ts
index fb98733..7ef6a7f 100644
--- a/openapi-ts.config.ts
+++ b/openapi-ts.config.ts
@@ -1,19 +1,19 @@
-import { defineConfig } from '@hey-api/openapi-ts';
+import { defineConfig } from "@hey-api/openapi-ts";
export default defineConfig({
- input: 'opt/wildcat/openapi.json',
+ input: "opt/wildcat/openapi.json",
output: {
- format: 'prettier',
- lint: 'eslint',
- path: './src/generated/client',
+ format: "prettier",
+ lint: "eslint",
+ path: "./src/generated/client",
},
plugins: [
- '@hey-api/client-fetch',
- '@hey-api/sdk',
+ "@hey-api/client-fetch",
+ "@hey-api/sdk",
/* '@hey-api/schemas', {
enums: 'javascript',
name: '@hey-api/typescript',
},*/
- '@tanstack/react-query',
+ "@tanstack/react-query",
],
-})
+});
diff --git a/opt/wildcat/openapi.json b/opt/wildcat/openapi.json
index 7e6b3b7..cf21b1d 100644
--- a/opt/wildcat/openapi.json
+++ b/opt/wildcat/openapi.json
@@ -832,7 +832,12 @@
},
"BillAcceptanceStatus": {
"type": "object",
- "required": ["requested_to_accept", "accepted", "request_to_accept_timed_out", "rejected_to_accept"],
+ "required": [
+ "requested_to_accept",
+ "accepted",
+ "request_to_accept_timed_out",
+ "rejected_to_accept"
+ ],
"properties": {
"time_of_request_to_accept": {
"type": ["integer", "null"],
@@ -1007,7 +1012,16 @@
},
"BillInfo": {
"type": "object",
- "required": ["id", "drawee", "drawer", "payee", "endorsees", "sum", "maturity_date", "file_urls"],
+ "required": [
+ "id",
+ "drawee",
+ "drawer",
+ "payee",
+ "endorsees",
+ "sum",
+ "maturity_date",
+ "file_urls"
+ ],
"properties": {
"id": {
"type": "string"
@@ -1077,7 +1091,14 @@
},
"BillParticipants": {
"type": "object",
- "required": ["drawee", "drawer", "payee", "endorsements", "endorsements_count", "all_participant_node_ids"],
+ "required": [
+ "drawee",
+ "drawer",
+ "payee",
+ "endorsements",
+ "endorsements_count",
+ "all_participant_node_ids"
+ ],
"properties": {
"drawee": {
"$ref": "#/components/schemas/BillIdentParticipant"
@@ -1119,7 +1140,12 @@
},
"BillPaymentStatus": {
"type": "object",
- "required": ["requested_to_pay", "paid", "request_to_pay_timed_out", "rejected_to_pay"],
+ "required": [
+ "requested_to_pay",
+ "paid",
+ "request_to_pay_timed_out",
+ "rejected_to_pay"
+ ],
"properties": {
"time_of_request_to_pay": {
"type": ["integer", "null"],
@@ -1180,7 +1206,12 @@
},
"BillSellStatus": {
"type": "object",
- "required": ["sold", "offered_to_sell", "offer_to_sell_timed_out", "rejected_offer_to_sell"],
+ "required": [
+ "sold",
+ "offered_to_sell",
+ "offer_to_sell_timed_out",
+ "rejected_offer_to_sell"
+ ],
"properties": {
"time_of_last_offer_to_sell": {
"type": ["integer", "null"],
@@ -1374,7 +1405,13 @@
"ClowderNodeInfo": {
"type": "object",
"description": "--------------------------- Clowder Node Information",
- "required": ["change_address", "node_id", "uptime_timestamp", "version", "network"],
+ "required": [
+ "change_address",
+ "node_id",
+ "uptime_timestamp",
+ "version",
+ "network"
+ ],
"properties": {
"change_address": {
"type": "string"
@@ -1639,7 +1676,14 @@
},
"Identity": {
"type": "object",
- "required": ["node_id", "name", "bitcoin_public_key", "npub", "postal_address", "nostr_relays"],
+ "required": [
+ "node_id",
+ "name",
+ "bitcoin_public_key",
+ "npub",
+ "postal_address",
+ "nostr_relays"
+ ],
"properties": {
"node_id": {
"type": "string"
@@ -1705,7 +1749,13 @@
"oneOf": [
{
"type": "object",
- "required": ["id", "bill", "submitted", "suggested_expiration", "status"],
+ "required": [
+ "id",
+ "bill",
+ "submitted",
+ "suggested_expiration",
+ "status"
+ ],
"properties": {
"id": {
"type": "string",
@@ -1751,7 +1801,14 @@
},
{
"type": "object",
- "required": ["id", "bill", "ttl", "keyset_id", "discounted", "status"],
+ "required": [
+ "id",
+ "bill",
+ "ttl",
+ "keyset_id",
+ "discounted",
+ "status"
+ ],
"properties": {
"id": {
"type": "string",
@@ -1878,7 +1935,14 @@
},
{
"type": "object",
- "required": ["id", "bill", "keyset_id", "discounted", "fee", "status"],
+ "required": [
+ "id",
+ "bill",
+ "keyset_id",
+ "discounted",
+ "fee",
+ "status"
+ ],
"properties": {
"id": {
"type": "string",
@@ -1909,7 +1973,16 @@
},
"InfoReplyDiscriminants": {
"type": "string",
- "enum": ["Pending", "Canceled", "Offered", "OfferExpired", "Denied", "Accepted", "Rejected", "MintingEnabled"]
+ "enum": [
+ "Pending",
+ "Canceled",
+ "Offered",
+ "OfferExpired",
+ "Denied",
+ "Accepted",
+ "Rejected",
+ "MintingEnabled"
+ ]
},
"KeySetInfo": {
"type": "object",
@@ -2095,7 +2168,13 @@
},
"Notification": {
"type": "object",
- "required": ["id", "notification_type", "description", "datetime", "active"],
+ "required": [
+ "id",
+ "notification_type",
+ "description",
+ "datetime",
+ "active"
+ ],
"properties": {
"id": {
"type": "string"
diff --git a/package-lock.json b/package-lock.json
index c234a99..c42ef73 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,76 +8,85 @@
"name": "wildcat-dashboard-ui",
"version": "0.1.1",
"dependencies": {
- "@radix-ui/react-avatar": "^1.1.3",
- "@radix-ui/react-checkbox": "^1.1.4",
- "@radix-ui/react-collapsible": "^1.1.3",
- "@radix-ui/react-dialog": "^1.1.6",
- "@radix-ui/react-dropdown-menu": "^2.1.6",
- "@radix-ui/react-hover-card": "^1.1.6",
- "@radix-ui/react-label": "^2.1.2",
- "@radix-ui/react-popover": "^1.1.6",
- "@radix-ui/react-progress": "^1.1.2",
- "@radix-ui/react-scroll-area": "^1.2.3",
- "@radix-ui/react-select": "^2.1.6",
- "@radix-ui/react-separator": "^1.1.2",
- "@radix-ui/react-slot": "^1.1.2",
- "@radix-ui/react-switch": "^1.1.3",
- "@radix-ui/react-tabs": "^1.1.3",
- "@radix-ui/react-toggle": "^1.1.2",
- "@radix-ui/react-toggle-group": "^1.1.2",
- "@radix-ui/react-tooltip": "^1.1.8",
- "@tailwindcss/vite": "^4.1.18",
- "@tanstack/react-query": "^5.67.3",
- "big.js": "^6.2.2",
+ "@radix-ui/react-avatar": "^1.1.11",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.12",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-hover-card": "^1.1.15",
+ "@radix-ui/react-label": "^2.1.8",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-toggle": "^1.1.10",
+ "@radix-ui/react-toggle-group": "^1.1.11",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@tailwindcss/vite": "^4.2.1",
+ "@tanstack/react-query": "^5.90.21",
+ "big.js": "^7.0.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
- "keycloak-js": "^26.2.0",
- "lucide-react": "^0.479.0",
+ "keycloak-js": "^26.2.3",
+ "lucide-react": "^0.577.0",
"next-themes": "^0.4.6",
"qrcode": "^1.5.4",
"qrcode.react": "^4.2.0",
- "react": "^19.0.0",
- "react-day-picker": "^9.6.4",
- "react-dom": "^19.0.0",
- "react-hook-form": "^7.54.2",
- "react-intl": "^8.0.11",
- "react-router": "7.13.0",
- "recharts": "^3.7.0",
- "sonner": "^2.0.1",
- "tailwind-merge": "^3.0.2",
- "tailwindcss": "^4.1.18",
+ "react": "^19.2.4",
+ "react-day-picker": "^9.14.0",
+ "react-dom": "^19.2.4",
+ "react-hook-form": "^7.71.2",
+ "react-intl": "^8.1.3",
+ "react-router": "7.13.1",
+ "recharts": "^3.8.0",
+ "sonner": "^2.0.7",
+ "tailwind-merge": "^3.5.0",
+ "tailwindcss": "^4.2.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2"
},
"devDependencies": {
- "@eslint/js": "^9.22.0",
- "@eslint/plugin-kit": ">=0.3.4",
- "@hey-api/openapi-ts": "^0.91.1",
+ "@eslint/js": "^9.39.4",
+ "@eslint/plugin-kit": ">=0.6.1",
+ "@hey-api/openapi-ts": "^0.94.0",
"@testing-library/jest-dom": "^6.9.1",
"@types/big.js": "^6.2.2",
- "@types/date-fns": "^2.5.3",
- "@types/node": "^22.13.10",
- "@types/qrcode": "^1.5.5",
- "@types/react": "^19.0.10",
- "@types/react-dom": "^19.0.4",
- "@vitejs/plugin-react": "^5.1.3",
+ "@types/node": "^25.3.5",
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18",
- "eslint": "^9.39.2",
- "eslint-config-prettier": "^10.1.1",
+ "eslint": "^9.39.4",
+ "eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
- "eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.19",
- "globals": "^16.0.0",
- "jsdom": "^26.0.0",
- "msw": "^2.7.3",
- "prettier": "^3.5.3",
- "typescript": "~5.8.2",
- "typescript-eslint": "^8.54.0",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.4.0",
+ "jsdom": "^28.1.0",
+ "msw": "^2.12.10",
+ "prettier": "^3.8.1",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.56.1",
"vite": "7.3.1",
"vitest": "^4.0.18"
+ },
+ "engines": {
+ "node": ">=25 <26"
}
},
+ "node_modules/@acemir/cssom": {
+ "version": "0.9.31",
+ "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz",
+ "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@adobe/css-tools": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
@@ -86,25 +95,62 @@
"license": "MIT"
},
"node_modules/@asamuzakjp/css-color": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
- "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz",
+ "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@csstools/css-calc": "^2.1.3",
- "@csstools/css-color-parser": "^3.0.9",
- "@csstools/css-parser-algorithms": "^3.0.4",
- "@csstools/css-tokenizer": "^3.0.3",
- "lru-cache": "^10.4.3"
+ "@csstools/css-calc": "^3.1.1",
+ "@csstools/css-color-parser": "^4.0.2",
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0",
+ "lru-cache": "^11.2.6"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"dev": true,
- "license": "ISC"
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz",
+ "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.1.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.6"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
@@ -173,9 +219,9 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.29.0",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz",
- "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==",
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -418,10 +464,23 @@
"node": ">=18"
}
},
+ "node_modules/@bramus/specificity": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
+ "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "^3.0.0"
+ },
+ "bin": {
+ "specificity": "bin/cli.js"
+ }
+ },
"node_modules/@csstools/color-helpers": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
- "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
+ "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
"dev": true,
"funding": [
{
@@ -435,13 +494,13 @@
],
"license": "MIT-0",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
}
},
"node_modules/@csstools/css-calc": {
- "version": "2.1.4",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
- "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz",
+ "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==",
"dev": true,
"funding": [
{
@@ -455,17 +514,17 @@
],
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.5",
- "@csstools/css-tokenizer": "^3.0.4"
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
- "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz",
+ "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==",
"dev": true,
"funding": [
{
@@ -479,21 +538,21 @@
],
"license": "MIT",
"dependencies": {
- "@csstools/color-helpers": "^5.1.0",
- "@csstools/css-calc": "^2.1.4"
+ "@csstools/color-helpers": "^6.0.2",
+ "@csstools/css-calc": "^3.1.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
},
"peerDependencies": {
- "@csstools/css-parser-algorithms": "^3.0.5",
- "@csstools/css-tokenizer": "^3.0.4"
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
"node_modules/@csstools/css-parser-algorithms": {
- "version": "3.0.5",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
- "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
"dev": true,
"funding": [
{
@@ -507,16 +566,33 @@
],
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
},
"peerDependencies": {
- "@csstools/css-tokenizer": "^3.0.4"
+ "@csstools/css-tokenizer": "^4.0.0"
}
},
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.0.tgz",
+ "integrity": "sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0"
+ },
"node_modules/@csstools/css-tokenizer": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
- "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
"dev": true,
"funding": [
{
@@ -530,7 +606,7 @@
],
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20.19.0"
}
},
"node_modules/@date-fns/tz": {
@@ -540,9 +616,9 @@
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
- "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
"cpu": [
"ppc64"
],
@@ -556,9 +632,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
- "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
"cpu": [
"arm"
],
@@ -572,9 +648,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
- "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
"cpu": [
"arm64"
],
@@ -588,9 +664,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
- "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
"cpu": [
"x64"
],
@@ -604,9 +680,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
- "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
"cpu": [
"arm64"
],
@@ -620,9 +696,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
- "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
"cpu": [
"x64"
],
@@ -636,9 +712,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
- "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
"cpu": [
"arm64"
],
@@ -652,9 +728,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
- "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
"cpu": [
"x64"
],
@@ -668,9 +744,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
- "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
"cpu": [
"arm"
],
@@ -684,9 +760,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
- "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
"cpu": [
"arm64"
],
@@ -700,9 +776,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
- "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
"cpu": [
"ia32"
],
@@ -716,9 +792,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
- "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
"cpu": [
"loong64"
],
@@ -732,9 +808,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
- "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
"cpu": [
"mips64el"
],
@@ -748,9 +824,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
- "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
"cpu": [
"ppc64"
],
@@ -764,9 +840,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
- "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
"cpu": [
"riscv64"
],
@@ -780,9 +856,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
- "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
"cpu": [
"s390x"
],
@@ -796,9 +872,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
- "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
"cpu": [
"x64"
],
@@ -812,9 +888,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
"cpu": [
"arm64"
],
@@ -828,9 +904,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
- "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
"cpu": [
"x64"
],
@@ -844,9 +920,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
- "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
"cpu": [
"arm64"
],
@@ -860,9 +936,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
- "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
"cpu": [
"x64"
],
@@ -876,9 +952,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
- "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
"cpu": [
"arm64"
],
@@ -892,9 +968,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
- "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
"cpu": [
"x64"
],
@@ -908,9 +984,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
- "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
"cpu": [
"arm64"
],
@@ -924,9 +1000,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
- "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
"cpu": [
"ia32"
],
@@ -940,9 +1016,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
- "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
"cpu": [
"x64"
],
@@ -998,15 +1074,15 @@
}
},
"node_modules/@eslint/config-array": {
- "version": "0.21.1",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
- "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@eslint/object-schema": "^2.1.7",
"debug": "^4.3.1",
- "minimatch": "^3.1.2"
+ "minimatch": "^3.1.5"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1025,7 +1101,7 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/@eslint/core": {
+ "node_modules/@eslint/config-helpers/node_modules/@eslint/core": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
@@ -1038,21 +1114,34 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@eslint/core": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz",
+ "integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
"node_modules/@eslint/eslintrc": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
- "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ajv": "^6.12.4",
+ "ajv": "^6.14.0",
"debug": "^4.3.2",
"espree": "^10.0.1",
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.1",
- "minimatch": "^3.1.2",
+ "minimatch": "^3.1.5",
"strip-json-comments": "^3.1.1"
},
"engines": {
@@ -1062,23 +1151,6 @@
"url": "https://opencollective.com/eslint"
}
},
- "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"
- }
- },
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
@@ -1092,17 +1164,10 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "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": "9.39.2",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
- "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1123,45 +1188,63 @@
}
},
"node_modules/@eslint/plugin-kit": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
- "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz",
+ "integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
- "@eslint/core": "^0.17.0",
+ "@eslint/core": "^1.1.1",
"levn": "^0.4.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@exodus/bytes": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz",
+ "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@noble/hashes": "^1.8.0 || ^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@noble/hashes": {
+ "optional": true
+ }
}
},
"node_modules/@floating-ui/core": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz",
- "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==",
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
"license": "MIT",
"dependencies": {
- "@floating-ui/utils": "^0.2.10"
+ "@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/dom": {
- "version": "1.7.5",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz",
- "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==",
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
"license": "MIT",
"dependencies": {
- "@floating-ui/core": "^1.7.4",
- "@floating-ui/utils": "^0.2.10"
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
}
},
"node_modules/@floating-ui/react-dom": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz",
- "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==",
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
+ "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
"license": "MIT",
"dependencies": {
- "@floating-ui/dom": "^1.7.5"
+ "@floating-ui/dom": "^1.7.6"
},
"peerDependencies": {
"react": ">=16.8.0",
@@ -1169,9 +1252,9 @@
}
},
"node_modules/@floating-ui/utils": {
- "version": "0.2.10",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
- "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
"license": "MIT"
},
"node_modules/@formatjs/ecma402-abstract": {
@@ -1248,9 +1331,9 @@
}
},
"node_modules/@hey-api/codegen-core": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.6.1.tgz",
- "integrity": "sha512-khTIpxhKEAqmRmeLUnAFJQs4Sbg9RPokovJk9rRcC8B5MWH1j3/BRSqfpAIiJUBDU1+nbVg2RVCV+eQ174cdvw==",
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.7.1.tgz",
+ "integrity": "sha512-X5qG+rr/BJvr+pEGcoW6l2azoZGrVuxsviEIhuf+3VwL9bk0atfubT65Xwo+4jDxXvjbhZvlwS0Ty3I7mLE2fg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1270,38 +1353,37 @@
}
},
"node_modules/@hey-api/json-schema-ref-parser": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.2.3.tgz",
- "integrity": "sha512-gRbyyTjzpFVNmbD+Upn3w4dWV+bCXGJbff3A7leDO/tfNxSz1xIb6Ad/5UKtvEW9kDt/2Uyc3XkFZ6rpafvbfQ==",
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@hey-api/json-schema-ref-parser/-/json-schema-ref-parser-1.3.1.tgz",
+ "integrity": "sha512-7atnpUkT8TyUPHYPLk91j/GyaqMuwTEHanLOe50Dlx0EEvNuQqFD52Yjg8x4KU0UFL1mWlyhE+sUE/wAtQ1N2A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jsdevtools/ono": "^7.1.3",
- "@types/json-schema": "^7.0.15",
- "js-yaml": "^4.1.1",
- "lodash": "^4.17.21"
+ "@jsdevtools/ono": "7.1.3",
+ "@types/json-schema": "7.0.15",
+ "js-yaml": "4.1.1"
},
"engines": {
- "node": ">= 16"
+ "node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/hey-api"
}
},
"node_modules/@hey-api/openapi-ts": {
- "version": "0.91.1",
- "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.91.1.tgz",
- "integrity": "sha512-d16WR35UtthK/ihAIwJaKxrj/zvb5LbYwtVJCyZFFMin2qzDU8Y3Lpk78ensAykrLoaDLzpd0iIyt9JCP5Qmww==",
+ "version": "0.94.0",
+ "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.94.0.tgz",
+ "integrity": "sha512-dbg3GG+v7sg9/Ahb7yFzwzQIJwm151JAtsnh9KtFyqiN0rGkMGA3/VqogEUq1kJB9XWrlMQwigwzhiEQ33VCSg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@hey-api/codegen-core": "0.6.1",
- "@hey-api/json-schema-ref-parser": "1.2.3",
- "@hey-api/shared": "0.1.1",
+ "@hey-api/codegen-core": "0.7.1",
+ "@hey-api/json-schema-ref-parser": "1.3.1",
+ "@hey-api/shared": "0.2.2",
"@hey-api/types": "0.1.3",
"ansi-colors": "4.1.3",
"color-support": "1.1.3",
- "commander": "14.0.2"
+ "commander": "14.0.3"
},
"bin": {
"openapi-ts": "bin/run.js"
@@ -1317,14 +1399,14 @@
}
},
"node_modules/@hey-api/shared": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@hey-api/shared/-/shared-0.1.1.tgz",
- "integrity": "sha512-/irgNGXw9TL5aKB3S7jCLgh07vgDFkYjSjz7vEWO9xEe6MUhx76zSFzkPspk2UrLghYayvmaKPf1ky4XjNI9ZQ==",
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@hey-api/shared/-/shared-0.2.2.tgz",
+ "integrity": "sha512-vMqCS+j7F9xpWoXC7TBbqZkaelwrdeuSB+s/3elu54V5iq++S59xhkSq5rOgDIpI1trpE59zZQa6dpyUxItOgw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@hey-api/codegen-core": "0.6.1",
- "@hey-api/json-schema-ref-parser": "1.2.3",
+ "@hey-api/codegen-core": "0.7.1",
+ "@hey-api/json-schema-ref-parser": "1.3.1",
"@hey-api/types": "0.1.3",
"ansi-colors": "4.1.3",
"cross-spawn": "7.0.6",
@@ -1463,66 +1545,6 @@
}
}
},
- "node_modules/@inquirer/core/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/@inquirer/core/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/@inquirer/core/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/@inquirer/core/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/@inquirer/core/node_modules/wrap-ansi": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
- "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/@inquirer/figures": {
"version": "1.0.15",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz",
@@ -1604,9 +1626,9 @@
"license": "MIT"
},
"node_modules/@mswjs/interceptors": {
- "version": "0.40.0",
- "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.40.0.tgz",
- "integrity": "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ==",
+ "version": "0.41.3",
+ "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz",
+ "integrity": "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3892,9 +3914,9 @@
}
},
"node_modules/@reduxjs/toolkit/node_modules/immer": {
- "version": "11.1.3",
- "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz",
- "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==",
+ "version": "11.1.4",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
+ "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -3902,16 +3924,16 @@
}
},
"node_modules/@rolldown/pluginutils": {
- "version": "1.0.0-rc.2",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
- "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==",
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
- "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
@@ -3922,9 +3944,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
- "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
@@ -3935,9 +3957,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
- "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
@@ -3948,9 +3970,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
- "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
@@ -3961,9 +3983,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
- "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
@@ -3974,9 +3996,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
- "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
@@ -3987,9 +4009,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
- "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
@@ -4000,9 +4022,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
- "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
@@ -4013,9 +4035,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
- "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
@@ -4026,9 +4048,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
- "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
@@ -4039,9 +4061,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
- "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
@@ -4052,9 +4074,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
- "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
@@ -4065,9 +4087,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
- "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
@@ -4078,9 +4100,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
- "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
@@ -4091,9 +4113,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
- "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
@@ -4104,9 +4126,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
- "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
@@ -4117,9 +4139,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
- "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
@@ -4130,9 +4152,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@@ -4143,9 +4165,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
- "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [
"x64"
],
@@ -4156,9 +4178,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
- "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
@@ -4169,9 +4191,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
- "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
@@ -4182,9 +4204,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
- "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
@@ -4195,9 +4217,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
- "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
@@ -4208,9 +4230,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@@ -4221,9 +4243,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
- "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@@ -4245,48 +4267,57 @@
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
+ "node_modules/@tabby_ai/hijri-converter": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@tabby_ai/hijri-converter/-/hijri-converter-1.0.5.tgz",
+ "integrity": "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
"node_modules/@tailwindcss/node": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
- "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
+ "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
"license": "MIT",
"dependencies": {
- "@jridgewell/remapping": "^2.3.4",
- "enhanced-resolve": "^5.18.3",
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
"jiti": "^2.6.1",
- "lightningcss": "1.30.2",
+ "lightningcss": "1.31.1",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
- "tailwindcss": "4.1.18"
+ "tailwindcss": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
- "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
+ "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
"license": "MIT",
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.1.18",
- "@tailwindcss/oxide-darwin-arm64": "4.1.18",
- "@tailwindcss/oxide-darwin-x64": "4.1.18",
- "@tailwindcss/oxide-freebsd-x64": "4.1.18",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
- "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
- "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
- "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
- "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
- "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ "@tailwindcss/oxide-android-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-x64": "4.2.1",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.1",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
- "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
+ "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
"cpu": [
"arm64"
],
@@ -4296,13 +4327,13 @@
"android"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
- "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
+ "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
"cpu": [
"arm64"
],
@@ -4312,13 +4343,13 @@
"darwin"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
- "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
+ "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
"cpu": [
"x64"
],
@@ -4328,13 +4359,13 @@
"darwin"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
- "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
+ "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
"cpu": [
"x64"
],
@@ -4344,13 +4375,13 @@
"freebsd"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
- "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
+ "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
"cpu": [
"arm"
],
@@ -4360,13 +4391,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
- "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
+ "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
"cpu": [
"arm64"
],
@@ -4376,13 +4407,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
- "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
+ "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
"cpu": [
"arm64"
],
@@ -4392,13 +4423,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
- "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
+ "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
"cpu": [
"x64"
],
@@ -4408,13 +4439,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
- "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
+ "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
"cpu": [
"x64"
],
@@ -4424,13 +4455,13 @@
"linux"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
- "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
+ "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -4445,21 +4476,21 @@
"license": "MIT",
"optional": true,
"dependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1",
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
"@emnapi/wasi-threads": "^1.1.0",
- "@napi-rs/wasm-runtime": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
"@tybys/wasm-util": "^0.10.1",
- "tslib": "^2.4.0"
+ "tslib": "^2.8.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
- "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
+ "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
"cpu": [
"arm64"
],
@@ -4469,13 +4500,13 @@
"win32"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
- "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
+ "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
"cpu": [
"x64"
],
@@ -4485,18 +4516,18 @@
"win32"
],
"engines": {
- "node": ">= 10"
+ "node": ">= 20"
}
},
"node_modules/@tailwindcss/vite": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
- "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz",
+ "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==",
"license": "MIT",
"dependencies": {
- "@tailwindcss/node": "4.1.18",
- "@tailwindcss/oxide": "4.1.18",
- "tailwindcss": "4.1.18"
+ "@tailwindcss/node": "4.2.1",
+ "@tailwindcss/oxide": "4.2.1",
+ "tailwindcss": "4.2.1"
},
"peerDependencies": {
"vite": "^5.2.0 || ^6 || ^7"
@@ -4513,9 +4544,9 @@
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.90.20",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz",
- "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==",
+ "version": "5.90.21",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
+ "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.90.20"
@@ -4674,13 +4705,6 @@
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
- "node_modules/@types/date-fns": {
- "version": "2.5.3",
- "resolved": "https://registry.npmjs.org/@types/date-fns/-/date-fns-2.5.3.tgz",
- "integrity": "sha512-4KVPD3g5RjSgZtdOjvI/TDFkLNUHhdoWxmierdQbDeEg17Rov0hbBYtIzNaQA67ORpteOhvR9YEMTb6xeDCang==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
@@ -4714,13 +4738,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "22.19.7",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz",
- "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
+ "version": "25.3.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
+ "integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~6.21.0"
+ "undici-types": "~7.18.0"
}
},
"node_modules/@types/qrcode": {
@@ -4734,9 +4758,9 @@
}
},
"node_modules/@types/react": {
- "version": "19.2.10",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
- "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
@@ -4766,17 +4790,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz",
- "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz",
+ "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.56.0",
- "@typescript-eslint/type-utils": "8.56.0",
- "@typescript-eslint/utils": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0",
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/type-utils": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.4.0"
@@ -4789,7 +4813,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.56.0",
+ "@typescript-eslint/parser": "^8.56.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -4805,16 +4829,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz",
- "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz",
+ "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.56.0",
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0",
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
"debug": "^4.4.3"
},
"engines": {
@@ -4830,14 +4854,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz",
- "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz",
+ "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.56.0",
- "@typescript-eslint/types": "^8.56.0",
+ "@typescript-eslint/tsconfig-utils": "^8.56.1",
+ "@typescript-eslint/types": "^8.56.1",
"debug": "^4.4.3"
},
"engines": {
@@ -4852,14 +4876,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz",
- "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz",
+ "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0"
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4870,9 +4894,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz",
- "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz",
+ "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4887,15 +4911,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz",
- "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz",
+ "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0",
- "@typescript-eslint/utils": "8.56.0",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.4.0"
},
@@ -4912,9 +4936,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz",
- "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz",
+ "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4926,18 +4950,18 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz",
- "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz",
+ "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.56.0",
- "@typescript-eslint/tsconfig-utils": "8.56.0",
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/visitor-keys": "8.56.0",
+ "@typescript-eslint/project-service": "8.56.1",
+ "@typescript-eslint/tsconfig-utils": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/visitor-keys": "8.56.1",
"debug": "^4.4.3",
- "minimatch": "^9.0.5",
+ "minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.4.0"
@@ -4953,43 +4977,56 @@
"typescript": ">=4.8.4 <6.0.0"
}
},
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
- "license": "ISC",
+ "license": "BlueOak-1.0.0",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "brace-expansion": "^5.0.2"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz",
- "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz",
+ "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.56.0",
- "@typescript-eslint/types": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0"
+ "@typescript-eslint/scope-manager": "8.56.1",
+ "@typescript-eslint/types": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5004,13 +5041,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz",
- "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz",
+ "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.56.0",
+ "@typescript-eslint/types": "8.56.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -5022,9 +5059,9 @@
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz",
- "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
@@ -5035,16 +5072,16 @@
}
},
"node_modules/@vitejs/plugin-react": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.3.tgz",
- "integrity": "sha512-NVUnA6gQCl8jfoYqKqQU5Clv0aPw14KkZYCsX6T9Lfu9slI0LOU10OTwFHS/WmptsMMpshNd/1tuWsHQ2Uk+cg==",
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz",
+ "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.29.0",
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
- "@rolldown/pluginutils": "1.0.0-rc.2",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.18.0"
},
@@ -5198,9 +5235,9 @@
}
},
"node_modules/acorn": {
- "version": "8.15.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
"bin": {
@@ -5230,6 +5267,23 @@
"node": ">= 14"
}
},
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "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/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -5240,6 +5294,15 @@
"node": ">=6"
}
},
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -5433,9 +5496,9 @@
}
},
"node_modules/ast-v8-to-istanbul": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz",
- "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==",
+ "version": "0.3.12",
+ "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz",
+ "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5485,19 +5548,32 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.9.19",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
- "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+ "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
- "baseline-browser-mapping": "dist/cli.js"
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
}
},
"node_modules/big.js": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz",
- "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-7.0.1.tgz",
+ "integrity": "sha512-iFgV784tD8kq4ccF1xtNMZnXeZzVuXWWM+ERFzKQjv+A5G9HC8CY3DuV45vgzFFcW+u2tIvmF95+AzWgs6BjCg==",
"license": "MIT",
"engines": {
"node": "*"
@@ -5507,10 +5583,21 @@
"url": "https://opencollective.com/bigjs"
}
},
- "node_modules/browserslist": {
- "version": "4.28.1",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
- "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true,
"funding": [
{
@@ -5656,9 +5743,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001767",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001767.tgz",
- "integrity": "sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==",
+ "version": "1.0.30001777",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+ "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
"dev": true,
"funding": [
{
@@ -5766,51 +5853,6 @@
"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/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/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/cliui/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
@@ -5867,9 +5909,9 @@
}
},
"node_modules/commander": {
- "version": "14.0.2",
- "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
- "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
+ "version": "14.0.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
+ "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -5884,9 +5926,9 @@
"license": "MIT"
},
"node_modules/confbox": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
- "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
"dev": true,
"license": "MIT"
},
@@ -5907,6 +5949,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -5922,6 +5977,20 @@
"node": ">= 8"
}
},
+ "node_modules/css-tree": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
"node_modules/css.escape": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
@@ -5930,17 +5999,29 @@
"license": "MIT"
},
"node_modules/cssstyle": {
- "version": "4.6.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
- "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.2.0.tgz",
+ "integrity": "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@asamuzakjp/css-color": "^3.2.0",
- "rrweb-cssom": "^0.8.0"
+ "@asamuzakjp/css-color": "^5.0.1",
+ "@csstools/css-syntax-patches-for-csstree": "^1.0.28",
+ "css-tree": "^3.1.0",
+ "lru-cache": "^11.2.6"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
+ }
+ },
+ "node_modules/cssstyle/node_modules/lru-cache": {
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
+ "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
}
},
"node_modules/csstype": {
@@ -6071,17 +6152,17 @@
}
},
"node_modules/data-urls": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
- "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "whatwg-mimetype": "^4.0.0",
- "whatwg-url": "^14.0.0"
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/data-view-buffer": {
@@ -6335,9 +6416,9 @@
"license": "MIT"
},
"node_modules/dotenv": {
- "version": "17.2.3",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
- "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+ "version": "17.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
+ "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
@@ -6363,20 +6444,26 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.283",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz",
- "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==",
+ "version": "1.5.307",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
+ "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
"dev": true,
"license": "ISC"
},
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
"node_modules/enhanced-resolve": {
- "version": "5.18.4",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
- "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
+ "version": "5.20.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
+ "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
+ "tapable": "^2.3.0"
},
"engines": {
"node": ">=10.13.0"
@@ -6580,9 +6667,9 @@
}
},
"node_modules/es-toolkit": {
- "version": "1.44.0",
- "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz",
- "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==",
+ "version": "1.45.1",
+ "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
+ "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
"license": "MIT",
"workspaces": [
"docs",
@@ -6590,9 +6677,9 @@
]
},
"node_modules/esbuild": {
- "version": "0.27.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
- "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
@@ -6602,32 +6689,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.2",
- "@esbuild/android-arm": "0.27.2",
- "@esbuild/android-arm64": "0.27.2",
- "@esbuild/android-x64": "0.27.2",
- "@esbuild/darwin-arm64": "0.27.2",
- "@esbuild/darwin-x64": "0.27.2",
- "@esbuild/freebsd-arm64": "0.27.2",
- "@esbuild/freebsd-x64": "0.27.2",
- "@esbuild/linux-arm": "0.27.2",
- "@esbuild/linux-arm64": "0.27.2",
- "@esbuild/linux-ia32": "0.27.2",
- "@esbuild/linux-loong64": "0.27.2",
- "@esbuild/linux-mips64el": "0.27.2",
- "@esbuild/linux-ppc64": "0.27.2",
- "@esbuild/linux-riscv64": "0.27.2",
- "@esbuild/linux-s390x": "0.27.2",
- "@esbuild/linux-x64": "0.27.2",
- "@esbuild/netbsd-arm64": "0.27.2",
- "@esbuild/netbsd-x64": "0.27.2",
- "@esbuild/openbsd-arm64": "0.27.2",
- "@esbuild/openbsd-x64": "0.27.2",
- "@esbuild/openharmony-arm64": "0.27.2",
- "@esbuild/sunos-x64": "0.27.2",
- "@esbuild/win32-arm64": "0.27.2",
- "@esbuild/win32-ia32": "0.27.2",
- "@esbuild/win32-x64": "0.27.2"
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
}
},
"node_modules/escalade": {
@@ -6654,25 +6741,25 @@
}
},
"node_modules/eslint": {
- "version": "9.39.2",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
- "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.21.1",
+ "@eslint/config-array": "^0.21.2",
"@eslint/config-helpers": "^0.4.2",
"@eslint/core": "^0.17.0",
- "@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.39.2",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
"@eslint/plugin-kit": "^0.4.1",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
"@types/estree": "^1.0.6",
- "ajv": "^6.12.4",
+ "ajv": "^6.14.0",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.6",
"debug": "^4.3.2",
@@ -6691,7 +6778,7 @@
"is-glob": "^4.0.0",
"json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
+ "minimatch": "^3.1.5",
"natural-compare": "^1.4.0",
"optionator": "^0.9.3"
},
@@ -6763,26 +6850,33 @@
}
},
"node_modules/eslint-plugin-react-hooks": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
- "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
"dev": true,
"license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"peerDependencies": {
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
}
},
"node_modules/eslint-plugin-react-refresh": {
- "version": "0.4.26",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
- "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz",
+ "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==",
"dev": true,
"license": "MIT",
"peerDependencies": {
- "eslint": ">=8.40"
+ "eslint": "^9 || ^10"
}
},
"node_modules/eslint-plugin-react/node_modules/semver": {
@@ -6825,29 +6919,32 @@
"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==",
+ "node_modules/eslint/node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
- "license": "MIT",
+ "license": "Apache-2.0",
"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"
+ "@types/json-schema": "^7.0.15"
},
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
- "node_modules/eslint/node_modules/json-schema-traverse": {
+ "node_modules/eslint/node_modules/@eslint/plugin-kit": {
"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==",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
"dev": true,
- "license": "MIT"
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
},
"node_modules/espree": {
"version": "10.4.0",
@@ -7029,9 +7126,9 @@
}
},
"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==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.0.tgz",
+ "integrity": "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==",
"dev": true,
"license": "ISC"
},
@@ -7233,9 +7330,9 @@
}
},
"node_modules/globals": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
- "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
+ "version": "17.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz",
+ "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7282,9 +7379,9 @@
"license": "ISC"
},
"node_modules/graphql": {
- "version": "16.12.0",
- "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
- "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==",
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.1.tgz",
+ "integrity": "sha512-gGgrVCoDKlIZ8fIqXBBb0pPKqDgki0Z/FSKNiQzSGj2uEYHr1tq5wmBegGwJx6QB5S5cM0khSBpi/JFHMCvsmQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -7392,6 +7489,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -7408,16 +7522,16 @@
"license": "MIT"
},
"node_modules/html-encoding-sniffer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
- "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
+ "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "whatwg-encoding": "^3.1.1"
+ "@exodus/bytes": "^1.6.0"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/html-escaper": {
@@ -8002,9 +8116,9 @@
}
},
"node_modules/is-wsl": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
- "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8118,35 +8232,36 @@
}
},
"node_modules/jsdom": {
- "version": "26.1.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
- "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "version": "28.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz",
+ "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==",
"dev": true,
"license": "MIT",
"dependencies": {
- "cssstyle": "^4.2.1",
- "data-urls": "^5.0.0",
- "decimal.js": "^10.5.0",
- "html-encoding-sniffer": "^4.0.0",
+ "@acemir/cssom": "^0.9.31",
+ "@asamuzakjp/dom-selector": "^6.8.1",
+ "@bramus/specificity": "^2.4.2",
+ "@exodus/bytes": "^1.11.0",
+ "cssstyle": "^6.0.1",
+ "data-urls": "^7.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^6.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"is-potential-custom-element-name": "^1.0.1",
- "nwsapi": "^2.2.16",
- "parse5": "^7.2.1",
- "rrweb-cssom": "^0.8.0",
+ "parse5": "^8.0.0",
"saxes": "^6.0.0",
"symbol-tree": "^3.2.4",
- "tough-cookie": "^5.1.1",
+ "tough-cookie": "^6.0.0",
+ "undici": "^7.21.0",
"w3c-xmlserializer": "^5.0.0",
- "webidl-conversions": "^7.0.0",
- "whatwg-encoding": "^3.1.1",
- "whatwg-mimetype": "^4.0.0",
- "whatwg-url": "^14.1.1",
- "ws": "^8.18.0",
+ "webidl-conversions": "^8.0.1",
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0",
"xml-name-validator": "^5.0.0"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"canvas": "^3.0.0"
@@ -8177,6 +8292,13 @@
"dev": true,
"license": "MIT"
},
+ "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/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",
@@ -8214,9 +8336,9 @@
}
},
"node_modules/keycloak-js": {
- "version": "26.2.2",
- "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.2.2.tgz",
- "integrity": "sha512-ug7pNZ1xNkd7PPkerOJCEU2VnUhS7CYStDOCFJgqCNQ64h53ppxaKrh4iXH0xM8hFu5b1W6e6lsyYWqBMvaQFg==",
+ "version": "26.2.3",
+ "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.2.3.tgz",
+ "integrity": "sha512-widjzw/9T6bHRgEp6H/Se3NCCarU7u5CwFKBcwtu7xfA1IfdZb+7Q7/KGusAnBo34Vtls8Oz9vzSqkQvQ7+b4Q==",
"license": "Apache-2.0",
"workspaces": [
"test"
@@ -8247,9 +8369,9 @@
}
},
"node_modules/lightningcss": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
- "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
+ "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -8262,23 +8384,23 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "lightningcss-android-arm64": "1.30.2",
- "lightningcss-darwin-arm64": "1.30.2",
- "lightningcss-darwin-x64": "1.30.2",
- "lightningcss-freebsd-x64": "1.30.2",
- "lightningcss-linux-arm-gnueabihf": "1.30.2",
- "lightningcss-linux-arm64-gnu": "1.30.2",
- "lightningcss-linux-arm64-musl": "1.30.2",
- "lightningcss-linux-x64-gnu": "1.30.2",
- "lightningcss-linux-x64-musl": "1.30.2",
- "lightningcss-win32-arm64-msvc": "1.30.2",
- "lightningcss-win32-x64-msvc": "1.30.2"
+ "lightningcss-android-arm64": "1.31.1",
+ "lightningcss-darwin-arm64": "1.31.1",
+ "lightningcss-darwin-x64": "1.31.1",
+ "lightningcss-freebsd-x64": "1.31.1",
+ "lightningcss-linux-arm-gnueabihf": "1.31.1",
+ "lightningcss-linux-arm64-gnu": "1.31.1",
+ "lightningcss-linux-arm64-musl": "1.31.1",
+ "lightningcss-linux-x64-gnu": "1.31.1",
+ "lightningcss-linux-x64-musl": "1.31.1",
+ "lightningcss-win32-arm64-msvc": "1.31.1",
+ "lightningcss-win32-x64-msvc": "1.31.1"
}
},
"node_modules/lightningcss-android-arm64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
- "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz",
+ "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==",
"cpu": [
"arm64"
],
@@ -8296,9 +8418,9 @@
}
},
"node_modules/lightningcss-darwin-arm64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
- "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz",
+ "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==",
"cpu": [
"arm64"
],
@@ -8316,9 +8438,9 @@
}
},
"node_modules/lightningcss-darwin-x64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
- "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz",
+ "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==",
"cpu": [
"x64"
],
@@ -8336,9 +8458,9 @@
}
},
"node_modules/lightningcss-freebsd-x64": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
- "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz",
+ "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==",
"cpu": [
"x64"
],
@@ -8356,9 +8478,9 @@
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
- "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz",
+ "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==",
"cpu": [
"arm"
],
@@ -8376,9 +8498,9 @@
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
- "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz",
+ "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==",
"cpu": [
"arm64"
],
@@ -8396,9 +8518,9 @@
}
},
"node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
- "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz",
+ "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==",
"cpu": [
"arm64"
],
@@ -8416,9 +8538,9 @@
}
},
"node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
- "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz",
+ "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==",
"cpu": [
"x64"
],
@@ -8436,9 +8558,9 @@
}
},
"node_modules/lightningcss-linux-x64-musl": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
- "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz",
+ "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==",
"cpu": [
"x64"
],
@@ -8456,9 +8578,9 @@
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
- "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz",
+ "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==",
"cpu": [
"arm64"
],
@@ -8476,9 +8598,9 @@
}
},
"node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.30.2",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
- "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "version": "1.31.1",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz",
+ "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==",
"cpu": [
"x64"
],
@@ -8511,13 +8633,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/lodash": {
- "version": "4.17.23",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
- "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
- "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",
@@ -8549,9 +8664,9 @@
}
},
"node_modules/lucide-react": {
- "version": "0.479.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz",
- "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==",
+ "version": "0.577.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz",
+ "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -8567,14 +8682,14 @@
}
},
"node_modules/magicast": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz",
- "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz",
+ "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.28.5",
- "@babel/types": "^7.28.5",
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
"source-map-js": "^1.2.1"
}
},
@@ -8604,6 +8719,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/mdn-data": {
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -8615,9 +8737,9 @@
}
},
"node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -8627,17 +8749,6 @@
"node": "*"
}
},
- "node_modules/minimatch/node_modules/brace-expansion": {
- "version": "1.1.12",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -8646,15 +8757,15 @@
"license": "MIT"
},
"node_modules/msw": {
- "version": "2.12.7",
- "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.7.tgz",
- "integrity": "sha512-retd5i3xCZDVWMYjHEVuKTmhqY8lSsxujjVrZiGbbdoxxIBg5S7rCuYy/YQpfrTYIxpd/o0Kyb/3H+1udBMoYg==",
+ "version": "2.12.10",
+ "resolved": "https://registry.npmjs.org/msw/-/msw-2.12.10.tgz",
+ "integrity": "sha512-G3VUymSE0/iegFnuipujpwyTM2GuZAKXNeerUSrG2+Eg391wW63xFs5ixWsK9MWzr1AGoSkYGmyAzNgbR3+urw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@inquirer/confirm": "^5.0.0",
- "@mswjs/interceptors": "^0.40.0",
+ "@mswjs/interceptors": "^0.41.2",
"@open-draft/deferred-promise": "^2.2.0",
"@types/statuses": "^2.0.6",
"cookie": "^1.0.2",
@@ -8664,7 +8775,7 @@
"outvariant": "^1.4.3",
"path-to-regexp": "^6.3.0",
"picocolors": "^1.1.1",
- "rettime": "^0.7.0",
+ "rettime": "^0.10.1",
"statuses": "^2.0.2",
"strict-event-emitter": "^0.5.1",
"tough-cookie": "^6.0.0",
@@ -8690,53 +8801,6 @@
}
}
},
- "node_modules/msw/node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/msw/node_modules/tldts": {
- "version": "7.0.21",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.21.tgz",
- "integrity": "sha512-Plu6V8fF/XU6d2k8jPtlQf5F4Xx2hAin4r2C2ca7wR8NK5MbRTo9huLUWRe28f3Uk8bYZfg74tit/dSjc18xnw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tldts-core": "^7.0.21"
- },
- "bin": {
- "tldts": "bin/cli.js"
- }
- },
- "node_modules/msw/node_modules/tldts-core": {
- "version": "7.0.21",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.21.tgz",
- "integrity": "sha512-oVOMdHvgjqyzUZH1rOESgJP1uNe2bVrfK0jUHHmiM2rpEiRbf3j4BrsIc6JigJRbHGanQwuZv/R+LTcHsw+bLA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/msw/node_modules/tough-cookie": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
- "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "tldts": "^7.0.5"
- },
- "engines": {
- "node": ">=16"
- }
- },
"node_modules/mute-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
@@ -8782,6 +8846,35 @@
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
+ "node_modules/node-exports-info": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
+ "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array.prototype.flatmap": "^1.3.3",
+ "es-errors": "^1.3.0",
+ "object.entries": "^1.1.9",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/node-exports-info/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/node-fetch-native": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
@@ -8790,23 +8883,16 @@
"license": "MIT"
},
"node_modules/node-releases": {
- "version": "2.0.27",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
- "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/nwsapi": {
- "version": "2.2.23",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
- "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
"dev": true,
"license": "MIT"
},
"node_modules/nypm": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.4.tgz",
- "integrity": "sha512-1TvCKjZyyklN+JJj2TS3P4uSQEInrM/HkkuSXsEzm1ApPgBffOn8gFguNnZf07r/1X6vlryfIqMUkJKQMzlZiw==",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
+ "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -8822,9 +8908,9 @@
}
},
"node_modules/nypm/node_modules/citty": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.0.tgz",
- "integrity": "sha512-8csy5IBFI2ex2hTVpaHN2j+LNE199AgiI7y4dMintrr8i0lQiFn+0AWMZrWdHKIgMOer65f8IThysYhoReqjWA==",
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz",
+ "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==",
"dev": true,
"license": "MIT"
},
@@ -9073,9 +9159,9 @@
}
},
"node_modules/parse5": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
- "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
+ "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9182,9 +9268,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.6",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
- "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"funding": [
{
"type": "opencollective",
@@ -9303,15 +9389,6 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
- "node_modules/qrcode/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/qrcode/node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -9323,12 +9400,6 @@
"wrap-ansi": "^6.2.0"
}
},
- "node_modules/qrcode/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
"node_modules/qrcode/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -9381,46 +9452,6 @@
"node": ">=8"
}
},
- "node_modules/qrcode/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/qrcode/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/qrcode/node_modules/wrap-ansi": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
- "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/qrcode/node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
@@ -9483,14 +9514,15 @@
}
},
"node_modules/react-day-picker": {
- "version": "9.13.0",
- "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.0.tgz",
- "integrity": "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==",
+ "version": "9.14.0",
+ "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.14.0.tgz",
+ "integrity": "sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==",
"license": "MIT",
"dependencies": {
"@date-fns/tz": "^1.4.1",
+ "@tabby_ai/hijri-converter": "1.0.5",
"date-fns": "^4.1.0",
- "date-fns-jalali": "^4.1.0-0"
+ "date-fns-jalali": "4.1.0-0"
},
"engines": {
"node": ">=18"
@@ -9516,9 +9548,9 @@
}
},
"node_modules/react-hook-form": {
- "version": "7.71.1",
- "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz",
- "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==",
+ "version": "7.71.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz",
+ "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -9644,9 +9676,9 @@
}
},
"node_modules/react-router": {
- "version": "7.13.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
- "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
+ "version": "7.13.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz",
+ "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
@@ -9665,19 +9697,6 @@
}
}
},
- "node_modules/react-router/node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
@@ -9715,15 +9734,15 @@
}
},
"node_modules/recharts": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz",
- "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==",
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.0.tgz",
+ "integrity": "sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==",
"license": "MIT",
"workspaces": [
"www"
],
"dependencies": {
- "@reduxjs/toolkit": "1.x.x || 2.x.x",
+ "@reduxjs/toolkit": "^1.9.0 || 2.x.x",
"clsx": "^2.1.1",
"decimal.js-light": "^2.5.1",
"es-toolkit": "^1.39.3",
@@ -9826,6 +9845,16 @@
"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/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
@@ -9839,19 +9868,25 @@
"license": "MIT"
},
"node_modules/resolve": {
- "version": "2.0.0-next.5",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
- "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==",
+ "version": "2.0.0-next.6",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz",
+ "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "is-core-module": "^2.13.0",
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "node-exports-info": "^1.6.0",
+ "object-keys": "^1.1.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"
}
@@ -9867,16 +9902,16 @@
}
},
"node_modules/rettime": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz",
- "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==",
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.10.1.tgz",
+ "integrity": "sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==",
"dev": true,
"license": "MIT"
},
"node_modules/rollup": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
- "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -9889,41 +9924,34 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.57.1",
- "@rollup/rollup-android-arm64": "4.57.1",
- "@rollup/rollup-darwin-arm64": "4.57.1",
- "@rollup/rollup-darwin-x64": "4.57.1",
- "@rollup/rollup-freebsd-arm64": "4.57.1",
- "@rollup/rollup-freebsd-x64": "4.57.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
- "@rollup/rollup-linux-arm64-gnu": "4.57.1",
- "@rollup/rollup-linux-arm64-musl": "4.57.1",
- "@rollup/rollup-linux-loong64-gnu": "4.57.1",
- "@rollup/rollup-linux-loong64-musl": "4.57.1",
- "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
- "@rollup/rollup-linux-ppc64-musl": "4.57.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
- "@rollup/rollup-linux-riscv64-musl": "4.57.1",
- "@rollup/rollup-linux-s390x-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-musl": "4.57.1",
- "@rollup/rollup-openbsd-x64": "4.57.1",
- "@rollup/rollup-openharmony-arm64": "4.57.1",
- "@rollup/rollup-win32-arm64-msvc": "4.57.1",
- "@rollup/rollup-win32-ia32-msvc": "4.57.1",
- "@rollup/rollup-win32-x64-gnu": "4.57.1",
- "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2"
}
},
- "node_modules/rrweb-cssom": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
- "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/run-applescript": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
@@ -9992,13 +10020,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -10275,6 +10296,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/string.prototype.matchall": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
@@ -10373,6 +10408,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -10446,9 +10493,9 @@
}
},
"node_modules/tailwind-merge": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
- "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
+ "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
"license": "MIT",
"funding": {
"type": "github",
@@ -10456,9 +10503,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
- "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
+ "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
"license": "MIT"
},
"node_modules/tailwindcss-animate": {
@@ -10533,49 +10580,49 @@
}
},
"node_modules/tldts": {
- "version": "6.1.86",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
- "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "version": "7.0.25",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz",
+ "integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tldts-core": "^6.1.86"
+ "tldts-core": "^7.0.25"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
- "version": "6.1.86",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
- "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "version": "7.0.25",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz",
+ "integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==",
"dev": true,
"license": "MIT"
},
"node_modules/tough-cookie": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
- "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz",
+ "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
- "tldts": "^6.1.32"
+ "tldts": "^7.0.5"
},
"engines": {
"node": ">=16"
}
},
"node_modules/tr46": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
- "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/ts-api-utils": {
@@ -10611,9 +10658,9 @@
}
},
"node_modules/type-fest": {
- "version": "5.4.3",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.3.tgz",
- "integrity": "sha512-AXSAQJu79WGc79/3e9/CR77I/KQgeY1AhNvcShIH4PTcGYyC4xv6H4R4AUOwkPS5799KlVDAu8zExeCrkGquiA==",
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz",
+ "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"dependencies": {
@@ -10705,9 +10752,9 @@
}
},
"node_modules/typescript": {
- "version": "5.8.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
- "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
@@ -10719,16 +10766,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.56.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz",
- "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==",
+ "version": "8.56.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz",
+ "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.56.0",
- "@typescript-eslint/parser": "8.56.0",
- "@typescript-eslint/typescript-estree": "8.56.0",
- "@typescript-eslint/utils": "8.56.0"
+ "@typescript-eslint/eslint-plugin": "8.56.1",
+ "@typescript-eslint/parser": "8.56.1",
+ "@typescript-eslint/typescript-estree": "8.56.1",
+ "@typescript-eslint/utils": "8.56.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10761,10 +10808,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/undici": {
+ "version": "7.22.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
+ "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
"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==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"devOptional": true,
"license": "MIT"
},
@@ -11072,64 +11129,38 @@
}
},
"node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
- "node": ">=12"
- }
- },
- "node_modules/whatwg-encoding": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
- "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
- "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "iconv-lite": "0.6.3"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/whatwg-encoding/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
+ "node": ">=20"
}
},
"node_modules/whatwg-mimetype": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
- "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
+ "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": ">=20"
}
},
"node_modules/whatwg-url": {
- "version": "14.2.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
- "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "tr46": "^5.1.0",
- "webidl-conversions": "^7.0.0"
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
},
"engines": {
- "node": ">=18"
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/which": {
@@ -11270,26 +11301,18 @@
"node": ">=0.10.0"
}
},
- "node_modules/ws": {
- "version": "8.19.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
- "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
- "dev": true,
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
},
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/wsl-utils": {
@@ -11372,51 +11395,6 @@
"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/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/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": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -11442,6 +11420,29 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index 87ac384..7b89f8f 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,9 @@
"private": true,
"version": "0.1.1",
"type": "module",
+ "engines": {
+ "node": ">=25 <26"
+ },
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
@@ -19,72 +22,71 @@
"crowdin:status": "crowdin status"
},
"dependencies": {
- "@radix-ui/react-avatar": "^1.1.3",
- "@radix-ui/react-checkbox": "^1.1.4",
- "@radix-ui/react-collapsible": "^1.1.3",
- "@radix-ui/react-dialog": "^1.1.6",
- "@radix-ui/react-dropdown-menu": "^2.1.6",
- "@radix-ui/react-hover-card": "^1.1.6",
- "@radix-ui/react-label": "^2.1.2",
- "@radix-ui/react-popover": "^1.1.6",
- "@radix-ui/react-progress": "^1.1.2",
- "@radix-ui/react-scroll-area": "^1.2.3",
- "@radix-ui/react-select": "^2.1.6",
- "@radix-ui/react-separator": "^1.1.2",
- "@radix-ui/react-slot": "^1.1.2",
- "@radix-ui/react-switch": "^1.1.3",
- "@radix-ui/react-tabs": "^1.1.3",
- "@radix-ui/react-toggle": "^1.1.2",
- "@radix-ui/react-toggle-group": "^1.1.2",
- "@radix-ui/react-tooltip": "^1.1.8",
- "@tailwindcss/vite": "^4.1.18",
- "@tanstack/react-query": "^5.67.3",
- "big.js": "^6.2.2",
+ "@radix-ui/react-avatar": "^1.1.11",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.12",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-hover-card": "^1.1.15",
+ "@radix-ui/react-label": "^2.1.8",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
+ "@radix-ui/react-scroll-area": "^1.2.10",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@radix-ui/react-tabs": "^1.1.13",
+ "@radix-ui/react-toggle": "^1.1.10",
+ "@radix-ui/react-toggle-group": "^1.1.11",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@tailwindcss/vite": "^4.2.1",
+ "@tanstack/react-query": "^5.90.21",
+ "big.js": "^7.0.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
- "keycloak-js": "^26.2.0",
- "lucide-react": "^0.479.0",
+ "keycloak-js": "^26.2.3",
+ "lucide-react": "^0.577.0",
"next-themes": "^0.4.6",
"qrcode": "^1.5.4",
"qrcode.react": "^4.2.0",
- "react": "^19.0.0",
- "react-day-picker": "^9.6.4",
- "react-dom": "^19.0.0",
- "react-hook-form": "^7.54.2",
- "react-intl": "^8.0.11",
- "react-router": "7.13.0",
- "recharts": "^3.7.0",
- "sonner": "^2.0.1",
- "tailwind-merge": "^3.0.2",
- "tailwindcss": "^4.1.18",
+ "react": "^19.2.4",
+ "react-day-picker": "^9.14.0",
+ "react-dom": "^19.2.4",
+ "react-hook-form": "^7.71.2",
+ "react-intl": "^8.1.3",
+ "react-router": "7.13.1",
+ "recharts": "^3.8.0",
+ "sonner": "^2.0.7",
+ "tailwind-merge": "^3.5.0",
+ "tailwindcss": "^4.2.1",
"tailwindcss-animate": "^1.0.7",
"vaul": "^1.1.2"
},
"devDependencies": {
- "@eslint/js": "^9.22.0",
- "@eslint/plugin-kit": ">=0.3.4",
- "@hey-api/openapi-ts": "^0.91.1",
+ "@eslint/js": "^9.39.4",
+ "@eslint/plugin-kit": ">=0.6.1",
+ "@hey-api/openapi-ts": "^0.94.0",
"@testing-library/jest-dom": "^6.9.1",
"@types/big.js": "^6.2.2",
- "@types/date-fns": "^2.5.3",
- "@types/node": "^22.13.10",
- "@types/qrcode": "^1.5.5",
- "@types/react": "^19.0.10",
- "@types/react-dom": "^19.0.4",
- "@vitejs/plugin-react": "^5.1.3",
+ "@types/node": "^25.3.5",
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.4",
"@vitest/coverage-v8": "^4.0.18",
- "eslint": "^9.39.2",
- "eslint-config-prettier": "^10.1.1",
+ "eslint": "^9.39.4",
+ "eslint-config-prettier": "^10.1.8",
"eslint-plugin-react": "^7.37.5",
- "eslint-plugin-react-hooks": "^5.2.0",
- "eslint-plugin-react-refresh": "^0.4.19",
- "globals": "^16.0.0",
- "jsdom": "^26.0.0",
- "msw": "^2.7.3",
- "prettier": "^3.5.3",
- "typescript": "~5.8.2",
- "typescript-eslint": "^8.54.0",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-react-refresh": "^0.5.2",
+ "globals": "^17.4.0",
+ "jsdom": "^28.1.0",
+ "msw": "^2.12.10",
+ "prettier": "^3.8.1",
+ "typescript": "^5.9.3",
+ "typescript-eslint": "^8.56.1",
"vite": "7.3.1",
"vitest": "^4.0.18"
},
diff --git a/public/config.js b/public/config.js
index 68a8529..913de41 100644
--- a/public/config.js
+++ b/public/config.js
@@ -1 +1 @@
-window.__ENV__ = window.__ENV__ || {}
+window.__ENV__ = window.__ENV__ || {};
diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js
index 461e260..daa58d0 100644
--- a/public/mockServiceWorker.js
+++ b/public/mockServiceWorker.js
@@ -7,7 +7,7 @@
* - Please do NOT modify this file.
*/
-const PACKAGE_VERSION = '2.12.7'
+const PACKAGE_VERSION = '2.12.10'
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()
diff --git a/src/__tests__/sample.test.ts b/src/__tests__/sample.test.ts
index a5c1202..7865688 100644
--- a/src/__tests__/sample.test.ts
+++ b/src/__tests__/sample.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect } from "vitest"
+import { describe, it, expect } from "vitest";
describe("Sample Test", () => {
it("should pass", () => {
- expect(true).toBe(true)
- })
-})
+ expect(true).toBe(true);
+ });
+});
diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx
index 131af4b..efd2e89 100644
--- a/src/components/AppSidebar.tsx
+++ b/src/components/AppSidebar.tsx
@@ -1,12 +1,24 @@
-import { Bitcoin, Home, Inbox, Key } from "lucide-react"
-import { useContext } from "react"
-import { Sidebar, SidebarContent, SidebarFooter, SidebarRail, SidebarSeparator } from "@/components/ui/sidebar"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+import { Bitcoin, Home, Inbox, Key } from "lucide-react";
+import { useContext } from "react";
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarRail,
+ SidebarSeparator,
+} from "@/components/ui/sidebar";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
// import { NavUser } from "./nav/NavUser"
-import { NavMain } from "./nav/NavMain"
+import { NavMain } from "./nav/NavMain";
// import { useKeycloak } from "../lib/keycloak-user"
-import { LanguageContext } from "@/context/language/LanguageContext"
-import { useIntl } from "react-intl"
+import { LanguageContext } from "@/context/language/LanguageContext";
+import { useIntl } from "react-intl";
const data = {
navMain: [
@@ -72,12 +84,12 @@ const data = {
icon: Key,
},
],
-}
+};
function LanguageSelector() {
- const intl = useIntl()
- const { locale, setLocale, availableLocales } = useContext(LanguageContext)
- const locales = availableLocales()
+ const intl = useIntl();
+ const { locale, setLocale, availableLocales } = useContext(LanguageContext);
+ const locales = availableLocales();
return (
@@ -87,7 +99,10 @@ function LanguageSelector() {
defaultMessage: "Language",
})}
-
+
{locales.map((loc) => (
-
+
{intl.formatMessage({
id: `locale.${loc}`,
defaultMessage: loc,
@@ -108,7 +126,7 @@ function LanguageSelector() {
- )
+ );
}
export function AppSidebar() {
@@ -128,5 +146,5 @@ export function AppSidebar() {
*/}
- )
+ );
}
diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx
index 2a89c3b..8fbd5f3 100644
--- a/src/components/Breadcrumbs.tsx
+++ b/src/components/Breadcrumbs.tsx
@@ -1,4 +1,4 @@
-import { PropsWithChildren } from "react"
+import { PropsWithChildren } from "react";
import {
Breadcrumb,
BreadcrumbItem,
@@ -6,12 +6,15 @@ import {
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
-} from "@/components/ui/breadcrumb"
-import { Link } from "react-router"
-import { useIntl } from "react-intl"
+} from "@/components/ui/breadcrumb";
+import { Link } from "react-router";
+import { useIntl } from "react-intl";
-export function Breadcrumbs({ parents, children }: PropsWithChildren<{ parents?: React.ReactNode[] }>) {
- const intl = useIntl()
+export function Breadcrumbs({
+ parents,
+ children,
+}: PropsWithChildren<{ parents?: React.ReactNode[] }>) {
+ const intl = useIntl();
return (
@@ -30,7 +33,10 @@ export function Breadcrumbs({ parents, children }: PropsWithChildren<{ parents?:
{parents && (
<>
{parents.map((it, index) => (
-
+
<>{it}>
@@ -44,5 +50,5 @@ export function Breadcrumbs({ parents, children }: PropsWithChildren<{ parents?:
- )
+ );
}
diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx
index bf9944f..827fcd0 100644
--- a/src/components/CopyButton.tsx
+++ b/src/components/CopyButton.tsx
@@ -1,16 +1,16 @@
-import { Button } from "@/components/ui/button"
-import { Copy, Check } from "lucide-react"
-import { toast } from "sonner"
-import { useState } from "react"
-import { useIntl } from "react-intl"
+import { Button } from "@/components/ui/button";
+import { Copy, Check } from "lucide-react";
+import { toast } from "sonner";
+import { useState } from "react";
+import { useIntl } from "react-intl";
interface CopyButtonProps {
- value: string
- label?: string
- variant?: "ghost" | "outline" | "default"
- size?: "sm" | "default" | "lg" | "icon"
- className?: string
- showCheckmark?: boolean
+ value: string;
+ label?: string;
+ variant?: "ghost" | "outline" | "default";
+ size?: "sm" | "default" | "lg" | "icon";
+ className?: string;
+ showCheckmark?: boolean;
}
export function CopyButton({
@@ -21,42 +21,57 @@ export function CopyButton({
className = "h-6 px-2",
showCheckmark = false,
}: CopyButtonProps) {
- const intl = useIntl()
- const [copied, setCopied] = useState(false)
+ const intl = useIntl();
+ const [copied, setCopied] = useState(false);
const fallbackLabel = intl.formatMessage({
id: "copyButton.defaultLabel",
defaultMessage: "Value",
- })
- const labelValue = label ?? fallbackLabel
+ });
+ const labelValue = label ?? fallbackLabel;
const handleCopy = () => {
navigator.clipboard
.writeText(value)
.then(() => {
if (showCheckmark) {
- setCopied(true)
- setTimeout(() => setCopied(false), 2000)
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
}
toast.success(
intl.formatMessage(
- { id: "copyButton.copied", defaultMessage: "{label} copied to clipboard" },
+ {
+ id: "copyButton.copied",
+ defaultMessage: "{label} copied to clipboard",
+ },
{ label: labelValue },
),
- )
+ );
})
.catch(() => {
toast.error(
intl.formatMessage(
- { id: "copyButton.failed", defaultMessage: "Failed to copy {label}" },
+ {
+ id: "copyButton.failed",
+ defaultMessage: "Failed to copy {label}",
+ },
{ label: labelValue },
),
- )
- })
- }
+ );
+ });
+ };
return (
-
- {copied && showCheckmark ? : }
+
+ {copied && showCheckmark ? (
+
+ ) : (
+
+ )}
- )
+ );
}
diff --git a/src/components/DatePicker/calendar.tsx b/src/components/DatePicker/calendar.tsx
index 49b4fb2..16a2063 100644
--- a/src/components/DatePicker/calendar.tsx
+++ b/src/components/DatePicker/calendar.tsx
@@ -1,34 +1,43 @@
-import React, { useCallback, useEffect, useState } from "react"
-import { DateRange, DayPicker, DayPickerProps, OnSelectHandler } from "react-day-picker"
-import { isSameDay } from "date-fns"
-import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react"
-import { useIntl } from "react-intl"
+import React, { useCallback, useEffect, useState } from "react";
+import {
+ DateRange,
+ DayPicker,
+ DayPickerProps,
+ OnSelectHandler,
+} from "react-day-picker";
+import { isSameDay } from "date-fns";
+import { ChevronDown, ChevronLeft, ChevronRight } from "lucide-react";
+import { useIntl } from "react-intl";
-import { cn } from "@/lib/utils"
-import { YearPicker } from "./yearPicker"
-import { MonthPicker } from "./monthPicker"
-import { useUtcDateFormatters } from "@/hooks/use-utc-date-formatters"
+import { cn } from "@/lib/utils";
+import { YearPicker } from "./yearPicker";
+import { MonthPicker } from "./monthPicker";
+import { useUtcDateFormatters } from "@/hooks/use-utc-date-formatters";
-export type CalendarProps = Omit & {
- mode: "single" | "range"
- onSelect?: OnSelectHandler
- selected: DateRange
- onCaptionLabelClicked?: () => void
- disableFutureNavigation?: boolean
- rangeFocus?: "from" | "to"
- className?: string
- ISOWeek?: boolean
- showOutsideDays?: boolean
- month?: Date
- minDate?: Date
- initialFocus?: boolean
- modifiers?: Record boolean>
- modifiersClassNames?: Record
-}
+export type CalendarProps = Omit<
+ DayPickerProps,
+ "mode" | "onSelect" | "selected"
+> & {
+ mode: "single" | "range";
+ onSelect?: OnSelectHandler;
+ selected: DateRange;
+ onCaptionLabelClicked?: () => void;
+ disableFutureNavigation?: boolean;
+ rangeFocus?: "from" | "to";
+ className?: string;
+ ISOWeek?: boolean;
+ showOutsideDays?: boolean;
+ month?: Date;
+ minDate?: Date;
+ initialFocus?: boolean;
+ modifiers?: Record boolean>;
+ modifiersClassNames?: Record;
+};
const classNames = {
root: "w-full",
- months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0 w-full",
+ months:
+ "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0 w-full",
month: "space-y-4 w-full",
month_caption: "flex justify-center relative items-center",
// Hide default DayPicker caption label; we render our own header.
@@ -47,28 +56,43 @@ const classNames = {
range_start: "day-range-start",
selected: "bg-elevation-200 hover:bg-elevation-200 border border-divider-100",
today: "bg-accent text-accent-foreground",
- outside: "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
+ outside:
+ "day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
disabled: "text-muted-foreground opacity-50",
range_middle: "aria-selected:bg-accent aria-selected:text-accent-foreground",
hidden: "invisible",
-}
+};
function getNextDate(current: Date, offset: number): Date {
- const year = current.getUTCFullYear()
- const month = current.getUTCMonth()
- const day = current.getUTCDate()
+ const year = current.getUTCFullYear();
+ const month = current.getUTCMonth();
+ const day = current.getUTCDate();
- const daysInCurrentMonth = new Date(Date.UTC(year, month + 1, 0)).getUTCDate()
- const isLastDay = day === daysInCurrentMonth
+ const daysInCurrentMonth = new Date(
+ Date.UTC(year, month + 1, 0),
+ ).getUTCDate();
+ const isLastDay = day === daysInCurrentMonth;
- const targetMonthDate = new Date(Date.UTC(year, month + offset, 1))
+ const targetMonthDate = new Date(Date.UTC(year, month + offset, 1));
const daysInTargetMonth = new Date(
- Date.UTC(targetMonthDate.getUTCFullYear(), targetMonthDate.getUTCMonth() + 1, 0),
- ).getUTCDate()
+ Date.UTC(
+ targetMonthDate.getUTCFullYear(),
+ targetMonthDate.getUTCMonth() + 1,
+ 0,
+ ),
+ ).getUTCDate();
- const newDay = isLastDay ? daysInTargetMonth : Math.min(day, daysInTargetMonth)
+ const newDay = isLastDay
+ ? daysInTargetMonth
+ : Math.min(day, daysInTargetMonth);
- return new Date(Date.UTC(targetMonthDate.getUTCFullYear(), targetMonthDate.getUTCMonth(), newDay))
+ return new Date(
+ Date.UTC(
+ targetMonthDate.getUTCFullYear(),
+ targetMonthDate.getUTCMonth(),
+ newDay,
+ ),
+ );
}
function Calendar({
@@ -88,91 +112,116 @@ function Calendar({
minDate,
...restProps
}: CalendarProps) {
- const intl = useIntl()
- const [selectedDate, setSelectedDate] = useState(selected.from)
- const [month, setMonth] = useState(selected.from ?? monthProp ?? new Date())
- const [showYearPicker, setShowYearPicker] = useState(false)
- const [showMonthPicker, setShowMonthPicker] = useState(false)
- const { formatDay2Digit, formatMonthShort, formatYearNumeric } = useUtcDateFormatters(intl.locale)
+ const intl = useIntl();
+ const [selectedDate, setSelectedDate] = useState(
+ selected.from,
+ );
+ const [month, setMonth] = useState(
+ selected.from ?? monthProp ?? new Date(),
+ );
+ const [showYearPicker, setShowYearPicker] = useState(false);
+ const [showMonthPicker, setShowMonthPicker] = useState(false);
+ const { formatDay2Digit, formatMonthShort, formatYearNumeric } =
+ useUtcDateFormatters(intl.locale);
useEffect(() => {
if (mode === "single") {
if (selected.from) {
- setSelectedDate(selected.from)
- setMonth(selected.from)
+ setSelectedDate(selected.from);
+ setMonth(selected.from);
} else {
- setSelectedDate(undefined)
- setMonth(monthProp ?? new Date())
+ setSelectedDate(undefined);
+ setMonth(monthProp ?? new Date());
}
} else {
if (!selected.from && !selected.to) {
- setMonth(monthProp ?? new Date())
- setSelectedDate(undefined)
+ setMonth(monthProp ?? new Date());
+ setSelectedDate(undefined);
}
}
- }, [selected, mode, monthProp])
+ }, [selected, mode, monthProp]);
- const handleOnSelectRange: OnSelectHandler = (range, selectedDay, modifiers, e) => {
+ const handleOnSelectRange: OnSelectHandler = (
+ range,
+ selectedDay,
+ modifiers,
+ e,
+ ) => {
if (mode === "single") {
- setSelectedDate(selectedDay)
+ setSelectedDate(selectedDay);
}
if (onSelect) {
- onSelect(range, selectedDay, modifiers, e)
+ onSelect(range, selectedDay, modifiers, e);
}
- }
+ };
- const handleOnSelectSingle: OnSelectHandler = (day, selectedDay, modifiers, e) => {
- handleOnSelectRange(day ? { from: day } : undefined, selectedDay, modifiers, e)
- }
+ const handleOnSelectSingle: OnSelectHandler = (
+ day,
+ selectedDay,
+ modifiers,
+ e,
+ ) => {
+ handleOnSelectRange(
+ day ? { from: day } : undefined,
+ selectedDay,
+ modifiers,
+ e,
+ );
+ };
const goToOffsetMonth = useCallback(
(offset: number) => {
- setMonth(getNextDate(month, offset))
+ setMonth(getNextDate(month, offset));
},
[month],
- )
+ );
- let touchStartX: number | null = null
+ let touchStartX: number | null = null;
const handleTouchStart = (e: React.TouchEvent) => {
- touchStartX = e.touches[0].clientX
- }
+ touchStartX = e.touches[0].clientX;
+ };
const handleTouchEnd = (e: React.TouchEvent) => {
if (touchStartX === null) {
- return
+ return;
}
- const diffX = e.changedTouches[0].clientX - touchStartX
+ const diffX = e.changedTouches[0].clientX - touchStartX;
if (diffX > 50) {
- goToOffsetMonth(-1)
+ goToOffsetMonth(-1);
} else if (diffX < -50) {
if (canGoForward) {
- goToOffsetMonth(1)
+ goToOffsetMonth(1);
}
}
- touchStartX = null
- }
+ touchStartX = null;
+ };
const isAtOrBeyondCurrentMonth = (d: Date) => {
- const today = new Date()
- const todayYear = today.getUTCFullYear()
- const todayMonth = today.getUTCMonth()
- const dateYear = d.getUTCFullYear()
- const dateMonth = d.getUTCMonth()
- return dateYear > todayYear || (dateYear === todayYear && dateMonth >= todayMonth)
- }
+ const today = new Date();
+ const todayYear = today.getUTCFullYear();
+ const todayMonth = today.getUTCMonth();
+ const dateYear = d.getUTCFullYear();
+ const dateMonth = d.getUTCMonth();
+ return (
+ dateYear > todayYear ||
+ (dateYear === todayYear && dateMonth >= todayMonth)
+ );
+ };
- const canGoForward = disableFutureNavigation ? !isAtOrBeyondCurrentMonth(month) : true
+ const canGoForward = disableFutureNavigation
+ ? !isAtOrBeyondCurrentMonth(month)
+ : true;
const NavigationHeader = () => (
{
- goToOffsetMonth(-1)
+ goToOffsetMonth(-1);
}}
className="absolute left-1 bg-transparent hover:bg-accent rounded-md p-2"
aria-label={intl.formatMessage({
@@ -183,7 +232,9 @@ function Calendar({
@@ -191,10 +242,10 @@ function Calendar({
{
- setShowYearPicker(!showYearPicker)
- setShowMonthPicker(false)
+ setShowYearPicker(!showYearPicker);
+ setShowMonthPicker(false);
if (onCaptionLabelClicked) {
- onCaptionLabelClicked()
+ onCaptionLabelClicked();
}
}}
>
@@ -203,11 +254,11 @@ function Calendar({
{
- e.stopPropagation()
- setShowYearPicker(!showYearPicker)
- setShowMonthPicker(false)
+ e.stopPropagation();
+ setShowYearPicker(!showYearPicker);
+ setShowMonthPicker(false);
if (onCaptionLabelClicked) {
- onCaptionLabelClicked()
+ onCaptionLabelClicked();
}
}}
>
@@ -218,21 +269,21 @@ function Calendar({
{
- setShowYearPicker(!showYearPicker)
- setShowMonthPicker(false)
+ setShowYearPicker(!showYearPicker);
+ setShowMonthPicker(false);
if (onCaptionLabelClicked) {
- onCaptionLabelClicked()
+ onCaptionLabelClicked();
}
}}
>
{formatMonthShort(month)}
{
- e.stopPropagation()
- setShowYearPicker(!showYearPicker)
- setShowMonthPicker(false)
+ e.stopPropagation();
+ setShowYearPicker(!showYearPicker);
+ setShowMonthPicker(false);
if (onCaptionLabelClicked) {
- onCaptionLabelClicked()
+ onCaptionLabelClicked();
}
}}
>
@@ -244,21 +295,21 @@ function Calendar({
{
- setShowYearPicker(!showYearPicker)
- setShowMonthPicker(false)
+ setShowYearPicker(!showYearPicker);
+ setShowMonthPicker(false);
if (onCaptionLabelClicked) {
- onCaptionLabelClicked()
+ onCaptionLabelClicked();
}
}}
>
{formatMonthShort(month)}
{
- e.stopPropagation()
- setShowYearPicker(!showYearPicker)
- setShowMonthPicker(false)
+ e.stopPropagation();
+ setShowYearPicker(!showYearPicker);
+ setShowMonthPicker(false);
if (onCaptionLabelClicked) {
- onCaptionLabelClicked()
+ onCaptionLabelClicked();
}
}}
className="hover:underline"
@@ -267,12 +318,17 @@ function Calendar({
)}
- {onCaptionLabelClicked && }
+ {onCaptionLabelClicked && (
+
+ )}
{
- goToOffsetMonth(1)
+ goToOffsetMonth(1);
}}
disabled={!canGoForward}
className={cn(
@@ -287,7 +343,7 @@ function Calendar({
- )
+ );
const pickerProps = {
...restProps,
@@ -302,21 +358,25 @@ function Calendar({
...(initialFocus && { initialFocus }),
...(modifiers && { modifiers }),
...(modifiersClassNames && { modifiersClassNames }),
- }
+ };
return (
-
+
{!showYearPicker && !showMonthPicker && }
{showYearPicker ? (
{
- setMonth(newDate)
- setShowYearPicker(false)
- setShowMonthPicker(true)
+ setMonth(newDate);
+ setShowYearPicker(false);
+ setShowMonthPicker(true);
}}
onCaptionLabelClicked={() => {
- setShowYearPicker(false)
+ setShowYearPicker(false);
}}
disableFutureNavigation={disableFutureNavigation}
minDate={minDate}
@@ -325,35 +385,46 @@ function Calendar({
{
- setMonth(newDate)
- setShowMonthPicker(false)
+ setMonth(newDate);
+ setShowMonthPicker(false);
}}
onCaptionLabelClicked={() => {
- setShowMonthPicker(false)
+ setShowMonthPicker(false);
}}
disableFutureNavigation={disableFutureNavigation}
minDate={minDate}
/>
) : mode === "single" ? (
-
+
) : (
{
- const currentRange = selected
- let nextRange: DateRange
+ const currentRange = selected;
+ let nextRange: DateRange;
if (rangeFocus === "from") {
- nextRange = { from: selectedDay, to: currentRange.to }
+ nextRange = { from: selectedDay, to: currentRange.to };
} else {
- const from = currentRange.from ?? selectedDay
- nextRange = selectedDay < from ? { from: selectedDay, to: from } : { from, to: selectedDay }
+ const from = currentRange.from ?? selectedDay;
+ nextRange =
+ selectedDay < from
+ ? { from: selectedDay, to: from }
+ : { from, to: selectedDay };
}
- if (month.getMonth() !== selectedDay.getMonth() || month.getFullYear() !== selectedDay.getFullYear()) {
- setMonth(selectedDay)
+ if (
+ month.getMonth() !== selectedDay.getMonth() ||
+ month.getFullYear() !== selectedDay.getFullYear()
+ ) {
+ setMonth(selectedDay);
}
- handleOnSelectRange(nextRange, selectedDay, modifiers, e)
+ handleOnSelectRange(nextRange, selectedDay, modifiers, e);
}}
modifiers={{
...(selected.from && {
@@ -364,7 +435,8 @@ function Calendar({
}),
...(selected.from &&
selected.to && {
- range_middle: (d: Date) => d > selected.from! && d < selected.to!,
+ range_middle: (d: Date) =>
+ d > selected.from! && d < selected.to!,
}),
...(selected.from &&
rangeFocus === "from" && {
@@ -387,9 +459,9 @@ function Calendar({
/>
)}
- )
+ );
}
-Calendar.displayName = "Calendar"
+Calendar.displayName = "Calendar";
-export { Calendar }
+export { Calendar };
diff --git a/src/components/DatePicker/dataRangeDropdown.tsx b/src/components/DatePicker/dataRangeDropdown.tsx
index 2771fbd..118fc71 100644
--- a/src/components/DatePicker/dataRangeDropdown.tsx
+++ b/src/components/DatePicker/dataRangeDropdown.tsx
@@ -1,7 +1,7 @@
-import { useIntl } from "react-intl"
-import { CircleX } from "lucide-react"
+import { useIntl } from "react-intl";
+import { CircleX } from "lucide-react";
-import { Button } from "@/components/ui/button"
+import { Button } from "@/components/ui/button";
import {
DropdownMenu,
@@ -11,45 +11,52 @@ import {
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
-} from "./dropdownMenu"
+} from "./dropdownMenu";
interface DateRangeDropdownProps {
- value?: number
- onRangeChange: (range: number) => void
- onClear?: () => void
+ value?: number;
+ onRangeChange: (range: number) => void;
+ onClear?: () => void;
}
-export function DateRangeDropdown({ value, onRangeChange, onClear }: DateRangeDropdownProps) {
- const intl = useIntl()
+export function DateRangeDropdown({
+ value,
+ onRangeChange,
+ onClear,
+}: DateRangeDropdownProps) {
+ const intl = useIntl();
const handleRangeChanged = (value: string) => {
- const range = Number(value)
- onRangeChange(range)
- }
+ const range = Number(value);
+ onRangeChange(range);
+ };
const handleDisplayRange = (value: number | undefined): string => {
switch (value) {
case 30:
case 60:
case 90:
- return intl.formatMessage({ id: "displayRange.days", defaultMessage: "{value} Days" }, { value })
+ return intl.formatMessage(
+ { id: "displayRange.days", defaultMessage: "{value} Days" },
+ { value },
+ );
case 180:
return intl.formatMessage({
id: "displayRange.sixMonths",
defaultMessage: "6 Months",
- })
+ });
case 365:
return intl.formatMessage({
id: "displayRange.oneYear",
defaultMessage: "1 Year",
- })
+ });
default:
return intl.formatMessage({
id: "displayRange.selectRange",
defaultMessage: "Select range",
- })
+ });
}
- }
+ };
return (
@@ -69,25 +76,28 @@ export function DateRangeDropdown({ value, onRangeChange, onClear }: DateRangeDr
})}
aria-pressed="false"
onPointerDown={(e) => {
- e.preventDefault()
- e.stopPropagation()
+ e.preventDefault();
+ e.stopPropagation();
}}
onClick={(e) => {
- e.stopPropagation()
- onClear?.()
+ e.stopPropagation();
+ onClear?.();
}}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
- e.preventDefault()
- e.stopPropagation()
- onClear?.()
+ e.preventDefault();
+ e.stopPropagation();
+ onClear?.();
} else if (e.key === "Escape") {
- e.stopPropagation()
+ e.stopPropagation();
}
}}
className="p-1 rounded-sm hover:bg-elevation-250 focus:outline-hidden focus:ring-2 focus:ring-brand-200 focus:ring-offset-1 cursor-pointer transition-colors"
>
-
+
)}
@@ -100,7 +110,10 @@ export function DateRangeDropdown({ value, onRangeChange, onClear }: DateRangeDr
})}
-
+
{intl.formatMessage({
id: "dropdown.option.30days",
@@ -134,5 +147,5 @@ export function DateRangeDropdown({ value, onRangeChange, onClear }: DateRangeDr
- )
+ );
}
diff --git a/src/components/DatePicker/datePicker.tsx b/src/components/DatePicker/datePicker.tsx
index ceefe1b..d664ab9 100644
--- a/src/components/DatePicker/datePicker.tsx
+++ b/src/components/DatePicker/datePicker.tsx
@@ -1,36 +1,36 @@
-import React, { useContext, useEffect, useMemo, useState } from "react"
-import { DateRange, dateMatchModifiers, type Matcher } from "react-day-picker"
-import { FormattedMessage } from "react-intl"
-import { addDays, isSameDay } from "date-fns"
-import { ArrowRight, CalendarIcon } from "lucide-react"
-
-import { Calendar } from "@/components/DatePicker/calendar"
-import { Button } from "@/components/ui/button"
-import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { LanguageContext } from "@/context/language/LanguageContext"
-import { cn } from "@/lib/utils"
-import { daysBetween, formatDateLong, formatDateShort } from "@/utils/dates"
-import { useUtcDateFormatters } from "@/hooks/use-utc-date-formatters"
-
-import { DateRangeDropdown } from "./dataRangeDropdown"
-import { MonthPicker } from "./monthPicker"
-import { YearPicker } from "./yearPicker"
+import React, { useContext, useEffect, useMemo, useState } from "react";
+import { DateRange, dateMatchModifiers, type Matcher } from "react-day-picker";
+import { FormattedMessage } from "react-intl";
+import { addDays, isSameDay } from "date-fns";
+import { ArrowRight, CalendarIcon } from "lucide-react";
+
+import { Calendar } from "@/components/DatePicker/calendar";
+import { Button } from "@/components/ui/button";
+import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { LanguageContext } from "@/context/language/LanguageContext";
+import { cn } from "@/lib/utils";
+import { daysBetween, formatDateLong, formatDateShort } from "@/utils/dates";
+import { useUtcDateFormatters } from "@/hooks/use-utc-date-formatters";
+
+import { DateRangeDropdown } from "./dataRangeDropdown";
+import { MonthPicker } from "./monthPicker";
+import { YearPicker } from "./yearPicker";
interface DatePickerProps {
- className?: string
- label?: string
- mode: "single" | "range"
- value?: DateRange
- onChange: (dateRange: DateRange | undefined) => void
- customComponent?: React.ReactElement
- disabled?: Matcher | Matcher[] | undefined
- displayIncrementButtons?: boolean
- disableFutureNavigation?: boolean
- disableAutoSelect?: boolean
- currentYearPosition?: "start" | "center" | "end"
- order?: "asc" | "desc"
- dateFilterType?: "issue" | "maturity"
- onDateFilterTypeChange?: (type: "issue" | "maturity") => void
+ className?: string;
+ label?: string;
+ mode: "single" | "range";
+ value?: DateRange;
+ onChange: (dateRange: DateRange | undefined) => void;
+ customComponent?: React.ReactElement;
+ disabled?: Matcher | Matcher[] | undefined;
+ displayIncrementButtons?: boolean;
+ disableFutureNavigation?: boolean;
+ disableAutoSelect?: boolean;
+ currentYearPosition?: "start" | "center" | "end";
+ order?: "asc" | "desc";
+ dateFilterType?: "issue" | "maturity";
+ onDateFilterTypeChange?: (type: "issue" | "maturity") => void;
}
export function DatePicker({
@@ -49,124 +49,135 @@ export function DatePicker({
dateFilterType = "issue",
onDateFilterTypeChange,
}: DatePickerProps) {
- const lang = useContext(LanguageContext)
- const { formatDateMmmDdYyyy } = useUtcDateFormatters(lang.locale)
- const [canSelect, setCanSelect] = useState(false)
- const [showCalendar, setShowCalendar] = useState(false)
- const [showYearPicker, setShowYearPicker] = useState(false)
- const [showMonthPicker, setShowMonthPicker] = useState(false)
- const [selectedRange, setSelectedRange] = useState()
- const allowRangeSelection = useMemo(() => mode === "range", [mode])
- const [hasBeenCleared, setHasBeenCleared] = useState(false)
+ const lang = useContext(LanguageContext);
+ const { formatDateMmmDdYyyy } = useUtcDateFormatters(lang.locale);
+ const [canSelect, setCanSelect] = useState(false);
+ const [showCalendar, setShowCalendar] = useState(false);
+ const [showYearPicker, setShowYearPicker] = useState(false);
+ const [showMonthPicker, setShowMonthPicker] = useState(false);
+ const [selectedRange, setSelectedRange] = useState();
+ const allowRangeSelection = useMemo(() => mode === "range", [mode]);
+ const [hasBeenCleared, setHasBeenCleared] = useState(false);
const getInitialDate = () => {
if (value) {
- return value
+ return value;
}
if (hasBeenCleared) {
- return { from: undefined, to: undefined }
+ return { from: undefined, to: undefined };
}
if (disableAutoSelect) {
- return { from: undefined, to: undefined }
+ return { from: undefined, to: undefined };
}
- return { from: new Date(), to: undefined }
- }
+ return { from: new Date(), to: undefined };
+ };
- const [current, setCurrent] = useState(getInitialDate())
- const [draft, setDraft] = useState(getInitialDate())
- const [rangeFocus, setRangeFocus] = useState<"from" | "to">("from")
- const baseDate = useMemo(() => current.from ?? new Date(), [current])
+ const [current, setCurrent] = useState(getInitialDate());
+ const [draft, setDraft] = useState(getInitialDate());
+ const [rangeFocus, setRangeFocus] = useState<"from" | "to">("from");
+ const baseDate = useMemo(() => current.from ?? new Date(), [current]);
useEffect(() => {
if (value) {
- setCurrent(value)
- setDraft(value)
- setHasBeenCleared(false)
+ setCurrent(value);
+ setDraft(value);
+ setHasBeenCleared(false);
}
- }, [value])
+ }, [value]);
const toggleCalendar = () => {
setShowCalendar((prev) => {
- const willOpen = !prev
+ const willOpen = !prev;
if (willOpen) {
- setDraft(current)
+ setDraft(current);
}
- return willOpen
- })
- }
+ return willOpen;
+ });
+ };
const toggleYearPicker = () => {
- setShowYearPicker((prev) => !prev)
- }
+ setShowYearPicker((prev) => !prev);
+ };
const clearSelection = () => {
- const clearedRange: DateRange = { from: draft.from ?? current.from, to: undefined }
- setSelectedRange(undefined)
- setDraft(clearedRange)
- setCurrent(clearedRange)
- onChange(clearedRange)
- setHasBeenCleared(true)
- setRangeFocus("to")
- setShowMonthPicker(false)
- setShowYearPicker(false)
- }
+ const clearedRange: DateRange = {
+ from: draft.from ?? current.from,
+ to: undefined,
+ };
+ setSelectedRange(undefined);
+ setDraft(clearedRange);
+ setCurrent(clearedRange);
+ onChange(clearedRange);
+ setHasBeenCleared(true);
+ setRangeFocus("to");
+ setShowMonthPicker(false);
+ setShowYearPicker(false);
+ };
useEffect(() => {
if (selectedRange === undefined) {
- return
+ return;
}
- const startDate = draft.from ?? current.from ?? new Date()
+ const startDate = draft.from ?? current.from ?? new Date();
const newRange = {
from: startDate,
to: addDays(startDate, selectedRange),
- }
+ };
- setCurrent(newRange)
- setDraft(newRange)
+ setCurrent(newRange);
+ setDraft(newRange);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [selectedRange])
+ }, [selectedRange]);
useEffect(() => {
setSelectedRange((val) => {
if (current.from === undefined || current.to === undefined) {
- return val
+ return val;
}
- const diffDays = daysBetween(current.from, current.to)
- return diffDays !== val ? undefined : val
- })
- }, [current])
+ const diffDays = daysBetween(current.from, current.to);
+ return diffDays !== val ? undefined : val;
+ });
+ }, [current]);
useEffect(() => {
if (!draft.from) {
- setCanSelect(false)
- return
+ setCanSelect(false);
+ return;
}
// single mode
if (mode === "single") {
- const isDisabled = disabled ? dateMatchModifiers(draft.from, [disabled as Matcher]) : false
- setCanSelect(!isDisabled)
+ const isDisabled = disabled
+ ? dateMatchModifiers(draft.from, [disabled as Matcher])
+ : false;
+ setCanSelect(!isDisabled);
}
// range mode
if (mode === "range") {
if (!draft.to) {
- setCanSelect(false)
- return
+ setCanSelect(false);
+ return;
}
- const isDisabledFrom = disabled ? dateMatchModifiers(draft.from, [disabled as Matcher]) : false
- const isDisabledTo = disabled ? dateMatchModifiers(draft.to, [disabled as Matcher]) : false
- setCanSelect(!isDisabledFrom && !isDisabledTo)
+ const isDisabledFrom = disabled
+ ? dateMatchModifiers(draft.from, [disabled as Matcher])
+ : false;
+ const isDisabledTo = disabled
+ ? dateMatchModifiers(draft.to, [disabled as Matcher])
+ : false;
+ setCanSelect(!isDisabledFrom && !isDisabledTo);
}
- }, [draft, disabled, mode])
+ }, [draft, disabled, mode]);
return (
<>
{customComponent ? (
- React.cloneElement(customComponent, { onClick: toggleCalendar } as React.HTMLAttributes)
+ React.cloneElement(customComponent, {
+ onClick: toggleCalendar,
+ } as React.HTMLAttributes)
) : (
-
+
{mode === "single" ? (
current.from ? (
@@ -199,7 +213,7 @@ export function DatePicker({
showCalendar ? "opacity-100" : "opacity-0 pointer-events-none",
)}
onClick={() => {
- setShowCalendar(false)
+ setShowCalendar(false);
}}
/>
@@ -227,20 +241,26 @@ export function DatePicker({
value={dateFilterType}
onValueChange={(value) => {
if (onDateFilterTypeChange) {
- onDateFilterTypeChange(value as "issue" | "maturity")
+ onDateFilterTypeChange(value as "issue" | "maturity");
}
}}
className="w-full"
>
-
+
-
+
{
- clearSelection()
+ clearSelection();
}}
/>
@@ -271,41 +291,55 @@ export function DatePicker({
{
- setRangeFocus("from")
+ setRangeFocus("from");
}}
className={cn(
"h-[46px] py-3 px-4 w-full bg-elevation-200 border rounded-lg truncate text-left",
- rangeFocus === "from" ? "border-brand-200" : "border-gray-200",
+ rangeFocus === "from"
+ ? "border-brand-200"
+ : "border-gray-200",
)}
>
{draft.from && formatDateShort(draft.from, lang.locale)}
{!draft.from && (
-
+
)}
{
- setRangeFocus("to")
+ setRangeFocus("to");
}}
className={cn(
"h-[46px] py-3 pl-4 pr-2 w-full bg-elevation-200 border rounded-lg truncate text-left",
- rangeFocus === "to" ? "border-brand-200" : "border-gray-200",
+ rangeFocus === "to"
+ ? "border-brand-200"
+ : "border-gray-200",
)}
>
{draft.to && formatDateShort(draft.to, lang.locale)}
{!draft.to && (
-
+
)}
@@ -329,13 +363,16 @@ export function DatePicker({
key={days}
className="bg-elevation-200/70 p-1.5 rounded-sm text-text-300 text-[10px] font-medium"
onClick={() => {
- const base = current.from ?? new Date() // always start from confirmed date
- const newDate = addDays(base, days)
+ const base = current.from ?? new Date(); // always start from confirmed date
+ const newDate = addDays(base, days);
setDraft({
from: newDate,
- to: mode === "range" ? addDays(newDate, days) : undefined,
- })
+ to:
+ mode === "range"
+ ? addDays(newDate, days)
+ : undefined,
+ });
}}
>
+{days}
@@ -344,7 +381,9 @@ export function DatePicker({
)}
- {current.from ? formatDateMmmDdYyyy(current.from) : "-"}
+
+ {current.from ? formatDateMmmDdYyyy(current.from) : "-"}
+
)}
@@ -357,13 +396,13 @@ export function DatePicker({
setDraft({
...draft,
from: date,
- })
- setShowYearPicker(false)
- setShowMonthPicker(true)
+ });
+ setShowYearPicker(false);
+ setShowMonthPicker(true);
}}
onCaptionLabelClicked={() => {
- setShowYearPicker(false)
- setShowMonthPicker(false)
+ setShowYearPicker(false);
+ setShowMonthPicker(false);
}}
disableFutureNavigation={disableFutureNavigation}
currentYearPosition={currentYearPosition}
@@ -377,13 +416,13 @@ export function DatePicker({
setDraft({
...draft,
from: date,
- })
- setShowYearPicker(false)
- setShowMonthPicker(false)
+ });
+ setShowYearPicker(false);
+ setShowMonthPicker(false);
}}
onCaptionLabelClicked={() => {
- setShowYearPicker(true)
- setShowMonthPicker(false)
+ setShowYearPicker(true);
+ setShowMonthPicker(false);
}}
disableFutureNavigation={disableFutureNavigation}
/>
@@ -397,13 +436,18 @@ export function DatePicker({
onSelect={(_ignored: DateRange | undefined, selectedDay) => {
setDraft((prev) => {
if (rangeFocus === "from") {
- const newTo = prev.to && selectedDay <= prev.to ? prev.to : undefined
- return { from: selectedDay, to: newTo }
+ const newTo =
+ prev.to && selectedDay <= prev.to ? prev.to : undefined;
+ return { from: selectedDay, to: newTo };
}
- const from = prev.from ?? selectedDay
- return selectedDay < from ? { from: selectedDay, to: from } : { from, to: selectedDay }
- })
- setRangeFocus((prevFocus) => (prevFocus === "from" ? "to" : "from"))
+ const from = prev.from ?? selectedDay;
+ return selectedDay < from
+ ? { from: selectedDay, to: from }
+ : { from, to: selectedDay };
+ });
+ setRangeFocus((prevFocus) =>
+ prevFocus === "from" ? "to" : "from",
+ );
}}
initialFocus
disabled={disabled}
@@ -427,9 +471,9 @@ export function DatePicker({
size="sm"
type="button"
onClick={() => {
- setShowMonthPicker(false)
- setShowYearPicker(false)
- setShowCalendar(false)
+ setShowMonthPicker(false);
+ setShowYearPicker(false);
+ setShowCalendar(false);
}}
>
{
- setCurrent(draft)
- onChange(draft)
- setShowMonthPicker(false)
- setShowYearPicker(false)
- setShowCalendar(false)
+ setCurrent(draft);
+ onChange(draft);
+ setShowMonthPicker(false);
+ setShowYearPicker(false);
+ setShowCalendar(false);
}}
>
>
- )
+ );
}
diff --git a/src/components/DatePicker/dropdownMenu.tsx b/src/components/DatePicker/dropdownMenu.tsx
index 033a75d..f76709b 100644
--- a/src/components/DatePicker/dropdownMenu.tsx
+++ b/src/components/DatePicker/dropdownMenu.tsx
@@ -1,25 +1,25 @@
-import * as React from "react"
-import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
-import { Check, ChevronRight, Circle } from "lucide-react"
+import * as React from "react";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { Check, ChevronRight, Circle } from "lucide-react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
-const DropdownMenu = DropdownMenuPrimitive.Root
+const DropdownMenu = DropdownMenuPrimitive.Root;
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
-const DropdownMenuGroup = DropdownMenuPrimitive.Group
+const DropdownMenuGroup = DropdownMenuPrimitive.Group;
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
-const DropdownMenuSub = DropdownMenuPrimitive.Sub
+const DropdownMenuSub = DropdownMenuPrimitive.Sub;
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
-))
-DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
+));
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef,
@@ -49,8 +50,9 @@ const DropdownMenuSubContent = React.forwardRef<
)}
{...props}
/>
-))
-DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
+));
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef,
@@ -67,13 +69,13 @@ const DropdownMenuContent = React.forwardRef<
{...props}
/>
-))
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+));
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
-))
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+));
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef,
@@ -108,8 +110,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
{children}
-))
-DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
+));
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef,
@@ -130,35 +133,51 @@ const DropdownMenuRadioItem = React.forwardRef<
{children}
-))
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+));
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef & {
- inset?: boolean
+ inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
-))
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+));
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => (
-
-))
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
-
-const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
- return
-}
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+));
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ );
+};
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
@@ -176,4 +195,4 @@ export {
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
-}
+};
diff --git a/src/components/DatePicker/monthPicker.tsx b/src/components/DatePicker/monthPicker.tsx
index 467e731..bfc1f2a 100644
--- a/src/components/DatePicker/monthPicker.tsx
+++ b/src/components/DatePicker/monthPicker.tsx
@@ -1,18 +1,18 @@
-import { useContext, useEffect, useState } from "react"
-import { ChevronLeft, ChevronRight, ChevronUp } from "lucide-react"
+import { useContext, useEffect, useState } from "react";
+import { ChevronLeft, ChevronRight, ChevronUp } from "lucide-react";
-import { LanguageContext } from "@/context/language/LanguageContext"
-import { cn } from "@/lib/utils"
-import { formatMonthLong, formatMonthYear } from "@/utils/dates"
+import { LanguageContext } from "@/context/language/LanguageContext";
+import { cn } from "@/lib/utils";
+import { formatMonthLong, formatMonthYear } from "@/utils/dates";
-import { buttonVariants } from "../ui/button"
+import { buttonVariants } from "../ui/button";
interface MonthPickerProps {
- value: Date
- onChange: (date: Date) => void
- onCaptionLabelClicked: () => void
- disableFutureNavigation?: boolean
- minDate?: Date
+ value: Date;
+ onChange: (date: Date) => void;
+ onCaptionLabelClicked: () => void;
+ disableFutureNavigation?: boolean;
+ minDate?: Date;
}
const MonthPicker = ({
@@ -22,64 +22,69 @@ const MonthPicker = ({
disableFutureNavigation = false,
minDate,
}: MonthPickerProps) => {
- const lang = useContext(LanguageContext)
- const now = new Date()
- const currentYear = now.getUTCFullYear()
- const currentMonth = now.getUTCMonth()
- const minYear = minDate?.getUTCFullYear()
- const minMonth = minDate?.getUTCMonth()
+ const lang = useContext(LanguageContext);
+ const now = new Date();
+ const currentYear = now.getUTCFullYear();
+ const currentMonth = now.getUTCMonth();
+ const minYear = minDate?.getUTCFullYear();
+ const minMonth = minDate?.getUTCMonth();
const [base, setBase] = useState(() => {
- let initYear = value.getUTCFullYear()
+ let initYear = value.getUTCFullYear();
if (disableFutureNavigation) {
- initYear = Math.min(initYear, currentYear)
+ initYear = Math.min(initYear, currentYear);
}
if (minYear !== undefined) {
- initYear = Math.max(initYear, minYear)
+ initYear = Math.max(initYear, minYear);
}
- return new Date(Date.UTC(initYear, value.getUTCMonth(), 1))
- })
+ return new Date(Date.UTC(initYear, value.getUTCMonth(), 1));
+ });
useEffect(() => {
- let nextYear = value.getUTCFullYear()
+ let nextYear = value.getUTCFullYear();
if (disableFutureNavigation) {
- nextYear = Math.min(nextYear, currentYear)
+ nextYear = Math.min(nextYear, currentYear);
}
if (minYear !== undefined) {
- nextYear = Math.max(nextYear, minYear)
+ nextYear = Math.max(nextYear, minYear);
}
if (nextYear !== base.getUTCFullYear()) {
- setBase(() => new Date(Date.UTC(nextYear, value.getUTCMonth(), 1)))
+ setBase(() => new Date(Date.UTC(nextYear, value.getUTCMonth(), 1)));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [value, disableFutureNavigation, currentYear])
+ }, [value, disableFutureNavigation, currentYear]);
const handleOnChange = (monthIndex: number) => {
- const newDate = new Date(base)
- newDate.setUTCMonth(monthIndex)
- onChange(newDate)
- }
+ const newDate = new Date(base);
+ newDate.setUTCMonth(monthIndex);
+ onChange(newDate);
+ };
const addYears = (years: number) => {
- setBase((val) => new Date(Date.UTC(val.getUTCFullYear() + years, val.getUTCMonth(), 1)))
- }
+ setBase(
+ (val) =>
+ new Date(Date.UTC(val.getUTCFullYear() + years, val.getUTCMonth(), 1)),
+ );
+ };
- const canGoBackward = minYear === undefined || base.getUTCFullYear() > minYear
- const canGoForward = !disableFutureNavigation || base.getUTCFullYear() < currentYear
+ const canGoBackward =
+ minYear === undefined || base.getUTCFullYear() > minYear;
+ const canGoForward =
+ !disableFutureNavigation || base.getUTCFullYear() < currentYear;
const nextYear = () => {
if (!canGoForward) {
- return
+ return;
}
- addYears(1)
- }
+ addYears(1);
+ };
const prevYear = () => {
if (!canGoBackward) {
- return
+ return;
}
- addYears(-1)
- }
+ addYears(-1);
+ };
return (
@@ -91,9 +96,15 @@ const MonthPicker = ({
})}
onClick={prevYear}
/>
-
+
{formatMonthYear(base, lang.locale)}
-
+
{Array.from({ length: 12 }, (_, index) => {
- const date = new Date(Date.UTC(base.getUTCFullYear(), index, 1))
+ const date = new Date(Date.UTC(base.getUTCFullYear(), index, 1));
const isFutureMonth =
disableFutureNavigation &&
- (date.getUTCFullYear() > currentYear || (date.getUTCFullYear() === currentYear && index > currentMonth))
+ (date.getUTCFullYear() > currentYear ||
+ (date.getUTCFullYear() === currentYear && index > currentMonth));
const isPastMonth =
minYear !== undefined &&
(date.getUTCFullYear() < minYear ||
- (date.getUTCFullYear() === minYear && minMonth !== undefined && index < minMonth))
+ (date.getUTCFullYear() === minYear &&
+ minMonth !== undefined &&
+ index < minMonth));
const isSelected =
- date.getUTCFullYear() === value.getUTCFullYear() && date.getUTCMonth() === value.getUTCMonth()
+ date.getUTCFullYear() === value.getUTCFullYear() &&
+ date.getUTCMonth() === value.getUTCMonth();
return (
{
- if (!isFutureMonth && !isPastMonth) handleOnChange(index)
+ if (!isFutureMonth && !isPastMonth) handleOnChange(index);
}}
- className={cn("h-[42px] flex justify-center items-center", buttonVariants({ variant: "ghost" }), {
- "cursor-pointer": !isFutureMonth && !isPastMonth,
- "opacity-40 text-text-200 pointer-events-none": isFutureMonth || isPastMonth,
- "bg-elevation-200 hover:bg-elevation-200 border border-divider-100": isSelected,
- })}
+ className={cn(
+ "h-[42px] flex justify-center items-center",
+ buttonVariants({ variant: "ghost" }),
+ {
+ "cursor-pointer": !isFutureMonth && !isPastMonth,
+ "opacity-40 text-text-200 pointer-events-none":
+ isFutureMonth || isPastMonth,
+ "bg-elevation-200 hover:bg-elevation-200 border border-divider-100":
+ isSelected,
+ },
+ )}
>
{formatMonthLong(date, lang.locale)}
- )
+ );
})}
- )
-}
+ );
+};
-export { MonthPicker }
+export { MonthPicker };
diff --git a/src/components/DatePicker/yearPicker.tsx b/src/components/DatePicker/yearPicker.tsx
index 2450840..e3027c2 100644
--- a/src/components/DatePicker/yearPicker.tsx
+++ b/src/components/DatePicker/yearPicker.tsx
@@ -1,21 +1,21 @@
-import { useContext, useEffect, useRef, useState } from "react"
-import { ChevronLeft, ChevronRight, ChevronUp } from "lucide-react"
+import { useContext, useEffect, useRef, useState } from "react";
+import { ChevronLeft, ChevronRight, ChevronUp } from "lucide-react";
-import { LanguageContext } from "@/context/language/LanguageContext"
-import { cn } from "@/lib/utils"
-import { formatYearNumeric } from "@/utils/dates"
+import { LanguageContext } from "@/context/language/LanguageContext";
+import { cn } from "@/lib/utils";
+import { formatYearNumeric } from "@/utils/dates";
-import { buttonVariants } from "../ui/button"
+import { buttonVariants } from "../ui/button";
interface YearPickerProps {
- value: Date
- onChange: (date: Date) => void
- onCaptionLabelClicked: () => void
- numberYears?: number
- disableFutureNavigation?: boolean
- minDate?: Date
- currentYearPosition?: "start" | "center" | "end"
- order?: "asc" | "desc"
+ value: Date;
+ onChange: (date: Date) => void;
+ onCaptionLabelClicked: () => void;
+ numberYears?: number;
+ disableFutureNavigation?: boolean;
+ minDate?: Date;
+ currentYearPosition?: "start" | "center" | "end";
+ order?: "asc" | "desc";
}
const YearPicker = ({
@@ -28,89 +28,107 @@ const YearPicker = ({
currentYearPosition = "start",
order = "asc",
}: YearPickerProps) => {
- const lang = useContext(LanguageContext)
- const currentYear = new Date().getUTCFullYear()
- const minYear = minDate?.getUTCFullYear()
- const total = numberYears
- const half = Math.floor(total / 2)
- const positionIndex = currentYearPosition === "center" ? half : currentYearPosition === "end" ? total - 1 : 0
- const maxBaseYear = currentYear - (total - 1 - positionIndex)
+ const lang = useContext(LanguageContext);
+ const currentYear = new Date().getUTCFullYear();
+ const minYear = minDate?.getUTCFullYear();
+ const total = numberYears;
+ const half = Math.floor(total / 2);
+ const positionIndex =
+ currentYearPosition === "center"
+ ? half
+ : currentYearPosition === "end"
+ ? total - 1
+ : 0;
+ const maxBaseYear = currentYear - (total - 1 - positionIndex);
const [base, setBase] = useState
(() => {
- let initial = value.getUTCFullYear()
+ let initial = value.getUTCFullYear();
if (disableFutureNavigation) {
- initial = Math.min(initial, maxBaseYear)
+ initial = Math.min(initial, maxBaseYear);
}
if (minYear !== undefined) {
- initial = Math.max(initial, minYear)
+ initial = Math.max(initial, minYear);
}
- return new Date(Date.UTC(initial, 0, 1))
- })
+ return new Date(Date.UTC(initial, 0, 1));
+ });
const handleOnChange = (year: number) => {
- const updateDate = new Date(value)
- updateDate.setUTCFullYear(year)
- onChange(updateDate)
- }
+ const updateDate = new Date(value);
+ updateDate.setUTCFullYear(year);
+ onChange(updateDate);
+ };
- const startYear = base.getUTCFullYear() - positionIndex
- const endYear = startYear + total - 1
- const prevSelectedRef = useRef(value.getUTCFullYear())
+ const startYear = base.getUTCFullYear() - positionIndex;
+ const endYear = startYear + total - 1;
+ const prevSelectedRef = useRef(value.getUTCFullYear());
useEffect(() => {
- const selected = value.getUTCFullYear()
+ const selected = value.getUTCFullYear();
if (selected === prevSelectedRef.current) {
- return
+ return;
}
- prevSelectedRef.current = selected
+ prevSelectedRef.current = selected;
- const maxBaseYear = currentYear - (total - 1 - positionIndex)
- let clamped = disableFutureNavigation ? Math.min(selected, maxBaseYear) : selected
+ const maxBaseYear = currentYear - (total - 1 - positionIndex);
+ let clamped = disableFutureNavigation
+ ? Math.min(selected, maxBaseYear)
+ : selected;
if (minYear !== undefined) {
- clamped = Math.max(clamped, minYear)
+ clamped = Math.max(clamped, minYear);
}
- setBase(new Date(Date.UTC(clamped, 0, 1)))
- }, [value, disableFutureNavigation, currentYear, total, positionIndex, minYear])
-
- const canGoForward = !disableFutureNavigation || endYear < currentYear
- const canGoBackward = minYear === undefined || startYear > minYear
+ setBase(new Date(Date.UTC(clamped, 0, 1)));
+ }, [
+ value,
+ disableFutureNavigation,
+ currentYear,
+ total,
+ positionIndex,
+ minYear,
+ ]);
+
+ const canGoForward = !disableFutureNavigation || endYear < currentYear;
+ const canGoBackward = minYear === undefined || startYear > minYear;
const nextYears = () => {
if (canGoForward) {
setBase((val) => {
- const target = val.getUTCFullYear() + numberYears
- const maxBaseYear = currentYear - (total - 1 - positionIndex)
- const clamped = disableFutureNavigation ? Math.min(target, maxBaseYear) : target
- return new Date(Date.UTC(clamped, 0, 1))
- })
+ const target = val.getUTCFullYear() + numberYears;
+ const maxBaseYear = currentYear - (total - 1 - positionIndex);
+ const clamped = disableFutureNavigation
+ ? Math.min(target, maxBaseYear)
+ : target;
+ return new Date(Date.UTC(clamped, 0, 1));
+ });
}
- }
+ };
const prevYears = () => {
if (!canGoBackward) {
- return
+ return;
}
- setBase((val) => new Date(Date.UTC(val.getUTCFullYear() - numberYears, 0, 1)))
- }
+ setBase(
+ (val) => new Date(Date.UTC(val.getUTCFullYear() - numberYears, 0, 1)),
+ );
+ };
- let touchStartX: number | null = null
+ let touchStartX: number | null = null;
const handleTouchStart = (e: React.TouchEvent) => {
- touchStartX = e.touches[0].clientX
- }
+ touchStartX = e.touches[0].clientX;
+ };
const handleTouchEnd = (e: React.TouchEvent) => {
- if (touchStartX === null) return
- const diffX = e.changedTouches[0].clientX - touchStartX
+ if (touchStartX === null) return;
+ const diffX = e.changedTouches[0].clientX - touchStartX;
if (diffX > 50) {
- prevYears()
+ prevYears();
} else if (diffX < -50) {
- nextYears()
+ nextYears();
}
- touchStartX = null
- }
+ touchStartX = null;
+ };
- const years = Array.from({ length: numberYears }, (_, i) => startYear + i)
- const displayYears = order === "desc" ? [...years].reverse() : years
+ const years = Array.from({ length: numberYears }, (_, i) => startYear + i);
+ const displayYears = order === "desc" ? [...years].reverse() : years;
return (
-
+
{formatYearNumeric(value, lang.locale)}
-
+
{displayYears.map((year, index) => {
- const isSelected = year === value.getUTCFullYear()
+ const isSelected = year === value.getUTCFullYear();
const isDisabled =
- (disableFutureNavigation && year > currentYear) || (minYear !== undefined && year < minYear)
+ (disableFutureNavigation && year > currentYear) ||
+ (minYear !== undefined && year < minYear);
return (
{
- if (!isDisabled) handleOnChange(year)
+ if (!isDisabled) handleOnChange(year);
}}
- className={cn("h-[42px] flex justify-center items-center", buttonVariants({ variant: "ghost" }), {
- "cursor-pointer": !isDisabled,
- "bg-elevation-200 hover:bg-elevation-200 border border-divider-100": isSelected,
- "opacity-40 text-text-200 pointer-events-none": isDisabled,
- })}
+ className={cn(
+ "h-[42px] flex justify-center items-center",
+ buttonVariants({ variant: "ghost" }),
+ {
+ "cursor-pointer": !isDisabled,
+ "bg-elevation-200 hover:bg-elevation-200 border border-divider-100":
+ isSelected,
+ "opacity-40 text-text-200 pointer-events-none": isDisabled,
+ },
+ )}
>
{year}
- )
+ );
})}
- )
-}
+ );
+};
-export { YearPicker }
+export { YearPicker };
diff --git a/src/components/Drawers.tsx b/src/components/Drawers.tsx
index 96e4409..c1f008d 100644
--- a/src/components/Drawers.tsx
+++ b/src/components/Drawers.tsx
@@ -1,4 +1,4 @@
-import { Button, buttonVariants } from "@/components/ui/button"
+import { Button, buttonVariants } from "@/components/ui/button";
import {
Drawer,
DrawerClose,
@@ -8,18 +8,24 @@ import {
DrawerHeader,
DrawerTitle,
DrawerTrigger,
-} from "@/components/ui/drawer"
-import { VariantProps } from "class-variance-authority"
-import { useIntl } from "react-intl"
+} from "@/components/ui/drawer";
+import { VariantProps } from "class-variance-authority";
+import { useIntl } from "react-intl";
-type DrawerProps = Parameters[0]
+type DrawerProps = Parameters[0];
type BaseDrawerProps = DrawerProps & {
- title: string
- description?: string
- trigger?: React.ReactNode
- children?: React.ReactNode
-}
-export function BaseDrawer({ title, description = "", trigger, children, ...drawerProps }: BaseDrawerProps) {
+ title: string;
+ description?: string;
+ trigger?: React.ReactNode;
+ children?: React.ReactNode;
+};
+export function BaseDrawer({
+ title,
+ description = "",
+ trigger,
+ children,
+ ...drawerProps
+}: BaseDrawerProps) {
return (
{trigger && {trigger} }
@@ -27,22 +33,24 @@ export function BaseDrawer({ title, description = "", trigger, children, ...draw
{title}
- {description && {description} }
+ {description && (
+ {description}
+ )}
{children}
- )
+ );
}
type ConfirmDrawerProps = BaseDrawerProps & {
- cancelButtonText?: string
- submitButtonText?: string
- submitButtonVariant?: VariantProps["variant"]
- submitButtonDisabled?: boolean
- onSubmit: () => void
-}
+ cancelButtonText?: string;
+ submitButtonText?: string;
+ submitButtonVariant?: VariantProps["variant"];
+ submitButtonDisabled?: boolean;
+ onSubmit: () => void;
+};
export function ConfirmDrawer({
cancelButtonText,
@@ -53,19 +61,19 @@ export function ConfirmDrawer({
children,
...drawerProps
}: ConfirmDrawerProps) {
- const intl = useIntl()
+ const intl = useIntl();
const resolvedCancelText =
cancelButtonText ??
intl.formatMessage({
id: "Cancel",
defaultMessage: "Cancel",
- })
+ });
const resolvedSubmitText =
submitButtonText ??
intl.formatMessage({
id: "Confirm",
defaultMessage: "Confirm",
- })
+ });
return (
{children}
@@ -81,12 +89,16 @@ export function ConfirmDrawer({
{resolvedSubmitText}
-
+
{resolvedCancelText}
- )
+ );
}
diff --git a/src/components/EndorsementChain.tsx b/src/components/EndorsementChain.tsx
index b79f8d2..b3e6ac6 100644
--- a/src/components/EndorsementChain.tsx
+++ b/src/components/EndorsementChain.tsx
@@ -1,5 +1,5 @@
-import { useState } from "react"
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
ChevronDown,
ChevronUp,
@@ -11,30 +11,40 @@ import {
Coins,
DollarSign,
ArrowUpDown,
-} from "lucide-react"
-import type { Endorsement, LightBillParticipant } from "@/generated/client/types.gen"
-import { Skeleton } from "@/components/ui/skeleton"
-import { Button } from "@/components/ui/button"
-import { Separator } from "@/components/ui/separator"
-import { TruncatedTextPopover } from "@/components/TruncatedTextPopover"
-import { useIntl } from "react-intl"
+} from "lucide-react";
+import type {
+ Endorsement,
+ LightBillParticipant,
+} from "@/generated/client/types.gen";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Button } from "@/components/ui/button";
+import { Separator } from "@/components/ui/separator";
+import { TruncatedTextPopover } from "@/components/TruncatedTextPopover";
+import { useIntl } from "react-intl";
interface EndorsementChainProps {
- endorsements?: Endorsement[]
- isLoading?: boolean
- issueDate?: string
- maturityDate?: string
- mintingEnabled?: boolean
- quoteOffered?: boolean
+ endorsements?: Endorsement[];
+ isLoading?: boolean;
+ issueDate?: string;
+ maturityDate?: string;
+ mintingEnabled?: boolean;
+ quoteOffered?: boolean;
}
-function LightParticipantInfo({ participant }: { participant: LightBillParticipant }) {
- const intl = useIntl()
+function LightParticipantInfo({
+ participant,
+}: {
+ participant: LightBillParticipant;
+}) {
+ const intl = useIntl();
if ("Anon" in participant) {
return (
- {intl.formatMessage({ id: "participants.role.bearer", defaultMessage: "Bearer" })}
+ {intl.formatMessage({
+ id: "participants.role.bearer",
+ defaultMessage: "Bearer",
+ })}
- )
+ );
} else if ("Ident" in participant) {
return (
-
+
{participant.Ident.city && participant.Ident.country && (
)}
- )
+ );
}
- return null
+ return null;
}
interface HistoryEvent {
@@ -70,9 +84,9 @@ interface HistoryEvent {
| "acceptance"
| "rejection"
| "minting"
- | "rejectedToPay"
- timestamp?: number
- data: Endorsement | null
+ | "rejectedToPay";
+ timestamp?: number;
+ data: Endorsement | null;
}
const EVENT_CONFIG = {
@@ -130,7 +144,7 @@ const EVENT_CONFIG = {
labelId: "endorsement.event.payment",
defaultLabel: "Payment received",
},
-} as const
+} as const;
export function EndorsementChain({
endorsements,
@@ -147,21 +161,21 @@ export function EndorsementChain({
quoteOffered,
offeredTimestamp,
}: EndorsementChainProps & {
- requestToPayTimestamp?: number
- rejectedToPayTimestamp?: number
- paymentTimestamp?: number
- acceptanceTimestamp?: number
- rejectionTimestamp?: number
- mintingTimestamp?: number
- offeredTimestamp?: number
+ requestToPayTimestamp?: number;
+ rejectedToPayTimestamp?: number;
+ paymentTimestamp?: number;
+ acceptanceTimestamp?: number;
+ rejectionTimestamp?: number;
+ mintingTimestamp?: number;
+ offeredTimestamp?: number;
}) {
- const intl = useIntl()
- const [isExpanded, setIsExpanded] = useState(false)
- const [sortByTimestamp, setSortByTimestamp] = useState(false)
+ const intl = useIntl();
+ const [isExpanded, setIsExpanded] = useState(false);
+ const [sortByTimestamp, setSortByTimestamp] = useState(false);
const titleLabel = intl.formatMessage({
id: "endorsement.history.title",
defaultMessage: "Bill history",
- })
+ });
if (isLoading) {
return (
@@ -173,18 +187,18 @@ export function EndorsementChain({
- )
+ );
}
- const events: HistoryEvent[] = []
+ const events: HistoryEvent[] = [];
if (issueDate) {
- const issueTimestamp = new Date(issueDate).getTime() / 1000
+ const issueTimestamp = new Date(issueDate).getTime() / 1000;
events.push({
type: "issue",
timestamp: issueTimestamp,
data: null,
- })
+ });
}
if (quoteOffered) {
@@ -192,7 +206,7 @@ export function EndorsementChain({
type: "offered",
timestamp: offeredTimestamp ?? undefined,
data: null,
- })
+ });
}
if (endorsements) {
@@ -201,8 +215,8 @@ export function EndorsementChain({
type: "endorsement",
timestamp: endorsement.signing_timestamp,
data: endorsement,
- })
- })
+ });
+ });
}
if (requestToPayTimestamp) {
@@ -210,7 +224,7 @@ export function EndorsementChain({
type: "requestToPay",
timestamp: requestToPayTimestamp,
data: null,
- })
+ });
}
if (rejectedToPayTimestamp) {
@@ -218,7 +232,7 @@ export function EndorsementChain({
type: "rejectedToPay",
timestamp: rejectedToPayTimestamp,
data: null,
- })
+ });
}
if (paymentTimestamp) {
@@ -226,7 +240,7 @@ export function EndorsementChain({
type: "payment",
timestamp: paymentTimestamp,
data: null,
- })
+ });
}
if (acceptanceTimestamp) {
@@ -234,7 +248,7 @@ export function EndorsementChain({
type: "acceptance",
timestamp: acceptanceTimestamp,
data: null,
- })
+ });
}
if (rejectionTimestamp) {
@@ -242,7 +256,7 @@ export function EndorsementChain({
type: "rejection",
timestamp: rejectionTimestamp,
data: null,
- })
+ });
}
if (mintingTimestamp !== undefined) {
@@ -250,13 +264,13 @@ export function EndorsementChain({
type: "minting",
timestamp: mintingTimestamp,
data: null,
- })
+ });
} else if (mintingEnabled) {
events.push({
type: "minting",
timestamp: undefined,
data: null,
- })
+ });
}
const typePriority: Record = {
@@ -269,20 +283,23 @@ export function EndorsementChain({
requestToPay: 5,
rejectedToPay: 6,
payment: 7,
- }
+ };
events.sort((a, b) => {
if (!sortByTimestamp) {
- return typePriority[a.type] - typePriority[b.type]
+ return typePriority[a.type] - typePriority[b.type];
}
- return typePriority[b.type] - typePriority[a.type]
- })
+ return typePriority[b.type] - typePriority[a.type];
+ });
- const eventCount = events.length
+ const eventCount = events.length;
return (
- setIsExpanded(!isExpanded)}>
+ setIsExpanded(!isExpanded)}
+ >
{titleLabel}
@@ -290,19 +307,34 @@ export function EndorsementChain({
{intl.formatMessage(
{
id: "endorsement.history.eventCount",
- defaultMessage: "({count, plural, one {# event} other {# events}})",
+ defaultMessage:
+ "({count, plural, one {# event} other {# events}})",
},
{ count: eventCount },
)}
-
+
{isExpanded
- ? intl.formatMessage({ id: "endorsement.history.hide", defaultMessage: "Hide history" })
- : intl.formatMessage({ id: "endorsement.history.show", defaultMessage: "Show history" })}
+ ? intl.formatMessage({
+ id: "endorsement.history.hide",
+ defaultMessage: "Hide history",
+ })
+ : intl.formatMessage({
+ id: "endorsement.history.show",
+ defaultMessage: "Show history",
+ })}
- {isExpanded ? : }
+ {isExpanded ? (
+
+ ) : (
+
+ )}
@@ -314,16 +346,22 @@ export function EndorsementChain({
variant="outline"
size="sm"
onClick={(e) => {
- e.stopPropagation()
- setSortByTimestamp(!sortByTimestamp)
+ e.stopPropagation();
+ setSortByTimestamp(!sortByTimestamp);
}}
className="gap-2"
>
{sortByTimestamp
- ? intl.formatMessage({ id: "endorsement.history.descending", defaultMessage: "Descending" })
- : intl.formatMessage({ id: "endorsement.history.ascending", defaultMessage: "Ascending" })}
+ ? intl.formatMessage({
+ id: "endorsement.history.descending",
+ defaultMessage: "Descending",
+ })
+ : intl.formatMessage({
+ id: "endorsement.history.ascending",
+ defaultMessage: "Ascending",
+ })}
@@ -338,12 +376,12 @@ export function EndorsementChain({
) : (
{events.map((event, index) => {
- const config = EVENT_CONFIG[event.type]
- const Icon = config.icon
+ const config = EVENT_CONFIG[event.type];
+ const Icon = config.icon;
const displayLabel = intl.formatMessage({
id: config.labelId,
defaultMessage: config.defaultLabel,
- })
+ });
return (
@@ -351,14 +389,21 @@ export function EndorsementChain({
{/* Event Header */}
- {displayLabel}
+
+ {displayLabel}
+
{/* Timestamp */}
{event.timestamp !== undefined && (
- {new Date(event.timestamp * 1000).toLocaleString(undefined, { timeZone: "UTC" })}
+
+ {new Date(event.timestamp * 1000).toLocaleString(
+ undefined,
+ { timeZone: "UTC" },
+ )}
+
)}
@@ -385,7 +430,9 @@ export function EndorsementChain({
defaultMessage: "Signed by",
})}
-
+
{event.data.signed.signatory && (
@@ -410,7 +457,9 @@ export function EndorsementChain({
defaultMessage: "Endorsed to",
})}
-
+
@@ -439,14 +488,16 @@ export function EndorsementChain({
)}
- {index < events.length - 1 && }
+ {index < events.length - 1 && (
+
+ )}
- )
+ );
})}
)}
)}
- )
+ );
}
diff --git a/src/components/GrossToNetDiscountForm.tsx b/src/components/GrossToNetDiscountForm.tsx
index c7bb341..c3d66c5 100644
--- a/src/components/GrossToNetDiscountForm.tsx
+++ b/src/components/GrossToNetDiscountForm.tsx
@@ -1,51 +1,51 @@
-import React, { useEffect, useMemo, useRef, useState } from "react"
-import { useForm } from "react-hook-form"
-import Big from "big.js"
-import { parseFloatSafe, parseIntSafe } from "@/utils/numbers"
-import { daysBetween } from "@/utils/dates"
-import { Act360 } from "@/utils/discount-util"
-import { Button } from "./ui/button"
-import { DrawerFooter, DrawerClose } from "./ui/drawer"
-import { setItem, getItem } from "@/utils/local-storage" // , removeItem
-import { useIntl } from "react-intl"
+import React, { useEffect, useMemo, useRef, useState } from "react";
+import { useForm } from "react-hook-form";
+import Big from "big.js";
+import { parseFloatSafe, parseIntSafe } from "@/utils/numbers";
+import { daysBetween } from "@/utils/dates";
+import { Act360 } from "@/utils/discount-util";
+import { Button } from "./ui/button";
+import { DrawerFooter, DrawerClose } from "./ui/drawer";
+import { setItem, getItem } from "@/utils/local-storage"; // , removeItem
+import { useIntl } from "react-intl";
interface CurrencyAmount {
- value: Big
- currency: string
+ value: Big;
+ currency: string;
}
interface CommonDiscountFormProps {
- startDate?: Date
- endDate: Date
- onSubmit: (values: FormResult) => void
+ startDate?: Date;
+ endDate: Date;
+ onSubmit: (values: FormResult) => void;
}
type GrossToNetProps = CommonDiscountFormProps & {
- gross: CurrencyAmount
- submitButtonText?: string
- onConfirm?: () => void
- quoteId?: string
-}
+ gross: CurrencyAmount;
+ submitButtonText?: string;
+ onConfirm?: () => void;
+ quoteId?: string;
+};
interface FormResult {
- days: number
- discountRate: Big
- net: CurrencyAmount
- gross: CurrencyAmount
+ days: number;
+ discountRate: Big;
+ net: CurrencyAmount;
+ gross: CurrencyAmount;
}
interface FormValues {
- daysInput?: string
- discountRateInput?: string
- netInput?: string
+ daysInput?: string;
+ discountRateInput?: string;
+ netInput?: string;
}
-const INPUT_DAYS_MIN_VALUE = 1
-const INPUT_DAYS_MAX_VALUE = 360
-const LOCAL_STORAGE_KEY_PREFIX = "offer-form-"
-const NET_INPUT_DECIMALS = 2
+const INPUT_DAYS_MIN_VALUE = 1;
+const INPUT_DAYS_MAX_VALUE = 360;
+const LOCAL_STORAGE_KEY_PREFIX = "offer-form-";
+const NET_INPUT_DECIMALS = 2;
-type GrossToNetFormValues = FormValues
+type GrossToNetFormValues = FormValues;
const GrossToNetDiscountForm = ({
startDate,
@@ -55,123 +55,129 @@ const GrossToNetDiscountForm = ({
submitButtonText,
quoteId,
}: GrossToNetProps) => {
- const intl = useIntl()
- const [hasSetInitialDays, setHasSetInitialDays] = useState(false)
- const [lastEdited, setLastEdited] = useState<"rate" | "net" | null>(null)
- const isSat = gross.currency === "sat"
+ const intl = useIntl();
+ const [hasSetInitialDays, setHasSetInitialDays] = useState(false);
+ const [lastEdited, setLastEdited] = useState<"rate" | "net" | null>(null);
+ const isSat = gross.currency === "sat";
const daysLabel = intl.formatMessage({
id: "discountForm.days",
defaultMessage: "Days",
- })
+ });
const discountRateLabel = intl.formatMessage({
id: "discountForm.discountRate",
defaultMessage: "Fee rate",
- })
+ });
const netAmountLabel = intl.formatMessage({
id: "discountForm.netAmount",
defaultMessage: "Net amount",
- })
+ });
const annualDiscountLabel = intl.formatMessage({
id: "discountForm.annualDiscount",
defaultMessage: "Annual fee",
- })
+ });
const grossAmountLabel = intl.formatMessage({
id: "discountForm.grossAmount",
defaultMessage: "Gross amount",
- })
+ });
const parseDigitsToInt = (value: unknown) => {
- let str = ""
+ let str = "";
if (typeof value === "string" || typeof value === "number") {
- str = String(value)
+ str = String(value);
}
- return str.replace(/\D/g, "")
- }
+ return str.replace(/\D/g, "");
+ };
const validateNetAmount = (value?: string) => {
if (value == null || value === "") {
return intl.formatMessage({
id: "discountForm.validation.net.required",
defaultMessage: "Net amount is required",
- })
+ });
}
- const parsed = isSat ? parseIntSafe(value) : parseFloatSafe(value)
+ const parsed = isSat ? parseIntSafe(value) : parseFloatSafe(value);
if (parsed === undefined || Number.isNaN(parsed)) {
return intl.formatMessage({
id: "discountForm.validation.net.invalid",
defaultMessage: "Net amount must be a valid number",
- })
+ });
}
if (parsed < 1) {
return intl.formatMessage(
- { id: "discountForm.validation.net.min", defaultMessage: "Net amount must be at least {min}" },
+ {
+ id: "discountForm.validation.net.min",
+ defaultMessage: "Net amount must be at least {min}",
+ },
{ min: 1 },
- )
+ );
}
if (new Big(parsed).gt(gross.value)) {
return intl.formatMessage({
id: "discountForm.validation.net.maxGross",
defaultMessage: "Net amount cannot exceed gross amount",
- })
- }
-
- return true
- }
-
- const validateMinInteger = (min: number, label: string) => (value?: string) => {
- if (value == null || value === "") {
- return intl.formatMessage(
- {
- id: "discountForm.validation.required",
- defaultMessage: "{label} is required",
- },
- { label },
- )
- }
- if (!/^\d+$/.test(value)) {
- return intl.formatMessage(
- {
- id: "discountForm.validation.wholeNumber",
- defaultMessage: "{label} must be a whole number",
- },
- { label },
- )
- }
+ });
+ }
+
+ return true;
+ };
+
+ const validateMinInteger =
+ (min: number, label: string) => (value?: string) => {
+ if (value == null || value === "") {
+ return intl.formatMessage(
+ {
+ id: "discountForm.validation.required",
+ defaultMessage: "{label} is required",
+ },
+ { label },
+ );
+ }
+ if (!/^\d+$/.test(value)) {
+ return intl.formatMessage(
+ {
+ id: "discountForm.validation.wholeNumber",
+ defaultMessage: "{label} must be a whole number",
+ },
+ { label },
+ );
+ }
- const n = parseInt(value, 10)
- if (Number.isNaN(n)) {
- return intl.formatMessage(
- {
- id: "discountForm.validation.invalid",
- defaultMessage: "{label} is invalid",
- },
- { label },
- )
- }
- if (n < min) {
- return intl.formatMessage(
- {
- id: "discountForm.validation.min",
- defaultMessage: "{label} must be at least {min}",
- },
- { label, min },
- )
- }
- if (n > INPUT_DAYS_MAX_VALUE) {
- return intl.formatMessage(
- {
- id: "discountForm.validation.max",
- defaultMessage: "{label} must be at most {max}",
- },
- { label, max: INPUT_DAYS_MAX_VALUE },
- )
- }
+ const n = parseInt(value, 10);
+ if (Number.isNaN(n)) {
+ return intl.formatMessage(
+ {
+ id: "discountForm.validation.invalid",
+ defaultMessage: "{label} is invalid",
+ },
+ { label },
+ );
+ }
+ if (n < min) {
+ return intl.formatMessage(
+ {
+ id: "discountForm.validation.min",
+ defaultMessage: "{label} must be at least {min}",
+ },
+ { label, min },
+ );
+ }
+ if (n > INPUT_DAYS_MAX_VALUE) {
+ return intl.formatMessage(
+ {
+ id: "discountForm.validation.max",
+ defaultMessage: "{label} must be at most {max}",
+ },
+ { label, max: INPUT_DAYS_MAX_VALUE },
+ );
+ }
- return true
- }
+ return true;
+ };
- const localStorageKey = quoteId ? `${LOCAL_STORAGE_KEY_PREFIX}${quoteId}` : null
+ const localStorageKey = quoteId
+ ? `${LOCAL_STORAGE_KEY_PREFIX}${quoteId}`
+ : null;
const {
watch,
register,
@@ -180,24 +186,24 @@ const GrossToNetDiscountForm = ({
formState: { isValid, errors },
} = useForm({
mode: "all",
- })
+ });
const discountRateRegister = register("discountRateInput", {
required: true,
min: 0,
max: 99.9999,
- })
+ });
const netInputRegister = register("netInput", {
required: true,
setValueAs: isSat ? parseDigitsToInt : undefined,
validate: validateNetAmount,
- })
+ });
const blockDecimalInput = (e: React.KeyboardEvent) => {
if ([".", ",", "e", "E", "+", "-", "^"].includes(e.key)) {
- e.preventDefault()
- return
+ e.preventDefault();
+ return;
}
- }
+ };
const handleKeyDown = (e: React.KeyboardEvent) => {
const allowed = new Set([
"Backspace",
@@ -209,86 +215,91 @@ const GrossToNetDiscountForm = ({
"Tab",
"Home",
"End",
- ])
+ ]);
if (e.key === "Enter") {
- e.preventDefault()
- e.currentTarget.blur()
- return
+ e.preventDefault();
+ e.currentTarget.blur();
+ return;
}
if (allowed.has(e.key)) {
- return
+ return;
}
if (e.key >= "0" && e.key <= "9") {
- return
+ return;
}
- e.preventDefault()
- }
+ e.preventDefault();
+ };
const blockNonDigitInput = (e: React.SyntheticEvent) => {
- const native = e.nativeEvent as InputEvent
- const data = native.data
+ const native = e.nativeEvent as InputEvent;
+ const data = native.data;
if (native.type === "beforeinput") {
if (
- (native.inputType === "insertText" || native.inputType === "insertCompositionText") &&
+ (native.inputType === "insertText" ||
+ native.inputType === "insertCompositionText") &&
data &&
/\D/.test(data)
) {
- e.preventDefault()
+ e.preventDefault();
}
}
- }
-
- const handlePasteDigitsFor = (field: "daysInput" | "netInput") => (e: React.ClipboardEvent) => {
- e.preventDefault()
- const text = e.clipboardData.getData("text") || ""
- const digits = text.replace(/\D/g, "")
- const input = e.currentTarget
- const start = input.selectionStart ?? input.value.length
- const end = input.selectionEnd ?? input.value.length
- const before = input.value.slice(0, start)
- const after = input.value.slice(end)
- const next = (before + digits + after).replace(/\D/g, "")
- input.value = next
- setValue(field, next, { shouldValidate: true, shouldDirty: true })
- if (field === "netInput") {
- setLastEdited("net")
- }
- const caret = (before + digits).length
- try {
- input.setSelectionRange(caret, caret)
- } catch {
- // ignore
- }
- }
+ };
+
+ const handlePasteDigitsFor =
+ (field: "daysInput" | "netInput") =>
+ (e: React.ClipboardEvent) => {
+ e.preventDefault();
+ const text = e.clipboardData.getData("text") || "";
+ const digits = text.replace(/\D/g, "");
+ const input = e.currentTarget;
+ const start = input.selectionStart ?? input.value.length;
+ const end = input.selectionEnd ?? input.value.length;
+ const before = input.value.slice(0, start);
+ const after = input.value.slice(end);
+ const next = (before + digits + after).replace(/\D/g, "");
+ input.value = next;
+ setValue(field, next, { shouldValidate: true, shouldDirty: true });
+ if (field === "netInput") {
+ setLastEdited("net");
+ }
+ const caret = (before + digits).length;
+ try {
+ input.setSelectionRange(caret, caret);
+ } catch {
+ // ignore
+ }
+ };
const handleDrop = (e: React.DragEvent) => {
- e.preventDefault()
- }
+ e.preventDefault();
+ };
- const { daysInput, discountRateInput, netInput } = watch()
+ const { daysInput, discountRateInput, netInput } = watch();
const days = useMemo(() => {
- return parseIntSafe(daysInput)
- }, [daysInput])
+ return parseIntSafe(daysInput);
+ }, [daysInput]);
const discountRate = useMemo(() => {
- const parsed = parseFloatSafe(discountRateInput)
- return parsed === undefined ? undefined : new Big(parsed).div(new Big("100"))
- }, [discountRateInput])
+ const parsed = parseFloatSafe(discountRateInput);
+ return parsed === undefined
+ ? undefined
+ : new Big(parsed).div(new Big("100"));
+ }, [discountRateInput]);
- const [net, setNet] = useState()
- const skipNetToRateRef = useRef(false)
+ const [net, setNet] = useState();
+ const skipNetToRateRef = useRef(false);
const netInputValue = useMemo(() => {
if (netInput == null || netInput === "") {
- return undefined
+ return undefined;
}
if (isSat) {
- const parsed = parseIntSafe(netInput)
- return parsed === undefined ? undefined : new Big(parsed)
+ const parsed = parseIntSafe(netInput);
+ return parsed === undefined ? undefined : new Big(parsed);
}
- const parsed = parseFloatSafe(netInput)
- return parsed === undefined ? undefined : new Big(parsed)
- }, [netInput, isSat])
+ const parsed = parseFloatSafe(netInput);
+ return parsed === undefined ? undefined : new Big(parsed);
+ }, [netInput, isSat]);
const discount = useMemo(() => {
return net === undefined
@@ -296,55 +307,70 @@ const GrossToNetDiscountForm = ({
: {
value: net.value.sub(gross.value),
currency: net.currency,
- }
- }, [gross, net])
+ };
+ }, [gross, net]);
- const prevNetInputRef = useRef(undefined)
+ const prevNetInputRef = useRef(undefined);
const formatAmount = (value: Big, currency: string) => {
if (currency === "sat") {
- return value.round(0, Big.roundDown).toFixed(0)
+ return value.round(0, Big.roundDown).toFixed(0);
}
- return value.toFixed(NET_INPUT_DECIMALS)
- }
+ return value.toFixed(NET_INPUT_DECIMALS);
+ };
useEffect(() => {
if (hasSetInitialDays) {
- return
+ return;
}
if (localStorageKey) {
- const savedData = getItem<{ daysInput: string; discountRateInput: string; netInput?: string }>(localStorageKey)
+ const savedData = getItem<{
+ daysInput: string;
+ discountRateInput: string;
+ netInput?: string;
+ }>(localStorageKey);
if (savedData) {
if (savedData.daysInput) {
- setValue("daysInput", savedData.daysInput, { shouldValidate: true })
+ setValue("daysInput", savedData.daysInput, { shouldValidate: true });
}
if (savedData.discountRateInput) {
- setValue("discountRateInput", savedData.discountRateInput, { shouldValidate: true })
+ setValue("discountRateInput", savedData.discountRateInput, {
+ shouldValidate: true,
+ });
}
if (savedData.netInput) {
- setValue("netInput", savedData.netInput, { shouldValidate: true })
- setLastEdited("net")
+ setValue("netInput", savedData.netInput, { shouldValidate: true });
+ setLastEdited("net");
}
- setHasSetInitialDays(true)
- return
+ setHasSetInitialDays(true);
+ return;
}
}
if (startDate !== undefined) {
- setValue("daysInput", String(Math.min(Math.max(1, daysBetween(startDate, endDate)), INPUT_DAYS_MAX_VALUE)), {
- shouldValidate: true,
- shouldDirty: true,
- shouldTouch: true,
- })
+ setValue(
+ "daysInput",
+ String(
+ Math.min(
+ Math.max(1, daysBetween(startDate, endDate)),
+ INPUT_DAYS_MAX_VALUE,
+ ),
+ ),
+ {
+ shouldValidate: true,
+ shouldDirty: true,
+ shouldTouch: true,
+ },
+ );
}
- setHasSetInitialDays(true)
- }, [startDate, endDate, setValue, localStorageKey, hasSetInitialDays])
+ setHasSetInitialDays(true);
+ }, [startDate, endDate, setValue, localStorageKey, hasSetInitialDays]);
useEffect(() => {
if (!localStorageKey || !hasSetInitialDays) {
- return
+ return;
}
if (daysInput || discountRateInput || netInput) {
@@ -352,86 +378,94 @@ const GrossToNetDiscountForm = ({
daysInput: daysInput ?? "",
discountRateInput: discountRateInput ?? "",
netInput: netInput ?? "",
- })
+ });
}
- }, [localStorageKey, daysInput, discountRateInput, netInput, hasSetInitialDays])
+ }, [
+ localStorageKey,
+ daysInput,
+ discountRateInput,
+ netInput,
+ hasSetInitialDays,
+ ]);
useEffect(() => {
if (netInput === prevNetInputRef.current) {
- return
+ return;
}
- prevNetInputRef.current = netInput
+ prevNetInputRef.current = netInput;
if (skipNetToRateRef.current) {
- return
+ return;
}
if (netInput !== undefined) {
- setLastEdited("net")
+ setLastEdited("net");
}
- }, [netInput])
+ }, [netInput]);
useEffect(() => {
if (discountRate === undefined || days === undefined) {
- setNet(undefined)
- return
+ setNet(undefined);
+ return;
}
if (lastEdited === "net") {
- return
+ return;
}
- const netValue = Act360.grossToNet(gross.value, discountRate, days)
- const roundedNetValue = isSat ? netValue.round(0, Big.roundDown) : netValue
+ const netValue = Act360.grossToNet(gross.value, discountRate, days);
+ const roundedNetValue = isSat ? netValue.round(0, Big.roundDown) : netValue;
setNet({
value: roundedNetValue,
currency: gross.currency,
- })
- const formattedNet = formatAmount(roundedNetValue, gross.currency)
+ });
+ const formattedNet = formatAmount(roundedNetValue, gross.currency);
if (formattedNet !== netInput) {
- skipNetToRateRef.current = true
- setValue("netInput", formattedNet, { shouldValidate: true })
+ skipNetToRateRef.current = true;
+ setValue("netInput", formattedNet, { shouldValidate: true });
}
- }, [gross, days, discountRate, lastEdited, setValue, isSat, netInput])
+ }, [gross, days, discountRate, lastEdited, setValue, isSat, netInput]);
useEffect(() => {
if (skipNetToRateRef.current) {
- skipNetToRateRef.current = false
- return
+ skipNetToRateRef.current = false;
+ return;
}
if (days === undefined || netInputValue === undefined) {
- setNet(undefined)
- return
+ setNet(undefined);
+ return;
}
if (netInputValue.lt(0)) {
- setNet(undefined)
- return
+ setNet(undefined);
+ return;
}
if (netInputValue.gt(gross.value)) {
- setNet(undefined)
- return
+ setNet(undefined);
+ return;
}
setNet({
value: netInputValue,
currency: gross.currency,
- })
+ });
- const grossValue = gross.value
+ const grossValue = gross.value;
if (grossValue.eq(0)) {
- return
+ return;
}
- const ratio = new Big(1).minus(netInputValue.div(grossValue))
- const rate = ratio.times(360).div(days)
+ const ratio = new Big(1).minus(netInputValue.div(grossValue));
+ const rate = ratio.times(360).div(days);
if (rate.lt(0) || rate.gt(1)) {
- return
+ return;
}
- const ratePercent = rate.times(100)
- setValue("discountRateInput", ratePercent.toFixed(4), { shouldValidate: true })
- }, [days, netInputValue, gross, setValue, netInput])
+ const ratePercent = rate.times(100);
+ setValue("discountRateInput", ratePercent.toFixed(4), {
+ shouldValidate: true,
+ });
+ }, [days, netInputValue, gross, setValue, netInput]);
const handleFormSubmit = () => {
if (net === undefined || discountRate === undefined || days === undefined) {
- return
+ return;
}
onSubmit({
@@ -439,38 +473,42 @@ const GrossToNetDiscountForm = ({
discountRate,
net,
gross,
- })
- }
-
- const handleIntegerInputFor = (field: "daysInput" | "netInput") => (e: React.SyntheticEvent) => {
- const input = e.currentTarget
- const cleaned = input.value.replace(/[^\d]/g, "")
- if (input.value !== cleaned) {
- const caret = input.selectionStart ?? cleaned.length
- input.value = cleaned
- setValue(field, cleaned, { shouldValidate: true, shouldDirty: true })
- const pos = Math.min(caret, cleaned.length)
- try {
- input.setSelectionRange(pos, pos)
- } catch {
- // ignore unsupported setSelectionRange
+ });
+ };
+
+ const handleIntegerInputFor =
+ (field: "daysInput" | "netInput") =>
+ (e: React.SyntheticEvent) => {
+ const input = e.currentTarget;
+ const cleaned = input.value.replace(/[^\d]/g, "");
+ if (input.value !== cleaned) {
+ const caret = input.selectionStart ?? cleaned.length;
+ input.value = cleaned;
+ setValue(field, cleaned, { shouldValidate: true, shouldDirty: true });
+ const pos = Math.min(caret, cleaned.length);
+ try {
+ input.setSelectionRange(pos, pos);
+ } catch {
+ // ignore unsupported setSelectionRange
+ }
}
- }
- if (field === "netInput") {
- setLastEdited("net")
- }
- if (input.value === cleaned) {
- setValue(field, cleaned, { shouldValidate: true, shouldDirty: true })
- }
- }
+ if (field === "netInput") {
+ setLastEdited("net");
+ }
+ if (input.value === cleaned) {
+ setValue(field, cleaned, { shouldValidate: true, shouldDirty: true });
+ }
+ };
- const handleConfirmClick: React.MouseEventHandler = (e) => {
- e.preventDefault()
- e.stopPropagation()
+ const handleConfirmClick: React.MouseEventHandler = (
+ e,
+ ) => {
+ e.preventDefault();
+ e.stopPropagation();
void handleSubmit(handleFormSubmit)().catch((err) => {
- console.error("Submit failed:", err)
- })
- }
+ console.error("Submit failed:", err);
+ });
+ };
return (
<>
@@ -478,14 +516,17 @@ const GrossToNetDiscountForm = ({
className="flex flex-col gap-6 min-w-[8rem] px-4"
onSubmit={(e) => {
handleSubmit(handleFormSubmit)(e).catch((err) => {
- console.error("Submit failed:", err)
- })
+ console.error("Submit failed:", err);
+ });
}}
>
-
+
{daysLabel}
{
- blockDecimalInput(e)
- handleKeyDown(e)
+ blockDecimalInput(e);
+ handleKeyDown(e);
}}
onInput={handleIntegerInputFor("daysInput")}
onBeforeInput={blockNonDigitInput}
@@ -517,7 +558,8 @@ const GrossToNetDiscountForm = ({
{intl.formatMessage(
{
id: "discountForm.validation.range",
- defaultMessage: "Please enter a valid value between {min} and {max}.",
+ defaultMessage:
+ "Please enter a valid value between {min} and {max}.",
},
{ min: INPUT_DAYS_MIN_VALUE, max: INPUT_DAYS_MAX_VALUE },
)}
@@ -527,7 +569,10 @@ const GrossToNetDiscountForm = ({
-
+
{discountRateLabel}
@@ -540,16 +585,18 @@ const GrossToNetDiscountForm = ({
{...discountRateRegister}
onKeyDown={(e) => {
if (e.key === "Enter") {
- e.preventDefault()
- e.currentTarget.blur()
+ e.preventDefault();
+ e.currentTarget.blur();
}
}}
onChange={(e) => {
- void discountRateRegister.onChange(e)
- setLastEdited("rate")
+ void discountRateRegister.onChange(e);
+ setLastEdited("rate");
}}
/>
- %
+
+ %
+
{errors.discountRateInput && (
@@ -557,7 +604,8 @@ const GrossToNetDiscountForm = ({
{intl.formatMessage(
{
id: "discountForm.validation.rateRange",
- defaultMessage: "Please enter a valid value between {min}% and {max}%.",
+ defaultMessage:
+ "Please enter a valid value between {min}% and {max}%.",
},
{ min: 0, max: 99.9999 },
)}
@@ -567,7 +615,10 @@ const GrossToNetDiscountForm = ({
-
+
{netAmountLabel}
@@ -580,51 +631,78 @@ const GrossToNetDiscountForm = ({
{...netInputRegister}
onKeyDown={(e) => {
if (isSat) {
- blockDecimalInput(e)
- handleKeyDown(e)
+ blockDecimalInput(e);
+ handleKeyDown(e);
} else if (e.key === "Enter") {
- e.preventDefault()
- e.currentTarget.blur()
+ e.preventDefault();
+ e.currentTarget.blur();
}
}}
- onInput={isSat ? handleIntegerInputFor("netInput") : undefined}
+ onInput={
+ isSat ? handleIntegerInputFor("netInput") : undefined
+ }
onBeforeInput={isSat ? blockNonDigitInput : undefined}
onPaste={isSat ? handlePasteDigitsFor("netInput") : undefined}
onDrop={handleDrop}
onChange={(e) => {
- void netInputRegister.onChange(e)
- setLastEdited("net")
+ void netInputRegister.onChange(e);
+ setLastEdited("net");
}}
/>
- {gross.currency}
+
+ {gross.currency}
+
- {errors.netInput &&
{errors.netInput.message}
}
+ {errors.netInput && (
+
+ {errors.netInput.message}
+
+ )}
-
{annualDiscountLabel}
+
+ {annualDiscountLabel}
+
- {discount === undefined ? (isSat ? "0" : "0.00") : formatAmount(discount.value.abs(), gross.currency)}
+ {discount === undefined
+ ? isSat
+ ? "0"
+ : "0.00"
+ : formatAmount(discount.value.abs(), gross.currency)}
+
+
+ {discount?.currency ?? gross.currency}
- {discount?.currency ?? gross.currency}
-
{grossAmountLabel}
+
+ {grossAmountLabel}
+
- +{formatAmount(gross.value, gross.currency)}
- {gross.currency}
+
+ +{formatAmount(gross.value, gross.currency)}
+
+
+ {gross.currency}
+
{submitButtonText && (
-
+
{submitButtonText}
)}
@@ -636,7 +714,12 @@ const GrossToNetDiscountForm = ({
size="sm"
type="button"
onClick={handleConfirmClick}
- disabled={!isValid || net === undefined || discountRate === undefined || days === undefined}
+ disabled={
+ !isValid ||
+ net === undefined ||
+ discountRate === undefined ||
+ days === undefined
+ }
>
{intl.formatMessage({
id: "Confirm",
@@ -644,7 +727,11 @@ const GrossToNetDiscountForm = ({
})}
-
+
{intl.formatMessage({
id: "Cancel",
defaultMessage: "Cancel",
@@ -653,7 +740,7 @@ const GrossToNetDiscountForm = ({
>
- )
-}
+ );
+};
-export { GrossToNetDiscountForm }
+export { GrossToNetDiscountForm };
diff --git a/src/components/Headings.test.tsx b/src/components/Headings.test.tsx
index 63ed338..1d102aa 100644
--- a/src/components/Headings.test.tsx
+++ b/src/components/Headings.test.tsx
@@ -1,33 +1,33 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it } from "vitest"
-import { H1, H2, H3 } from "./Headings"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it } from "vitest";
+import { H1, H2, H3 } from "./Headings";
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
beforeEach(() => {
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
-})
+});
describe("Headings", () => {
it("renders H1, H2 and H3 tags", () => {
@@ -37,10 +37,10 @@ describe("Headings", () => {
Title Two
Title Three
,
- )
+ );
- expect(page.querySelector("h1")?.textContent).toBe("Title One")
- expect(page.querySelector("h2")?.textContent).toBe("Title Two")
- expect(page.querySelector("h3")?.textContent).toBe("Title Three")
- })
-})
+ expect(page.querySelector("h1")?.textContent).toBe("Title One");
+ expect(page.querySelector("h2")?.textContent).toBe("Title Two");
+ expect(page.querySelector("h3")?.textContent).toBe("Title Three");
+ });
+});
diff --git a/src/components/Headings.tsx b/src/components/Headings.tsx
index 752edc8..d364eeb 100644
--- a/src/components/Headings.tsx
+++ b/src/components/Headings.tsx
@@ -1,13 +1,25 @@
-import { PropsWithChildren } from "react"
+import { PropsWithChildren } from "react";
export function H1({ children }: PropsWithChildren
) {
- return {children}
+ return (
+
+ {children}
+
+ );
}
export function H2({ children }: PropsWithChildren) {
- return {children}
+ return (
+
+ {children}
+
+ );
}
export function H3({ children }: PropsWithChildren) {
- return {children}
+ return (
+
+ {children}
+
+ );
}
diff --git a/src/components/InputContainer.tsx b/src/components/InputContainer.tsx
index 4134289..5cf6e7b 100644
--- a/src/components/InputContainer.tsx
+++ b/src/components/InputContainer.tsx
@@ -1,10 +1,10 @@
-import React, { LabelHTMLAttributes, PropsWithChildren } from "react"
-import { cn } from "@/lib/utils"
+import React, { LabelHTMLAttributes, PropsWithChildren } from "react";
+import { cn } from "@/lib/utils";
type InputContainerProps = PropsWithChildren<{
- htmlFor: LabelHTMLAttributes["htmlFor"]
- label: React.ReactNode
-}>
+ htmlFor: LabelHTMLAttributes["htmlFor"];
+ label: React.ReactNode;
+}>;
const InputContainer = ({ children, htmlFor, label }: InputContainerProps) => {
return (
@@ -18,7 +18,7 @@ const InputContainer = ({ children, htmlFor, label }: InputContainerProps) => {
{label}
{children}
- )
-}
+ );
+};
-export { InputContainer }
+export { InputContainer };
diff --git a/src/components/PageTitle.tsx b/src/components/PageTitle.tsx
index 812c32f..6776af2 100644
--- a/src/components/PageTitle.tsx
+++ b/src/components/PageTitle.tsx
@@ -1,6 +1,6 @@
-import { PropsWithChildren } from "react"
-import { H1 } from "./Headings"
+import { PropsWithChildren } from "react";
+import { H1 } from "./Headings";
export function PageTitle({ children }: PropsWithChildren
) {
- return {children}
+ return {children} ;
}
diff --git a/src/components/ParticipantsOverview.test.tsx b/src/components/ParticipantsOverview.test.tsx
index b91586a..5d1265f 100644
--- a/src/components/ParticipantsOverview.test.tsx
+++ b/src/components/ParticipantsOverview.test.tsx
@@ -1,62 +1,85 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { IntlProvider } from "react-intl"
-import { ParticipantDetail, ParticipantsOverviewCard } from "./ParticipantsOverview"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { IntlProvider } from "react-intl";
+import {
+ ParticipantDetail,
+ ParticipantsOverviewCard,
+} from "./ParticipantsOverview";
vi.mock("@/components/ui/avatar", () => ({
- Avatar: ({ children, className }: { children: React.ReactNode; className?: string }) => (
- {children}
- ),
- AvatarFallback: ({ children, className }: { children: React.ReactNode; className?: string }) => (
- {children}
- ),
-}))
+ Avatar: ({
+ children,
+ className,
+ }: {
+ children: React.ReactNode;
+ className?: string;
+ }) => {children}
,
+ AvatarFallback: ({
+ children,
+ className,
+ }: {
+ children: React.ReactNode;
+ className?: string;
+ }) => {children}
,
+}));
vi.mock("@/components/ui/tooltip", () => ({
- TooltipProvider: ({ children }: { children: React.ReactNode }) => {children}
,
- Tooltip: ({ children }: { children: React.ReactNode }) => {children}
,
- TooltipTrigger: ({ children }: { children: React.ReactNode }) => {children}
,
- TooltipContent: ({ children }: { children: React.ReactNode }) => {children}
,
-}))
+ TooltipProvider: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ Tooltip: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ TooltipTrigger: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ TooltipContent: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}));
vi.mock("@/components/TruncatedTextPopover", () => ({
- TruncatedTextPopover: ({ text }: { text: React.ReactNode }) => {text} ,
-}))
+ TruncatedTextPopover: ({ text }: { text: React.ReactNode }) => (
+ {text}
+ ),
+}));
vi.mock("@/components/icons/UserAnonymous", () => ({
- UserAnonymousIcon: ({ className }: { className?: string }) => AnonIcon ,
-}))
+ UserAnonymousIcon: ({ className }: { className?: string }) => (
+ AnonIcon
+ ),
+}));
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
function renderWithIntl(element: ReactElement): HTMLDivElement {
- return renderIntoDom({element} )
+ return renderIntoDom({element} );
}
beforeEach(() => {
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
-})
+});
describe("ParticipantsOverview", () => {
it("renders participants overview with ident and anon entries", () => {
@@ -103,16 +126,16 @@ describe("ParticipantsOverview", () => {
},
]}
/>,
- )
+ );
- expect(page.textContent).toContain("Drawee")
- expect(page.textContent).toContain("Drawer")
- expect(page.textContent).toContain("Payee")
- expect(page.textContent).toContain("Holder")
- expect(page.textContent).toContain("payee@example.com")
- expect(page.textContent).toContain("holder-anon-node")
- expect(page.textContent).toContain("Bearer")
- })
+ expect(page.textContent).toContain("Drawee");
+ expect(page.textContent).toContain("Drawer");
+ expect(page.textContent).toContain("Payee");
+ expect(page.textContent).toContain("Holder");
+ expect(page.textContent).toContain("payee@example.com");
+ expect(page.textContent).toContain("holder-anon-node");
+ expect(page.textContent).toContain("Bearer");
+ });
it("renders anonymous participant detail", () => {
const page = renderWithIntl(
@@ -124,12 +147,12 @@ describe("ParticipantsOverview", () => {
},
}}
/>,
- )
+ );
- expect(page.textContent).toContain("AnonIcon")
- expect(page.textContent).toContain("Bearer")
- expect(page.textContent).toContain("anon-node-123")
- })
+ expect(page.textContent).toContain("AnonIcon");
+ expect(page.textContent).toContain("Bearer");
+ expect(page.textContent).toContain("anon-node-123");
+ });
it("renders identified participant detail with contact data", () => {
const page = renderWithIntl(
@@ -145,16 +168,18 @@ describe("ParticipantsOverview", () => {
nostr_relays: [],
}}
/>,
- )
+ );
- expect(page.textContent).toContain("Ident Name")
- expect(page.textContent).toContain("Paris, FR")
- expect(page.textContent).toContain("ident-node-123")
- expect(page.querySelector('a[href="mailto:ident@example.com"]')).not.toBeNull()
- })
+ expect(page.textContent).toContain("Ident Name");
+ expect(page.textContent).toContain("Paris, FR");
+ expect(page.textContent).toContain("ident-node-123");
+ expect(
+ page.querySelector('a[href="mailto:ident@example.com"]'),
+ ).not.toBeNull();
+ });
it("returns nothing when participant detail input is missing", () => {
- const page = renderWithIntl( )
- expect(page.textContent).toBe("")
- })
-})
+ const page = renderWithIntl( );
+ expect(page.textContent).toBe("");
+ });
+});
diff --git a/src/components/ParticipantsOverview.tsx b/src/components/ParticipantsOverview.tsx
index 9db06a9..9bb4d40 100644
--- a/src/components/ParticipantsOverview.tsx
+++ b/src/components/ParticipantsOverview.tsx
@@ -1,31 +1,49 @@
-import { Avatar, AvatarFallback } from "@/components/ui/avatar"
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
-import { getDeterministicColor, getInitials } from "@/utils/strings"
-import type { BillIdentParticipant, BillParticipant, BillAnonParticipant } from "@/generated/client/types.gen"
-import { cn } from "@/lib/utils"
-import { TruncatedTextPopover } from "@/components/TruncatedTextPopover"
-import { UserAnonymousIcon } from "@/components/icons/UserAnonymous"
-import type React from "react"
-import { useIntl } from "react-intl"
+import { Avatar, AvatarFallback } from "@/components/ui/avatar";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+import { getDeterministicColor, getInitials } from "@/utils/strings";
+import type {
+ BillIdentParticipant,
+ BillParticipant,
+ BillAnonParticipant,
+} from "@/generated/client/types.gen";
+import { cn } from "@/lib/utils";
+import { TruncatedTextPopover } from "@/components/TruncatedTextPopover";
+import { UserAnonymousIcon } from "@/components/icons/UserAnonymous";
+import type React from "react";
+import { useIntl } from "react-intl";
-type IdentityPublicData = BillIdentParticipant
-type AnonPublicData = BillAnonParticipant
-type IdentOrAnonParticipant = BillParticipant
+type IdentityPublicData = BillIdentParticipant;
+type AnonPublicData = BillAnonParticipant;
+type IdentOrAnonParticipant = BillParticipant;
-function AnonPublicAvatar({ value, tooltip }: { value?: AnonPublicData; tooltip?: React.ReactNode }) {
- const initials = "?"
- const backgroundColor = getDeterministicColor(value?.node_id)
+function AnonPublicAvatar({
+ value,
+ tooltip,
+}: {
+ value?: AnonPublicData;
+ tooltip?: React.ReactNode;
+}) {
+ const initials = "?";
+ const backgroundColor = getDeterministicColor(value?.node_id);
const avatar = (
-
+
{initials}
- )
+ );
if (!tooltip) {
- return avatar
+ return avatar;
}
return (
@@ -35,28 +53,37 @@ function AnonPublicAvatar({ value, tooltip }: { value?: AnonPublicData; tooltip?
{tooltip}
- )
+ );
}
-function IdentityPublicAvatar({ value, tooltip }: { value?: IdentityPublicData; tooltip?: React.ReactNode }) {
- const initials = getInitials(value?.name)
- const backgroundColor = getDeterministicColor(value?.name ?? value?.node_id)
- const isCompany = (value?.type as unknown as number) === 1
- const shapeClass = isCompany ? "rounded-lg" : "rounded-full"
+function IdentityPublicAvatar({
+ value,
+ tooltip,
+}: {
+ value?: IdentityPublicData;
+ tooltip?: React.ReactNode;
+}) {
+ const initials = getInitials(value?.name);
+ const backgroundColor = getDeterministicColor(value?.name ?? value?.node_id);
+ const isCompany = (value?.type as unknown as number) === 1;
+ const shapeClass = isCompany ? "rounded-lg" : "rounded-full";
const avatar = (
{initials}
- )
+ );
if (!tooltip) {
- return avatar
+ return avatar;
}
return (
@@ -66,23 +93,39 @@ function IdentityPublicAvatar({ value, tooltip }: { value?: IdentityPublicData;
{tooltip}
- )
+ );
}
-function IdentOrAnonAvatar({ value, tooltip }: { value?: IdentOrAnonParticipant; tooltip?: React.ReactNode }) {
+function IdentOrAnonAvatar({
+ value,
+ tooltip,
+}: {
+ value?: IdentOrAnonParticipant;
+ tooltip?: React.ReactNode;
+}) {
if (!value) {
- return null
+ return null;
}
if ("Ident" in value) {
- const identData = value.Ident
- return
+ const identData = value.Ident;
+ return (
+
+ );
} else if ("Anon" in value) {
- const anonData = value.Anon
- return
+ const anonData = value.Anon;
+ return (
+
+ );
}
- return null
+ return null;
}
export function ParticipantsOverviewCard({
@@ -92,36 +135,39 @@ export function ParticipantsOverviewCard({
holder,
className,
}: {
- drawee?: IdentityPublicData
- drawer?: IdentityPublicData
- holder?: IdentOrAnonParticipant[]
- payee?: IdentOrAnonParticipant
- className?: string
+ drawee?: IdentityPublicData;
+ drawer?: IdentityPublicData;
+ holder?: IdentOrAnonParticipant[];
+ payee?: IdentOrAnonParticipant;
+ className?: string;
}) {
- const intl = useIntl()
+ const intl = useIntl();
const getRoleLabel = (role: "drawee" | "drawer" | "payee" | "holder") => {
const defaults = {
drawee: "Drawee",
drawer: "Drawer",
payee: "Payee",
holder: "Holder",
- }
+ };
return intl.formatMessage(
{
id: `participants.role.${role}`,
defaultMessage: defaults[role],
},
{},
- )
- }
+ );
+ };
const bearerLabel = intl.formatMessage({
id: "participants.role.bearer",
defaultMessage: "Bearer",
- })
+ });
- const getIdentTooltip = (data: IdentityPublicData | undefined, role: string) => {
+ const getIdentTooltip = (
+ data: IdentityPublicData | undefined,
+ role: string,
+ ) => {
if (!data) {
- return role
+ return role;
}
return (
@@ -135,21 +181,26 @@ export function ParticipantsOverviewCard({
)}
{data.node_id}
- )
- }
+ );
+ };
- const getIdentOrAnonTooltip = (data: IdentOrAnonParticipant | undefined, role: string) => {
+ const getIdentOrAnonTooltip = (
+ data: IdentOrAnonParticipant | undefined,
+ role: string,
+ ) => {
if (!data) {
- return role
+ return role;
}
if ("Ident" in data) {
- const identData = data.Ident
+ const identData = data.Ident;
return (
{role}
{identData.name}
- {identData.email &&
{identData.email}
}
+ {identData.email && (
+
{identData.email}
+ )}
{identData.city && identData.country && (
{identData.city}, {identData.country}
@@ -157,65 +208,81 @@ export function ParticipantsOverviewCard({
)}
{identData.node_id}
- )
+ );
} else if ("Anon" in data) {
- const anonData = data.Anon
+ const anonData = data.Anon;
return (
{role}
{bearerLabel}
- {anonData?.node_id &&
{anonData.node_id}
}
+ {anonData?.node_id && (
+
+ {anonData.node_id}
+
+ )}
- )
+ );
}
- return role
- }
+ return role;
+ };
return (
{drawee && (
-
+
)}
{drawer && (
-
+
)}
{payee && (
-
+
)}
{holder && holder.length > 0 && (
)}
- )
+ );
}
export function ParticipantDetail({
participant,
}: {
- participant: BillIdentParticipant | BillParticipant | undefined
+ participant: BillIdentParticipant | BillParticipant | undefined;
}) {
- const intl = useIntl()
+ const intl = useIntl();
if (!participant) {
- return null
+ return null;
}
- let data: BillIdentParticipant | undefined
- let avatar: React.ReactNode
+ let data: BillIdentParticipant | undefined;
+ let avatar: React.ReactNode;
if ("Anon" in participant) {
- const anonData = participant.Anon
+ const anonData = participant.Anon;
return (
@@ -239,32 +306,50 @@ export function ParticipantDetail({
)}
- )
+ );
} else if ("Ident" in participant) {
- data = participant.Ident
- avatar =
+ data = participant.Ident;
+ avatar = ;
} else {
- data = participant
- avatar =
+ data = participant;
+ avatar = ;
}
if (!data) {
- return null
+ return null;
}
return (
{avatar}
-
+
{data.email && (
-
-
+
+
)}
{"city" in data && data.city && data.country && (
-
+
)}
@@ -278,5 +363,5 @@ export function ParticipantDetail({
- )
+ );
}
diff --git a/src/components/QRCodeWithErrorBoundary.test.tsx b/src/components/QRCodeWithErrorBoundary.test.tsx
index b3dc416..dacd940 100644
--- a/src/components/QRCodeWithErrorBoundary.test.tsx
+++ b/src/components/QRCodeWithErrorBoundary.test.tsx
@@ -1,106 +1,133 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { IntlProvider } from "react-intl"
-import { FeeTokenQRCodeModal, QRCode, QRCodeModal } from "./QRCodeWithErrorBoundary"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { IntlProvider } from "react-intl";
+import {
+ FeeTokenQRCodeModal,
+ QRCode,
+ QRCodeModal,
+} from "./QRCodeWithErrorBoundary";
-const mockCanGenerateQRCode = vi.fn<(value: string) => boolean>()
-const mockCanGenerateQRCodeAsync = vi.fn<(value: string) => Promise>()
-const mockQrSvg = vi.fn<(props: { value: string }) => React.ReactNode>()
+const mockCanGenerateQRCode = vi.fn<(value: string) => boolean>();
+const mockCanGenerateQRCodeAsync = vi.fn<(value: string) => Promise>();
+const mockQrSvg = vi.fn<(props: { value: string }) => React.ReactNode>();
vi.mock("qrcode.react", () => ({
QRCodeSVG: ({ value }: { value: string }) => mockQrSvg({ value }),
-}))
+}));
vi.mock("@/utils/qrCodeUtils.ts", () => ({
QR_CODE_MAX_LENGTH: 4296,
canGenerateQRCode: (value: string) => mockCanGenerateQRCode(value),
canGenerateQRCodeAsync: (value: string) => mockCanGenerateQRCodeAsync(value),
-}))
+}));
vi.mock("@/components/ui/drawer", () => ({
- Drawer: ({ children }: { children: React.ReactNode }) => {children}
,
- DrawerTrigger: ({ children }: { children: React.ReactNode }) => {children}
,
- DrawerContent: ({ children }: { children: React.ReactNode }) => {children}
,
- DrawerHeader: ({ children }: { children: React.ReactNode }) => {children}
,
- DrawerTitle: ({ children }: { children: React.ReactNode }) => {children}
,
-}))
+ Drawer: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ DrawerTrigger: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ DrawerContent: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ DrawerHeader: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ DrawerTitle: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}));
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
function renderWithIntl(element: ReactElement): HTMLDivElement {
- return renderIntoDom({element} )
+ return renderIntoDom({element} );
}
beforeEach(() => {
- vi.clearAllMocks()
- mockCanGenerateQRCode.mockReturnValue(true)
- mockCanGenerateQRCodeAsync.mockResolvedValue(true)
- mockQrSvg.mockImplementation(({ value }: { value: string }) => )
+ vi.clearAllMocks();
+ mockCanGenerateQRCode.mockReturnValue(true);
+ mockCanGenerateQRCodeAsync.mockResolvedValue(true);
+ mockQrSvg.mockImplementation(({ value }: { value: string }) => (
+
+ ));
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
-})
+});
describe("QRCodeWithErrorBoundary", () => {
it("shows too-large warning when QR cannot be generated", () => {
- mockCanGenerateQRCode.mockReturnValue(false)
- const page = renderWithIntl( )
- expect(page.textContent).toContain("Data too large for QR code")
- })
+ mockCanGenerateQRCode.mockReturnValue(false);
+ const page = renderWithIntl( );
+ expect(page.textContent).toContain("Data too large for QR code");
+ });
it("renders QRCode with label when generation is allowed", () => {
- const page = renderWithIntl( )
- expect(page.querySelector('svg[data-value="hello-qr"]')).not.toBeNull()
- expect(page.textContent).toContain("Scan me")
- })
+ const page = renderWithIntl(
+ ,
+ );
+ expect(page.querySelector('svg[data-value="hello-qr"]')).not.toBeNull();
+ expect(page.textContent).toContain("Scan me");
+ });
it("renders fallback when QR renderer throws", () => {
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined)
+ const errorSpy = vi
+ .spyOn(console, "error")
+ .mockImplementation(() => undefined);
mockQrSvg.mockImplementation(() => {
- throw new Error("render error")
- })
+ throw new Error("render error");
+ });
- const page = renderWithIntl( )
- expect(page.textContent).toContain("QR code cannot be generated")
- errorSpy.mockRestore()
- })
+ const page = renderWithIntl( );
+ expect(page.textContent).toContain("QR code cannot be generated");
+ errorSpy.mockRestore();
+ });
it("returns null for modal when async capability check fails", async () => {
- mockCanGenerateQRCodeAsync.mockResolvedValue(false)
- const page = renderWithIntl( )
+ mockCanGenerateQRCodeAsync.mockResolvedValue(false);
+ const page = renderWithIntl( );
await act(async () => {
- await Promise.resolve()
- })
- expect(page.textContent).toBe("")
- })
+ await Promise.resolve();
+ });
+ expect(page.textContent).toBe("");
+ });
it("renders modal trigger and fee-token wrapper labels", async () => {
- const page = renderWithIntl( )
+ const page = renderWithIntl(
+ ,
+ );
await act(async () => {
- await Promise.resolve()
- })
- const triggerButton = page.querySelector('button[aria-label="Show QR code for fee token"]')
- expect(triggerButton).not.toBeNull()
- expect(page.textContent).toContain("Fee Token QR Code")
- })
-})
+ await Promise.resolve();
+ });
+ const triggerButton = page.querySelector(
+ 'button[aria-label="Show QR code for fee token"]',
+ );
+ expect(triggerButton).not.toBeNull();
+ expect(page.textContent).toContain("Fee Token QR Code");
+ });
+});
diff --git a/src/components/QRCodeWithErrorBoundary.tsx b/src/components/QRCodeWithErrorBoundary.tsx
index d500a00..c2265d2 100644
--- a/src/components/QRCodeWithErrorBoundary.tsx
+++ b/src/components/QRCodeWithErrorBoundary.tsx
@@ -1,10 +1,20 @@
-import { Component, ReactNode, ErrorInfo, useEffect, useState } from "react"
-import { QRCodeSVG } from "qrcode.react"
-import { AlertTriangle, QrCode } from "lucide-react"
-import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"
-import { Button } from "@/components/ui/button"
-import { canGenerateQRCode, canGenerateQRCodeAsync, QR_CODE_MAX_LENGTH } from "@/utils/qrCodeUtils.ts"
-import { useIntl } from "react-intl"
+import { Component, ReactNode, ErrorInfo, useEffect, useState } from "react";
+import { QRCodeSVG } from "qrcode.react";
+import { AlertTriangle, QrCode } from "lucide-react";
+import {
+ Drawer,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from "@/components/ui/drawer";
+import { Button } from "@/components/ui/button";
+import {
+ canGenerateQRCode,
+ canGenerateQRCodeAsync,
+ QR_CODE_MAX_LENGTH,
+} from "@/utils/qrCodeUtils.ts";
+import { useIntl } from "react-intl";
/**
* Generic QR Code Components
@@ -26,26 +36,29 @@ import { useIntl } from "react-intl"
*/
interface QRCodeErrorBoundaryProps {
- children: ReactNode
- fallbackMessage: string
+ children: ReactNode;
+ fallbackMessage: string;
}
interface QRCodeErrorBoundaryState {
- hasError: boolean
+ hasError: boolean;
}
-class QRCodeErrorBoundary extends Component {
+class QRCodeErrorBoundary extends Component<
+ QRCodeErrorBoundaryProps,
+ QRCodeErrorBoundaryState
+> {
constructor(props: QRCodeErrorBoundaryProps) {
- super(props)
- this.state = { hasError: false }
+ super(props);
+ this.state = { hasError: false };
}
static getDerivedStateFromError(): QRCodeErrorBoundaryState {
- return { hasError: true }
+ return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
- console.error("QR Code Error:", error, errorInfo)
+ console.error("QR Code Error:", error, errorInfo);
}
render() {
@@ -55,31 +68,31 @@ class QRCodeErrorBoundary extends Component
{this.props.fallbackMessage}
- )
+ );
}
- return this.props.children
+ return this.props.children;
}
}
interface QRCodeProps {
- value: string
- size?: number
- label?: string
- className?: string
+ value: string;
+ size?: number;
+ label?: string;
+ className?: string;
}
interface QRCodeModalProps extends QRCodeProps {
- title?: string
- triggerLabel?: string
+ title?: string;
+ triggerLabel?: string;
}
export function QRCode({ value, size = 200, label, className }: QRCodeProps) {
- const intl = useIntl()
+ const intl = useIntl();
const errorFallback = intl.formatMessage({
id: "qrCode.error.generic",
defaultMessage: "QR code cannot be generated (data too large)",
- })
+ });
if (!canGenerateQRCode(value)) {
return (
@@ -89,60 +102,91 @@ export function QRCode({ value, size = 200, label, className }: QRCodeProps) {
{intl.formatMessage(
{
id: "qrCode.error.tooLarge",
- defaultMessage: "Data too large for QR code ({length} characters, max {max})",
+ defaultMessage:
+ "Data too large for QR code ({length} characters, max {max})",
},
{ length: value.length, max: QR_CODE_MAX_LENGTH },
)}
- )
+ );
}
return (
-
-
- {label &&
{label} }
+
+
+ {label && (
+
+ {label}
+
+ )}
- )
+ );
}
-export function QRCodeModal({ value, size = 768, label, title, triggerLabel }: QRCodeModalProps) {
- const intl = useIntl()
- const [open, setOpen] = useState(false)
- const [canRender, setCanRender] = useState(false)
- const resolvedTitle = title ?? intl.formatMessage({ id: "qrCode.modal.title", defaultMessage: "QR Code" })
+export function QRCodeModal({
+ value,
+ size = 768,
+ label,
+ title,
+ triggerLabel,
+}: QRCodeModalProps) {
+ const intl = useIntl();
+ const [open, setOpen] = useState(false);
+ const [canRender, setCanRender] = useState(false);
+ const resolvedTitle =
+ title ??
+ intl.formatMessage({ id: "qrCode.modal.title", defaultMessage: "QR Code" });
const resolvedTriggerLabel =
- triggerLabel ?? intl.formatMessage({ id: "qrCode.modal.triggerLabel", defaultMessage: "Show QR code" })
+ triggerLabel ??
+ intl.formatMessage({
+ id: "qrCode.modal.triggerLabel",
+ defaultMessage: "Show QR code",
+ });
const errorFallback = intl.formatMessage({
id: "qrCode.error.generic",
defaultMessage: "QR code cannot be generated (data too large)",
- })
+ });
useEffect(() => {
- let isActive = true
+ let isActive = true;
void (async () => {
- const result = await canGenerateQRCodeAsync(value)
+ const result = await canGenerateQRCodeAsync(value);
if (isActive) {
- setCanRender(result)
+ setCanRender(result);
}
- })()
+ })();
return () => {
- isActive = false
- }
- }, [value])
+ isActive = false;
+ };
+ }, [value]);
if (!canGenerateQRCode(value) || !canRender) {
- return null
+ return null;
}
return (
-
+
-
+
@@ -153,18 +197,33 @@ export function QRCodeModal({ value, size = 768, label, title, triggerLabel }: Q
-
- {label && {label} }
+
+ {label && (
+
+ {label}
+
+ )}
- )
+ );
}
-export function FeeTokenQRCodeModal({ feeToken, size = 512 }: { feeToken: string; size?: number }) {
- const intl = useIntl()
+export function FeeTokenQRCodeModal({
+ feeToken,
+ size = 512,
+}: {
+ feeToken: string;
+ size?: number;
+}) {
+ const intl = useIntl();
return (
- )
+ );
}
diff --git a/src/components/SortButtons.tsx b/src/components/SortButtons.tsx
index 64c9268..1444593 100644
--- a/src/components/SortButtons.tsx
+++ b/src/components/SortButtons.tsx
@@ -1,31 +1,39 @@
-import { Button } from "@/components/ui/button"
-import { ArrowUp, ArrowDown } from "lucide-react"
-import { useIntl } from "react-intl"
+import { Button } from "@/components/ui/button";
+import { ArrowUp, ArrowDown } from "lucide-react";
+import { useIntl } from "react-intl";
export interface SortConfig {
- field: T
- direction: "asc" | "desc"
+ field: T;
+ direction: "asc" | "desc";
}
interface SortOption {
- field: T
- label: string
+ field: T;
+ label: string;
}
interface SortButtonsProps {
- sortBy: string
- onSortChange: (field: T) => void
- options: SortOption[]
+ sortBy: string;
+ onSortChange: (field: T) => void;
+ options: SortOption[];
}
-export function SortButtons({ sortBy, onSortChange, options }: SortButtonsProps) {
- const intl = useIntl()
+export function SortButtons({
+ sortBy,
+ onSortChange,
+ options,
+}: SortButtonsProps) {
+ const intl = useIntl();
const getSortIcon = (field: T) => {
if (!sortBy.startsWith(field)) {
- return null
+ return null;
}
- return sortBy.endsWith("asc") ? :
- }
+ return sortBy.endsWith("asc") ? (
+
+ ) : (
+
+ );
+ };
const getTitle = (field: T, label: string) => {
if (sortBy.startsWith(field)) {
@@ -43,7 +51,7 @@ export function SortButtons({ sortBy, onSortChange, options }:
defaultMessage: "{label} Descending",
},
{ label },
- )
+ );
}
return intl.formatMessage(
{
@@ -51,8 +59,8 @@ export function SortButtons({ sortBy, onSortChange, options }:
defaultMessage: "Sort by {label}",
},
{ label },
- )
- }
+ );
+ };
return (
@@ -75,5 +83,5 @@ export function SortButtons({ sortBy, onSortChange, options }:
))}
- )
+ );
}
diff --git a/src/components/TruncatedTextPopover.test.tsx b/src/components/TruncatedTextPopover.test.tsx
index e90c301..19b3b62 100644
--- a/src/components/TruncatedTextPopover.test.tsx
+++ b/src/components/TruncatedTextPopover.test.tsx
@@ -1,86 +1,104 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { IntlProvider } from "react-intl"
-import { TruncatedTextPopover } from "./TruncatedTextPopover"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { IntlProvider } from "react-intl";
+import { TruncatedTextPopover } from "./TruncatedTextPopover";
-const toastSuccess = vi.fn<(msg: string) => void>()
-const toastError = vi.fn<(msg: string) => void>()
+const toastSuccess = vi.fn<(msg: string) => void>();
+const toastError = vi.fn<(msg: string) => void>();
vi.mock("sonner", () => ({
toast: {
success: (msg: string) => toastSuccess(msg),
error: (msg: string) => toastError(msg),
},
-}))
+}));
vi.mock("@/components/ui/popover", () => ({
- Popover: ({ children }: { children: React.ReactNode }) => {children}
,
- PopoverTrigger: ({ children }: { children: React.ReactNode }) => {children}
,
- PopoverContent: ({ children }: { children: React.ReactNode }) => {children}
,
-}))
-
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+ Popover: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ PopoverTrigger: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+ PopoverContent: ({ children }: { children: React.ReactNode }) => (
+ {children}
+ ),
+}));
+
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
function renderWithIntl(element: ReactElement): HTMLDivElement {
- return renderIntoDom({element} )
+ return renderIntoDom({element} );
}
beforeEach(() => {
- vi.clearAllMocks()
- vi.useFakeTimers()
+ vi.clearAllMocks();
+ vi.useFakeTimers();
Object.defineProperty(document.documentElement, "clientWidth", {
configurable: true,
value: 1200,
- })
+ });
Object.defineProperty(window.navigator, "maxTouchPoints", {
configurable: true,
value: 0,
- })
- window.matchMedia = vi.fn().mockReturnValue({ matches: false }) as unknown as typeof window.matchMedia
+ });
+ window.matchMedia = vi
+ .fn()
+ .mockReturnValue({ matches: false }) as unknown as typeof window.matchMedia;
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
-})
+});
describe("TruncatedTextPopover", () => {
it("renders plain text when no truncation is needed", () => {
- const page = renderWithIntl( )
- expect(page.textContent).toContain("short text")
- expect(page.querySelector("button")).toBeNull()
- })
+ const page = renderWithIntl(
+ ,
+ );
+ expect(page.textContent).toContain("short text");
+ expect(page.querySelector("button")).toBeNull();
+ });
it("renders truncated trigger and full content for long text", () => {
Object.defineProperty(document.documentElement, "clientWidth", {
configurable: true,
value: 320,
- })
- const longText = "12345678901234567890"
- const page = renderWithIntl( )
- expect(page.textContent).toContain("123")
- expect(page.textContent).toContain("…")
- expect(page.textContent).toContain("890")
- expect(page.textContent).toContain(longText)
- })
+ });
+ const longText = "12345678901234567890";
+ const page = renderWithIntl(
+ ,
+ );
+ expect(page.textContent).toContain("123");
+ expect(page.textContent).toContain("…");
+ expect(page.textContent).toContain("890");
+ expect(page.textContent).toContain(longText);
+ });
it("copies text and shows success toast", async () => {
Object.defineProperty(window.navigator, "clipboard", {
@@ -88,22 +106,28 @@ describe("TruncatedTextPopover", () => {
value: {
writeText: vi.fn().mockResolvedValue(undefined),
},
- })
-
- const page = renderWithIntl( )
- const button = page.querySelector('button[title="Copy to clipboard"]')
- expect(button).not.toBeNull()
+ });
+
+ const page = renderWithIntl(
+ ,
+ );
+ const button = page.querySelector('button[title="Copy to clipboard"]');
+ expect(button).not.toBeNull();
await act(async () => {
- button?.dispatchEvent(new MouseEvent("click", { bubbles: true }))
- await Promise.resolve()
- })
+ button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ await Promise.resolve();
+ });
- expect(toastSuccess).toHaveBeenCalled()
+ expect(toastSuccess).toHaveBeenCalled();
act(() => {
- vi.runAllTimers()
- })
- })
+ vi.runAllTimers();
+ });
+ });
it("shows error toast when copy fails", async () => {
Object.defineProperty(window.navigator, "clipboard", {
@@ -111,18 +135,26 @@ describe("TruncatedTextPopover", () => {
value: {
writeText: vi.fn().mockRejectedValue(new Error("copy failed")),
},
- })
-
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined)
- const page = renderWithIntl( )
- const button = page.querySelector('button[title="Copy to clipboard"]')
+ });
+
+ const errorSpy = vi
+ .spyOn(console, "error")
+ .mockImplementation(() => undefined);
+ const page = renderWithIntl(
+ ,
+ );
+ const button = page.querySelector('button[title="Copy to clipboard"]');
await act(async () => {
- button?.dispatchEvent(new MouseEvent("click", { bubbles: true }))
- await Promise.resolve()
- })
-
- expect(toastError).toHaveBeenCalled()
- errorSpy.mockRestore()
- })
-})
+ button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ await Promise.resolve();
+ });
+
+ expect(toastError).toHaveBeenCalled();
+ errorSpy.mockRestore();
+ });
+});
diff --git a/src/components/TruncatedTextPopover.tsx b/src/components/TruncatedTextPopover.tsx
index d825c24..a2b5cf9 100644
--- a/src/components/TruncatedTextPopover.tsx
+++ b/src/components/TruncatedTextPopover.tsx
@@ -1,89 +1,98 @@
-import * as React from "react"
-import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
-import { cn } from "@/lib/utils"
-import { truncateString } from "@/utils/strings"
-import { Copy, Check } from "lucide-react"
-import { Button } from "@/components/ui/button"
-import { toast } from "sonner"
-import { useIntl } from "react-intl"
+import * as React from "react";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { cn } from "@/lib/utils";
+import { truncateString } from "@/utils/strings";
+import { Copy, Check } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { toast } from "sonner";
+import { useIntl } from "react-intl";
interface TruncatedTextPopoverProps {
- text: React.ReactNode
- maxLength?: number
- showFullOnDesktop?: boolean
- className?: string
- contentClassName?: string
- title?: string
- as?: "button" | "span"
- showCopyButton?: boolean
+ text: React.ReactNode;
+ maxLength?: number;
+ showFullOnDesktop?: boolean;
+ className?: string;
+ contentClassName?: string;
+ title?: string;
+ as?: "button" | "span";
+ showCopyButton?: boolean;
}
-function useResponsiveMaxLength(maxLength: number, showFullOnDesktop: boolean): number {
- const [effectiveMaxLength, setEffectiveMaxLength] = React.useState(maxLength)
+function useResponsiveMaxLength(
+ maxLength: number,
+ showFullOnDesktop: boolean,
+): number {
+ const [effectiveMaxLength, setEffectiveMaxLength] = React.useState(maxLength);
React.useEffect(() => {
const calculateMaxLength = () => {
// Use document.documentElement.clientWidth for more accurate width
// This excludes scrollbar and respects the actual viewport
- const width = document.documentElement.clientWidth || window.innerWidth
+ const width = document.documentElement.clientWidth || window.innerWidth;
// Modern touch device detection using multiple signals
const isTouchDevice =
- window.matchMedia("(pointer: coarse)").matches || navigator.maxTouchPoints > 0 || "ontouchstart" in window
+ window.matchMedia("(pointer: coarse)").matches ||
+ navigator.maxTouchPoints > 0 ||
+ "ontouchstart" in window;
// If showFullOnDesktop is true and we're on a large screen without touch, skip truncation
if (showFullOnDesktop && width >= 1024 && !isTouchDevice) {
- setEffectiveMaxLength(Infinity)
- return
+ setEffectiveMaxLength(Infinity);
+ return;
}
// Define breakpoints and scaling factors based on viewport width
// Touch devices get more aggressive truncation at larger widths
if (width < 480) {
// Extra small mobile: reduce to 30% of maxLength
- setEffectiveMaxLength(Math.max(12, Math.floor(maxLength * 0.3)))
+ setEffectiveMaxLength(Math.max(12, Math.floor(maxLength * 0.3)));
} else if (width < 768) {
// Small mobile/tablet: reduce to 50% of maxLength
- setEffectiveMaxLength(Math.max(16, Math.floor(maxLength * 0.5)))
+ setEffectiveMaxLength(Math.max(16, Math.floor(maxLength * 0.5)));
} else if (width < 1024) {
// Tablet/small desktop: reduce to 70% of maxLength
- setEffectiveMaxLength(Math.max(16, Math.floor(maxLength * 0.7)))
+ setEffectiveMaxLength(Math.max(16, Math.floor(maxLength * 0.7)));
} else if (width < 1440) {
// Medium desktop: reduce to 90% of maxLength
- setEffectiveMaxLength(Math.max(24, Math.floor(maxLength * 0.9)))
+ setEffectiveMaxLength(Math.max(24, Math.floor(maxLength * 0.9)));
} else {
// Large desktop: use full maxLength
- setEffectiveMaxLength(maxLength)
+ setEffectiveMaxLength(maxLength);
}
- }
+ };
- calculateMaxLength()
- window.addEventListener("resize", calculateMaxLength)
- return () => window.removeEventListener("resize", calculateMaxLength)
- }, [maxLength, showFullOnDesktop])
+ calculateMaxLength();
+ window.addEventListener("resize", calculateMaxLength);
+ return () => window.removeEventListener("resize", calculateMaxLength);
+ }, [maxLength, showFullOnDesktop]);
- return effectiveMaxLength
+ return effectiveMaxLength;
}
function extractTextFromNode(node: unknown): string {
if (node == null) {
- return ""
+ return "";
}
if (typeof node === "string" || typeof node === "number") {
- return String(node)
+ return String(node);
}
if (Array.isArray(node)) {
- return node.map((n) => extractTextFromNode(n)).join("")
+ return node.map((n) => extractTextFromNode(n)).join("");
}
if (React.isValidElement(node)) {
- const el = node as React.ReactElement<{ children?: React.ReactNode }>
- return extractTextFromNode(el.props.children)
+ const el = node as React.ReactElement<{ children?: React.ReactNode }>;
+ return extractTextFromNode(el.props.children);
}
- return ""
+ return "";
}
export function TruncatedTextPopover({
@@ -96,48 +105,58 @@ export function TruncatedTextPopover({
as = "button",
showCopyButton = false,
}: TruncatedTextPopoverProps) {
- const intl = useIntl()
- const effectiveMaxLength = useResponsiveMaxLength(maxLength, showFullOnDesktop)
- const [copied, setCopied] = React.useState(false)
-
- const textStr = extractTextFromNode(text)
- const rawLines = textStr.split(/\r?\n/)
- const lines = rawLines.map((l) => l.replace(/\s+$/g, ""))
- const needsTruncation = lines.some((line) => line.length > effectiveMaxLength)
+ const intl = useIntl();
+ const effectiveMaxLength = useResponsiveMaxLength(
+ maxLength,
+ showFullOnDesktop,
+ );
+ const [copied, setCopied] = React.useState(false);
+
+ const textStr = extractTextFromNode(text);
+ const rawLines = textStr.split(/\r?\n/);
+ const lines = rawLines.map((l) => l.replace(/\s+$/g, ""));
+ const needsTruncation = lines.some(
+ (line) => line.length > effectiveMaxLength,
+ );
const handleCopy = async () => {
try {
- await navigator.clipboard.writeText(textStr)
- setCopied(true)
+ await navigator.clipboard.writeText(textStr);
+ setCopied(true);
toast.success(
intl.formatMessage({
id: "truncatedTextPopover.copied",
defaultMessage: "Copied to clipboard",
}),
- )
- setTimeout(() => setCopied(false), 2000)
+ );
+ setTimeout(() => setCopied(false), 2000);
} catch (err) {
- console.error("Failed to copy text:", err)
+ console.error("Failed to copy text:", err);
toast.error(
intl.formatMessage({
id: "truncatedTextPopover.copyFailed",
defaultMessage: "Failed to copy to clipboard",
}),
- )
+ );
}
- }
- const flatLabel = lines.join(", ")
- const truncatedLines = needsTruncation ? lines.map((line) => truncateString(line, effectiveMaxLength)) : lines
+ };
+ const flatLabel = lines.join(", ");
+ const truncatedLines = needsTruncation
+ ? lines.map((line) => truncateString(line, effectiveMaxLength))
+ : lines;
if (!needsTruncation && !showCopyButton) {
return (
-
+
{text}
- )
+ );
}
- const TriggerTag = as
+ const TriggerTag = as;
const popoverContent = (
@@ -154,7 +173,10 @@ export function TruncatedTextPopover({
>
{needsTruncation ? (
truncatedLines.map((line, i) => (
-
+
{line}
))
@@ -176,7 +198,7 @@ export function TruncatedTextPopover({
{textStr}
- )
+ );
if (showCopyButton) {
return (
@@ -211,11 +233,15 @@ export function TruncatedTextPopover({
})
}
>
- {copied ? : }
+ {copied ? (
+
+ ) : (
+
+ )}
- )
+ );
}
- return popoverContent
+ return popoverContent;
}
diff --git a/src/components/icons/UserAnonymous.tsx b/src/components/icons/UserAnonymous.tsx
index 339dd84..2ea8e8c 100644
--- a/src/components/icons/UserAnonymous.tsx
+++ b/src/components/icons/UserAnonymous.tsx
@@ -1,7 +1,7 @@
-import { useIntl } from "react-intl"
+import { useIntl } from "react-intl";
export function UserAnonymousIcon({ className }: { className?: string }) {
- const intl = useIntl()
+ const intl = useIntl();
return (
- )
+ );
}
diff --git a/src/components/nav/NavMain.tsx b/src/components/nav/NavMain.tsx
index b67460c..eea8781 100644
--- a/src/components/nav/NavMain.tsx
+++ b/src/components/nav/NavMain.tsx
@@ -1,7 +1,11 @@
-import { ChevronRight, type LucideIcon } from "lucide-react"
-import { NavLink } from "react-router"
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"
-import { useIntl } from "react-intl"
+import { ChevronRight, type LucideIcon } from "lucide-react";
+import { NavLink } from "react-router";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@/components/ui/collapsible";
+import { useIntl } from "react-intl";
import {
SidebarGroup,
SidebarGroupLabel,
@@ -12,40 +16,52 @@ import {
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
-} from "@/components/ui/sidebar"
-import { cn } from "@/lib/utils"
+} from "@/components/ui/sidebar";
+import { cn } from "@/lib/utils";
export function NavMain({
items,
}: {
items: {
- titleId: string
- titleDefaultMessage: string
- url: string
- icon?: LucideIcon
- isActive?: boolean
- disabled?: boolean
+ titleId: string;
+ titleDefaultMessage: string;
+ url: string;
+ icon?: LucideIcon;
+ isActive?: boolean;
+ disabled?: boolean;
items?: {
- titleId: string
- titleDefaultMessage: string
- url: string
- disabled?: boolean
- }[]
- }[]
+ titleId: string;
+ titleDefaultMessage: string;
+ url: string;
+ disabled?: boolean;
+ }[];
+ }[];
}) {
- const { state } = useSidebar()
- const intl = useIntl()
+ const { state } = useSidebar();
+ const intl = useIntl();
return (
- {intl.formatMessage({ id: "nav.dashboard", defaultMessage: "Dashboard" })}
+
+ {intl.formatMessage({
+ id: "nav.dashboard",
+ defaultMessage: "Dashboard",
+ })}
+
{items.map((item) => {
- const title = intl.formatMessage({ id: item.titleId, defaultMessage: item.titleDefaultMessage })
+ const title = intl.formatMessage({
+ id: item.titleId,
+ defaultMessage: item.titleDefaultMessage,
+ });
return (item.items ?? []).length === 0 || state === "collapsed" ? (
-
+
{item.disabled === true ? (
<>
{item.icon && }
@@ -60,10 +76,19 @@ export function NavMain({
) : (
-
+
-
+
{item.icon && }
{title}
@@ -73,7 +98,10 @@ export function NavMain({
@@ -87,14 +115,18 @@ export function NavMain({
const subTitle = intl.formatMessage({
id: subItem.titleId,
defaultMessage: subItem.titleDefaultMessage,
- })
+ });
return (
e.preventDefault() : undefined}
+ onClick={
+ subItem.disabled
+ ? (e) => e.preventDefault()
+ : undefined
+ }
className={cn({
"opacity-50": subItem.disabled,
"cursor-not-allowed": subItem.disabled,
@@ -104,15 +136,15 @@ export function NavMain({
- )
+ );
})}
- )
+ );
})}
- )
+ );
}
diff --git a/src/components/nav/NavSecondary.tsx b/src/components/nav/NavSecondary.tsx
index 7cb52d6..1db076c 100644
--- a/src/components/nav/NavSecondary.tsx
+++ b/src/components/nav/NavSecondary.tsx
@@ -1,5 +1,5 @@
-import * as React from "react"
-import { type LucideIcon } from "lucide-react"
+import * as React from "react";
+import { type LucideIcon } from "lucide-react";
import {
SidebarGroup,
@@ -7,23 +7,23 @@ import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
-} from "@/components/ui/sidebar"
-import { Link } from "react-router"
-import { useIntl } from "react-intl"
+} from "@/components/ui/sidebar";
+import { Link } from "react-router";
+import { useIntl } from "react-intl";
export function NavSecondary({
items,
...props
}: {
items: {
- titleId?: string
- titleDefaultMessage?: string
- title: string
- url: string
- icon: LucideIcon
- }[]
+ titleId?: string;
+ titleDefaultMessage?: string;
+ title: string;
+ url: string;
+ icon: LucideIcon;
+ }[];
} & React.ComponentPropsWithoutRef) {
- const intl = useIntl()
+ const intl = useIntl();
return (
@@ -34,21 +34,25 @@ export function NavSecondary({
id: item.titleId,
defaultMessage: item.titleDefaultMessage ?? item.title,
})
- : item.title
+ : item.title;
return (
-
+
{title}
- )
+ );
})}
- )
+ );
}
diff --git a/src/components/nav/NavUser.tsx b/src/components/nav/NavUser.tsx
index c3eff52..80c171c 100644
--- a/src/components/nav/NavUser.tsx
+++ b/src/components/nav/NavUser.tsx
@@ -1,7 +1,7 @@
-import { BadgeCheck, Bell, ChevronsUpDown, LogOut } from "lucide-react"
-import { useIntl } from "react-intl"
+import { BadgeCheck, Bell, ChevronsUpDown, LogOut } from "lucide-react";
+import { useIntl } from "react-intl";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
@@ -10,37 +10,45 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "@/components/ui/sidebar"
+} from "@/components/ui/dropdown-menu";
+import {
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ useSidebar,
+} from "@/components/ui/sidebar";
export function NavUser({
user,
}: {
user: {
- name: string
- email: string
- avatar: string
- }
+ name: string;
+ email: string;
+ avatar: string;
+ };
}) {
- const { isMobile } = useSidebar()
- const intl = useIntl()
+ const { isMobile } = useSidebar();
+ const intl = useIntl();
- const initials = user.name.length > 0 ? user.name[0].toUpperCase() : ""
+ const initials = user.name.length > 0 ? user.name[0].toUpperCase() : "";
const unknownUser = intl.formatMessage({
id: "nav.user.unknown",
defaultMessage: "Unknown User",
- })
+ });
const initialsFallback = intl.formatMessage({
id: "nav.user.initials.fallback",
defaultMessage: "U",
- })
- const tooltipLabel = user.name || unknownUser
+ });
+ const tooltipLabel = user.name || unknownUser;
return (
-
+
-
+
{intl.formatMessage({
id: "nav.user.avatarFallback",
@@ -111,5 +122,5 @@ export function NavUser({
- )
+ );
}
diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx
index b9d4458..2ae746f 100644
--- a/src/components/ui/chart.tsx
+++ b/src/components/ui/chart.tsx
@@ -195,7 +195,7 @@ const ChartTooltipContent = React.forwardRef<
return (
svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5",
indicator === "dot" && "items-center"
diff --git a/src/constants/api.ts b/src/constants/api.ts
index e55ac1d..d6d95e1 100644
--- a/src/constants/api.ts
+++ b/src/constants/api.ts
@@ -1,3 +1,3 @@
-import meta from "@/constants/meta"
+import meta from "@/constants/meta";
-export const API_URL = meta.apiBaseUrl
+export const API_URL = meta.apiBaseUrl;
diff --git a/src/constants/endpoints.ts b/src/constants/endpoints.ts
index 98b8955..caac7c8 100644
--- a/src/constants/endpoints.ts
+++ b/src/constants/endpoints.ts
@@ -1,10 +1,17 @@
-const INFO = "/v1/info"
+const INFO = "/v1/info";
-const ADMIN_QUOTE = "/v1/admin/credit/quote"
-const ADMIN_QUOTE_PENDING = "/v1/admin/credit/quote"
-const ADMIN_QUOTE_BY_ID = "/v1/admin/credit/quote/:id"
+const ADMIN_QUOTE = "/v1/admin/credit/quote";
+const ADMIN_QUOTE_PENDING = "/v1/admin/credit/quote";
+const ADMIN_QUOTE_BY_ID = "/v1/admin/credit/quote/:id";
-const CREDIT_QUOTE = "/v1/credit/mint/quote"
-const CREDIT_QUOTES_BY_ID = "/v1/credit/mint/quote/:id"
+const CREDIT_QUOTE = "/v1/credit/mint/quote";
+const CREDIT_QUOTES_BY_ID = "/v1/credit/mint/quote/:id";
-export { INFO, ADMIN_QUOTE, ADMIN_QUOTE_PENDING, ADMIN_QUOTE_BY_ID, CREDIT_QUOTE, CREDIT_QUOTES_BY_ID }
+export {
+ INFO,
+ ADMIN_QUOTE,
+ ADMIN_QUOTE_PENDING,
+ ADMIN_QUOTE_BY_ID,
+ CREDIT_QUOTE,
+ CREDIT_QUOTES_BY_ID,
+};
diff --git a/src/constants/meta.ts b/src/constants/meta.ts
index 647852c..22cb61e 100644
--- a/src/constants/meta.ts
+++ b/src/constants/meta.ts
@@ -1,8 +1,8 @@
-import { env } from "@/lib/env"
+import { env } from "@/lib/env";
export default {
devModeEnabled: env.devModeEnabled,
apiBaseUrl: env.apiBaseUrl,
apiMocksEnabled: env.apiMocksEnabled,
crowdinInContextToolingEnabled: env.crowdinInContextToolingEnabled,
-}
+};
diff --git a/src/constants/routes.ts b/src/constants/routes.ts
index b9203c7..a300ba6 100644
--- a/src/constants/routes.ts
+++ b/src/constants/routes.ts
@@ -1,9 +1,9 @@
-const ROOT = "/"
-const HOME = "/"
-const BALANCES = "/balances"
+const ROOT = "/";
+const HOME = "/";
+const BALANCES = "/balances";
export default {
ROOT,
HOME,
BALANCES,
-}
+};
diff --git a/src/context/language/LanguageContext.tsx b/src/context/language/LanguageContext.tsx
index 6e5fa68..fb680c6 100644
--- a/src/context/language/LanguageContext.tsx
+++ b/src/context/language/LanguageContext.tsx
@@ -1,20 +1,24 @@
-import { createContext } from "react"
-import meta from "@/constants/meta"
+import { createContext } from "react";
+import meta from "@/constants/meta";
/**
* Crowdin "pseudo-language" for In-Context tooling
* See: https://support.crowdin.com/developer/in-context-localization/
*/
-const CROWDIN_PSEUDO_LOCALE = "ach-UG"
+const CROWDIN_PSEUDO_LOCALE = "ach-UG";
-const DEFAULT_LOCALE_PROD = "en-US"
-const DEFAULT_LOCALE_DEV = meta.crowdinInContextToolingEnabled ? CROWDIN_PSEUDO_LOCALE : DEFAULT_LOCALE_PROD
-export const DEFAULT_LOCALE = meta.devModeEnabled ? DEFAULT_LOCALE_DEV : DEFAULT_LOCALE_PROD
+const DEFAULT_LOCALE_PROD = "en-US";
+const DEFAULT_LOCALE_DEV = meta.crowdinInContextToolingEnabled
+ ? CROWDIN_PSEUDO_LOCALE
+ : DEFAULT_LOCALE_PROD;
+export const DEFAULT_LOCALE = meta.devModeEnabled
+ ? DEFAULT_LOCALE_DEV
+ : DEFAULT_LOCALE_PROD;
export interface LanguageContextType {
- locale: string
- setLocale: (locale: string) => void
- availableLocales: () => string[]
+ locale: string;
+ setLocale: (locale: string) => void;
+ availableLocales: () => string[];
}
export const LanguageContext = createContext({
@@ -22,4 +26,4 @@ export const LanguageContext = createContext({
// eslint-disable-next-line @typescript-eslint/no-empty-function
setLocale: () => {},
availableLocales: () => [DEFAULT_LOCALE],
-})
+});
diff --git a/src/context/language/LanguageProvider.tsx b/src/context/language/LanguageProvider.tsx
index 86ed972..1081700 100644
--- a/src/context/language/LanguageProvider.tsx
+++ b/src/context/language/LanguageProvider.tsx
@@ -1,25 +1,30 @@
-import { ReactNode, useMemo, useState } from "react"
-import { IntlProvider } from "react-intl"
-import { LanguageContext, DEFAULT_LOCALE } from "@/context/language/LanguageContext"
-import { messagesByLocale, supportedLocales } from "@/i18n/messages"
-import { getItem, setItem } from "@/utils/local-storage"
+import { ReactNode, useMemo, useState } from "react";
+import { IntlProvider } from "react-intl";
+import {
+ LanguageContext,
+ DEFAULT_LOCALE,
+} from "@/context/language/LanguageContext";
+import { messagesByLocale, supportedLocales } from "@/i18n/messages";
+import { getItem, setItem } from "@/utils/local-storage";
-const LOCALE_STORAGE_KEY = "locale"
+const LOCALE_STORAGE_KEY = "locale";
interface LanguageProviderProps {
- children: ReactNode
+ children: ReactNode;
}
export function LanguageProvider({ children }: LanguageProviderProps) {
- const [locale, setLocaleState] = useState(() => getItem(LOCALE_STORAGE_KEY) ?? DEFAULT_LOCALE)
+ const [locale, setLocaleState] = useState(
+ () => getItem(LOCALE_STORAGE_KEY) ?? DEFAULT_LOCALE,
+ );
const setLocale = (nextLocale: string) => {
- setLocaleState(nextLocale)
- setItem(LOCALE_STORAGE_KEY, nextLocale)
- }
+ setLocaleState(nextLocale);
+ setItem(LOCALE_STORAGE_KEY, nextLocale);
+ };
- const availableLocales = () => supportedLocales
- const messages = messagesByLocale[locale] ?? messagesByLocale[DEFAULT_LOCALE]
+ const availableLocales = () => supportedLocales;
+ const messages = messagesByLocale[locale] ?? messagesByLocale[DEFAULT_LOCALE];
const contextValue = useMemo(
() => ({
@@ -28,13 +33,17 @@ export function LanguageProvider({ children }: LanguageProviderProps) {
availableLocales,
}),
[locale],
- )
+ );
return (
-
+
{children}
- )
+ );
}
diff --git a/src/hooks/use-api-client.ts b/src/hooks/use-api-client.ts
index ca37afe..d803005 100644
--- a/src/hooks/use-api-client.ts
+++ b/src/hooks/use-api-client.ts
@@ -1,5 +1,5 @@
-import * as client from "@/generated/client"
+import * as client from "@/generated/client";
export default function useApiClient() {
- return client
+ return client;
}
diff --git a/src/hooks/use-bills-by-quote.ts b/src/hooks/use-bills-by-quote.ts
index 2779e54..cb155be 100644
--- a/src/hooks/use-bills-by-quote.ts
+++ b/src/hooks/use-bills-by-quote.ts
@@ -1,6 +1,6 @@
-import { useQuery } from "@tanstack/react-query"
-import { listEbillsOptions } from "@/generated/client/@tanstack/react-query.gen"
-import type { BitcreditBill } from "@/generated/client/types.gen"
+import { useQuery } from "@tanstack/react-query";
+import { listEbillsOptions } from "@/generated/client/@tanstack/react-query.gen";
+import type { BitcreditBill } from "@/generated/client/types.gen";
/**
* Hook to fetch all bills and provide lookup utilities
@@ -17,31 +17,41 @@ export function useAllBills() {
meta: {
errorMessage: "Failed to fetch bills from /v1/admin/ebill/bills",
},
- })
+ });
}
/**
* Helper to find a bill by ID from a list of bills
*/
-export function findBillById(bills: BitcreditBill[] | undefined, billId: string): BitcreditBill | undefined {
- return bills?.find((bill) => bill.id === billId)
+export function findBillById(
+ bills: BitcreditBill[] | undefined,
+ billId: string,
+): BitcreditBill | undefined {
+ return bills?.find((bill) => bill.id === billId);
}
/**
* Helper to filter bills by participant node ID
*/
-export function filterBillsByParticipant(bills: BitcreditBill[] | undefined, nodeId: string): BitcreditBill[] {
+export function filterBillsByParticipant(
+ bills: BitcreditBill[] | undefined,
+ nodeId: string,
+): BitcreditBill[] {
if (!bills) {
- return []
+ return [];
}
- return bills.filter((bill) => bill.participants.all_participant_node_ids.includes(nodeId))
+ return bills.filter((bill) =>
+ bill.participants.all_participant_node_ids.includes(nodeId),
+ );
}
/**
* Helper to get participant display name
*/
-export function getParticipantName(participant: { name?: string; node_id: string } | undefined): string {
- if (!participant) return "Unknown"
- return participant.name ?? `Node ${participant.node_id.slice(0, 8)}...`
+export function getParticipantName(
+ participant: { name?: string; node_id: string } | undefined,
+): string {
+ if (!participant) return "Unknown";
+ return participant.name ?? `Node ${participant.node_id.slice(0, 8)}...`;
}
diff --git a/src/hooks/use-local-storage.ts b/src/hooks/use-local-storage.ts
index 313a572..d2a4829 100644
--- a/src/hooks/use-local-storage.ts
+++ b/src/hooks/use-local-storage.ts
@@ -1,31 +1,31 @@
-import { useState } from "react"
-import { getItem, setItem, removeItem } from "@/utils/local-storage"
+import { useState } from "react";
+import { getItem, setItem, removeItem } from "@/utils/local-storage";
-type DispatchAction = T | ((prevState: T) => T)
+type DispatchAction = T | ((prevState: T) => T);
export default function useLocalStorage(key: string, initialValue: T) {
const [value, setValue] = useState(() => {
- const data = getItem(key)
- return (data ?? initialValue) as T
- })
+ const data = getItem(key);
+ return (data ?? initialValue) as T;
+ });
function handleDispatch(action: DispatchAction) {
if (typeof action === "function") {
setValue((prevState) => {
- const newValue = (action as (prevState: T) => T)(prevState)
- setItem(key, newValue)
- return newValue
- })
+ const newValue = (action as (prevState: T) => T)(prevState);
+ setItem(key, newValue);
+ return newValue;
+ });
} else {
- setValue(action)
- setItem(key, action)
+ setValue(action);
+ setItem(key, action);
}
}
function clearState() {
- setValue(undefined as T)
- removeItem(key)
+ setValue(undefined as T);
+ removeItem(key);
}
- return [value, handleDispatch, clearState] as const
+ return [value, handleDispatch, clearState] as const;
}
diff --git a/src/hooks/use-mobile.ts b/src/hooks/use-mobile.ts
index 2b0fe1d..a93d583 100644
--- a/src/hooks/use-mobile.ts
+++ b/src/hooks/use-mobile.ts
@@ -1,19 +1,21 @@
-import * as React from "react"
+import * as React from "react";
-const MOBILE_BREAKPOINT = 768
+const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
+ const [isMobile, setIsMobile] = React.useState(
+ undefined,
+ );
React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ mql.addEventListener("change", onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => mql.removeEventListener("change", onChange);
+ }, []);
- return !!isMobile
+ return !!isMobile;
}
diff --git a/src/hooks/use-utc-date-formatters.ts b/src/hooks/use-utc-date-formatters.ts
index 00b0a8b..ea2794c 100644
--- a/src/hooks/use-utc-date-formatters.ts
+++ b/src/hooks/use-utc-date-formatters.ts
@@ -1,33 +1,42 @@
-import { useMemo } from "react"
+import { useMemo } from "react";
-const UTC_TIME_ZONE = "UTC"
+const UTC_TIME_ZONE = "UTC";
interface UtcDateFormatters {
- formatMonthShort: (date: Date) => string
- formatDay2Digit: (date: Date) => string
- formatYearNumeric: (date: Date) => string
- formatDateMmmDdYyyy: (date: Date) => string
+ formatMonthShort: (date: Date) => string;
+ formatDay2Digit: (date: Date) => string;
+ formatYearNumeric: (date: Date) => string;
+ formatDateMmmDdYyyy: (date: Date) => string;
}
export const useUtcDateFormatters = (locale: string): UtcDateFormatters => {
const formatters = useMemo(() => {
return {
- monthShort: new Intl.DateTimeFormat(locale, { month: "short", timeZone: UTC_TIME_ZONE }),
- day2Digit: new Intl.DateTimeFormat(locale, { day: "2-digit", timeZone: UTC_TIME_ZONE }),
- yearNumeric: new Intl.DateTimeFormat(locale, { year: "numeric", timeZone: UTC_TIME_ZONE }),
+ monthShort: new Intl.DateTimeFormat(locale, {
+ month: "short",
+ timeZone: UTC_TIME_ZONE,
+ }),
+ day2Digit: new Intl.DateTimeFormat(locale, {
+ day: "2-digit",
+ timeZone: UTC_TIME_ZONE,
+ }),
+ yearNumeric: new Intl.DateTimeFormat(locale, {
+ year: "numeric",
+ timeZone: UTC_TIME_ZONE,
+ }),
dateMmmDdYyyy: new Intl.DateTimeFormat(locale, {
month: "short",
day: "2-digit",
year: "numeric",
timeZone: UTC_TIME_ZONE,
}),
- }
- }, [locale])
+ };
+ }, [locale]);
return {
formatMonthShort: (date) => formatters.monthShort.format(date),
formatDay2Digit: (date) => formatters.day2Digit.format(date),
formatYearNumeric: (date) => formatters.yearNumeric.format(date),
formatDateMmmDdYyyy: (date) => formatters.dateMmmDdYyyy.format(date),
- }
-}
+ };
+};
diff --git a/src/i18n/messages.ts b/src/i18n/messages.ts
index f476922..8a71575 100644
--- a/src/i18n/messages.ts
+++ b/src/i18n/messages.ts
@@ -1,13 +1,13 @@
-import source from "./source.json"
-import enUS from "./en-US/translation.json"
-import achUG from "./ach-UG/translation.json"
-import enGB from "./en-GB/translation.json"
-import deAT from "./de-AT/translation.json"
-import deDE from "./de-DE/translation.json"
-import esAR from "./es-AR/translation.json"
-import esES from "./es-ES/translation.json"
-import itIT from "./it-IT/translation.json"
-import trTR from "./tr-TR/translation.json"
+import source from "./source.json";
+import enUS from "./en-US/translation.json";
+import achUG from "./ach-UG/translation.json";
+import enGB from "./en-GB/translation.json";
+import deAT from "./de-AT/translation.json";
+import deDE from "./de-DE/translation.json";
+import esAR from "./es-AR/translation.json";
+import esES from "./es-ES/translation.json";
+import itIT from "./it-IT/translation.json";
+import trTR from "./tr-TR/translation.json";
export const messagesByLocale: Record> = {
"en-US": enUS,
@@ -19,8 +19,8 @@ export const messagesByLocale: Record> = {
"es-ES": esES,
"it-IT": itIT,
"tr-TR": trTR,
-}
+};
-export const supportedLocales = Object.keys(messagesByLocale)
+export const supportedLocales = Object.keys(messagesByLocale);
-export { source }
+export { source };
diff --git a/src/keycloak.tsx b/src/keycloak.tsx
index 8a2396d..3525bf1 100644
--- a/src/keycloak.tsx
+++ b/src/keycloak.tsx
@@ -1,30 +1,33 @@
-import Keycloak from "keycloak-js"
-import { env } from "@/lib/env"
+import Keycloak from "keycloak-js";
+import { env } from "@/lib/env";
const keycloak = new Keycloak({
url: env.keycloakUrl,
realm: env.keycloakRealm,
clientId: env.keycloakClientId,
-})
+});
export const initKeycloak = async (): Promise => {
try {
- console.log("loading keycloak")
+ console.log("loading keycloak");
const authenticated = await keycloak.init({
onLoad: "login-required",
- })
+ });
if (authenticated) {
- console.log("User is authenticated")
+ console.log("User is authenticated");
} else {
- console.log("User is not authenticated")
+ console.log("User is not authenticated");
}
- return authenticated
+ return authenticated;
} catch (error: unknown) {
- console.error("Failed to initialize adapter:", error instanceof Error ? error : String(error))
- return false
+ console.error(
+ "Failed to initialize adapter:",
+ error instanceof Error ? error : String(error),
+ );
+ return false;
}
-}
+};
-export default keycloak
+export default keycloak;
diff --git a/src/layout.tsx b/src/layout.tsx
index ef91f02..530c7a5 100644
--- a/src/layout.tsx
+++ b/src/layout.tsx
@@ -1,6 +1,6 @@
-import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"
-import { AppSidebar } from "@/components/AppSidebar"
-import { Outlet } from "react-router"
+import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
+import { AppSidebar } from "@/components/AppSidebar";
+import { Outlet } from "react-router";
export default function Layout() {
return (
@@ -15,5 +15,5 @@ export default function Layout() {
- )
+ );
}
diff --git a/src/lib/api-client.ts b/src/lib/api-client.ts
index 33153b1..3c5dce9 100644
--- a/src/lib/api-client.ts
+++ b/src/lib/api-client.ts
@@ -1,46 +1,46 @@
-import { client as heyApiClient } from "@/generated/client/client.gen"
-import * as sdk from "@/generated/client/sdk.gen"
-import { normalizeApiError } from "@/lib/api-error"
-import { env } from "@/lib/env"
-import keycloak from "../keycloak"
+import { client as heyApiClient } from "@/generated/client/client.gen";
+import * as sdk from "@/generated/client/sdk.gen";
+import { normalizeApiError } from "@/lib/api-error";
+import { env } from "@/lib/env";
+import keycloak from "../keycloak";
heyApiClient.setConfig({
baseUrl: env.apiBaseUrl,
throwOnError: true,
-})
+});
heyApiClient.interceptors.error.use((error, response) =>
normalizeApiError(error, {
status: (response as Response | undefined)?.status,
}),
-)
+);
// Add the auth token interceptor
heyApiClient.interceptors.request.use(async (request) => {
try {
- await keycloak.updateToken(30)
+ await keycloak.updateToken(30);
} catch (error) {
- console.error("Failed to refresh token:", error)
+ console.error("Failed to refresh token:", error);
}
- const token = keycloak.token
+ const token = keycloak.token;
if (!token) {
- return request
+ return request;
}
- let headers = request.headers
+ let headers = request.headers;
if (!(headers instanceof Headers)) {
- headers = new Headers(headers as HeadersInit)
+ headers = new Headers(headers as HeadersInit);
}
- headers.set("Authorization", `Bearer ${token}`)
+ headers.set("Authorization", `Bearer ${token}`);
if (!(request.headers instanceof Headers)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
- ;(request as any).headers = headers
+ (request as any).headers = headers;
}
- return request
-})
+ return request;
+});
-export const client = heyApiClient
-export { sdk }
+export const client = heyApiClient;
+export { sdk };
diff --git a/src/lib/api-error.ts b/src/lib/api-error.ts
index 2f5a3bb..3d3d1b0 100644
--- a/src/lib/api-error.ts
+++ b/src/lib/api-error.ts
@@ -1,79 +1,95 @@
export class ApiError extends Error {
- status?: number
- details?: unknown
- raw?: unknown
+ status?: number;
+ details?: unknown;
+ raw?: unknown;
- constructor(message: string, options?: { status?: number; details?: unknown; raw?: unknown }) {
- super(message)
- this.name = "ApiError"
- this.status = options?.status
- this.details = options?.details
- this.raw = options?.raw
+ constructor(
+ message: string,
+ options?: { status?: number; details?: unknown; raw?: unknown },
+ ) {
+ super(message);
+ this.name = "ApiError";
+ this.status = options?.status;
+ this.details = options?.details;
+ this.raw = options?.raw;
}
}
function isRecord(value: unknown): value is Record {
- return typeof value === "object" && value !== null
+ return typeof value === "object" && value !== null;
}
function firstString(values: unknown[]): string | undefined {
for (const value of values) {
if (typeof value === "string" && value.trim().length > 0) {
- return value
+ return value;
}
}
- return undefined
+ return undefined;
}
function extractMessage(error: unknown): string | undefined {
if (typeof error === "string" && error.trim().length > 0) {
- return error
+ return error;
}
if (error instanceof Error && error.message.trim().length > 0) {
- return error.message
+ return error.message;
}
if (!isRecord(error)) {
- return undefined
+ return undefined;
}
- const nestedError = error.error
- const nestedMessage = isRecord(nestedError) ? nestedError.message : undefined
+ const nestedError = error.error;
+ const nestedMessage = isRecord(nestedError) ? nestedError.message : undefined;
- return firstString([error.message, error.error, error.detail, error.title, nestedMessage])
+ return firstString([
+ error.message,
+ error.error,
+ error.detail,
+ error.title,
+ nestedMessage,
+ ]);
}
function extractStatus(error: unknown): number | undefined {
if (!isRecord(error)) {
- return undefined
+ return undefined;
}
- const statusValue = error.status
+ const statusValue = error.status;
if (typeof statusValue === "number" && Number.isFinite(statusValue)) {
- return statusValue
+ return statusValue;
}
- return undefined
+ return undefined;
}
-export function normalizeApiError(error: unknown, context?: { status?: number; fallbackMessage?: string }): ApiError {
+export function normalizeApiError(
+ error: unknown,
+ context?: { status?: number; fallbackMessage?: string },
+): ApiError {
if (error instanceof ApiError) {
if (error.status === undefined && context?.status !== undefined) {
- error.status = context.status
+ error.status = context.status;
}
- return error
+ return error;
}
- const message = extractMessage(error) ?? context?.fallbackMessage ?? "Request failed"
- const status = context?.status ?? extractStatus(error)
+ const message =
+ extractMessage(error) ?? context?.fallbackMessage ?? "Request failed";
+ const status = context?.status ?? extractStatus(error);
return new ApiError(message, {
status,
details: isRecord(error) ? error : undefined,
raw: error,
- })
+ });
}
-export function getApiErrorMessage(error: unknown, fallbackMessage = "Unexpected error"): string {
- return normalizeApiError(error, { fallbackMessage }).message
+export function getApiErrorMessage(
+ error: unknown,
+ fallbackMessage = "Unexpected error",
+): string {
+ return normalizeApiError(error, { fallbackMessage }).message;
}
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 8e9ddf8..ea67d8c 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -1,8 +1,11 @@
-import { API_URL } from "@/constants/api"
-import { INFO } from "@/constants/endpoints"
+import { API_URL } from "@/constants/api";
+import { INFO } from "@/constants/endpoints";
-const apiFetch = async (endpoint: string, options: RequestInit = {}): Promise => {
- const url = `${API_URL}${endpoint}`
+const apiFetch = async (
+ endpoint: string,
+ options: RequestInit = {},
+): Promise => {
+ const url = `${API_URL}${endpoint}`;
const response = await fetch(url, {
...options,
@@ -11,43 +14,46 @@ const apiFetch = async (endpoint: string, options: RequestInit = {}
...(options.headers || {}),
}, */
headers: options.headers ?? [],
- })
+ });
if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.statusText}`)
+ throw new Error(`HTTP error! status: ${response.statusText}`);
}
- const contentLength = response.headers.get("Content-Length")
+ const contentLength = response.headers.get("Content-Length");
- if (contentLength === "0" || response.headers.get("Content-Type")?.includes("application/json") === false) {
- return {} as T
+ if (
+ contentLength === "0" ||
+ response.headers.get("Content-Type")?.includes("application/json") === false
+ ) {
+ return {} as T;
}
- return response.json() as Promise
-}
+ return response.json() as Promise;
+};
export interface InfoResponse {
- name?: string
- pubkey?: string
- version?: string
- description?: string
- description_long?: string
+ name?: string;
+ pubkey?: string;
+ version?: string;
+ description?: string;
+ description_long?: string;
contact?: {
- method?: string
- info?: string
- }[]
- motd?: string
- icon_url?: string
- urls?: string[]
- time?: number
+ method?: string;
+ info?: string;
+ }[];
+ motd?: string;
+ icon_url?: string;
+ urls?: string[];
+ time?: number;
nuts?: Record<
string,
{
- methods?: Record[]
- disabled?: boolean
- supported?: boolean
+ methods?: Record[];
+ disabled?: boolean;
+ supported?: boolean;
}
- >
+ >;
}
export async function fetchInfo(): Promise {
@@ -55,24 +61,24 @@ export async function fetchInfo(): Promise {
headers: {
"Content-Type": "application/json",
},
- })
+ });
}
export interface BalancesResponse {
bitcoin: {
- value: string
- currency: string
- }
+ value: string;
+ currency: string;
+ };
eiou: {
- value: string
- currency: string
- }
+ value: string;
+ currency: string;
+ };
debit: {
- value: string
- currency: string
- }
+ value: string;
+ currency: string;
+ };
credit: {
- value: string
- currency: string
- }
+ value: string;
+ currency: string;
+ };
}
diff --git a/src/lib/env.test.ts b/src/lib/env.test.ts
index 9df5ab1..08f7b0c 100644
--- a/src/lib/env.test.ts
+++ b/src/lib/env.test.ts
@@ -1,22 +1,22 @@
-import { afterEach, describe, expect, it, vi } from "vitest"
+import { afterEach, describe, expect, it, vi } from "vitest";
const loadEnv = async () => {
- const module = await import("./env")
- return module.env
-}
+ const module = await import("./env");
+ return module.env;
+};
afterEach(() => {
- vi.resetModules()
- vi.unstubAllGlobals()
- vi.unstubAllEnvs()
-})
+ vi.resetModules();
+ vi.unstubAllGlobals();
+ vi.unstubAllEnvs();
+});
describe("env runtime resolution", () => {
it("prefers runtime env values when provided", async () => {
- vi.stubEnv("VITE_API_BASE_URL", "https://fallback.example.com")
- vi.stubEnv("VITE_KEYCLOAK_URL", "https://fallback-keycloak.example.com")
- vi.stubEnv("VITE_KEYCLOAK_REALM", "fallback-realm")
- vi.stubEnv("VITE_KEYCLOAK_CLIENT_ID", "fallback-client")
+ vi.stubEnv("VITE_API_BASE_URL", "https://fallback.example.com");
+ vi.stubEnv("VITE_KEYCLOAK_URL", "https://fallback-keycloak.example.com");
+ vi.stubEnv("VITE_KEYCLOAK_REALM", "fallback-realm");
+ vi.stubEnv("VITE_KEYCLOAK_CLIENT_ID", "fallback-client");
vi.stubGlobal("window", {
__ENV__: {
@@ -27,25 +27,25 @@ describe("env runtime resolution", () => {
VITE_KEYCLOAK_CLIENT_ID: "runtime-client",
VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING: "true",
},
- })
+ });
- const env = await loadEnv()
+ const env = await loadEnv();
- expect(env.apiBaseUrl).toBe("https://runtime.example.com")
- expect(env.apiMocksEnabled).toBe(true)
- expect(env.keycloakUrl).toBe("https://runtime-keycloak.example.com")
- expect(env.keycloakRealm).toBe("runtime-realm")
- expect(env.keycloakClientId).toBe("runtime-client")
- expect(env.crowdinInContextToolingEnabled).toBe(true)
- })
+ expect(env.apiBaseUrl).toBe("https://runtime.example.com");
+ expect(env.apiMocksEnabled).toBe(true);
+ expect(env.keycloakUrl).toBe("https://runtime-keycloak.example.com");
+ expect(env.keycloakRealm).toBe("runtime-realm");
+ expect(env.keycloakClientId).toBe("runtime-client");
+ expect(env.crowdinInContextToolingEnabled).toBe(true);
+ });
it("falls back to build-time env when runtime values are empty", async () => {
- vi.stubEnv("VITE_API_BASE_URL", "https://fallback.example.com")
- vi.stubEnv("VITE_API_MOCKING_ENABLED", "true")
- vi.stubEnv("VITE_KEYCLOAK_URL", "https://fallback-keycloak.example.com")
- vi.stubEnv("VITE_KEYCLOAK_REALM", "fallback-realm")
- vi.stubEnv("VITE_KEYCLOAK_CLIENT_ID", "fallback-client")
- vi.stubEnv("VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING", "false")
+ vi.stubEnv("VITE_API_BASE_URL", "https://fallback.example.com");
+ vi.stubEnv("VITE_API_MOCKING_ENABLED", "true");
+ vi.stubEnv("VITE_KEYCLOAK_URL", "https://fallback-keycloak.example.com");
+ vi.stubEnv("VITE_KEYCLOAK_REALM", "fallback-realm");
+ vi.stubEnv("VITE_KEYCLOAK_CLIENT_ID", "fallback-client");
+ vi.stubEnv("VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING", "false");
vi.stubGlobal("window", {
__ENV__: {
@@ -56,31 +56,31 @@ describe("env runtime resolution", () => {
VITE_KEYCLOAK_CLIENT_ID: "",
VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING: "",
},
- })
+ });
- const env = await loadEnv()
+ const env = await loadEnv();
- expect(env.apiBaseUrl).toBe("https://fallback.example.com")
- expect(env.apiMocksEnabled).toBe(true)
- expect(env.keycloakUrl).toBe("https://fallback-keycloak.example.com")
- expect(env.keycloakRealm).toBe("fallback-realm")
- expect(env.keycloakClientId).toBe("fallback-client")
- expect(env.crowdinInContextToolingEnabled).toBe(false)
- })
+ expect(env.apiBaseUrl).toBe("https://fallback.example.com");
+ expect(env.apiMocksEnabled).toBe(true);
+ expect(env.keycloakUrl).toBe("https://fallback-keycloak.example.com");
+ expect(env.keycloakRealm).toBe("fallback-realm");
+ expect(env.keycloakClientId).toBe("fallback-client");
+ expect(env.crowdinInContextToolingEnabled).toBe(false);
+ });
it("handles SSR where window is undefined", async () => {
- vi.stubEnv("VITE_API_BASE_URL", "https://fallback.example.com")
- vi.stubEnv("VITE_KEYCLOAK_URL", "https://fallback-keycloak.example.com")
- vi.stubEnv("VITE_KEYCLOAK_REALM", "fallback-realm")
- vi.stubEnv("VITE_KEYCLOAK_CLIENT_ID", "fallback-client")
+ vi.stubEnv("VITE_API_BASE_URL", "https://fallback.example.com");
+ vi.stubEnv("VITE_KEYCLOAK_URL", "https://fallback-keycloak.example.com");
+ vi.stubEnv("VITE_KEYCLOAK_REALM", "fallback-realm");
+ vi.stubEnv("VITE_KEYCLOAK_CLIENT_ID", "fallback-client");
- vi.stubGlobal("window", undefined)
+ vi.stubGlobal("window", undefined);
- const env = await loadEnv()
+ const env = await loadEnv();
- expect(env.apiBaseUrl).toBe("https://fallback.example.com")
- expect(env.keycloakUrl).toBe("https://fallback-keycloak.example.com")
- expect(env.keycloakRealm).toBe("fallback-realm")
- expect(env.keycloakClientId).toBe("fallback-client")
- })
-})
+ expect(env.apiBaseUrl).toBe("https://fallback.example.com");
+ expect(env.keycloakUrl).toBe("https://fallback-keycloak.example.com");
+ expect(env.keycloakRealm).toBe("fallback-realm");
+ expect(env.keycloakClientId).toBe("fallback-client");
+ });
+});
diff --git a/src/lib/env.ts b/src/lib/env.ts
index eadccc6..4e9f39f 100644
--- a/src/lib/env.ts
+++ b/src/lib/env.ts
@@ -1,33 +1,40 @@
type RuntimeEnv = Partial<{
- VITE_API_BASE_URL: string
- VITE_API_MOCKING_ENABLED: string
- VITE_KEYCLOAK_URL: string
- VITE_KEYCLOAK_REALM: string
- VITE_KEYCLOAK_CLIENT_ID: string
- VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING: string
-}>
+ VITE_API_BASE_URL: string;
+ VITE_API_MOCKING_ENABLED: string;
+ VITE_KEYCLOAK_URL: string;
+ VITE_KEYCLOAK_REALM: string;
+ VITE_KEYCLOAK_CLIENT_ID: string;
+ VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING: string;
+}>;
-const runtimeEnv: RuntimeEnv = typeof window !== "undefined" ? ((window as { __ENV__?: RuntimeEnv }).__ENV__ ?? {}) : {}
+const runtimeEnv: RuntimeEnv =
+ typeof window !== "undefined"
+ ? ((window as { __ENV__?: RuntimeEnv }).__ENV__ ?? {})
+ : {};
-const fallbackEnv = import.meta.env as ImportMetaEnv & RuntimeEnv
+const fallbackEnv = import.meta.env as ImportMetaEnv & RuntimeEnv;
-const getEnvValue = (key: K): RuntimeEnv[K] | undefined => {
- const value = runtimeEnv[key]
+const getEnvValue = (
+ key: K,
+): RuntimeEnv[K] | undefined => {
+ const value = runtimeEnv[key];
if (value !== undefined && value !== null && value !== "") {
- return value
+ return value;
}
- return fallbackEnv[key]
-}
+ return fallbackEnv[key];
+};
export const env = {
devModeEnabled: fallbackEnv.DEV,
apiBaseUrl: getEnvValue("VITE_API_BASE_URL")!,
- apiMocksEnabled: (getEnvValue("VITE_API_MOCKING_ENABLED") ?? "false") === "true",
+ apiMocksEnabled:
+ (getEnvValue("VITE_API_MOCKING_ENABLED") ?? "false") === "true",
keycloakUrl: getEnvValue("VITE_KEYCLOAK_URL")!,
keycloakRealm: getEnvValue("VITE_KEYCLOAK_REALM")!,
keycloakClientId: getEnvValue("VITE_KEYCLOAK_CLIENT_ID")!,
crowdinInContextToolingEnabled:
- (getEnvValue("VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING") ?? "false") === "true",
-}
+ (getEnvValue("VITE_BITCR_DEV_INCLUDE_CROWDIN_IN_CONTEXT_TOOLING") ??
+ "false") === "true",
+};
diff --git a/src/lib/keycloak-user.ts b/src/lib/keycloak-user.ts
index b9cc87b..3130c82 100644
--- a/src/lib/keycloak-user.ts
+++ b/src/lib/keycloak-user.ts
@@ -1,94 +1,96 @@
-import { useState, useEffect } from "react"
-import keycloak from "../keycloak"
+import { useState, useEffect } from "react";
+import keycloak from "../keycloak";
interface KeycloakProfile {
- username?: string
- email?: string
+ username?: string;
+ email?: string;
}
interface KeycloakTokenParsed {
- preferred_username?: string
- email?: string
+ preferred_username?: string;
+ email?: string;
}
interface KeycloakUser {
- name: string
- email: string
- avatar: string
+ name: string;
+ email: string;
+ avatar: string;
}
interface UseKeycloakReturn {
- isAuthenticated: boolean
- isLoading: boolean
- user: KeycloakUser | null
+ isAuthenticated: boolean;
+ isLoading: boolean;
+ user: KeycloakUser | null;
}
export function useKeycloak(): UseKeycloakReturn {
- const [isAuthenticated, setIsAuthenticated] = useState(false)
- const [isLoading, setIsLoading] = useState(true)
- const [user, setUser] = useState(null)
+ const [isAuthenticated, setIsAuthenticated] = useState(false);
+ const [isLoading, setIsLoading] = useState(true);
+ const [user, setUser] = useState(null);
useEffect(() => {
const updateAuthState = () => {
if (keycloak.authenticated) {
- setIsAuthenticated(true)
+ setIsAuthenticated(true);
setUser({
name:
(keycloak.profile as KeycloakProfile)?.username ??
(keycloak.tokenParsed as KeycloakTokenParsed)?.preferred_username ??
"Unknown User",
email:
- (keycloak.profile as KeycloakProfile)?.email ?? (keycloak.tokenParsed as KeycloakTokenParsed)?.email ?? "",
+ (keycloak.profile as KeycloakProfile)?.email ??
+ (keycloak.tokenParsed as KeycloakTokenParsed)?.email ??
+ "",
avatar: "",
- })
+ });
} else {
- setIsAuthenticated(false)
- setUser(null)
+ setIsAuthenticated(false);
+ setUser(null);
}
- setIsLoading(false)
- }
+ setIsLoading(false);
+ };
if (keycloak.authenticated) {
- updateAuthState()
+ updateAuthState();
}
keycloak.onAuthSuccess = () => {
- updateAuthState()
- }
+ updateAuthState();
+ };
keycloak.onAuthError = () => {
- setIsAuthenticated(false)
- setUser(null)
- setIsLoading(false)
- }
+ setIsAuthenticated(false);
+ setUser(null);
+ setIsLoading(false);
+ };
keycloak.onAuthLogout = () => {
- setIsAuthenticated(false)
- setUser(null)
- setIsLoading(false)
- }
+ setIsAuthenticated(false);
+ setUser(null);
+ setIsLoading(false);
+ };
if (!keycloak.authenticated && !keycloak.loginRequired) {
const checkAuth = () => {
if (keycloak.authenticated || keycloak.loginRequired) {
- updateAuthState()
+ updateAuthState();
} else {
- setTimeout(checkAuth, 100)
+ setTimeout(checkAuth, 100);
}
- }
- checkAuth()
+ };
+ checkAuth();
}
return () => {
- keycloak.onAuthSuccess = undefined
- keycloak.onAuthError = undefined
- keycloak.onAuthLogout = undefined
- }
- }, [])
+ keycloak.onAuthSuccess = undefined;
+ keycloak.onAuthError = undefined;
+ keycloak.onAuthLogout = undefined;
+ };
+ }, []);
return {
isAuthenticated,
isLoading,
user,
- }
+ };
}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 12ed9a2..0d70de9 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -1,16 +1,16 @@
-import { clsx, type ClassValue } from "clsx"
-import { twMerge } from "tailwind-merge"
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
- return twMerge(clsx(inputs))
+ return twMerge(clsx(inputs));
}
export const getInitials = (name: string | undefined): string => {
- if (!name) return ""
+ if (!name) return "";
return name
.split(" ")
.filter(Boolean)
.slice(0, 2)
.map((word) => word[0].toUpperCase())
- .join("")
-}
+ .join("");
+};
diff --git a/src/main.tsx b/src/main.tsx
index fa24e99..96c3036 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,29 +1,29 @@
-import { StrictMode } from "react"
-import { createRoot } from "react-dom/client"
-import { BrowserRouter, Route, Routes } from "react-router"
-import "./index.css"
-import Layout from "./layout"
-import HomePage from "./pages/home/HomePage"
-import BalancesPage from "./pages/balances/BalancesPage"
-import SettingsPage from "./pages/settings/SettingsPage"
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
-import InfoPage from "./pages/info/InfoPage"
-import QuotePage from "./pages/quotes/QuotePage"
-import StatusQuotePage from "./pages/quotes/StatusQuotePage"
-import { Toaster } from "./components/ui/sonner"
-import EarningsPage from "./pages/balances/EarningsPage"
-import CashFlowPage from "./pages/balances/CashFlowPage"
-import { initKeycloak } from "./keycloak"
-import "./lib/api-client"
-import KeysetsPage from "@/pages/keysets/KeysetsPage"
-import KeysetDetailPage from "@/pages/keysets/KeysetDetailPage"
-import { LanguageProvider } from "@/context/language/LanguageProvider"
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { BrowserRouter, Route, Routes } from "react-router";
+import "./index.css";
+import Layout from "./layout";
+import HomePage from "./pages/home/HomePage";
+import BalancesPage from "./pages/balances/BalancesPage";
+import SettingsPage from "./pages/settings/SettingsPage";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import InfoPage from "./pages/info/InfoPage";
+import QuotePage from "./pages/quotes/QuotePage";
+import StatusQuotePage from "./pages/quotes/StatusQuotePage";
+import { Toaster } from "./components/ui/sonner";
+import EarningsPage from "./pages/balances/EarningsPage";
+import CashFlowPage from "./pages/balances/CashFlowPage";
+import { initKeycloak } from "./keycloak";
+import "./lib/api-client";
+import KeysetsPage from "@/pages/keysets/KeysetsPage";
+import KeysetDetailPage from "@/pages/keysets/KeysetDetailPage";
+import { LanguageProvider } from "@/context/language/LanguageProvider";
-const queryClient = new QueryClient()
+const queryClient = new QueryClient();
const prepare = async () => {
- await initKeycloak()
-}
+ await initKeycloak();
+};
function App() {
return (
@@ -31,28 +31,79 @@ function App() {
}>
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
- )
+ );
}
void prepare().then(() => {
@@ -63,7 +114,7 @@ void prepare().then(() => {
,
- )
-})
+ );
+});
-export { App }
+export { App };
diff --git a/src/pages/balances/BalancesPage.tsx b/src/pages/balances/BalancesPage.tsx
index 927669c..cc61cff 100644
--- a/src/pages/balances/BalancesPage.tsx
+++ b/src/pages/balances/BalancesPage.tsx
@@ -1,13 +1,18 @@
-import { PropsWithChildren, Suspense } from "react"
-import { useQuery } from "@tanstack/react-query"
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { PageTitle } from "@/components/PageTitle"
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
-import { Skeleton } from "@/components/ui/skeleton"
-import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
-import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } from "@/components/ui/chart"
-import { getClowderLocalCoverageOptions } from "@/generated/client/@tanstack/react-query.gen"
-import { FormattedMessage, useIntl } from "react-intl"
+import { PropsWithChildren, Suspense } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { PageTitle } from "@/components/PageTitle";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
+import {
+ type ChartConfig,
+ ChartContainer,
+ ChartLegend,
+ ChartLegendContent,
+} from "@/components/ui/chart";
+import { getClowderLocalCoverageOptions } from "@/generated/client/@tanstack/react-query.gen";
+import { FormattedMessage, useIntl } from "react-intl";
function Loader() {
return (
@@ -23,7 +28,7 @@ function Loader() {
- )
+ );
}
const getMonthLabels = (intl: ReturnType) => [
@@ -39,18 +44,21 @@ const getMonthLabels = (intl: ReturnType) => [
intl.formatMessage({ id: "month.oct.short", defaultMessage: "Oct" }),
intl.formatMessage({ id: "month.nov.short", defaultMessage: "Nov" }),
intl.formatMessage({ id: "month.dec.short", defaultMessage: "Dec" }),
-]
+];
export function BitcoinBalanceChart() {
- const intl = useIntl()
+ const intl = useIntl();
const config = {
bitcoin: {
- label: intl.formatMessage({ id: "balances.chart.bitcoin", defaultMessage: "Bitcoin" }),
+ label: intl.formatMessage({
+ id: "balances.chart.bitcoin",
+ defaultMessage: "Bitcoin",
+ }),
color: "#2563eb",
},
- } satisfies ChartConfig
+ } satisfies ChartConfig;
- const months = getMonthLabels(intl)
+ const months = getMonthLabels(intl);
const data = [
{ month: months[0], bitcoin: 186 },
@@ -65,11 +73,17 @@ export function BitcoinBalanceChart() {
{ month: months[9], bitcoin: 0 },
{ month: months[10], bitcoin: 0 },
{ month: months[11], bitcoin: 0 },
- ]
+ ];
return (
-
-
+
+
value}
/>
-
-
+
+
} />
- )
+ );
}
export function OtherBalanceChart() {
- const intl = useIntl()
+ const intl = useIntl();
const config = {
eIOU: {
- label: intl.formatMessage({ id: "balances.chart.eiou", defaultMessage: "e-IOU" }),
+ label: intl.formatMessage({
+ id: "balances.chart.eiou",
+ defaultMessage: "e-IOU",
+ }),
color: "#911198",
},
credit: {
- label: intl.formatMessage({ id: "balances.chart.creditToken", defaultMessage: "Credit token" }),
+ label: intl.formatMessage({
+ id: "balances.chart.creditToken",
+ defaultMessage: "Credit token",
+ }),
color: "#e9d4ff",
},
debit: {
- label: intl.formatMessage({ id: "balances.chart.debitToken", defaultMessage: "Debit token" }),
+ label: intl.formatMessage({
+ id: "balances.chart.debitToken",
+ defaultMessage: "Debit token",
+ }),
color: "#c27aff",
},
- } satisfies ChartConfig
+ } satisfies ChartConfig;
- const months = getMonthLabels(intl)
+ const months = getMonthLabels(intl);
const data = [
{ month: months[0], credit: 121, debit: 0 },
@@ -118,11 +150,17 @@ export function OtherBalanceChart() {
{ month: months[9], credit: 1782, debit: 1232 },
{ month: months[10], credit: 0, debit: 0 },
{ month: months[11], credit: 0, debit: 0 },
- ]
+ ];
return (
-
-
+
+
value}
/>
-
-
-
-
+
+
+
+
} />
- )
+ );
}
interface BalanceDisplay {
- amount: string
- unit: string
+ amount: string;
+ unit: string;
}
-export function BalanceText({ amount, unit, children }: PropsWithChildren) {
+export function BalanceText({
+ amount,
+ unit,
+ children,
+}: PropsWithChildren) {
return (
<>
@@ -154,7 +214,7 @@ export function BalanceText({ amount, unit, children }: PropsWithChildren
{children}
>
- )
+ );
}
function useBalances() {
@@ -167,9 +227,9 @@ function useBalances() {
refetchInterval: 30_000,
staleTime: 25_000,
retry: 2,
- })
+ });
- const error = isError ? "Failed to load coverage data" : null
+ const error = isError ? "Failed to load coverage data" : null;
const balances: Record = {
bitcoin: {
@@ -188,13 +248,13 @@ function useBalances() {
amount: coverage?.debit_circulating_supply?.toString() ?? "0",
unit: "sat",
},
- }
+ };
- return { balances, error, refetch }
+ return { balances, error, refetch };
}
function PageBodyWithDevSection() {
- const { balances, error } = useBalances()
+ const { balances, error } = useBalances();
if (error) {
return (
@@ -213,7 +273,7 @@ function PageBodyWithDevSection() {
>
- )
+ );
}
return (
@@ -223,41 +283,65 @@ function PageBodyWithDevSection() {
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -277,22 +361,28 @@ function PageBodyWithDevSection() {
*/}
>
- )
+ );
}
export default function BalancesPage() {
return (
<>
-
+
-
+
}>
>
- )
+ );
}
diff --git a/src/pages/balances/CashFlowPage.tsx b/src/pages/balances/CashFlowPage.tsx
index 5dc342a..0ee74ce 100644
--- a/src/pages/balances/CashFlowPage.tsx
+++ b/src/pages/balances/CashFlowPage.tsx
@@ -1,11 +1,23 @@
-import { Suspense } from "react"
-import { Link } from "react-router"
-import { CartesianGrid, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts"
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { PageTitle } from "@/components/PageTitle"
-import { Skeleton } from "@/components/ui/skeleton"
-import { ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } from "@/components/ui/chart"
-import { FormattedMessage, useIntl } from "react-intl"
+import { Suspense } from "react";
+import { Link } from "react-router";
+import {
+ CartesianGrid,
+ Line,
+ LineChart,
+ Tooltip,
+ XAxis,
+ YAxis,
+} from "recharts";
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { PageTitle } from "@/components/PageTitle";
+import { Skeleton } from "@/components/ui/skeleton";
+import {
+ ChartConfig,
+ ChartContainer,
+ ChartLegend,
+ ChartLegendContent,
+} from "@/components/ui/chart";
+import { FormattedMessage, useIntl } from "react-intl";
function Loader() {
return (
@@ -15,17 +27,20 @@ function Loader() {
- )
+ );
}
function CashFlowChart() {
- const intl = useIntl()
+ const intl = useIntl();
const config = {
bitcoin: {
- label: intl.formatMessage({ id: "balances.chart.bitcoin", defaultMessage: "Bitcoin" }),
+ label: intl.formatMessage({
+ id: "balances.chart.bitcoin",
+ defaultMessage: "Bitcoin",
+ }),
color: "#2563eb",
},
- } satisfies ChartConfig
+ } satisfies ChartConfig;
const months = [
intl.formatMessage({ id: "month.jan.short", defaultMessage: "Jan" }),
@@ -40,7 +55,7 @@ function CashFlowChart() {
intl.formatMessage({ id: "month.oct.short", defaultMessage: "Oct" }),
intl.formatMessage({ id: "month.nov.short", defaultMessage: "Nov" }),
intl.formatMessage({ id: "month.dec.short", defaultMessage: "Dec" }),
- ]
+ ];
const data = [
{ month: months[0], bitcoin: 186 },
@@ -55,10 +70,13 @@ function CashFlowChart() {
{ month: months[9], bitcoin: 0 },
{ month: months[10], bitcoin: 0 },
{ month: months[11], bitcoin: 0 },
- ]
+ ];
return (
-
+
value}
/>
-
-
-
+
+
+
} />
- )
+ );
}
function CashFlow() {
@@ -91,7 +122,7 @@ function CashFlow() {
- )
+ );
}
function PageBody() {
@@ -99,7 +130,7 @@ function PageBody() {
- )
+ );
}
export default function CashFlowPage() {
@@ -109,20 +140,29 @@ export default function CashFlowPage() {
parents={[
<>
-
+
>,
]}
>
-
+
-
+
}>
>
- )
+ );
}
diff --git a/src/pages/balances/EarningsPage.tsx b/src/pages/balances/EarningsPage.tsx
index 71110ae..48e13c7 100644
--- a/src/pages/balances/EarningsPage.tsx
+++ b/src/pages/balances/EarningsPage.tsx
@@ -1,12 +1,12 @@
-import { Suspense, useState } from "react"
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { PageTitle } from "@/components/PageTitle"
-import { Skeleton } from "@/components/ui/skeleton"
-import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
-import { Link } from "react-router"
-import { Button } from "@/components/ui/button"
-import { ChartColumnIncreasingIcon } from "lucide-react"
-import { FormattedMessage } from "react-intl"
+import { Suspense, useState } from "react";
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { PageTitle } from "@/components/PageTitle";
+import { Skeleton } from "@/components/ui/skeleton";
+import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
+import { Link } from "react-router";
+import { Button } from "@/components/ui/button";
+import { ChartColumnIncreasingIcon } from "lucide-react";
+import { FormattedMessage } from "react-intl";
function Loader() {
return (
@@ -16,21 +16,26 @@ function Loader() {
- )
+ );
}
function Earnings() {
- const [timeframe, setTimeframe] = useState("1d")
+ const [timeframe, setTimeframe] = useState("1d");
return (
-
0.00 000 000 BTC
+
+ 0.00 000 000 BTC
+
@@ -43,35 +48,74 @@ function Earnings() {
value={timeframe}
onValueChange={(val) => setTimeframe((curr) => val || curr)}
>
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
- )
+ );
}
function PageBody() {
@@ -80,27 +124,36 @@ function PageBody() {
-
+
- )
+ );
}
export default function EarningsPage() {
return (
<>
-
+
-
+
}>
>
- )
+ );
}
diff --git a/src/pages/home/HomePage.tsx b/src/pages/home/HomePage.tsx
index d007f4b..b18b5a6 100644
--- a/src/pages/home/HomePage.tsx
+++ b/src/pages/home/HomePage.tsx
@@ -1,30 +1,30 @@
-import { PageTitle } from "@/components/PageTitle"
-import { Skeleton } from "@/components/ui/skeleton"
-import { useQuery } from "@tanstack/react-query"
-import { Suspense } from "react"
-import { CopyButton } from "@/components/CopyButton"
+import { PageTitle } from "@/components/PageTitle";
+import { Skeleton } from "@/components/ui/skeleton";
+import { useQuery } from "@tanstack/react-query";
+import { Suspense } from "react";
+import { CopyButton } from "@/components/CopyButton";
import {
getIdentityOptions,
getClowderInfoOptions,
getMintInfoOptions,
-} from "@/generated/client/@tanstack/react-query.gen"
-import { FormattedMessage, useIntl } from "react-intl"
+} from "@/generated/client/@tanstack/react-query.gen";
+import { FormattedMessage, useIntl } from "react-intl";
function Loader() {
return (
- )
+ );
}
function PageBody() {
- const intl = useIntl()
+ const intl = useIntl();
const { data: identityData } = useQuery({
...getIdentityOptions(),
staleTime: Infinity,
gcTime: Infinity,
- })
+ });
const {
data: mintData,
@@ -33,7 +33,7 @@ function PageBody() {
} = useQuery({
...getMintInfoOptions(),
staleTime: 60_000,
- })
+ });
const {
data: clowderData,
@@ -42,45 +42,67 @@ function PageBody() {
} = useQuery({
...getClowderInfoOptions(),
staleTime: 60_000,
- })
+ });
return (
-
+
{identityData ? (
-
+
+
+
+ {identityData.name}
- {identityData.name}
{identityData.email && (
-
+
+
+
+ {identityData.email}
- {identityData.email}
)}
-
+
-
+
@@ -90,7 +112,10 @@ function PageBody() {
-
+
-
+
-
+
-
{identityData.postal_address.address}
+
+ {identityData.postal_address.address}
+
{identityData.postal_address.city}
- {identityData.postal_address.zip && `, ${identityData.postal_address.zip}`}
+ {identityData.postal_address.zip &&
+ `, ${identityData.postal_address.zip}`}
+
+
+ {identityData.postal_address.country}
-
{identityData.postal_address.country}
@@ -144,33 +180,51 @@ function PageBody() {
) : (
-
+
)}
-
+
{clowderLoading ? (
-
+
) : clowderError ? (
-
+
) : clowderData ? (
-
+
@@ -180,26 +234,40 @@ function PageBody() {
-
+
{clowderData.version}
-
+
+
+
+ {clowderData.network}
- {clowderData.network}
-
+
@@ -210,38 +278,58 @@ function PageBody() {
{clowderData.uptime_timestamp && (
-
+
- {new Date(clowderData.uptime_timestamp * 1000).toLocaleString(undefined, { timeZone: "UTC" })}
+ {new Date(
+ clowderData.uptime_timestamp * 1000,
+ ).toLocaleString(undefined, { timeZone: "UTC" })}
)}
) : (
-
+
)}
-
+
{mintLoading ? (
-
+
) : mintError ? (
-
+
) : mintData ? (
-
+
{mintData.network}
@@ -249,7 +337,10 @@ function PageBody() {
-
+
-
+
-
+
-
+
+
+
+ {mintData.versions.wildcat}
- {mintData.versions.wildcat}
-
+
+
+
+ {mintData.versions.bcr_ebill_core}
- {mintData.versions.bcr_ebill_core}
-
+
+
+
+ {mintData.versions.cdk_mintd}
- {mintData.versions.cdk_mintd}
-
+
+
+
+ {mintData.versions.clowder}
- {mintData.versions.clowder}
@@ -318,19 +435,31 @@ function PageBody() {
-
+
- {new Date(mintData.uptime_timestamp).toLocaleString(undefined, { timeZone: "UTC" })}
+ {new Date(mintData.uptime_timestamp).toLocaleString(
+ undefined,
+ { timeZone: "UTC" },
+ )}
{mintData.build_time && (
-
+
- {new Date(mintData.build_time).toLocaleString(undefined, { timeZone: "UTC" })}
+ {new Date(mintData.build_time).toLocaleString(
+ undefined,
+ { timeZone: "UTC" },
+ )}
)}
@@ -339,24 +468,30 @@ function PageBody() {
) : (
-
+
)}
- )
+ );
}
export default function HomePage() {
return (
<>
-
+
}>
>
- )
+ );
}
diff --git a/src/pages/info/InfoPage.tsx b/src/pages/info/InfoPage.tsx
index e303c3b..0fa30b5 100644
--- a/src/pages/info/InfoPage.tsx
+++ b/src/pages/info/InfoPage.tsx
@@ -1,24 +1,24 @@
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { PageTitle } from "@/components/PageTitle"
-import { Skeleton } from "@/components/ui/skeleton"
-import { fetchInfo } from "@/lib/api"
-import { useSuspenseQuery } from "@tanstack/react-query"
-import { Suspense } from "react"
-import { FormattedMessage } from "react-intl"
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { PageTitle } from "@/components/PageTitle";
+import { Skeleton } from "@/components/ui/skeleton";
+import { fetchInfo } from "@/lib/api";
+import { useSuspenseQuery } from "@tanstack/react-query";
+import { Suspense } from "react";
+import { FormattedMessage } from "react-intl";
function Loader() {
return (
- )
+ );
}
function PageBody() {
const { data } = useSuspenseQuery({
queryKey: ["info"],
queryFn: fetchInfo,
- })
+ });
return (
<>
@@ -26,21 +26,27 @@ function PageBody() {
{JSON.stringify(data, null, 2)}
>
- )
+ );
}
export default function InfoPage() {
return (
<>
-
+
-
+
}>
>
- )
+ );
}
diff --git a/src/pages/keysets/KeysetDetailPage.test.tsx b/src/pages/keysets/KeysetDetailPage.test.tsx
index 981ae92..0f3b934 100644
--- a/src/pages/keysets/KeysetDetailPage.test.tsx
+++ b/src/pages/keysets/KeysetDetailPage.test.tsx
@@ -1,82 +1,87 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { IntlProvider } from "react-intl"
-import { MemoryRouter, Route, Routes } from "react-router"
-import KeysetDetailPage from "./KeysetDetailPage"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { IntlProvider } from "react-intl";
+import { MemoryRouter, Route, Routes } from "react-router";
+import KeysetDetailPage from "./KeysetDetailPage";
interface QueryKeyEntry {
- _id: string
+ _id: string;
}
interface QueryOptions {
- queryKey: QueryKeyEntry[]
+ queryKey: QueryKeyEntry[];
}
interface QueryResult {
- data: unknown
- isLoading: boolean
+ data: unknown;
+ isLoading: boolean;
}
interface UseQueriesArgs {
- queries: { queryKey?: { _id?: string }[] }[]
+ queries: { queryKey?: { _id?: string }[] }[];
}
interface UseQueriesResultItem {
- isLoading: boolean
- data?: { bill?: { id?: string; maturity_date?: string }; complete?: boolean }
+ isLoading: boolean;
+ data?: { bill?: { id?: string; maturity_date?: string }; complete?: boolean };
}
interface MutationResult {
- mutate: (value: { body: { kid: string } }) => void
- isPending: boolean
+ mutate: (value: { body: { kid: string } }) => void;
+ isPending: boolean;
}
-const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>()
-const mockUseQueries = vi.fn<(args: UseQueriesArgs) => UseQueriesResultItem[]>()
-const mockUseMutation = vi.fn<() => MutationResult>()
-const mutateSpy = vi.fn<(value: { body: { kid: string } }) => void>()
+const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>();
+const mockUseQueries =
+ vi.fn<(args: UseQueriesArgs) => UseQueriesResultItem[]>();
+const mockUseMutation = vi.fn<() => MutationResult>();
+const mutateSpy = vi.fn<(value: { body: { kid: string } }) => void>();
vi.mock("sonner", () => ({
toast: { success: vi.fn(), error: vi.fn() },
-}))
+}));
vi.mock("@tanstack/react-query", async () => {
- const actual = await vi.importActual
("@tanstack/react-query")
+ const actual = await vi.importActual(
+ "@tanstack/react-query",
+ );
return {
...actual,
useQuery: (options: QueryOptions) => mockUseQuery(options),
useQueries: (args: UseQueriesArgs) => mockUseQueries(args),
useMutation: () => mockUseMutation(),
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
- }
-})
+ };
+});
vi.mock("@/generated/client/@tanstack/react-query.gen", () => ({
listKeysetInfosOptions: () => ({ queryKey: [{ _id: "listKeysetInfos" }] }),
listQuotesOptions: () => ({ queryKey: [{ _id: "listQuotes" }] }),
listEbillsOptions: () => ({ queryKey: [{ _id: "listEbills" }] }),
- getQuoteOptions: ({ path }: { path: { qid: string } }) => ({ queryKey: [{ _id: "getQuote", path }] }),
+ getQuoteOptions: ({ path }: { path: { qid: string } }) => ({
+ queryKey: [{ _id: "getQuote", path }],
+ }),
getEbillMintCompleteOptions: ({ path }: { path: { bid: string } }) => ({
queryKey: [{ _id: "getEbillMintComplete", path }],
}),
postEnableRedemptionMutation: () => ({ mutationFn: vi.fn() }),
listKeysetInfosQueryKey: () => [{ _id: "listKeysetInfos" }],
-}))
+}));
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
function renderPage(route: string): HTMLDivElement {
@@ -84,86 +89,112 @@ function renderPage(route: string): HTMLDivElement {
- } />
- } />
+ }
+ />
+ }
+ />
,
- )
+ );
}
beforeEach(() => {
- vi.clearAllMocks()
+ vi.clearAllMocks();
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
- mockUseMutation.mockReturnValue({ mutate: mutateSpy, isPending: false })
+ mockUseMutation.mockReturnValue({ mutate: mutateSpy, isPending: false });
mockUseQueries.mockImplementation(({ queries }: UseQueriesArgs) => {
- const id = queries[0]?.queryKey?.[0]?._id
+ const id = queries[0]?.queryKey?.[0]?._id;
if (id === "getQuote") {
- return [{ isLoading: false, data: { bill: { id: "bill-1", maturity_date: "2026-02-20" } } }]
+ return [
+ {
+ isLoading: false,
+ data: { bill: { id: "bill-1", maturity_date: "2026-02-20" } },
+ },
+ ];
}
if (id === "getEbillMintComplete") {
- return [{ isLoading: false, data: { complete: true } }]
+ return [{ isLoading: false, data: { complete: true } }];
}
- return []
- })
-})
+ return [];
+ });
+});
describe("KeysetDetailPage", () => {
it("shows invalid keyset id when route has no :keysetId", () => {
- const page = renderPage("/keysets")
- expect(page.textContent).toContain("Invalid keyset ID")
- })
+ const page = renderPage("/keysets");
+ expect(page.textContent).toContain("Invalid keyset ID");
+ });
it("shows not found when keyset does not exist", () => {
mockUseQuery.mockImplementation((opts: QueryOptions) => {
- const id = opts.queryKey[0]._id
+ const id = opts.queryKey[0]._id;
if (id === "listKeysetInfos") {
- return { data: [{ id: "other-keyset" }], isLoading: false }
+ return { data: [{ id: "other-keyset" }], isLoading: false };
}
if (id === "listQuotes") {
- return { data: { quotes: [] }, isLoading: false }
+ return { data: { quotes: [] }, isLoading: false };
}
if (id === "listEbills") {
- return { data: [], isLoading: false }
+ return { data: [], isLoading: false };
}
- return { data: undefined, isLoading: false }
- })
+ return { data: undefined, isLoading: false };
+ });
- const page = renderPage("/keysets/target-keyset")
- expect(page.textContent).toContain("Keyset not found")
- })
+ const page = renderPage("/keysets/target-keyset");
+ expect(page.textContent).toContain("Keyset not found");
+ });
it("enables redemption and calls mutation when Redeem is clicked", () => {
mockUseQuery.mockImplementation((opts: QueryOptions) => {
- const id = opts.queryKey[0]._id
+ const id = opts.queryKey[0]._id;
if (id === "listKeysetInfos") {
return {
- data: [{ id: "keyset-1", active: true, final_expiry: 1771545600, unit: "sat" }],
+ data: [
+ {
+ id: "keyset-1",
+ active: true,
+ final_expiry: 1771545600,
+ unit: "sat",
+ },
+ ],
isLoading: false,
- }
+ };
}
if (id === "listQuotes") {
- return { data: { quotes: [{ id: "quote-1", status: "Pending", sum: 100 }] }, isLoading: false }
+ return {
+ data: { quotes: [{ id: "quote-1", status: "Pending", sum: 100 }] },
+ isLoading: false,
+ };
}
if (id === "listEbills") {
- return { data: [{ id: "bill-1", status: { payment: { paid: true } } }], isLoading: false }
+ return {
+ data: [{ id: "bill-1", status: { payment: { paid: true } } }],
+ isLoading: false,
+ };
}
- return { data: undefined, isLoading: false }
- })
-
- const page = renderPage("/keysets/keyset-1")
- const redeemButton = Array.from(page.querySelectorAll("button")).find((button) => button.textContent === "Redeem")
- expect(redeemButton?.disabled).toBe(false)
+ return { data: undefined, isLoading: false };
+ });
+
+ const page = renderPage("/keysets/keyset-1");
+ const redeemButton = Array.from(page.querySelectorAll("button")).find(
+ (button) => button.textContent === "Redeem",
+ );
+ expect(redeemButton?.disabled).toBe(false);
act(() => {
- redeemButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }))
- })
- expect(mutateSpy).toHaveBeenCalledWith({ body: { kid: "keyset-1" } })
- })
-})
+ redeemButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ });
+ expect(mutateSpy).toHaveBeenCalledWith({ body: { kid: "keyset-1" } });
+ });
+});
diff --git a/src/pages/keysets/KeysetDetailPage.tsx b/src/pages/keysets/KeysetDetailPage.tsx
index f61f1f4..9d0f03a 100644
--- a/src/pages/keysets/KeysetDetailPage.tsx
+++ b/src/pages/keysets/KeysetDetailPage.tsx
@@ -1,7 +1,12 @@
-import { PageTitle } from "@/components/PageTitle"
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { useParams, Link, useLocation } from "react-router"
-import { useQuery, useQueries, useMutation, useQueryClient } from "@tanstack/react-query"
+import { PageTitle } from "@/components/PageTitle";
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { useParams, Link, useLocation } from "react-router";
+import {
+ useQuery,
+ useQueries,
+ useMutation,
+ useQueryClient,
+} from "@tanstack/react-query";
import {
listKeysetInfosOptions,
listKeysetInfosQueryKey,
@@ -10,18 +15,24 @@ import {
listEbillsOptions,
postEnableRedemptionMutation,
getEbillMintCompleteOptions,
-} from "@/generated/client/@tanstack/react-query.gen"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
-import { Skeleton } from "@/components/ui/skeleton"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { ArrowRight } from "lucide-react"
-import { BreadcrumbLink } from "@/components/ui/breadcrumb"
-import { truncateString, formatStatusLabel } from "@/utils/strings"
-import { getQuoteStatusVariant } from "@/utils/quote-status"
-import { toast } from "sonner"
-import { useMemo } from "react"
-import { FormattedMessage, useIntl } from "react-intl"
+} from "@/generated/client/@tanstack/react-query.gen";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { ArrowRight } from "lucide-react";
+import { BreadcrumbLink } from "@/components/ui/breadcrumb";
+import { truncateString, formatStatusLabel } from "@/utils/strings";
+import { getQuoteStatusVariant } from "@/utils/quote-status";
+import { toast } from "sonner";
+import { useMemo } from "react";
+import { FormattedMessage, useIntl } from "react-intl";
/**
* Check if a bill's maturity date matches a keyset's final expiry date
@@ -29,19 +40,22 @@ import { FormattedMessage, useIntl } from "react-intl"
* @param billMaturityDate - Bill maturity date string (YYYY-MM-DD)
* @returns true if dates match (year, month, day)
*/
-function doesBillMatchKeysetMaturity(keysetFinalExpiry: number, billMaturityDate: string): boolean {
- const keysetDate = new Date(keysetFinalExpiry * 1000)
- const billDate = new Date(billMaturityDate)
+function doesBillMatchKeysetMaturity(
+ keysetFinalExpiry: number,
+ billMaturityDate: string,
+): boolean {
+ const keysetDate = new Date(keysetFinalExpiry * 1000);
+ const billDate = new Date(billMaturityDate);
return (
keysetDate.getFullYear() === billDate.getFullYear() &&
keysetDate.getMonth() === billDate.getMonth() &&
keysetDate.getDate() === billDate.getDate()
- )
+ );
}
interface LocationState {
- from?: string
+ from?: string;
}
function Loader() {
@@ -50,18 +64,24 @@ function Loader() {
- )
+ );
}
function PageBody({ keysetId }: { keysetId: string }) {
- const intl = useIntl()
- const queryClient = useQueryClient()
- const { data: keysets, isLoading: keysetsLoading } = useQuery(listKeysetInfosOptions())
- const { data: allQuotesData, isLoading: quotesLoading } = useQuery(listQuotesOptions())
- const allQuotes = useMemo(() => allQuotesData?.quotes ?? [], [allQuotesData?.quotes])
- const { data: ebills } = useQuery(listEbillsOptions())
-
- const keyset = keysets?.find((k) => k.id === keysetId)
+ const intl = useIntl();
+ const queryClient = useQueryClient();
+ const { data: keysets, isLoading: keysetsLoading } = useQuery(
+ listKeysetInfosOptions(),
+ );
+ const { data: allQuotesData, isLoading: quotesLoading } =
+ useQuery(listQuotesOptions());
+ const allQuotes = useMemo(
+ () => allQuotesData?.quotes ?? [],
+ [allQuotesData?.quotes],
+ );
+ const { data: ebills } = useQuery(listEbillsOptions());
+
+ const keyset = keysets?.find((k) => k.id === keysetId);
const redemptionMutation = useMutation({
...postEnableRedemptionMutation(),
@@ -71,14 +91,14 @@ function PageBody({ keysetId }: { keysetId: string }) {
id: "keyset.detail.redeem.success",
defaultMessage: "Redemption enabled successfully",
}),
- )
+ );
void queryClient.invalidateQueries({
queryKey: listKeysetInfosQueryKey(),
exact: false,
- })
+ });
},
onError: (error) => {
- const message = error instanceof Error ? error.message : String(error)
+ const message = error instanceof Error ? error.message : String(error);
toast.error(
intl.formatMessage(
{
@@ -87,9 +107,9 @@ function PageBody({ keysetId }: { keysetId: string }) {
},
{ error: message },
),
- )
+ );
},
- })
+ });
const quoteDetailsQueries = useQueries({
queries: allQuotes.map((quote) =>
@@ -97,82 +117,94 @@ function PageBody({ keysetId }: { keysetId: string }) {
path: { qid: quote.id },
}),
),
- })
+ });
- const quoteDetailsLoading = quoteDetailsQueries.some((q) => q.isLoading)
+ const quoteDetailsLoading = quoteDetailsQueries.some((q) => q.isLoading);
const quoteDetailsDepsKey = useMemo(() => {
const billKeys = quoteDetailsQueries.map((query) => {
- const billId = query.data?.bill?.id ?? ""
- const maturityDate = query.data?.bill?.maturity_date ?? ""
- return `${billId}|${maturityDate}`
- })
- return billKeys.join(",")
- }, [quoteDetailsQueries])
+ const billId = query.data?.bill?.id ?? "";
+ const maturityDate = query.data?.bill?.maturity_date ?? "";
+ return `${billId}|${maturityDate}`;
+ });
+ return billKeys.join(",");
+ }, [quoteDetailsQueries]);
const matchingBillIds = useMemo(() => {
- const billIds: string[] = []
+ const billIds: string[] = [];
if (!keyset?.final_expiry || quoteDetailsLoading) {
- return billIds
+ return billIds;
}
- const keysetFinalExpiry = keyset.final_expiry
+ const keysetFinalExpiry = keyset.final_expiry;
allQuotes.forEach((_quote, index) => {
- const quoteDetails = quoteDetailsQueries[index]?.data
- const billMaturityDate = quoteDetails?.bill?.maturity_date
- const billId = quoteDetails?.bill?.id
+ const quoteDetails = quoteDetailsQueries[index]?.data;
+ const billMaturityDate = quoteDetails?.bill?.maturity_date;
+ const billId = quoteDetails?.bill?.id;
if (!billMaturityDate || !billId) {
- return
+ return;
}
if (doesBillMatchKeysetMaturity(keysetFinalExpiry, billMaturityDate)) {
- billIds.push(billId)
+ billIds.push(billId);
}
- })
+ });
- return billIds
+ return billIds;
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [keyset?.final_expiry, allQuotes, quoteDetailsDepsKey, quoteDetailsLoading])
+ }, [
+ keyset?.final_expiry,
+ allQuotes,
+ quoteDetailsDepsKey,
+ quoteDetailsLoading,
+ ]);
- const MINT_COMPLETE_POLL_INTERVAL_MS = 60_000
- const MINT_COMPLETE_RETRY_COUNT = 3
- const MINT_COMPLETE_RETRY_DELAY_MS = 30_000
+ const MINT_COMPLETE_POLL_INTERVAL_MS = 60_000;
+ const MINT_COMPLETE_RETRY_COUNT = 3;
+ const MINT_COMPLETE_RETRY_DELAY_MS = 30_000;
const mintCompleteQueries = useQueries({
queries: matchingBillIds.map((billId) => ({
...getEbillMintCompleteOptions({
path: { bid: billId },
}),
- refetchInterval: (query: { state: { data?: { complete?: boolean }; error?: unknown } }) => {
- if (query.state.error) return false
- return query.state.data?.complete === false ? MINT_COMPLETE_POLL_INTERVAL_MS : false
+ refetchInterval: (query: {
+ state: { data?: { complete?: boolean }; error?: unknown };
+ }) => {
+ if (query.state.error) return false;
+ return query.state.data?.complete === false
+ ? MINT_COMPLETE_POLL_INTERVAL_MS
+ : false;
},
retry: MINT_COMPLETE_RETRY_COUNT,
retryDelay: MINT_COMPLETE_RETRY_DELAY_MS,
refetchOnWindowFocus: false,
})),
- })
+ });
const allBillsPaid =
matchingBillIds.length > 0 &&
matchingBillIds.every((billId) => {
- const ebill = ebills?.find((e) => e.id === billId)
- return ebill?.status?.payment?.paid === true
- })
+ const ebill = ebills?.find((e) => e.id === billId);
+ return ebill?.status?.payment?.paid === true;
+ });
const allMintComplete =
- matchingBillIds.length > 0 && mintCompleteQueries.every((query) => query.data?.complete === true)
+ matchingBillIds.length > 0 &&
+ mintCompleteQueries.every((query) => query.data?.complete === true);
- const canEnableRedemption = allBillsPaid && allMintComplete
- const anyMintCompleteLoading = mintCompleteQueries.some((query) => query.isLoading)
+ const canEnableRedemption = allBillsPaid && allMintComplete;
+ const anyMintCompleteLoading = mintCompleteQueries.some(
+ (query) => query.isLoading,
+ );
- const hasNoMatchingBills = matchingBillIds.length === 0
+ const hasNoMatchingBills = matchingBillIds.length === 0;
if (keysetsLoading) {
- return
+ return
;
}
if (!keyset) {
@@ -181,18 +213,21 @@ function PageBody({ keysetId }: { keysetId: string }) {
-
+
- )
+ );
}
const noExpiryText = intl.formatMessage({
id: "keysets.noExpiry",
defaultMessage: "No expiry",
- })
+ });
const finalExpiryDate = keyset.final_expiry
? new Date(keyset.final_expiry * 1000)
@@ -203,27 +238,28 @@ function PageBody({ keysetId }: { keysetId: string }) {
timeZone: "UTC",
})
.replace(/(\d{2}) (\w{3}), (\d{4})/, "$1. $2. $3")
- : noExpiryText
- const currencyUnit = typeof keyset.unit === "string" ? keyset.unit : keyset.unit.Custom
+ : noExpiryText;
+ const currencyUnit =
+ typeof keyset.unit === "string" ? keyset.unit : keyset.unit.Custom;
- type EbillType = NonNullable
[number]
- const billIdToEbillMap = new Map()
+ type EbillType = NonNullable[number];
+ const billIdToEbillMap = new Map();
if (ebills) {
for (const ebill of ebills) {
- billIdToEbillMap.set(ebill.id, ebill)
+ billIdToEbillMap.set(ebill.id, ebill);
}
}
const matchingQuotes = allQuotes.filter((_quote, index) => {
- const quoteDetails = quoteDetailsQueries[index]?.data
- const billMaturityDate = quoteDetails?.bill?.maturity_date
+ const quoteDetails = quoteDetailsQueries[index]?.data;
+ const billMaturityDate = quoteDetails?.bill?.maturity_date;
if (!keyset.final_expiry || !billMaturityDate) {
- return false
+ return false;
}
- return doesBillMatchKeysetMaturity(keyset.final_expiry, billMaturityDate)
- })
+ return doesBillMatchKeysetMaturity(keyset.final_expiry, billMaturityDate);
+ });
return (
@@ -246,9 +282,15 @@ function PageBody({ keysetId }: { keysetId: string }) {
{keyset.active ? (
-
+
) : (
-
+
)}
@@ -260,13 +302,16 @@ function PageBody({ keysetId }: { keysetId: string }) {
size="sm"
variant="default"
disabled={
- redemptionMutation.isPending || !canEnableRedemption || anyMintCompleteLoading || hasNoMatchingBills
+ redemptionMutation.isPending ||
+ !canEnableRedemption ||
+ anyMintCompleteLoading ||
+ hasNoMatchingBills
}
onClick={() => {
redemptionMutation.mutate({
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
body: { kid: keyset.id as any },
- })
+ });
}}
>
{redemptionMutation.isPending
@@ -320,56 +365,96 @@ function PageBody({ keysetId }: { keysetId: string }) {
-
+
-
+
-
+
-
+
-
+
-
+
{matchingQuotes.map((quote) => {
- const quoteIndex = allQuotes.findIndex((q) => q.id === quote.id)
- const quoteDetails = quoteDetailsQueries[quoteIndex]?.data
- const billId = quoteDetails?.bill?.id
- const ebill = billId ? billIdToEbillMap.get(billId) : null
- const paymentStatus = ebill?.status?.payment
- const cws = ebill?.current_waiting_state
- const isPaid = paymentStatus?.paid === true
- const isInMempool = cws && "Payment" in cws && cws.Payment.payment_data?.in_mempool === true
- const hasPaymentRequestInWaitingState = Boolean(cws && "Payment" in cws)
+ const quoteIndex = allQuotes.findIndex(
+ (q) => q.id === quote.id,
+ );
+ const quoteDetails =
+ quoteDetailsQueries[quoteIndex]?.data;
+ const billId = quoteDetails?.bill?.id;
+ const ebill = billId
+ ? billIdToEbillMap.get(billId)
+ : null;
+ const paymentStatus = ebill?.status?.payment;
+ const cws = ebill?.current_waiting_state;
+ const isPaid = paymentStatus?.paid === true;
+ const isInMempool =
+ cws &&
+ "Payment" in cws &&
+ cws.Payment.payment_data?.in_mempool === true;
+ const hasPaymentRequestInWaitingState = Boolean(
+ cws && "Payment" in cws,
+ );
const requestedToPay = Boolean(
paymentStatus?.requested_to_pay ??
ebill?.status?.has_requested_funds ??
hasPaymentRequestInWaitingState,
- )
- const rejectedToPay = Boolean(paymentStatus?.rejected_to_pay)
-
- const billIdIndex = billId ? matchingBillIds.indexOf(billId) : -1
- const mintCompleteQuery = billId && billIdIndex >= 0 ? mintCompleteQueries[billIdIndex] : null
- const isMintComplete = mintCompleteQuery?.data?.complete === true
- const isMintLoading = mintCompleteQuery?.isLoading
-
- let paymentAddress: string | undefined
+ );
+ const rejectedToPay = Boolean(
+ paymentStatus?.rejected_to_pay,
+ );
+
+ const billIdIndex = billId
+ ? matchingBillIds.indexOf(billId)
+ : -1;
+ const mintCompleteQuery =
+ billId && billIdIndex >= 0
+ ? mintCompleteQueries[billIdIndex]
+ : null;
+ const isMintComplete =
+ mintCompleteQuery?.data?.complete === true;
+ const isMintLoading = mintCompleteQuery?.isLoading;
+
+ let paymentAddress: string | undefined;
if (cws && "Payment" in cws) {
- paymentAddress = cws.Payment.payment_data?.address_to_pay
+ paymentAddress =
+ cws.Payment.payment_data?.address_to_pay;
}
return (
-
+
-
+
{intl.formatMessage({
id: `quote.status.${quote.status}`,
defaultMessage: formatStatusLabel(quote.status),
@@ -390,55 +477,122 @@ function PageBody({ keysetId }: { keysetId: string }) {
{ebill ? (
isPaid ? (
-
-
+
+
) : rejectedToPay ? (
-
-
+
+
) : isInMempool ? (
-
-
+
+
) : !requestedToPay ? (
-
-
+
+
) : (
-
-
+
+
)
) : (
-
-
+
+
)}
{!isPaid ? (
-
-
+
+
) : isMintLoading || !mintCompleteQuery ? (
-
-
+
+
) : (
-
+
{isMintComplete ? (
-
+
) : (
-
+
)}
)}
{paymentAddress ?? (
-
-
+
+
)}
@@ -453,7 +607,7 @@ function PageBody({ keysetId }: { keysetId: string }) {
- )
+ );
})}
@@ -461,22 +615,25 @@ function PageBody({ keysetId }: { keysetId: string }) {
) : (
-
+
)}
- )
+ );
}
export default function KeysetDetailPage() {
- const { keysetId } = useParams<{ keysetId: string }>()
- const location = useLocation()
- const state = location.state as LocationState | null
- const fromPath = state?.from
- const fromQuote = fromPath?.startsWith("/quotes/")
- const quoteId = fromQuote && fromPath ? fromPath.split("/quotes/")[1] : null
+ const { keysetId } = useParams<{ keysetId: string }>();
+ const location = useLocation();
+ const state = location.state as LocationState | null;
+ const fromPath = state?.from;
+ const fromQuote = fromPath?.startsWith("/quotes/");
+ const quoteId = fromQuote && fromPath ? fromPath.split("/quotes/")[1] : null;
if (!keysetId) {
return (
@@ -484,21 +641,30 @@ export default function KeysetDetailPage() {
-
+
- )
+ );
}
return (
<>
+
-
+
,
]}
@@ -511,18 +677,33 @@ export default function KeysetDetailPage() {
id="keyset.detail.title"
defaultMessage="Keyset {id}"
values={{
- id: {truncateString(keysetId, 16)} ,
+ id: (
+
+ {truncateString(keysetId, 16)}
+
+ ),
}}
/>
{fromQuote && quoteId && (
-
-
+
+
{truncateString(quoteId, 16)},
+ id: (
+
+ {truncateString(quoteId, 16)}
+
+ ),
}}
/>
@@ -531,5 +712,5 @@ export default function KeysetDetailPage() {
>
- )
+ );
}
diff --git a/src/pages/keysets/KeysetsPage.test.tsx b/src/pages/keysets/KeysetsPage.test.tsx
index ed8633a..3c6eb69 100644
--- a/src/pages/keysets/KeysetsPage.test.tsx
+++ b/src/pages/keysets/KeysetsPage.test.tsx
@@ -1,38 +1,46 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { IntlProvider } from "react-intl"
-import { MemoryRouter } from "react-router"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { IntlProvider } from "react-intl";
+import { MemoryRouter } from "react-router";
interface QueryOptions {
- queryKey: { _id: string }[]
+ queryKey: { _id: string }[];
}
interface QueryResult {
- data: unknown
- isLoading: boolean
+ data: unknown;
+ isLoading: boolean;
}
-const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>()
-let nextSearchQuery = ""
+const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>();
+let nextSearchQuery = "";
vi.mock("@tanstack/react-query", async () => {
- const actual = await vi.importActual
("@tanstack/react-query")
+ const actual = await vi.importActual(
+ "@tanstack/react-query",
+ );
return {
...actual,
useQuery: (options: QueryOptions) => mockUseQuery(options),
- }
-})
+ };
+});
vi.mock("@/generated/client/@tanstack/react-query.gen", () => ({
listKeysetInfosOptions: () => ({ queryKey: [{ _id: "listKeysetInfos" }] }),
-}))
+}));
vi.mock("@/components/ui/search", () => ({
- default: ({ onChange, onSearch }: { onChange?: (value: string) => void; onSearch: (value: string) => void }) => (
+ default: ({
+ onChange,
+ onSearch,
+ }: {
+ onChange?: (value: string) => void;
+ onSearch: (value: string) => void;
+ }) => (
{
- onChange?.(nextSearchQuery)
- onSearch(nextSearchQuery)
+ onChange?.(nextSearchQuery);
+ onSearch(nextSearchQuery);
}}
type="button"
>
@@ -40,41 +48,45 @@ vi.mock("@/components/ui/search", () => ({
),
HighlightText: ({ text }: { text: string }) => <>{text}>,
-}))
+}));
vi.mock("@/components/SortButtons.tsx", () => ({
SortButtons: ({
options,
onSortChange,
}: {
- options: { field: "maturity" | "status" | "currency"; label: string }[]
- onSortChange: (field: "maturity" | "status" | "currency") => void
+ options: { field: "maturity" | "status" | "currency"; label: string }[];
+ onSortChange: (field: "maturity" | "status" | "currency") => void;
}) => (
{options.map((option) => (
- onSortChange(option.field)} type="button">
+ onSortChange(option.field)}
+ type="button"
+ >
{`sort-${option.field}`}
))}
),
-}))
+}));
-import KeysetsPage from "./KeysetsPage"
+import KeysetsPage from "./KeysetsPage";
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
function renderPage(): HTMLDivElement {
@@ -84,109 +96,145 @@ function renderPage(): HTMLDivElement {
,
- )
+ );
}
function clickButtonByText(page: HTMLDivElement, label: string) {
- const button = Array.from(page.querySelectorAll("button")).find((node) => node.textContent === label)
- expect(button).not.toBeUndefined()
+ const button = Array.from(page.querySelectorAll("button")).find(
+ (node) => node.textContent === label,
+ );
+ expect(button).not.toBeUndefined();
act(() => {
- button?.dispatchEvent(new MouseEvent("click", { bubbles: true }))
- })
+ button?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
+ });
}
function orderedKeysetHrefs(page: HTMLDivElement): string[] {
- const hrefs = Array.from(page.querySelectorAll('a[href^="/keysets/"]')).map((node) => node.getAttribute("href") ?? "")
- const unique: string[] = []
+ const hrefs = Array.from(page.querySelectorAll('a[href^="/keysets/"]')).map(
+ (node) => node.getAttribute("href") ?? "",
+ );
+ const unique: string[] = [];
for (const href of hrefs) {
if (!unique.includes(href)) {
- unique.push(href)
+ unique.push(href);
}
}
- return unique
+ return unique;
}
beforeEach(() => {
- vi.clearAllMocks()
- vi.useFakeTimers()
- vi.setSystemTime(new Date("2026-02-20T00:00:00.000Z"))
- nextSearchQuery = ""
+ vi.clearAllMocks();
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date("2026-02-20T00:00:00.000Z"));
+ nextSearchQuery = "";
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
-})
+});
describe("KeysetsPage", () => {
it("shows empty state when no keysets are returned", () => {
- mockUseQuery.mockReturnValue({ data: [], isLoading: false })
+ mockUseQuery.mockReturnValue({ data: [], isLoading: false });
- const page = renderPage()
- expect(page.textContent).toContain("No keysets found")
- })
+ const page = renderPage();
+ expect(page.textContent).toContain("No keysets found");
+ });
it("renders inactive keyset without expiry", () => {
mockUseQuery.mockReturnValue({
- data: [{ id: "keyset-no-expiry", active: false, final_expiry: null, unit: { Custom: "usd" } }],
+ data: [
+ {
+ id: "keyset-no-expiry",
+ active: false,
+ final_expiry: null,
+ unit: { Custom: "usd" },
+ },
+ ],
isLoading: false,
- })
+ });
- const page = renderPage()
- expect(page.textContent).toContain("Inactive")
- expect(page.textContent).toContain("No expiry")
- expect(page.textContent).toContain("usd")
- })
+ const page = renderPage();
+ expect(page.textContent).toContain("Inactive");
+ expect(page.textContent).toContain("No expiry");
+ expect(page.textContent).toContain("usd");
+ });
it("filters out all rows and shows no-match state from search", () => {
mockUseQuery.mockReturnValue({
data: [
- { id: "keyset-aaa", active: true, final_expiry: 1771545600, unit: "sat" },
- { id: "keyset-bbb", active: false, final_expiry: 1771632000, unit: { Custom: "usd" } },
+ {
+ id: "keyset-aaa",
+ active: true,
+ final_expiry: 1771545600,
+ unit: "sat",
+ },
+ {
+ id: "keyset-bbb",
+ active: false,
+ final_expiry: 1771632000,
+ unit: { Custom: "usd" },
+ },
],
isLoading: false,
- })
- nextSearchQuery = "definitely-missing"
+ });
+ nextSearchQuery = "definitely-missing";
- const page = renderPage()
- clickButtonByText(page, "SearchMock")
+ const page = renderPage();
+ clickButtonByText(page, "SearchMock");
- expect(page.textContent).toContain("No keysets match your search criteria")
- })
+ expect(page.textContent).toContain("No keysets match your search criteria");
+ });
it("sorts by maturity, then currency, then status via sort controls", () => {
mockUseQuery.mockReturnValue({
data: [
- { id: "keyset-expired", active: true, final_expiry: 1735689600, unit: "sat" },
- { id: "keyset-future", active: false, final_expiry: 1798761600, unit: { Custom: "usd" } },
- { id: "keyset-no-expiry", active: false, final_expiry: null, unit: { Custom: "eur" } },
+ {
+ id: "keyset-expired",
+ active: true,
+ final_expiry: 1735689600,
+ unit: "sat",
+ },
+ {
+ id: "keyset-future",
+ active: false,
+ final_expiry: 1798761600,
+ unit: { Custom: "usd" },
+ },
+ {
+ id: "keyset-no-expiry",
+ active: false,
+ final_expiry: null,
+ unit: { Custom: "eur" },
+ },
],
isLoading: false,
- })
+ });
- const page = renderPage()
+ const page = renderPage();
// Default maturity-asc: expired first, no-expiry last.
expect(orderedKeysetHrefs(page)).toEqual([
"/keysets/keyset-expired",
"/keysets/keyset-future",
"/keysets/keyset-no-expiry",
- ])
+ ]);
// Currency-asc: eur, sat, usd.
- clickButtonByText(page, "sort-currency")
+ clickButtonByText(page, "sort-currency");
expect(orderedKeysetHrefs(page)).toEqual([
"/keysets/keyset-no-expiry",
"/keysets/keyset-expired",
"/keysets/keyset-future",
- ])
+ ]);
// Status-asc in this implementation sorts active first.
- clickButtonByText(page, "sort-status")
- expect(orderedKeysetHrefs(page)[0]).toBe("/keysets/keyset-expired")
- })
-})
+ clickButtonByText(page, "sort-status");
+ expect(orderedKeysetHrefs(page)[0]).toBe("/keysets/keyset-expired");
+ });
+});
diff --git a/src/pages/keysets/KeysetsPage.tsx b/src/pages/keysets/KeysetsPage.tsx
index ef67dbb..48f0163 100644
--- a/src/pages/keysets/KeysetsPage.tsx
+++ b/src/pages/keysets/KeysetsPage.tsx
@@ -1,16 +1,22 @@
-import { PageTitle } from "@/components/PageTitle"
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { useQuery } from "@tanstack/react-query"
-import { listKeysetInfosOptions } from "@/generated/client/@tanstack/react-query.gen"
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
-import { Skeleton } from "@/components/ui/skeleton"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { Link } from "react-router"
-import { useState } from "react"
-import SearchComponent, { HighlightText } from "@/components/ui/search"
-import { SortButtons } from "@/components/SortButtons.tsx"
-import { FormattedMessage, useIntl } from "react-intl"
+import { PageTitle } from "@/components/PageTitle";
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { useQuery } from "@tanstack/react-query";
+import { listKeysetInfosOptions } from "@/generated/client/@tanstack/react-query.gen";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Link } from "react-router";
+import { useState } from "react";
+import SearchComponent, { HighlightText } from "@/components/ui/search";
+import { SortButtons } from "@/components/SortButtons.tsx";
+import { FormattedMessage, useIntl } from "react-intl";
function Loader() {
return (
@@ -18,41 +24,54 @@ function Loader() {
- )
+ );
}
-type SortBy = "maturity-asc" | "maturity-desc" | "status-asc" | "status-desc" | "currency-asc" | "currency-desc"
+type SortBy =
+ | "maturity-asc"
+ | "maturity-desc"
+ | "status-asc"
+ | "status-desc"
+ | "currency-asc"
+ | "currency-desc";
function PageBody() {
- const { data: keysets, isLoading: keysetsLoading } = useQuery(listKeysetInfosOptions())
- const [searchQuery, setSearchQuery] = useState("")
- const [sortBy, setSortBy] = useState
("maturity-asc")
- const intl = useIntl()
+ const { data: keysets, isLoading: keysetsLoading } = useQuery(
+ listKeysetInfosOptions(),
+ );
+ const [searchQuery, setSearchQuery] = useState("");
+ const [sortBy, setSortBy] = useState("maturity-asc");
+ const intl = useIntl();
const noExpiryText = intl.formatMessage({
id: "keysets.noExpiry",
defaultMessage: "No expiry",
- })
+ });
if (keysetsLoading) {
- return
+ return ;
}
if (!keysets || keysets.length === 0) {
return (
-
+
- )
+ );
}
const filteredKeysets = keysets.filter((keyset) => {
if (!searchQuery) {
- return true
+ return true;
}
- const query = searchQuery.toLowerCase()
- const keysetId = keyset.id.toLowerCase()
- const currencyUnit = (typeof keyset.unit === "string" ? keyset.unit : keyset.unit.Custom).toLowerCase()
+ const query = searchQuery.toLowerCase();
+ const keysetId = keyset.id.toLowerCase();
+ const currencyUnit = (
+ typeof keyset.unit === "string" ? keyset.unit : keyset.unit.Custom
+ ).toLowerCase();
const finalExpiryDate = keyset.final_expiry
? new Date(keyset.final_expiry * 1000)
.toLocaleDateString("en-US", {
@@ -63,84 +82,98 @@ function PageBody() {
})
.replace(/(\d{2}) (\w{3}), (\d{4})/, "$1. $2. $3")
.toLowerCase()
- : noExpiryText.toLowerCase()
+ : noExpiryText.toLowerCase();
const status = keyset.active
- ? intl.formatMessage({ id: "keysets.status.active", defaultMessage: "Active" }).toLowerCase()
- : intl.formatMessage({ id: "keysets.status.inactive", defaultMessage: "Inactive" }).toLowerCase()
+ ? intl
+ .formatMessage({
+ id: "keysets.status.active",
+ defaultMessage: "Active",
+ })
+ .toLowerCase()
+ : intl
+ .formatMessage({
+ id: "keysets.status.inactive",
+ defaultMessage: "Inactive",
+ })
+ .toLowerCase();
return (
keysetId.includes(query) ||
currencyUnit.includes(query) ||
finalExpiryDate.includes(query) ||
status.includes(query)
- )
- })
+ );
+ });
const sortedKeysets = [...filteredKeysets].sort((a, b) => {
- let comparison = 0
+ let comparison = 0;
switch (sortBy) {
case "maturity-asc":
case "maturity-desc": {
- const aExpiry = a.final_expiry ? new Date(a.final_expiry * 1000) : null
- const bExpiry = b.final_expiry ? new Date(b.final_expiry * 1000) : null
+ const aExpiry = a.final_expiry ? new Date(a.final_expiry * 1000) : null;
+ const bExpiry = b.final_expiry ? new Date(b.final_expiry * 1000) : null;
if (!aExpiry && !bExpiry) {
- comparison = 0
+ comparison = 0;
} else if (!aExpiry) {
- comparison = 1
+ comparison = 1;
} else if (!bExpiry) {
- comparison = -1
+ comparison = -1;
} else {
- const now = new Date()
- const aIsExpired = aExpiry < now
- const bIsExpired = bExpiry < now
+ const now = new Date();
+ const aIsExpired = aExpiry < now;
+ const bIsExpired = bExpiry < now;
if (aIsExpired && !bIsExpired) {
- comparison = -1
+ comparison = -1;
} else if (!aIsExpired && bIsExpired) {
- comparison = 1
+ comparison = 1;
} else {
- comparison = aExpiry.getTime() - bExpiry.getTime()
+ comparison = aExpiry.getTime() - bExpiry.getTime();
}
}
if (sortBy === "maturity-desc") {
- comparison = -comparison
+ comparison = -comparison;
}
- break
+ break;
}
case "status-asc":
case "status-desc": {
- const aStatus = a.active ? 1 : 0
- const bStatus = b.active ? 1 : 0
- comparison = bStatus - aStatus
+ const aStatus = a.active ? 1 : 0;
+ const bStatus = b.active ? 1 : 0;
+ comparison = bStatus - aStatus;
if (sortBy === "status-desc") {
- comparison = -comparison
+ comparison = -comparison;
}
- break
+ break;
}
case "currency-asc":
case "currency-desc": {
- const aCurrency = typeof a.unit === "string" ? a.unit : a.unit.Custom
- const bCurrency = typeof b.unit === "string" ? b.unit : b.unit.Custom
- comparison = aCurrency.localeCompare(bCurrency)
+ const aCurrency = typeof a.unit === "string" ? a.unit : a.unit.Custom;
+ const bCurrency = typeof b.unit === "string" ? b.unit : b.unit.Custom;
+ comparison = aCurrency.localeCompare(bCurrency);
if (sortBy === "currency-desc") {
- comparison = -comparison
+ comparison = -comparison;
}
- break
+ break;
}
}
- return comparison
- })
+ return comparison;
+ });
const toggleSort = (field: "maturity" | "status" | "currency") => {
if (sortBy.startsWith(field)) {
- setSortBy(sortBy.endsWith("asc") ? (`${field}-desc` as SortBy) : (`${field}-asc` as SortBy))
+ setSortBy(
+ sortBy.endsWith("asc")
+ ? (`${field}-desc` as SortBy)
+ : (`${field}-asc` as SortBy),
+ );
} else {
- setSortBy(`${field}-asc` as SortBy)
+ setSortBy(`${field}-asc` as SortBy);
}
- }
+ };
const sortOptions = [
{
@@ -164,7 +197,7 @@ function PageBody() {
defaultMessage: "Status",
}),
},
- ]
+ ];
return (
@@ -174,18 +207,26 @@ function PageBody() {
className="flex-1 max-w-md"
placeholder={intl.formatMessage({
id: "keysets.search.placeholder",
- defaultMessage: "Search by keyset ID, currency, maturity date, or status...",
+ defaultMessage:
+ "Search by keyset ID, currency, maturity date, or status...",
})}
onSearch={setSearchQuery}
onChange={setSearchQuery}
size="sm"
/>
-
+
{sortedKeysets.length === 0 ? (
-
+
) : (
<>
@@ -199,8 +240,11 @@ function PageBody() {
timeZone: "UTC",
})
.replace(/(\d{2}) (\w{3}), (\d{4})/, "$1. $2. $3")
- : noExpiryText
- const currencyUnit = typeof keyset.unit === "string" ? keyset.unit : keyset.unit.Custom
+ : noExpiryText;
+ const currencyUnit =
+ typeof keyset.unit === "string"
+ ? keyset.unit
+ : keyset.unit.Custom;
const statusText = keyset.active
? intl.formatMessage({
id: "keysets.status.active",
@@ -209,16 +253,25 @@ function PageBody() {
: intl.formatMessage({
id: "keysets.status.inactive",
defaultMessage: "Inactive",
- })
+ });
return (
-
+
-
+
-
+
@@ -226,46 +279,73 @@ function PageBody() {
id="keysets.card.meta"
defaultMessage="Currency: {currency} | Maturity date: {maturityDate}"
values={{
- currency: ,
- maturityDate: ,
+ currency: (
+
+ ),
+ maturityDate: (
+
+ ),
}}
/>
-
+
-
+
-
+
- )
+ );
})}
>
)}
- )
+ );
}
export default function KeysetsPage() {
return (
<>
-
+
-
+
>
- )
+ );
}
diff --git a/src/pages/quotes/DenyConfirmDrawer.tsx b/src/pages/quotes/DenyConfirmDrawer.tsx
index dcd035e..6d323b1 100644
--- a/src/pages/quotes/DenyConfirmDrawer.tsx
+++ b/src/pages/quotes/DenyConfirmDrawer.tsx
@@ -1,23 +1,30 @@
-import { ConfirmDrawer } from "@/components/Drawers"
-import type { ReactNode } from "react"
-import { useIntl } from "react-intl"
+import { ConfirmDrawer } from "@/components/Drawers";
+import type { ReactNode } from "react";
+import { useIntl } from "react-intl";
interface DenyConfirmDrawerProps {
- title: string
- open: boolean
- onOpenChange: (open: boolean) => void
- onSubmit: () => void
- children: ReactNode
+ title: string;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSubmit: () => void;
+ children: ReactNode;
}
-export function DenyConfirmDrawer({ title, open, onOpenChange, onSubmit, children }: DenyConfirmDrawerProps) {
- const intl = useIntl()
+export function DenyConfirmDrawer({
+ title,
+ open,
+ onOpenChange,
+ onSubmit,
+ children,
+}: DenyConfirmDrawerProps) {
+ const intl = useIntl();
return (
- )
+ );
}
diff --git a/src/pages/quotes/OfferFormDrawer.tsx b/src/pages/quotes/OfferFormDrawer.tsx
index 086566c..3327ea2 100644
--- a/src/pages/quotes/OfferFormDrawer.tsx
+++ b/src/pages/quotes/OfferFormDrawer.tsx
@@ -1,35 +1,35 @@
-import Big from "big.js"
-import { BaseDrawer } from "@/components/Drawers"
-import { GrossToNetDiscountForm } from "@/components/GrossToNetDiscountForm"
-import type { InfoReply } from "@/generated/client/types.gen"
-import type { ReactNode } from "react"
+import Big from "big.js";
+import { BaseDrawer } from "@/components/Drawers";
+import { GrossToNetDiscountForm } from "@/components/GrossToNetDiscountForm";
+import type { InfoReply } from "@/generated/client/types.gen";
+import type { ReactNode } from "react";
export interface OfferFormResult {
discount: {
- days: number
- discountRate: Big
+ days: number;
+ discountRate: Big;
net: {
- value: Big
- currency: string
- }
+ value: Big;
+ currency: string;
+ };
gross: {
- value: Big
- currency: string
- }
- }
+ value: Big;
+ currency: string;
+ };
+ };
ttl: {
- ttl: Date
- }
+ ttl: Date;
+ };
}
interface OfferFormDrawerProps {
- title: string
- description: string
- value: InfoReply
- open: boolean
- onOpenChange: (open: boolean) => void
- onSubmit: (data: OfferFormResult) => void
- children: ReactNode
+ title: string;
+ description: string;
+ value: InfoReply;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSubmit: (data: OfferFormResult) => void;
+ children: ReactNode;
}
export function OfferFormDrawer({
@@ -42,28 +42,37 @@ export function OfferFormDrawer({
children,
}: OfferFormDrawerProps) {
const handleFormSubmit = (values: {
- days: number
- discountRate: Big
- net: { value: Big; currency: string }
- gross: { value: Big; currency: string }
+ days: number;
+ discountRate: Big;
+ net: { value: Big; currency: string };
+ gross: { value: Big; currency: string };
}) => {
- const now = new Date()
- const ttl = new Date(now.getTime() + values.days * 24 * 60 * 60 * 1000)
+ const now = new Date();
+ const ttl = new Date(now.getTime() + values.days * 24 * 60 * 60 * 1000);
const result: OfferFormResult = {
discount: values,
ttl: { ttl },
- }
+ };
- onSubmit(result)
- }
+ onSubmit(result);
+ };
- const startDate = value.status === "Pending" ? new Date(value.submitted) : new Date()
+ const startDate =
+ value.status === "Pending" ? new Date(value.submitted) : new Date();
const endDate =
- value.status === "Pending" ? new Date(value.suggested_expiration) : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
+ value.status === "Pending"
+ ? new Date(value.suggested_expiration)
+ : new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
return (
-
+
- )
+ );
}
diff --git a/src/pages/quotes/QuoteActions.tsx b/src/pages/quotes/QuoteActions.tsx
index 1edabd7..659b344 100644
--- a/src/pages/quotes/QuoteActions.tsx
+++ b/src/pages/quotes/QuoteActions.tsx
@@ -1,26 +1,32 @@
-import { useState } from "react"
-import { useQuery } from "@tanstack/react-query"
-import { LoaderIcon } from "lucide-react"
-import { Button } from "@/components/ui/button"
-import { getEbillOptions } from "@/generated/client/@tanstack/react-query.gen"
-import type { InfoReply, BillWaitingStatePaymentData } from "@/generated/client/types.gen"
-import { OfferFormDrawer, type OfferFormResult } from "./components/OfferFormDrawer.tsx"
-import { DenyConfirmDrawer } from "./components/DenyConfirmDrawer.tsx"
-import { removeItem } from "@/utils/local-storage"
-import { PaymentRequestCard } from "./components/PaymentRequestCard.tsx"
-import { OfferConfirmation } from "./components/OfferConfirmation.tsx"
-import { RequestToPayConfirmation } from "./components/RequestToPayConfirmation.tsx"
-import { useQuoteMutations } from "./components/useQuoteMutations.ts"
-import { useIntl } from "react-intl"
+import { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { LoaderIcon } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { getEbillOptions } from "@/generated/client/@tanstack/react-query.gen";
+import type {
+ InfoReply,
+ BillWaitingStatePaymentData,
+} from "@/generated/client/types.gen";
+import {
+ OfferFormDrawer,
+ type OfferFormResult,
+} from "./components/OfferFormDrawer.tsx";
+import { DenyConfirmDrawer } from "./components/DenyConfirmDrawer.tsx";
+import { removeItem } from "@/utils/local-storage";
+import { PaymentRequestCard } from "./components/PaymentRequestCard.tsx";
+import { OfferConfirmation } from "./components/OfferConfirmation.tsx";
+import { RequestToPayConfirmation } from "./components/RequestToPayConfirmation.tsx";
+import { useQuoteMutations } from "./components/useQuoteMutations.ts";
+import { useIntl } from "react-intl";
interface QuoteActionsProps {
- value: InfoReply
- isFetching: boolean
- ebillPaid: boolean
- isMintComplete: boolean
- requestedToPay: boolean
- paymentDeadlineTs?: number | null
- timeOfRequestToPay?: number | null
+ value: InfoReply;
+ isFetching: boolean;
+ ebillPaid: boolean;
+ isMintComplete: boolean;
+ requestedToPay: boolean;
+ paymentDeadlineTs?: number | null;
+ timeOfRequestToPay?: number | null;
}
export function QuoteActions({
@@ -32,61 +38,79 @@ export function QuoteActions({
paymentDeadlineTs,
timeOfRequestToPay,
}: QuoteActionsProps) {
- const intl = useIntl()
- const billId = value.bill.id
+ const intl = useIntl();
+ const billId = value.bill.id;
const ebillQuery = useQuery({
...getEbillOptions({ path: { bid: billId } }),
retry: 1,
enabled: !!billId,
- })
+ });
- const ebill = ebillQuery.data
- const quoteStatus = value.status as string
- const paymentStatus = ebill?.status.payment
- const cws = ebill?.current_waiting_state
+ const ebill = ebillQuery.data;
+ const quoteStatus = value.status as string;
+ const paymentStatus = ebill?.status.payment;
+ const cws = ebill?.current_waiting_state;
- let waitingPaymentData: BillWaitingStatePaymentData | undefined
+ let waitingPaymentData: BillWaitingStatePaymentData | undefined;
if (cws && "Payment" in cws) {
- waitingPaymentData = cws.Payment.payment_data
+ waitingPaymentData = cws.Payment.payment_data;
}
- const requestedToPayEff = Boolean(requestedToPay || paymentStatus?.requested_to_pay)
- const ebillPaidEff = Boolean(ebillPaid || (paymentStatus?.paid && isMintComplete))
+ const requestedToPayEff = Boolean(
+ requestedToPay || paymentStatus?.requested_to_pay,
+ );
+ const ebillPaidEff = Boolean(
+ ebillPaid || (paymentStatus?.paid && isMintComplete),
+ );
const effectiveRequestTime =
- timeOfRequestToPay ?? paymentStatus?.time_of_request_to_pay ?? waitingPaymentData?.time_of_request ?? null
+ timeOfRequestToPay ??
+ paymentStatus?.time_of_request_to_pay ??
+ waitingPaymentData?.time_of_request ??
+ null;
const effectiveDeadlineTs =
- paymentDeadlineTs ?? paymentStatus?.payment_deadline_timestamp ?? waitingPaymentData?.payment_deadline ?? null
- const linkToPay: string | undefined = waitingPaymentData?.mempool_link_for_address_to_pay
- const addressToPay: string | undefined = waitingPaymentData?.address_to_pay
+ paymentDeadlineTs ??
+ paymentStatus?.payment_deadline_timestamp ??
+ waitingPaymentData?.payment_deadline ??
+ null;
+ const linkToPay: string | undefined =
+ waitingPaymentData?.mempool_link_for_address_to_pay;
+ const addressToPay: string | undefined = waitingPaymentData?.address_to_pay;
- const [offerFormData, setOfferFormData] = useState()
- const [offerFormDrawerOpen, setOfferFormDrawerOpen] = useState(false)
- const [offerConfirmDrawerOpen, setOfferConfirmDrawerOpen] = useState(false)
- const [denyConfirmDrawerOpen, setDenyConfirmDrawerOpen] = useState(false)
- const [requestToPayConfirmDrawerOpen, setRequestToPayConfirmDrawerOpen] = useState(false)
+ const [offerFormData, setOfferFormData] = useState();
+ const [offerFormDrawerOpen, setOfferFormDrawerOpen] = useState(false);
+ const [offerConfirmDrawerOpen, setOfferConfirmDrawerOpen] = useState(false);
+ const [denyConfirmDrawerOpen, setDenyConfirmDrawerOpen] = useState(false);
+ const [requestToPayConfirmDrawerOpen, setRequestToPayConfirmDrawerOpen] =
+ useState(false);
const denyTitle = intl.formatMessage({
id: "quotes.actions.deny.title",
defaultMessage: "Confirm denying quote",
- })
+ });
const denyButtonLabel = intl.formatMessage({
id: "quotes.actions.deny.button",
defaultMessage: "Deny",
- })
+ });
const offerTitle = intl.formatMessage({
id: "quotes.actions.offer.title",
defaultMessage: "Offer quote",
- })
+ });
const offerDescription = intl.formatMessage({
id: "quotes.actions.offer.description",
defaultMessage: "Make an offer to the current holder of this bill",
- })
+ });
const offerButtonLabel = intl.formatMessage({
id: "quotes.actions.offer.button",
defaultMessage: "Offer",
- })
- const { denyQuote, offerQuote, requestToPayMutation, handleDenyQuote, handleOfferQuote, handleRequestToPay } =
- useQuoteMutations(value.id, billId)
+ });
+ const {
+ denyQuote,
+ offerQuote,
+ requestToPayMutation,
+ handleDenyQuote,
+ handleOfferQuote,
+ handleRequestToPay,
+ } = useQuoteMutations(value.id, billId);
return (
<>
@@ -97,12 +121,19 @@ export function QuoteActions({
open={denyConfirmDrawerOpen}
onOpenChange={setDenyConfirmDrawerOpen}
onSubmit={() => {
- handleDenyQuote()
- setDenyConfirmDrawerOpen(false)
+ handleDenyQuote();
+ setDenyConfirmDrawerOpen(false);
}}
>
-
- {denyButtonLabel} {denyQuote.isPending && }
+
+ {denyButtonLabel}{" "}
+ {denyQuote.isPending && (
+
+ )}
)}
@@ -115,13 +146,19 @@ export function QuoteActions({
open={offerFormDrawerOpen}
onOpenChange={setOfferFormDrawerOpen}
onSubmit={(data) => {
- setOfferFormData(data)
- setOfferConfirmDrawerOpen(true)
- setOfferFormDrawerOpen(false)
+ setOfferFormData(data);
+ setOfferConfirmDrawerOpen(true);
+ setOfferFormDrawerOpen(false);
}}
>
-
- {offerButtonLabel} {offerQuote.isPending && }
+
+ {offerButtonLabel}{" "}
+ {offerQuote.isPending && (
+
+ )}
)}
@@ -131,9 +168,9 @@ export function QuoteActions({
open={offerConfirmDrawerOpen}
onOpenChange={setOfferConfirmDrawerOpen}
onSubmit={(finalData) => {
- removeItem(`offer-form-${value.id}`)
- handleOfferQuote(finalData)
- setOfferConfirmDrawerOpen(false)
+ removeItem(`offer-form-${value.id}`);
+ handleOfferQuote(finalData);
+ setOfferConfirmDrawerOpen(false);
}}
quoteId={value.id}
/>
@@ -147,8 +184,8 @@ export function QuoteActions({
open={requestToPayConfirmDrawerOpen}
onOpenChange={setRequestToPayConfirmDrawerOpen}
onSubmit={(deadline) => {
- handleRequestToPay(value.bill.sum, deadline)
- setRequestToPayConfirmDrawerOpen(false)
+ handleRequestToPay(value.bill.sum, deadline);
+ setRequestToPayConfirmDrawerOpen(false);
}}
isFetching={isFetching}
isPending={requestToPayMutation.isPending}
@@ -167,5 +204,5 @@ export function QuoteActions({
/>
)}
>
- )
+ );
}
diff --git a/src/pages/quotes/QuotePage.test.tsx b/src/pages/quotes/QuotePage.test.tsx
index d49287f..13caa1f 100644
--- a/src/pages/quotes/QuotePage.test.tsx
+++ b/src/pages/quotes/QuotePage.test.tsx
@@ -1,62 +1,66 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { IntlProvider } from "react-intl"
-import { MemoryRouter, Route, Routes } from "react-router"
-import QuotePage from "./QuotePage"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { IntlProvider } from "react-intl";
+import { MemoryRouter, Route, Routes } from "react-router";
+import QuotePage from "./QuotePage";
interface QueryKeyEntry {
- _id: string
- path?: { qid?: string; bid?: string }
+ _id: string;
+ path?: { qid?: string; bid?: string };
}
interface QueryOptions {
- queryKey: QueryKeyEntry[]
+ queryKey: QueryKeyEntry[];
}
interface QueryResult {
- data: unknown
- isLoading: boolean
- isFetching?: boolean
- error: Error | null
+ data: unknown;
+ isLoading: boolean;
+ isFetching?: boolean;
+ error: Error | null;
}
interface MutationResult {
- mutate: (value: { body: { token: string } }) => void
- isPending: boolean
- isSuccess: boolean
- isError: boolean
- data: unknown
+ mutate: (value: { body: { token: string } }) => void;
+ isPending: boolean;
+ isSuccess: boolean;
+ isError: boolean;
+ data: unknown;
}
-const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>()
-const mockUseMutation = vi.fn<() => MutationResult>()
+const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>();
+const mockUseMutation = vi.fn<() => MutationResult>();
vi.mock("sonner", () => ({
toast: { error: vi.fn() },
-}))
+}));
vi.mock("./QuoteActions.tsx", () => ({
QuoteActions: () => QuoteActionsMock
,
-}))
+}));
vi.mock("@/components/EndorsementChain", () => ({
EndorsementChain: () => EndorsementChainMock
,
-}))
+}));
vi.mock("@/components/ParticipantsOverview", () => ({
ParticipantsOverviewCard: () => ParticipantsOverviewMock
,
ParticipantDetail: () => ParticipantDetailMock
,
-}))
+}));
vi.mock("@tanstack/react-query", async () => {
- const actual = await vi.importActual("@tanstack/react-query")
+ const actual = await vi.importActual(
+ "@tanstack/react-query",
+ );
return {
...actual,
useQuery: (options: QueryOptions) => mockUseQuery(options),
useMutation: () => mockUseMutation(),
- }
-})
+ };
+});
vi.mock("@/generated/client/@tanstack/react-query.gen", () => ({
- getQuoteOptions: ({ path }: { path: { qid: string } }) => ({ queryKey: [{ _id: "getQuote", path }] }),
+ getQuoteOptions: ({ path }: { path: { qid: string } }) => ({
+ queryKey: [{ _id: "getQuote", path }],
+ }),
listEbillsOptions: () => ({ queryKey: [{ _id: "listEbills" }] }),
getEbillEndorsementsOptions: ({ path }: { path: { bid: string } }) => ({
queryKey: [{ _id: "getEbillEndorsements", path }],
@@ -65,44 +69,49 @@ vi.mock("@/generated/client/@tanstack/react-query.gen", () => ({
queryKey: [{ _id: "getEbillMintComplete", path }],
}),
postTokenStatusMutation: () => ({ mutationFn: vi.fn() }),
-}))
+}));
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
-function renderPage(entry: string | { pathname: string; state?: Record }): HTMLDivElement {
+function renderPage(
+ entry: string | { pathname: string; state?: Record },
+): HTMLDivElement {
return renderIntoDom(
- } />
+ }
+ />
,
- )
+ );
}
beforeEach(() => {
- vi.clearAllMocks()
+ vi.clearAllMocks();
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
mockUseMutation.mockReturnValue({
@@ -111,10 +120,10 @@ beforeEach(() => {
isSuccess: false,
isError: false,
data: undefined,
- })
+ });
mockUseQuery.mockImplementation((opts: QueryOptions) => {
- const id = opts.queryKey[0]._id
+ const id = opts.queryKey[0]._id;
if (id === "getQuote") {
return {
data: {
@@ -134,37 +143,45 @@ beforeEach(() => {
isLoading: false,
isFetching: false,
error: null,
- }
+ };
}
if (id === "listEbills") {
- return { data: [], isLoading: false, error: null }
+ return { data: [], isLoading: false, error: null };
}
if (id === "getEbillEndorsements") {
- return { data: [], isLoading: false, error: null }
+ return { data: [], isLoading: false, error: null };
}
if (id === "getEbillMintComplete") {
- return { data: { complete: false }, isLoading: false, error: null }
+ return { data: { complete: false }, isLoading: false, error: null };
}
- return { data: undefined, isLoading: false, isFetching: false, error: null }
- })
-})
+ return {
+ data: undefined,
+ isLoading: false,
+ isFetching: false,
+ error: null,
+ };
+ });
+});
describe("QuotePage", () => {
it("shows back-to-keyset action when navigated from a keyset page", () => {
- const page = renderPage({ pathname: "/quotes/quote-1", state: { from: "/keysets/keyset-1234" } })
- const link = page.querySelector('a[href="/keysets/keyset-1234"]')
- expect(link?.textContent).toContain("Back to keyset")
- })
+ const page = renderPage({
+ pathname: "/quotes/quote-1",
+ state: { from: "/keysets/keyset-1234" },
+ });
+ const link = page.querySelector('a[href="/keysets/keyset-1234"]');
+ expect(link?.textContent).toContain("Back to keyset");
+ });
it("shows go-to-keyset action from quote data when no navigation state is provided", () => {
- const page = renderPage("/quotes/quote-1")
- const link = page.querySelector('a[href="/keysets/keyset-from-quote"]')
- expect(link?.textContent).toContain("Go to keyset")
- })
+ const page = renderPage("/quotes/quote-1");
+ const link = page.querySelector('a[href="/keysets/keyset-from-quote"]');
+ expect(link?.textContent).toContain("Go to keyset");
+ });
it("shows quote load error state", () => {
mockUseQuery.mockImplementation((opts: QueryOptions) => {
@@ -174,15 +191,20 @@ describe("QuotePage", () => {
isLoading: false,
isFetching: false,
error: new Error("boom"),
- }
+ };
}
- return { data: undefined, isLoading: false, isFetching: false, error: null }
- })
+ return {
+ data: undefined,
+ isLoading: false,
+ isFetching: false,
+ error: null,
+ };
+ });
- const page = renderPage("/quotes/quote-error")
- expect(page.textContent).toContain("Failed to load quote")
- expect(page.textContent).toContain("boom")
- })
+ const page = renderPage("/quotes/quote-error");
+ expect(page.textContent).toContain("Failed to load quote");
+ expect(page.textContent).toContain("boom");
+ });
it("shows empty quote state when bill data is missing", () => {
mockUseQuery.mockImplementation((opts: QueryOptions) => {
@@ -192,14 +214,19 @@ describe("QuotePage", () => {
isLoading: false,
isFetching: false,
error: null,
- }
+ };
}
- return { data: undefined, isLoading: false, isFetching: false, error: null }
- })
+ return {
+ data: undefined,
+ isLoading: false,
+ isFetching: false,
+ error: null,
+ };
+ });
- const page = renderPage("/quotes/quote-1")
- expect(page.textContent).toContain("No quote data available")
- })
+ const page = renderPage("/quotes/quote-1");
+ expect(page.textContent).toContain("No quote data available");
+ });
it("does not render keyset action when quote does not expose keyset id", () => {
mockUseQuery.mockImplementation((opts: QueryOptions) => {
@@ -221,13 +248,18 @@ describe("QuotePage", () => {
isLoading: false,
isFetching: false,
error: null,
- }
+ };
}
- return { data: undefined, isLoading: false, isFetching: false, error: null }
- })
-
- const page = renderPage("/quotes/quote-2")
- const keysetLink = page.querySelector('a[href^="/keysets/"]')
- expect(keysetLink).toBeNull()
- })
-})
+ return {
+ data: undefined,
+ isLoading: false,
+ isFetching: false,
+ error: null,
+ };
+ });
+
+ const page = renderPage("/quotes/quote-2");
+ const keysetLink = page.querySelector('a[href^="/keysets/"]');
+ expect(keysetLink).toBeNull();
+ });
+});
diff --git a/src/pages/quotes/QuotePage.tsx b/src/pages/quotes/QuotePage.tsx
index b9322a8..5e92b8f 100644
--- a/src/pages/quotes/QuotePage.tsx
+++ b/src/pages/quotes/QuotePage.tsx
@@ -1,35 +1,38 @@
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { PageTitle } from "@/components/PageTitle"
-import { Badge } from "@/components/ui/badge"
-import { Button } from "@/components/ui/button"
-import { Card, CardContent } from "@/components/ui/card"
-import { Skeleton } from "@/components/ui/skeleton"
-import { ParticipantsOverviewCard, ParticipantDetail } from "@/components/ParticipantsOverview"
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { PageTitle } from "@/components/PageTitle";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent } from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+import {
+ ParticipantsOverviewCard,
+ ParticipantDetail,
+} from "@/components/ParticipantsOverview";
import {
getQuoteOptions,
listEbillsOptions,
getEbillEndorsementsOptions,
getEbillMintCompleteOptions,
postTokenStatusMutation,
-} from "@/generated/client/@tanstack/react-query.gen"
-import { useMutation, useQuery } from "@tanstack/react-query"
-import { useParams, Link, useLocation } from "react-router"
-import { humanReadableDurationDays } from "@/utils/dates"
-import { BreadcrumbLink } from "@/components/ui/breadcrumb"
-import { QuoteActions } from "./QuoteActions.tsx"
-import { truncateString, formatStatusLabel } from "@/utils/strings.ts"
-import { getQuoteStatusVariant } from "@/utils/quote-status"
-import { TruncatedTextPopover } from "@/components/TruncatedTextPopover.tsx"
-import { EndorsementChain } from "@/components/EndorsementChain"
-import { FeeTokenQRCodeModal } from "@/components/QRCodeWithErrorBoundary"
-import { serializeKeysetId } from "@/utils/keyset"
-import { useIntl } from "react-intl"
-import { useEffect, useRef } from "react"
-import { toast } from "sonner"
-import { getApiErrorMessage } from "@/lib/api-error"
+} from "@/generated/client/@tanstack/react-query.gen";
+import { useMutation, useQuery } from "@tanstack/react-query";
+import { useParams, Link, useLocation } from "react-router";
+import { humanReadableDurationDays } from "@/utils/dates";
+import { BreadcrumbLink } from "@/components/ui/breadcrumb";
+import { QuoteActions } from "./QuoteActions.tsx";
+import { truncateString, formatStatusLabel } from "@/utils/strings.ts";
+import { getQuoteStatusVariant } from "@/utils/quote-status";
+import { TruncatedTextPopover } from "@/components/TruncatedTextPopover.tsx";
+import { EndorsementChain } from "@/components/EndorsementChain";
+import { FeeTokenQRCodeModal } from "@/components/QRCodeWithErrorBoundary";
+import { serializeKeysetId } from "@/utils/keyset";
+import { useIntl } from "react-intl";
+import { useEffect, useRef } from "react";
+import { toast } from "sonner";
+import { getApiErrorMessage } from "@/lib/api-error";
interface LocationState {
- from?: string
+ from?: string;
}
function Loader() {
@@ -37,12 +40,12 @@ function Loader() {
- )
+ );
}
function PageBody({ id }: { id: string }) {
- const intl = useIntl()
- const EBILL_POLL_INTERVAL_MS = 30_000
+ const intl = useIntl();
+ const EBILL_POLL_INTERVAL_MS = 30_000;
const {
data: quoteData,
isFetching,
@@ -53,33 +56,34 @@ function PageBody({ id }: { id: string }) {
path: { qid: id },
}),
retry: 1,
- })
+ });
- const billId = quoteData?.bill?.id
- const quoteStatus = quoteData?.status as string | undefined
+ const billId = quoteData?.bill?.id;
+ const quoteStatus = quoteData?.status as string | undefined;
const ebillsQuery = useQuery({
...listEbillsOptions(),
retry: 1,
enabled: !!billId,
refetchInterval: (query) => {
- if (query.state.error) return false
- const ebill = (query.state.data ?? []).find((item) => item.id === billId)
- return ebill?.status?.payment?.paid ? false : EBILL_POLL_INTERVAL_MS
+ if (query.state.error) return false;
+ const ebill = (query.state.data ?? []).find((item) => item.id === billId);
+ return ebill?.status?.payment?.paid ? false : EBILL_POLL_INTERVAL_MS;
},
- })
+ });
const endorsementsQuery = useQuery({
...getEbillEndorsementsOptions({ path: { bid: billId ?? "" } }),
retry: 1,
enabled: !!billId,
- })
+ });
- const ebill = ebillsQuery.data?.find((item) => item.id === billId)
- const isPaid = ebill?.status?.payment?.paid === true
- const shouldCheckMintComplete = quoteStatus === "Accepted" || quoteStatus === "MintingEnabled" || isPaid
+ const ebill = ebillsQuery.data?.find((item) => item.id === billId);
+ const isPaid = ebill?.status?.payment?.paid === true;
+ const shouldCheckMintComplete =
+ quoteStatus === "Accepted" || quoteStatus === "MintingEnabled" || isPaid;
- const feeTokenRequestRef = useRef(null)
+ const feeTokenRequestRef = useRef(null);
const {
mutate: requestFeeTokenStatus,
@@ -91,7 +95,7 @@ function PageBody({ id }: { id: string }) {
...postTokenStatusMutation(),
retry: 5,
onError: (error) => {
- const message = getApiErrorMessage(error)
+ const message = getApiErrorMessage(error);
toast.error(
intl.formatMessage(
{
@@ -100,10 +104,10 @@ function PageBody({ id }: { id: string }) {
},
{ error: message },
),
- )
- feeTokenRequestRef.current = null
+ );
+ feeTokenRequestRef.current = null;
},
- })
+ });
const mintCompleteQuery = useQuery({
...getEbillMintCompleteOptions({ path: { bid: billId ?? "" } }),
@@ -111,38 +115,45 @@ function PageBody({ id }: { id: string }) {
enabled: !!billId && shouldCheckMintComplete,
refetchInterval: (query) => {
if (!shouldCheckMintComplete) {
- return false
+ return false;
}
- const data = query.state.data
- return data?.complete === false ? 60000 : false
+ const data = query.state.data;
+ return data?.complete === false ? 60000 : false;
},
- })
+ });
- const feeTokenFromQuote = quoteData && "fee" in quoteData ? quoteData.fee : null
- const quoteStatusForEffect = quoteData?.status
+ const feeTokenFromQuote =
+ quoteData && "fee" in quoteData ? quoteData.fee : null;
+ const quoteStatusForEffect = quoteData?.status;
useEffect(() => {
if (!feeTokenFromQuote || quoteStatusForEffect !== "MintingEnabled") {
- return
+ return;
}
if (feeTokenRequestRef.current === feeTokenFromQuote) {
- return
+ return;
}
if (isFeeTokenStatusPending || isFeeTokenStatusSuccess) {
- return
+ return;
}
- feeTokenRequestRef.current = feeTokenFromQuote
+ feeTokenRequestRef.current = feeTokenFromQuote;
requestFeeTokenStatus({
body: { token: feeTokenFromQuote },
- })
- }, [feeTokenFromQuote, isFeeTokenStatusPending, isFeeTokenStatusSuccess, quoteStatusForEffect, requestFeeTokenStatus])
+ });
+ }, [
+ feeTokenFromQuote,
+ isFeeTokenStatusPending,
+ isFeeTokenStatusSuccess,
+ quoteStatusForEffect,
+ requestFeeTokenStatus,
+ ]);
if (error) {
- const errorMessage = getApiErrorMessage(error)
+ const errorMessage = getApiErrorMessage(error);
return (
@@ -165,34 +176,39 @@ function PageBody({ id }: { id: string }) {
})}
- )
+ );
}
if (isLoading) {
- return
+ return ;
}
- const quote = quoteData!
- const bill = quote?.bill
- const quoteStatusValue = quote.status as string
- const feeToken = "fee" in quote && typeof quote.fee === "string" ? quote.fee : null
-
- const billStatus = ebill?.status
- const paymentStatus = billStatus?.payment
- const cws = ebill?.current_waiting_state
- const isMintComplete = mintCompleteQuery.data?.complete ?? false
- const isMintCompleteLoading = mintCompleteQuery.isLoading
- const ebillPaid = Boolean(paymentStatus?.paid)
- const hasPaymentRequestInWaitingState = Boolean(cws && "Payment" in cws)
+ const quote = quoteData!;
+ const bill = quote?.bill;
+ const quoteStatusValue = quote.status as string;
+ const feeToken =
+ "fee" in quote && typeof quote.fee === "string" ? quote.fee : null;
+
+ const billStatus = ebill?.status;
+ const paymentStatus = billStatus?.payment;
+ const cws = ebill?.current_waiting_state;
+ const isMintComplete = mintCompleteQuery.data?.complete ?? false;
+ const isMintCompleteLoading = mintCompleteQuery.isLoading;
+ const ebillPaid = Boolean(paymentStatus?.paid);
+ const hasPaymentRequestInWaitingState = Boolean(cws && "Payment" in cws);
const requestedToPay = Boolean(
- paymentStatus?.requested_to_pay ?? billStatus?.has_requested_funds ?? hasPaymentRequestInWaitingState,
- )
- const rejectedToPay = Boolean(paymentStatus?.rejected_to_pay)
- const paymentDeadlineTs = paymentStatus?.payment_deadline_timestamp ?? null
- const timeOfRequestToPay = paymentStatus?.time_of_request_to_pay ?? null
-
- const isInMempool = cws && "Payment" in cws && cws.Payment.payment_data?.in_mempool === true
- const showPayment = quoteStatus === "Accepted" || quoteStatus === "MintingEnabled"
+ paymentStatus?.requested_to_pay ??
+ billStatus?.has_requested_funds ??
+ hasPaymentRequestInWaitingState,
+ );
+ const rejectedToPay = Boolean(paymentStatus?.rejected_to_pay);
+ const paymentDeadlineTs = paymentStatus?.payment_deadline_timestamp ?? null;
+ const timeOfRequestToPay = paymentStatus?.time_of_request_to_pay ?? null;
+
+ const isInMempool =
+ cws && "Payment" in cws && cws.Payment.payment_data?.in_mempool === true;
+ const showPayment =
+ quoteStatus === "Accepted" || quoteStatus === "MintingEnabled";
if (!quote || !bill) {
return (
@@ -202,16 +218,16 @@ function PageBody({ id }: { id: string }) {
defaultMessage: "No quote data available",
})}
- )
+ );
}
- const maturityDate = bill.maturity_date ? new Date(bill.maturity_date) : null
+ const maturityDate = bill.maturity_date ? new Date(bill.maturity_date) : null;
const maturityLabel = maturityDate
? humanReadableDurationDays(intl.locale, maturityDate)
: intl.formatMessage({
id: "quotes.common.unknown",
defaultMessage: "Unknown",
- })
+ });
return (
@@ -260,14 +276,22 @@ function PageBody({ id }: { id: string }) {
})}
{isMintCompleteLoading ? (
-
+
{intl.formatMessage({
id: "quotes.redemption.pending",
defaultMessage: "Pending",
})}
) : (
-
+
{isMintComplete
? intl.formatMessage({
id: "quotes.redemption.complete",
@@ -302,35 +326,50 @@ function PageBody({ id }: { id: string }) {
})}
{ebillPaid ? (
-
+
{intl.formatMessage({
id: "quotes.payment.paid",
defaultMessage: "Paid",
})}
) : rejectedToPay ? (
-
+
{intl.formatMessage({
id: "quotes.payment.rejected",
defaultMessage: "Rejected to pay",
})}
) : isInMempool ? (
-
+
{intl.formatMessage({
id: "quotes.payment.inMempool",
defaultMessage: "In mempool",
})}
) : !requestedToPay ? (
-
+
{intl.formatMessage({
id: "quotes.payment.notRequested",
defaultMessage: "Not requested",
})}
) : (
-
+
{intl.formatMessage({
id: "quotes.payment.requested",
defaultMessage: "Requested",
@@ -358,7 +397,9 @@ function PageBody({ id }: { id: string }) {
defaultMessage: "Fee:",
})}
- {quote.discounted} sat
+
+ {quote.discounted} sat
+
@@ -367,7 +408,9 @@ function PageBody({ id }: { id: string }) {
defaultMessage: "Effective fee (absolute):",
})}
- {bill.sum - quote.discounted} sat
+
+ {bill.sum - quote.discounted} sat
+
@@ -377,7 +420,10 @@ function PageBody({ id }: { id: string }) {
})}
- {(((bill.sum - quote.discounted) / bill.sum) * 100).toFixed(4)}%
+ {(((bill.sum - quote.discounted) / bill.sum) * 100).toFixed(
+ 4,
+ )}
+ %
>
@@ -398,35 +444,50 @@ function PageBody({ id }: { id: string }) {
/>
{isFeeTokenStatusPending ? (
-
+
{intl.formatMessage({
id: "quotes.feeToken.badge.checking",
defaultMessage: "Checking...",
})}
) : feeTokenStatusData?.state === "Spent" ? (
-
+
{intl.formatMessage({
id: "quotes.feeToken.badge.spent",
defaultMessage: "Spent",
})}
) : feeTokenStatusData?.state === "Unspent" ? (
-
+
{intl.formatMessage({
id: "quotes.feeToken.badge.active",
defaultMessage: "Active",
})}
) : isFeeTokenStatusError ? (
-
+
{intl.formatMessage({
id: "quotes.feeToken.badge.error",
defaultMessage: "Error",
})}
) : feeTokenStatusData?.state ? (
-
+
{intl.formatMessage({
id: "quotes.feeToken.badge.unknown",
defaultMessage: "Unknown",
@@ -500,7 +561,9 @@ function PageBody({ id }: { id: string }) {
})}
:
-
+
)}
@@ -523,22 +586,35 @@ function PageBody({ id }: { id: string }) {
isLoading={endorsementsQuery.isLoading}
issueDate={ebill?.data?.issue_date}
maturityDate={bill.maturity_date}
- requestToPayTimestamp={ebill?.status?.payment?.time_of_request_to_pay ?? undefined}
+ requestToPayTimestamp={
+ ebill?.status?.payment?.time_of_request_to_pay ?? undefined
+ }
rejectedToPayTimestamp={
- ebill?.status?.payment?.rejected_to_pay ? (ebill?.status?.last_block_time ?? undefined) : undefined
+ ebill?.status?.payment?.rejected_to_pay
+ ? (ebill?.status?.last_block_time ?? undefined)
+ : undefined
+ }
+ paymentTimestamp={
+ ebill?.status?.payment?.paid
+ ? (ebill?.status?.last_block_time ?? undefined)
+ : undefined
}
- paymentTimestamp={ebill?.status?.payment?.paid ? (ebill?.status?.last_block_time ?? undefined) : undefined}
acceptanceTimestamp={
ebill?.status?.acceptance?.accepted
- ? (ebill?.status?.acceptance?.time_of_request_to_accept ?? undefined)
+ ? (ebill?.status?.acceptance?.time_of_request_to_accept ??
+ undefined)
: undefined
}
rejectionTimestamp={
- ebill?.status?.acceptance?.rejected_to_accept ? (ebill?.status?.last_block_time ?? undefined) : undefined
+ ebill?.status?.acceptance?.rejected_to_accept
+ ? (ebill?.status?.last_block_time ?? undefined)
+ : undefined
}
mintingEnabled={quoteStatusValue === "MintingEnabled"}
quoteOffered={
- quoteStatusValue === "Offered" || quoteStatusValue === "Accepted" || quoteStatusValue === "MintingEnabled"
+ quoteStatusValue === "Offered" ||
+ quoteStatusValue === "Accepted" ||
+ quoteStatusValue === "MintingEnabled"
}
offeredTimestamp={
"submitted" in quote
@@ -549,35 +625,41 @@ function PageBody({ id }: { id: string }) {
}
/>
- )
+ );
}
export default function QuotePage() {
- const intl = useIntl()
- const { id } = useParams()
- const quoteId = id ?? ""
- const location = useLocation()
- const state = location.state as LocationState | null
- const fromPath = state?.from
- const fromKeyset = fromPath?.startsWith("/keysets/")
- const keysetIdFromState = fromKeyset && fromPath ? fromPath.split("/keysets/")[1] : null
+ const intl = useIntl();
+ const { id } = useParams();
+ const quoteId = id ?? "";
+ const location = useLocation();
+ const state = location.state as LocationState | null;
+ const fromPath = state?.from;
+ const fromKeyset = fromPath?.startsWith("/keysets/");
+ const keysetIdFromState =
+ fromKeyset && fromPath ? fromPath.split("/keysets/")[1] : null;
const { data: quoteData } = useQuery({
...getQuoteOptions({
path: { qid: quoteId },
}),
retry: 1,
- })
+ });
- const quoteDataStatus = quoteData?.status as string | undefined
+ const quoteDataStatus = quoteData?.status as string | undefined;
const hasKeysetId =
- quoteData && (quoteDataStatus === "Accepted" || quoteDataStatus === "MintingEnabled") && "keyset_id" in quoteData
+ quoteData &&
+ (quoteDataStatus === "Accepted" || quoteDataStatus === "MintingEnabled") &&
+ "keyset_id" in quoteData;
return (
<>
+
{intl.formatMessage({
id: "quotes.breadcrumb",
@@ -599,28 +681,46 @@ export default function QuotePage() {
{truncateString(quoteId, 16)}
{fromKeyset && keysetIdFromState ? (
-
-
+
+
{intl.formatMessage({
id: "quotes.detail.backToKeyset",
defaultMessage: "Back to keyset",
})}{" "}
- {truncateString(keysetIdFromState, 16)}
+
+ {truncateString(keysetIdFromState, 16)}
+
) : hasKeysetId ? (
-
-
+
+
{intl.formatMessage({
id: "quotes.detail.goToKeyset",
defaultMessage: "Go to keyset",
})}{" "}
- {truncateString(serializeKeysetId(quoteData.keyset_id), 16)}
+
+ {truncateString(serializeKeysetId(quoteData.keyset_id), 16)}
+
) : null}
>
- )
+ );
}
diff --git a/src/pages/quotes/StatusQuotePage.test.tsx b/src/pages/quotes/StatusQuotePage.test.tsx
index 25e5b6d..e6783cf 100644
--- a/src/pages/quotes/StatusQuotePage.test.tsx
+++ b/src/pages/quotes/StatusQuotePage.test.tsx
@@ -1,65 +1,70 @@
-import { act, type ReactElement } from "react"
-import { createRoot, type Root } from "react-dom/client"
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { IntlProvider } from "react-intl"
-import { MemoryRouter } from "react-router"
-import StatusQuotePage from "./StatusQuotePage"
+import { act, type ReactElement } from "react";
+import { createRoot, type Root } from "react-dom/client";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { IntlProvider } from "react-intl";
+import { MemoryRouter } from "react-router";
+import StatusQuotePage from "./StatusQuotePage";
interface QueryKeyEntry {
- _id: string
- path?: { qid: string }
+ _id: string;
+ path?: { qid: string };
}
interface QueryOptions {
- queryKey: QueryKeyEntry[]
+ queryKey: QueryKeyEntry[];
}
interface QueryResult {
- data: unknown
- isLoading: boolean
- isFetching?: boolean
- error: Error | null
+ data: unknown;
+ isLoading: boolean;
+ isFetching?: boolean;
+ error: Error | null;
}
interface UseQueriesArgs {
- queries: { queryKey?: { path?: { qid: string } }[] }[]
+ queries: { queryKey?: { path?: { qid: string } }[] }[];
}
interface UseQueriesResultItem {
- data: unknown
- isLoading: boolean
+ data: unknown;
+ isLoading: boolean;
}
-const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>()
-const mockUseQueries = vi.fn<(args: UseQueriesArgs) => UseQueriesResultItem[]>()
+const mockUseQuery = vi.fn<(options: QueryOptions) => QueryResult>();
+const mockUseQueries =
+ vi.fn<(args: UseQueriesArgs) => UseQueriesResultItem[]>();
vi.mock("sonner", () => ({
toast: { error: vi.fn() },
-}))
+}));
vi.mock("@tanstack/react-query", async () => {
- const actual = await vi.importActual("@tanstack/react-query")
+ const actual = await vi.importActual(
+ "@tanstack/react-query",
+ );
return {
...actual,
useQuery: (options: QueryOptions) => mockUseQuery(options),
useQueries: (args: UseQueriesArgs) => mockUseQueries(args),
- }
-})
+ };
+});
vi.mock("@/generated/client/@tanstack/react-query.gen", () => ({
listQuotesOptions: () => ({ queryKey: [{ _id: "listQuotes" }] }),
- getQuoteOptions: ({ path }: { path: { qid: string } }) => ({ queryKey: [{ _id: "getQuote", path }] }),
-}))
+ getQuoteOptions: ({ path }: { path: { qid: string } }) => ({
+ queryKey: [{ _id: "getQuote", path }],
+ }),
+}));
-let root: Root | null = null
-let container: HTMLDivElement | null = null
+let root: Root | null = null;
+let container: HTMLDivElement | null = null;
function renderIntoDom(element: ReactElement): HTMLDivElement {
- const mount = document.createElement("div")
- document.body.appendChild(mount)
- const mountRoot = createRoot(mount)
+ const mount = document.createElement("div");
+ document.body.appendChild(mount);
+ const mountRoot = createRoot(mount);
act(() => {
- mountRoot.render(element)
- })
- root = mountRoot
- container = mount
- return mount
+ mountRoot.render(element);
+ });
+ root = mountRoot;
+ container = mount;
+ return mount;
}
function renderPage(status?: "Accepted" | "Pending"): HTMLDivElement {
@@ -69,22 +74,22 @@ function renderPage(status?: "Accepted" | "Pending"): HTMLDivElement {
,
- )
+ );
}
beforeEach(() => {
- vi.clearAllMocks()
+ vi.clearAllMocks();
if (root && container) {
act(() => {
- root?.unmount()
- })
- container.remove()
- root = null
- container = null
+ root?.unmount();
+ });
+ container.remove();
+ root = null;
+ container = null;
}
mockUseQuery.mockImplementation((opts: QueryOptions) => {
- const id = opts.queryKey[0]._id
+ const id = opts.queryKey[0]._id;
if (id === "listQuotes") {
return {
data: {
@@ -96,7 +101,7 @@ beforeEach(() => {
isLoading: false,
isFetching: false,
error: null,
- }
+ };
}
if (id === "getQuote") {
@@ -113,11 +118,16 @@ beforeEach(() => {
},
isLoading: false,
error: null,
- }
+ };
}
- return { data: undefined, isLoading: false, isFetching: false, error: null }
- })
+ return {
+ data: undefined,
+ isLoading: false,
+ isFetching: false,
+ error: null,
+ };
+ });
mockUseQueries.mockImplementation(({ queries }: UseQueriesArgs) =>
queries.map((query) => ({
@@ -129,21 +139,21 @@ beforeEach(() => {
},
isLoading: false,
})),
- )
-})
+ );
+});
describe("StatusQuotePage", () => {
it("shows all quotes page title when no status filter is passed", () => {
- const page = renderPage()
- expect(page.textContent).toContain("All quotes")
- })
+ const page = renderPage();
+ expect(page.textContent).toContain("All quotes");
+ });
it("filters cards by status", () => {
- const page = renderPage("Accepted")
- expect(page.textContent).toContain("Accepted quotes")
- expect(page.textContent).toContain("quote-accepted")
- expect(page.textContent).not.toContain("quote-pending")
- })
+ const page = renderPage("Accepted");
+ expect(page.textContent).toContain("Accepted quotes");
+ expect(page.textContent).toContain("quote-accepted");
+ expect(page.textContent).not.toContain("quote-pending");
+ });
it("shows API error state when quotes query fails", () => {
mockUseQuery.mockImplementation((opts: QueryOptions) => {
@@ -153,16 +163,21 @@ describe("StatusQuotePage", () => {
isLoading: false,
isFetching: false,
error: new Error("network down"),
- }
+ };
}
- return { data: undefined, isLoading: false, isFetching: false, error: null }
- })
- mockUseQueries.mockReturnValue([])
+ return {
+ data: undefined,
+ isLoading: false,
+ isFetching: false,
+ error: null,
+ };
+ });
+ mockUseQueries.mockReturnValue([]);
- const page = renderPage()
- expect(page.textContent).toContain("Failed to load quotes")
- expect(page.textContent).toContain("network down")
- })
+ const page = renderPage();
+ expect(page.textContent).toContain("Failed to load quotes");
+ expect(page.textContent).toContain("network down");
+ });
it("shows empty state when quotes list is empty", () => {
mockUseQuery.mockImplementation((opts: QueryOptions) => {
@@ -172,13 +187,18 @@ describe("StatusQuotePage", () => {
isLoading: false,
isFetching: false,
error: null,
- }
+ };
}
- return { data: undefined, isLoading: false, isFetching: false, error: null }
- })
- mockUseQueries.mockReturnValue([])
-
- const page = renderPage()
- expect(page.textContent).toContain("No quotes available.")
- })
-})
+ return {
+ data: undefined,
+ isLoading: false,
+ isFetching: false,
+ error: null,
+ };
+ });
+ mockUseQueries.mockReturnValue([]);
+
+ const page = renderPage();
+ expect(page.textContent).toContain("No quotes available.");
+ });
+});
diff --git a/src/pages/quotes/StatusQuotePage.tsx b/src/pages/quotes/StatusQuotePage.tsx
index 6fb76eb..0ce3a5a 100644
--- a/src/pages/quotes/StatusQuotePage.tsx
+++ b/src/pages/quotes/StatusQuotePage.tsx
@@ -1,26 +1,33 @@
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { PageTitle } from "@/components/PageTitle"
-import { Button } from "@/components/ui/button"
-import { Card, CardTitle } from "@/components/ui/card"
-import { Skeleton } from "@/components/ui/skeleton"
-import { listQuotesOptions, getQuoteOptions } from "@/generated/client/@tanstack/react-query.gen"
-import { useQuery, useQueries } from "@tanstack/react-query"
-import { LoaderIcon } from "lucide-react"
-import { Link, useNavigate } from "react-router"
-import { formatNumber, truncateString, formatStatusLabel } from "@/utils/strings"
-import { getQuoteStatusVariant } from "@/utils/quote-status"
-import { Badge } from "@/components/ui/badge"
-import { cn } from "@/lib/utils"
-import type { LightInfo } from "@/generated/client/types.gen"
-import { ParticipantsOverviewCard } from "@/components/ParticipantsOverview"
-import { toast } from "sonner"
-import * as React from "react"
-import SearchComponent, { HighlightText } from "@/components/ui/search"
-import { useState } from "react"
-import { BreadcrumbLink } from "@/components/ui/breadcrumb"
-import { SortButtons } from "@/components/SortButtons"
-import { useIntl } from "react-intl"
-import { getApiErrorMessage } from "@/lib/api-error"
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { PageTitle } from "@/components/PageTitle";
+import { Button } from "@/components/ui/button";
+import { Card, CardTitle } from "@/components/ui/card";
+import { Skeleton } from "@/components/ui/skeleton";
+import {
+ listQuotesOptions,
+ getQuoteOptions,
+} from "@/generated/client/@tanstack/react-query.gen";
+import { useQuery, useQueries } from "@tanstack/react-query";
+import { LoaderIcon } from "lucide-react";
+import { Link, useNavigate } from "react-router";
+import {
+ formatNumber,
+ truncateString,
+ formatStatusLabel,
+} from "@/utils/strings";
+import { getQuoteStatusVariant } from "@/utils/quote-status";
+import { Badge } from "@/components/ui/badge";
+import { cn } from "@/lib/utils";
+import type { LightInfo } from "@/generated/client/types.gen";
+import { ParticipantsOverviewCard } from "@/components/ParticipantsOverview";
+import { toast } from "sonner";
+import * as React from "react";
+import SearchComponent, { HighlightText } from "@/components/ui/search";
+import { useState } from "react";
+import { BreadcrumbLink } from "@/components/ui/breadcrumb";
+import { SortButtons } from "@/components/SortButtons";
+import { useIntl } from "react-intl";
+import { getApiErrorMessage } from "@/lib/api-error";
type QuoteStatus =
| "Accepted"
@@ -30,14 +37,20 @@ type QuoteStatus =
| "Pending"
| "Rejected"
| "Canceled"
- | "MintingEnabled"
-type SortBy = "status-asc" | "status-desc" | "sum-asc" | "sum-desc" | "maturity-asc" | "maturity-desc"
+ | "MintingEnabled";
+type SortBy =
+ | "status-asc"
+ | "status-desc"
+ | "sum-asc"
+ | "sum-desc"
+ | "maturity-asc"
+ | "maturity-desc";
-const RETRY_COUNT = 2
-const retryDelay = (attempt: number) => Math.min(1000 * 2 ** attempt, 10_000)
+const RETRY_COUNT = 2;
+const retryDelay = (attempt: number) => Math.min(1000 * 2 ** attempt, 10_000);
interface StatusQuotePageProps {
- status?: QuoteStatus
+ status?: QuoteStatus;
}
function Loader() {
@@ -51,12 +64,18 @@ function Loader() {
- )
+ );
}
-function QuoteItemCard({ quote, searchQuery }: { quote: LightInfo; searchQuery: string }) {
- const intl = useIntl()
- const navigate = useNavigate()
+function QuoteItemCard({
+ quote,
+ searchQuery,
+}: {
+ quote: LightInfo;
+ searchQuery: string;
+}) {
+ const intl = useIntl();
+ const navigate = useNavigate();
const queryResult = useQuery({
...getQuoteOptions({
@@ -65,35 +84,48 @@ function QuoteItemCard({ quote, searchQuery }: { quote: LightInfo; searchQuery:
retry: RETRY_COUNT,
retryDelay,
enabled: !!quote.id,
- })
+ });
- const { data: quoteDetails, isLoading: isLoadingDetails, error: detailsError } = queryResult
- const bill = quoteDetails?.bill
+ const {
+ data: quoteDetails,
+ isLoading: isLoadingDetails,
+ error: detailsError,
+ } = queryResult;
+ const bill = quoteDetails?.bill;
const handleQuoteClick = (e: React.MouseEvent) => {
if (detailsError) {
- e.preventDefault()
- const errorMessage = getApiErrorMessage(detailsError)
- toast.error(intl.formatMessage({ id: "quotes.card.error.title", defaultMessage: "Cannot load quote" }), {
- description: intl.formatMessage(
- {
- id: "quotes.card.error.description",
- defaultMessage: "Quote {id} is unavailable. {message}",
- },
- {
- id: truncateString(quote.id, 12),
- message:
- errorMessage ||
- intl.formatMessage({ id: "quotes.error.tryAgain", defaultMessage: "Please try again later." }),
- },
- ),
- id: `quote-error-${quote.id}`,
- duration: 5000,
- })
+ e.preventDefault();
+ const errorMessage = getApiErrorMessage(detailsError);
+ toast.error(
+ intl.formatMessage({
+ id: "quotes.card.error.title",
+ defaultMessage: "Cannot load quote",
+ }),
+ {
+ description: intl.formatMessage(
+ {
+ id: "quotes.card.error.description",
+ defaultMessage: "Quote {id} is unavailable. {message}",
+ },
+ {
+ id: truncateString(quote.id, 12),
+ message:
+ errorMessage ||
+ intl.formatMessage({
+ id: "quotes.error.tryAgain",
+ defaultMessage: "Please try again later.",
+ }),
+ },
+ ),
+ id: `quote-error-${quote.id}`,
+ duration: 5000,
+ },
+ );
} else {
- void navigate(`/quotes/${quote.id}`)
+ void navigate(`/quotes/${quote.id}`);
}
- }
+ };
return (
@@ -101,8 +133,14 @@ function QuoteItemCard({ quote, searchQuery }: { quote: LightInfo; searchQuery:
-
-
+
+
@@ -110,7 +148,10 @@ function QuoteItemCard({ quote, searchQuery }: { quote: LightInfo; searchQuery:
-
+
-
+
{intl.formatMessage({
id: "quotes.card.view",
defaultMessage: "View",
@@ -167,19 +212,19 @@ function QuoteItemCard({ quote, searchQuery }: { quote: LightInfo; searchQuery:
)}
- )
+ );
}
function QuoteList({ status }: { status?: QuoteStatus }) {
- const intl = useIntl()
- const [searchQuery, setSearchQuery] = useState("")
- const [sortBy, setSortBy] = useState
("maturity-asc")
+ const intl = useIntl();
+ const [searchQuery, setSearchQuery] = useState("");
+ const [sortBy, setSortBy] = useState("maturity-asc");
const { data, isFetching, error, isLoading } = useQuery({
...listQuotesOptions(),
retry: RETRY_COUNT,
retryDelay,
- })
+ });
/* TODO: optimize this with pagination or batch fetching if API supports it */
const quoteDetailsQueries = useQueries({
@@ -191,15 +236,16 @@ function QuoteList({ status }: { status?: QuoteStatus }) {
retryDelay,
enabled: !!quote.id,
})),
- })
+ });
const noQuotesMessage = intl.formatMessage({
id: "quotes.list.empty",
defaultMessage: "No quotes available.",
- })
+ });
if (error) {
- const errorMessage = (error as { message?: string }).message ?? String(error)
+ const errorMessage =
+ (error as { message?: string }).message ?? String(error);
return (
@@ -222,71 +268,85 @@ function QuoteList({ status }: { status?: QuoteStatus }) {
})}
- )
+ );
}
if (isLoading) {
- return
+ return ;
}
const filteredQuotes =
data?.quotes.filter((quote) => {
if (status && quote.status !== status) {
- return false
+ return false;
}
if (!searchQuery) {
- return true
+ return true;
}
- const query = searchQuery.toLowerCase()
- const quoteId = quote.id.toLowerCase()
- const quoteStatus = quote.status.toLowerCase()
- const quoteSum = quote.sum.toString()
+ const query = searchQuery.toLowerCase();
+ const quoteId = quote.id.toLowerCase();
+ const quoteStatus = quote.status.toLowerCase();
+ const quoteSum = quote.sum.toString();
- return quoteId.includes(query) || quoteStatus.includes(query) || quoteSum.includes(query)
- }) ?? []
+ return (
+ quoteId.includes(query) ||
+ quoteStatus.includes(query) ||
+ quoteSum.includes(query)
+ );
+ }) ?? [];
const sortedQuotes = [...filteredQuotes].sort((a, b) => {
- const aIndex = data?.quotes.findIndex((q) => q.id === a.id) ?? -1
- const bIndex = data?.quotes.findIndex((q) => q.id === b.id) ?? -1
+ const aIndex = data?.quotes.findIndex((q) => q.id === a.id) ?? -1;
+ const bIndex = data?.quotes.findIndex((q) => q.id === b.id) ?? -1;
- const aBill = aIndex >= 0 ? quoteDetailsQueries[aIndex]?.data?.bill : null
- const bBill = bIndex >= 0 ? quoteDetailsQueries[bIndex]?.data?.bill : null
+ const aBill = aIndex >= 0 ? quoteDetailsQueries[aIndex]?.data?.bill : null;
+ const bBill = bIndex >= 0 ? quoteDetailsQueries[bIndex]?.data?.bill : null;
switch (sortBy) {
case "status-asc":
- return a.status.localeCompare(b.status)
+ return a.status.localeCompare(b.status);
case "status-desc":
- return b.status.localeCompare(a.status)
+ return b.status.localeCompare(a.status);
case "sum-asc":
- return a.sum - b.sum
+ return a.sum - b.sum;
case "sum-desc":
- return b.sum - a.sum
+ return b.sum - a.sum;
case "maturity-asc": {
- if (!aBill?.maturity_date && !bBill?.maturity_date) return 0
- if (!aBill?.maturity_date) return 1
- if (!bBill?.maturity_date) return -1
- return new Date(aBill.maturity_date).getTime() - new Date(bBill.maturity_date).getTime()
+ if (!aBill?.maturity_date && !bBill?.maturity_date) return 0;
+ if (!aBill?.maturity_date) return 1;
+ if (!bBill?.maturity_date) return -1;
+ return (
+ new Date(aBill.maturity_date).getTime() -
+ new Date(bBill.maturity_date).getTime()
+ );
}
case "maturity-desc": {
- if (!aBill?.maturity_date && !bBill?.maturity_date) return 0
- if (!aBill?.maturity_date) return 1
- if (!bBill?.maturity_date) return -1
- return new Date(bBill.maturity_date).getTime() - new Date(aBill.maturity_date).getTime()
+ if (!aBill?.maturity_date && !bBill?.maturity_date) return 0;
+ if (!aBill?.maturity_date) return 1;
+ if (!bBill?.maturity_date) return -1;
+ return (
+ new Date(bBill.maturity_date).getTime() -
+ new Date(aBill.maturity_date).getTime()
+ );
}
default:
- return 0
+ return 0;
}
- })
+ });
const toggleSort = (field: "status" | "sum" | "maturity") => {
if (sortBy.startsWith(field)) {
- setSortBy(sortBy.endsWith("asc") ? (`${field}-desc` as SortBy) : (`${field}-asc` as SortBy))
+ setSortBy(
+ sortBy.endsWith("asc")
+ ? (`${field}-desc` as SortBy)
+ : (`${field}-asc` as SortBy),
+ );
} else {
- setSortBy(`${field}-asc` as SortBy)
+ setSortBy(`${field}-asc` as SortBy);
}
- }
+ };
const sortOptions = [
{
@@ -310,7 +370,7 @@ function QuoteList({ status }: { status?: QuoteStatus }) {
defaultMessage: "Status",
}),
},
- ]
+ ];
return (
<>
@@ -326,7 +386,11 @@ function QuoteList({ status }: { status?: QuoteStatus }) {
onChange={setSearchQuery}
size="sm"
/>
-
+
@@ -347,20 +411,25 @@ function QuoteList({ status }: { status?: QuoteStatus }) {
})}
)}
- {sortedQuotes.length === 0 && !searchQuery && {noQuotesMessage}
}
+ {sortedQuotes.length === 0 && !searchQuery && (
+ {noQuotesMessage}
+ )}
{sortedQuotes.map((quote, index) => {
if (!quote.id) {
- console.warn(`Quote at index ${index} is missing an ID:`, quote)
+ console.warn(`Quote at index ${index} is missing an ID:`, quote);
}
return (
-
+
- )
+ );
})}
>
- )
+ );
}
function PageBody({ status }: { status?: QuoteStatus }) {
@@ -370,17 +439,17 @@ function PageBody({ status }: { status?: QuoteStatus }) {
- )
+ );
}
export default function StatusQuotePage({ status }: StatusQuotePageProps) {
- const intl = useIntl()
+ const intl = useIntl();
const statusLabel = status
? intl.formatMessage({
id: `quote.status.${status}`,
defaultMessage: formatStatusLabel(status),
})
- : undefined
+ : undefined;
const pageTitle = status
? intl.formatMessage(
{
@@ -392,7 +461,7 @@ export default function StatusQuotePage({ status }: StatusQuotePageProps) {
: intl.formatMessage({
id: "quotes.statusPage.titleAll",
defaultMessage: "All quotes",
- })
+ });
return (
<>
@@ -400,7 +469,10 @@ export default function StatusQuotePage({ status }: StatusQuotePageProps) {
parents={
status
? [
-
+
{intl.formatMessage({
id: "quotes.breadcrumb",
@@ -422,5 +494,5 @@ export default function StatusQuotePage({ status }: StatusQuotePageProps) {
{pageTitle}
>
- )
+ );
}
diff --git a/src/pages/quotes/components/CalendarModal.tsx b/src/pages/quotes/components/CalendarModal.tsx
index 64e59cb..00b5a57 100644
--- a/src/pages/quotes/components/CalendarModal.tsx
+++ b/src/pages/quotes/components/CalendarModal.tsx
@@ -1,29 +1,49 @@
-import { Button } from "@/components/ui/button.tsx"
-import { Calendar } from "@/components/DatePicker/calendar.tsx"
-import { CalendarIcon } from "lucide-react"
-import { addDays, isAfter, isBefore, isSameDay } from "date-fns"
-import { cn } from "@/lib/utils.ts"
-import { useIntl } from "react-intl"
-import { useUtcDateFormatters } from "@/hooks/use-utc-date-formatters"
+import { Button } from "@/components/ui/button.tsx";
+import { Calendar } from "@/components/DatePicker/calendar.tsx";
+import { CalendarIcon } from "lucide-react";
+import { addDays, isAfter, isBefore, isSameDay } from "date-fns";
+import { cn } from "@/lib/utils.ts";
+import { useIntl } from "react-intl";
+import { useUtcDateFormatters } from "@/hooks/use-utc-date-formatters";
interface CalendarModalProps {
- isOpen: boolean
- selectedDate?: Date
- draftDate?: Date
- title: string
- minDate?: Date
- maxDate?: Date
- onClose: () => void
- onDateChange: (date: Date) => void
- onConfirm: () => void
- onCancel: () => void
+ isOpen: boolean;
+ selectedDate?: Date;
+ draftDate?: Date;
+ title: string;
+ minDate?: Date;
+ maxDate?: Date;
+ onClose: () => void;
+ onDateChange: (date: Date) => void;
+ onConfirm: () => void;
+ onCancel: () => void;
}
const toUtcStartOfDay = (date: Date) =>
- new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0, 0))
+ new Date(
+ Date.UTC(
+ date.getUTCFullYear(),
+ date.getUTCMonth(),
+ date.getUTCDate(),
+ 0,
+ 0,
+ 0,
+ 0,
+ ),
+ );
const toUtcEndOfDay = (date: Date) =>
- new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 23, 59, 59, 999))
+ new Date(
+ Date.UTC(
+ date.getUTCFullYear(),
+ date.getUTCMonth(),
+ date.getUTCDate(),
+ 23,
+ 59,
+ 59,
+ 999,
+ ),
+ );
export function CalendarModal({
isOpen,
@@ -37,13 +57,14 @@ export function CalendarModal({
onConfirm,
onCancel,
}: CalendarModalProps) {
- const intl = useIntl()
- const { formatDateMmmDdYyyy } = useUtcDateFormatters(intl.locale)
- const fallbackMin = addDays(new Date(Date.now()), 1)
- const minDay = toUtcStartOfDay(minDate ?? fallbackMin)
- const maxDay = maxDate ? toUtcEndOfDay(maxDate) : null
- const disabled = (date: Date) => isBefore(date, minDay) || (maxDay ? isAfter(date, maxDay) : false)
- const displayMonth = draftDate ?? selectedDate ?? minDate ?? new Date()
+ const intl = useIntl();
+ const { formatDateMmmDdYyyy } = useUtcDateFormatters(intl.locale);
+ const fallbackMin = addDays(new Date(Date.now()), 1);
+ const minDay = toUtcStartOfDay(minDate ?? fallbackMin);
+ const maxDay = maxDate ? toUtcEndOfDay(maxDate) : null;
+ const disabled = (date: Date) =>
+ isBefore(date, minDay) || (maxDay ? isAfter(date, maxDay) : false);
+ const displayMonth = draftDate ?? selectedDate ?? minDate ?? new Date();
return (
<>
@@ -67,7 +88,9 @@ export function CalendarModal({
>
{title}
-
{draftDate ? formatDateMmmDdYyyy(draftDate) : "-"}
+
+ {draftDate ? formatDateMmmDdYyyy(draftDate) : "-"}
+
{
if (range?.from) {
- onDateChange(range.from)
+ onDateChange(range.from);
}
}}
disabled={disabled}
@@ -102,7 +125,13 @@ export function CalendarModal({
defaultMessage: "Cancel",
})}
-
+
{intl.formatMessage({
id: "Confirm",
defaultMessage: "Confirm",
@@ -113,17 +142,17 @@ export function CalendarModal({
>
- )
+ );
}
interface DatePickerButtonProps {
- date?: Date
- onClick: () => void
+ date?: Date;
+ onClick: () => void;
}
export function DatePickerButton({ date, onClick }: DatePickerButtonProps) {
- const intl = useIntl()
- const { formatDateMmmDdYyyy } = useUtcDateFormatters(intl.locale)
+ const intl = useIntl();
+ const { formatDateMmmDdYyyy } = useUtcDateFormatters(intl.locale);
return (
-
+
{date
? formatDateMmmDdYyyy(date)
@@ -141,5 +173,5 @@ export function DatePickerButton({ date, onClick }: DatePickerButtonProps) {
})}
- )
+ );
}
diff --git a/src/pages/quotes/components/DenyConfirmDrawer.tsx b/src/pages/quotes/components/DenyConfirmDrawer.tsx
index 8a46e7e..2c8dd54 100644
--- a/src/pages/quotes/components/DenyConfirmDrawer.tsx
+++ b/src/pages/quotes/components/DenyConfirmDrawer.tsx
@@ -1,23 +1,30 @@
-import { ConfirmDrawer } from "@/components/Drawers.tsx"
-import type { ReactNode } from "react"
-import { useIntl } from "react-intl"
+import { ConfirmDrawer } from "@/components/Drawers.tsx";
+import type { ReactNode } from "react";
+import { useIntl } from "react-intl";
interface DenyConfirmDrawerProps {
- title: string
- open: boolean
- onOpenChange: (open: boolean) => void
- onSubmit: () => void
- children: ReactNode
+ title: string;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSubmit: () => void;
+ children: ReactNode;
}
-export function DenyConfirmDrawer({ title, open, onOpenChange, onSubmit, children }: DenyConfirmDrawerProps) {
- const intl = useIntl()
+export function DenyConfirmDrawer({
+ title,
+ open,
+ onOpenChange,
+ onSubmit,
+ children,
+}: DenyConfirmDrawerProps) {
+ const intl = useIntl();
return (
- )
+ );
}
diff --git a/src/pages/quotes/components/OfferConfirmation.tsx b/src/pages/quotes/components/OfferConfirmation.tsx
index 5c5f45a..3880df1 100644
--- a/src/pages/quotes/components/OfferConfirmation.tsx
+++ b/src/pages/quotes/components/OfferConfirmation.tsx
@@ -1,58 +1,78 @@
-import { useEffect, useMemo, useState } from "react"
-import { ConfirmDrawer } from "@/components/Drawers.tsx"
-import { CalendarModal, DatePickerButton } from "./CalendarModal.tsx"
-import Big from "big.js"
-import type { OfferFormResult } from "./OfferFormDrawer.tsx"
-import { addDays, addYears } from "date-fns"
-import { getItem, removeItem, setItem } from "@/utils/local-storage"
-import { useIntl } from "react-intl"
+import { useEffect, useMemo, useState } from "react";
+import { ConfirmDrawer } from "@/components/Drawers.tsx";
+import { CalendarModal, DatePickerButton } from "./CalendarModal.tsx";
+import Big from "big.js";
+import type { OfferFormResult } from "./OfferFormDrawer.tsx";
+import { addDays, addYears } from "date-fns";
+import { getItem, removeItem, setItem } from "@/utils/local-storage";
+import { useIntl } from "react-intl";
interface OfferConfirmationProps {
- offerFormData?: OfferFormResult
- open: boolean
- onOpenChange: (open: boolean) => void
- onSubmit: (data: OfferFormResult) => void
- quoteId?: string
+ offerFormData?: OfferFormResult;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSubmit: (data: OfferFormResult) => void;
+ quoteId?: string;
}
-const OFFER_VALID_UNTIL_STORAGE_KEY_PREFIX = "offer-valid-until-"
+const OFFER_VALID_UNTIL_STORAGE_KEY_PREFIX = "offer-valid-until-";
-export function OfferConfirmation({ offerFormData, open, onOpenChange, onSubmit, quoteId }: OfferConfirmationProps) {
- const intl = useIntl()
- const [validUntilDate, setValidUntilDate] = useState(undefined)
- const [showValidUntilCalendar, setShowValidUntilCalendar] = useState(false)
- const [draftValidUntilDate, setDraftValidUntilDate] = useState(undefined)
+export function OfferConfirmation({
+ offerFormData,
+ open,
+ onOpenChange,
+ onSubmit,
+ quoteId,
+}: OfferConfirmationProps) {
+ const intl = useIntl();
+ const [validUntilDate, setValidUntilDate] = useState(
+ undefined,
+ );
+ const [showValidUntilCalendar, setShowValidUntilCalendar] = useState(false);
+ const [draftValidUntilDate, setDraftValidUntilDate] = useState<
+ Date | undefined
+ >(undefined);
const minDate = useMemo(() => {
- const date = addDays(new Date(), 1)
- date.setHours(0, 0, 0, 0)
- return date
- }, [])
+ const date = addDays(new Date(), 1);
+ date.setHours(0, 0, 0, 0);
+ return date;
+ }, []);
const maxDate = useMemo(() => {
- const date = addYears(new Date(), 1)
- date.setHours(23, 59, 59, 999)
- return date
- }, [])
- const storageKey = quoteId ? `${OFFER_VALID_UNTIL_STORAGE_KEY_PREFIX}${quoteId}` : null
+ const date = addYears(new Date(), 1);
+ date.setHours(23, 59, 59, 999);
+ return date;
+ }, []);
+ const storageKey = quoteId
+ ? `${OFFER_VALID_UNTIL_STORAGE_KEY_PREFIX}${quoteId}`
+ : null;
useEffect(() => {
if (!open || validUntilDate || !storageKey) {
- return
+ return;
}
- const stored = getItem(storageKey)
+ const stored = getItem(storageKey);
if (!stored) {
- return
+ return;
}
- const parsed = new Date(stored)
- if (!Number.isNaN(parsed.getTime()) && parsed >= minDate && parsed <= maxDate) {
- setValidUntilDate(parsed)
+ const parsed = new Date(stored);
+ if (
+ !Number.isNaN(parsed.getTime()) &&
+ parsed >= minDate &&
+ parsed <= maxDate
+ ) {
+ setValidUntilDate(parsed);
} else {
- removeItem(storageKey)
+ removeItem(storageKey);
}
- }, [open, validUntilDate, storageKey, minDate, maxDate])
+ }, [open, validUntilDate, storageKey, minDate, maxDate]);
const effectiveDiscount = offerFormData
- ? new Big(1).minus(offerFormData.discount.net.value.div(offerFormData.discount.gross.value))
- : undefined
+ ? new Big(1).minus(
+ offerFormData.discount.net.value.div(
+ offerFormData.discount.gross.value,
+ ),
+ )
+ : undefined;
return (
<>
@@ -67,17 +87,20 @@ export function OfferConfirmation({ offerFormData, open, onOpenChange, onSubmit,
})}
open={open}
onOpenChange={(isOpen) => {
- onOpenChange(isOpen)
+ onOpenChange(isOpen);
}}
submitButtonDisabled={!validUntilDate}
onSubmit={() => {
if (!offerFormData || !validUntilDate) {
- return
+ return;
}
- const finalOfferData = { ...offerFormData, ttl: { ttl: validUntilDate } }
+ const finalOfferData = {
+ ...offerFormData,
+ ttl: { ttl: validUntilDate },
+ };
- onSubmit(finalOfferData)
+ onSubmit(finalOfferData);
}}
>
@@ -88,7 +111,9 @@ export function OfferConfirmation({ offerFormData, open, onOpenChange, onSubmit,
defaultMessage: "Effective fee (relative):",
})}
- {effectiveDiscount?.mul(new Big("100")).toFixed(2)}%
+
+ {effectiveDiscount?.mul(new Big("100")).toFixed(2)}%
+
@@ -98,7 +123,9 @@ export function OfferConfirmation({ offerFormData, open, onOpenChange, onSubmit,
})}
- {offerFormData?.discount.gross.value.minus(offerFormData?.discount.net.value).toFixed(0)}{" "}
+ {offerFormData?.discount.gross.value
+ .minus(offerFormData?.discount.net.value)
+ .toFixed(0)}{" "}
{offerFormData?.discount.net.currency}
@@ -110,7 +137,8 @@ export function OfferConfirmation({ offerFormData, open, onOpenChange, onSubmit,
})}
- {offerFormData?.discount.net.value.round(0).toFixed(0)} {offerFormData?.discount.net.currency}
+ {offerFormData?.discount.net.value.round(0).toFixed(0)}{" "}
+ {offerFormData?.discount.net.currency}
@@ -123,9 +151,9 @@ export function OfferConfirmation({ offerFormData, open, onOpenChange, onSubmit,
{
- setDraftValidUntilDate(validUntilDate)
- onOpenChange(false)
- setShowValidUntilCalendar(true)
+ setDraftValidUntilDate(validUntilDate);
+ onOpenChange(false);
+ setShowValidUntilCalendar(true);
}}
/>
@@ -143,26 +171,26 @@ export function OfferConfirmation({ offerFormData, open, onOpenChange, onSubmit,
minDate={minDate}
maxDate={maxDate}
onClose={() => {
- setShowValidUntilCalendar(false)
- onOpenChange(true)
+ setShowValidUntilCalendar(false);
+ onOpenChange(true);
}}
onDateChange={setDraftValidUntilDate}
onConfirm={() => {
if (draftValidUntilDate) {
- setValidUntilDate(draftValidUntilDate)
+ setValidUntilDate(draftValidUntilDate);
if (storageKey) {
- setItem(storageKey, draftValidUntilDate.toISOString())
+ setItem(storageKey, draftValidUntilDate.toISOString());
}
}
- setShowValidUntilCalendar(false)
- onOpenChange(true)
+ setShowValidUntilCalendar(false);
+ onOpenChange(true);
}}
onCancel={() => {
- setShowValidUntilCalendar(false)
- setDraftValidUntilDate(undefined)
- onOpenChange(true)
+ setShowValidUntilCalendar(false);
+ setDraftValidUntilDate(undefined);
+ onOpenChange(true);
}}
/>
>
- )
+ );
}
diff --git a/src/pages/quotes/components/OfferFormDrawer.tsx b/src/pages/quotes/components/OfferFormDrawer.tsx
index 8fcf77b..899957f 100644
--- a/src/pages/quotes/components/OfferFormDrawer.tsx
+++ b/src/pages/quotes/components/OfferFormDrawer.tsx
@@ -1,35 +1,35 @@
-import Big from "big.js"
-import { BaseDrawer } from "@/components/Drawers.tsx"
-import { GrossToNetDiscountForm } from "@/components/GrossToNetDiscountForm.tsx"
-import type { InfoReply } from "@/generated/client/types.gen.ts"
-import type { ReactNode } from "react"
+import Big from "big.js";
+import { BaseDrawer } from "@/components/Drawers.tsx";
+import { GrossToNetDiscountForm } from "@/components/GrossToNetDiscountForm.tsx";
+import type { InfoReply } from "@/generated/client/types.gen.ts";
+import type { ReactNode } from "react";
export interface OfferFormResult {
discount: {
- days: number
- discountRate: Big
+ days: number;
+ discountRate: Big;
net: {
- value: Big
- currency: string
- }
+ value: Big;
+ currency: string;
+ };
gross: {
- value: Big
- currency: string
- }
- }
+ value: Big;
+ currency: string;
+ };
+ };
ttl: {
- ttl: Date
- }
+ ttl: Date;
+ };
}
interface OfferFormDrawerProps {
- title: string
- description: string
- value: InfoReply
- open: boolean
- onOpenChange: (open: boolean) => void
- onSubmit: (data: OfferFormResult) => void
- children: ReactNode
+ title: string;
+ description: string;
+ value: InfoReply;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSubmit: (data: OfferFormResult) => void;
+ children: ReactNode;
}
export function OfferFormDrawer({
@@ -41,29 +41,40 @@ export function OfferFormDrawer({
onSubmit,
children,
}: OfferFormDrawerProps) {
- const addDays = 30 * 24 * 60 * 60 * 1000
+ const addDays = 30 * 24 * 60 * 60 * 1000;
const handleFormSubmit = (values: {
- days: number
- discountRate: Big
- net: { value: Big; currency: string }
- gross: { value: Big; currency: string }
+ days: number;
+ discountRate: Big;
+ net: { value: Big; currency: string };
+ gross: { value: Big; currency: string };
}) => {
- const endDate = value.status === "Pending" ? new Date(value.suggested_expiration) : new Date(Date.now() + addDays)
+ const endDate =
+ value.status === "Pending"
+ ? new Date(value.suggested_expiration)
+ : new Date(Date.now() + addDays);
const result: OfferFormResult = {
discount: values,
ttl: { ttl: endDate },
- }
+ };
- onSubmit(result)
- }
+ onSubmit(result);
+ };
- const startDate = new Date()
- const endDate = value.bill.maturity_date ? new Date(value.bill.maturity_date) : new Date()
+ const startDate = new Date();
+ const endDate = value.bill.maturity_date
+ ? new Date(value.bill.maturity_date)
+ : new Date();
return (
-
+
- )
+ );
}
diff --git a/src/pages/quotes/components/PaymentRequestCard.tsx b/src/pages/quotes/components/PaymentRequestCard.tsx
index aae0503..3ebbf08 100644
--- a/src/pages/quotes/components/PaymentRequestCard.tsx
+++ b/src/pages/quotes/components/PaymentRequestCard.tsx
@@ -1,11 +1,11 @@
-import { TruncatedTextPopover } from "@/components/TruncatedTextPopover.tsx"
-import { useIntl } from "react-intl"
+import { TruncatedTextPopover } from "@/components/TruncatedTextPopover.tsx";
+import { useIntl } from "react-intl";
interface PaymentRequestCardProps {
- addressToPay?: string
- linkToPay?: string
- effectiveRequestTime: number | null
- effectiveDeadlineTs: number | null
+ addressToPay?: string;
+ linkToPay?: string;
+ effectiveRequestTime: number | null;
+ effectiveDeadlineTs: number | null;
}
export function PaymentRequestCard({
@@ -14,7 +14,7 @@ export function PaymentRequestCard({
effectiveRequestTime,
effectiveDeadlineTs,
}: PaymentRequestCardProps) {
- const intl = useIntl()
+ const intl = useIntl();
return (
@@ -32,7 +32,11 @@ export function PaymentRequestCard({
defaultMessage: "Address to pay",
})}
-
+
)}
{linkToPay && (
@@ -49,7 +53,11 @@ export function PaymentRequestCard({
rel="noopener noreferrer"
className="text-blue-600 hover:underline flex items-center"
>
-
+
)}
@@ -62,7 +70,10 @@ export function PaymentRequestCard({
})}
- {new Date(effectiveRequestTime * 1000).toLocaleString(intl.locale, { timeZone: "UTC" })}
+ {new Date(effectiveRequestTime * 1000).toLocaleString(
+ intl.locale,
+ { timeZone: "UTC" },
+ )}
)}
@@ -75,11 +86,14 @@ export function PaymentRequestCard({
})}
- {new Date(effectiveDeadlineTs * 1000).toLocaleString(intl.locale, { timeZone: "UTC" })}
+ {new Date(effectiveDeadlineTs * 1000).toLocaleString(
+ intl.locale,
+ { timeZone: "UTC" },
+ )}
)}
- )
+ );
}
diff --git a/src/pages/quotes/components/RequestToPayConfirmation.tsx b/src/pages/quotes/components/RequestToPayConfirmation.tsx
index 95327c4..72df31d 100644
--- a/src/pages/quotes/components/RequestToPayConfirmation.tsx
+++ b/src/pages/quotes/components/RequestToPayConfirmation.tsx
@@ -1,36 +1,46 @@
-import { useEffect, useMemo, useState } from "react"
-import { Button } from "@/components/ui/button.tsx"
-import { ConfirmDrawer } from "@/components/Drawers.tsx"
-import { LoaderIcon } from "lucide-react"
-import { CalendarModal, DatePickerButton } from "./CalendarModal.tsx"
-import { useQuery } from "@tanstack/react-query"
-import { getEbillOptions } from "@/generated/client/@tanstack/react-query.gen"
-import { useIntl } from "react-intl"
-import { getItem, setItem } from "@/utils/local-storage"
+import { useEffect, useMemo, useState } from "react";
+import { Button } from "@/components/ui/button.tsx";
+import { ConfirmDrawer } from "@/components/Drawers.tsx";
+import { LoaderIcon } from "lucide-react";
+import { CalendarModal, DatePickerButton } from "./CalendarModal.tsx";
+import { useQuery } from "@tanstack/react-query";
+import { getEbillOptions } from "@/generated/client/@tanstack/react-query.gen";
+import { useIntl } from "react-intl";
+import { getItem, setItem } from "@/utils/local-storage";
interface RequestToPayConfirmationProps {
- open: boolean
- onOpenChange: (open: boolean) => void
- onSubmit: (deadline: Date) => void
- isFetching: boolean
- isPending: boolean
- maturityDate?: string | null
- billId: string
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSubmit: (deadline: Date) => void;
+ isFetching: boolean;
+ isPending: boolean;
+ maturityDate?: string | null;
+ billId: string;
}
-const REQUEST_TO_PAY_DEADLINE_STORAGE_KEY = "requestToPayDeadlineUtc"
-const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000
+const REQUEST_TO_PAY_DEADLINE_STORAGE_KEY = "requestToPayDeadlineUtc";
+const TWO_DAYS_MS = 2 * 24 * 60 * 60 * 1000;
const getMinSelectableDate = (maturityDate?: string | null): Date => {
- const now = new Date()
- const maturity = maturityDate ? new Date(maturityDate) : null
- const baseDate = maturity && maturity > now ? maturity : now
- return new Date(baseDate.getTime() + TWO_DAYS_MS)
-}
+ const now = new Date();
+ const maturity = maturityDate ? new Date(maturityDate) : null;
+ const baseDate = maturity && maturity > now ? maturity : now;
+ return new Date(baseDate.getTime() + TWO_DAYS_MS);
+};
const toUtcEndOfDay = (date: Date): Date => {
- return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 23, 59, 59, 999))
-}
+ return new Date(
+ Date.UTC(
+ date.getUTCFullYear(),
+ date.getUTCMonth(),
+ date.getUTCDate(),
+ 23,
+ 59,
+ 59,
+ 999,
+ ),
+ );
+};
export function RequestToPayConfirmation({
open,
@@ -41,44 +51,55 @@ export function RequestToPayConfirmation({
maturityDate,
billId,
}: RequestToPayConfirmationProps) {
- const intl = useIntl()
- const [validUntilDate, setValidUntilDate] = useState(undefined)
- const [showPaymentCalendar, setShowPaymentCalendar] = useState(false)
- const [draftValidUntilDate, setDraftValidUntilDate] = useState(undefined)
- const minSelectableDate = useMemo(() => getMinSelectableDate(maturityDate), [maturityDate])
+ const intl = useIntl();
+ const [validUntilDate, setValidUntilDate] = useState(
+ undefined,
+ );
+ const [showPaymentCalendar, setShowPaymentCalendar] = useState(false);
+ const [draftValidUntilDate, setDraftValidUntilDate] = useState<
+ Date | undefined
+ >(undefined);
+ const minSelectableDate = useMemo(
+ () => getMinSelectableDate(maturityDate),
+ [maturityDate],
+ );
const ebillQuery = useQuery({
...getEbillOptions({ path: { bid: billId } }),
enabled: true,
retry: 0,
refetchInterval: (query) => {
- return query.state.data ? false : 2000
+ return query.state.data ? false : 2000;
},
refetchIntervalInBackground: true,
- })
+ });
- const ebillAvailable = !ebillQuery.isLoading && !ebillQuery.error && !!ebillQuery.data
+ const ebillAvailable =
+ !ebillQuery.isLoading && !ebillQuery.error && !!ebillQuery.data;
useEffect(() => {
if (!open || validUntilDate) {
- return
+ return;
}
- const stored = getItem(REQUEST_TO_PAY_DEADLINE_STORAGE_KEY)
- const fallbackDeadline = toUtcEndOfDay(minSelectableDate)
+ const stored = getItem(REQUEST_TO_PAY_DEADLINE_STORAGE_KEY);
+ const fallbackDeadline = toUtcEndOfDay(minSelectableDate);
if (stored) {
- const parsed = new Date(stored)
+ const parsed = new Date(stored);
if (!Number.isNaN(parsed.getTime()) && parsed >= minSelectableDate) {
- setValidUntilDate(parsed)
- setDraftValidUntilDate(parsed)
- return
+ setValidUntilDate(parsed);
+ setDraftValidUntilDate(parsed);
+ return;
}
}
- setValidUntilDate(fallbackDeadline)
- setDraftValidUntilDate(fallbackDeadline)
- setItem(REQUEST_TO_PAY_DEADLINE_STORAGE_KEY, fallbackDeadline.toISOString())
- }, [open, validUntilDate, minSelectableDate])
+ setValidUntilDate(fallbackDeadline);
+ setDraftValidUntilDate(fallbackDeadline);
+ setItem(
+ REQUEST_TO_PAY_DEADLINE_STORAGE_KEY,
+ fallbackDeadline.toISOString(),
+ );
+ }, [open, validUntilDate, minSelectableDate]);
return (
<>
@@ -89,15 +110,16 @@ export function RequestToPayConfirmation({
})}
description={intl.formatMessage({
id: "quotes.requestToPay.confirmDescription",
- defaultMessage: "Are you sure you want to request to pay this e-bill?",
+ defaultMessage:
+ "Are you sure you want to request to pay this e-bill?",
})}
open={open}
onOpenChange={(isOpen) => {
- onOpenChange(isOpen)
+ onOpenChange(isOpen);
}}
onSubmit={() => {
if (validUntilDate) {
- onSubmit(validUntilDate)
+ onSubmit(validUntilDate);
}
}}
submitButtonText={intl.formatMessage({
@@ -111,9 +133,9 @@ export function RequestToPayConfirmation({
disabled={isFetching || isPending || !ebillAvailable}
variant="default"
onClick={(e) => {
- e.preventDefault()
- e.stopPropagation()
- onOpenChange(true)
+ e.preventDefault();
+ e.stopPropagation();
+ onOpenChange(true);
}}
>
{!ebillAvailable ? (
@@ -147,9 +169,9 @@ export function RequestToPayConfirmation({
{
- setDraftValidUntilDate(validUntilDate)
- onOpenChange(false)
- setShowPaymentCalendar(true)
+ setDraftValidUntilDate(validUntilDate);
+ onOpenChange(false);
+ setShowPaymentCalendar(true);
}}
/>
@@ -166,25 +188,28 @@ export function RequestToPayConfirmation({
})}
minDate={minSelectableDate}
onClose={() => {
- setShowPaymentCalendar(false)
- onOpenChange(true)
+ setShowPaymentCalendar(false);
+ onOpenChange(true);
}}
onDateChange={setDraftValidUntilDate}
onConfirm={() => {
if (draftValidUntilDate) {
- const utcDeadline = toUtcEndOfDay(draftValidUntilDate)
- setValidUntilDate(utcDeadline)
- setItem(REQUEST_TO_PAY_DEADLINE_STORAGE_KEY, utcDeadline.toISOString())
+ const utcDeadline = toUtcEndOfDay(draftValidUntilDate);
+ setValidUntilDate(utcDeadline);
+ setItem(
+ REQUEST_TO_PAY_DEADLINE_STORAGE_KEY,
+ utcDeadline.toISOString(),
+ );
}
- setShowPaymentCalendar(false)
- onOpenChange(true)
+ setShowPaymentCalendar(false);
+ onOpenChange(true);
}}
onCancel={() => {
- setShowPaymentCalendar(false)
- setDraftValidUntilDate(undefined)
- onOpenChange(true)
+ setShowPaymentCalendar(false);
+ setDraftValidUntilDate(undefined);
+ onOpenChange(true);
}}
/>
>
- )
+ );
}
diff --git a/src/pages/quotes/components/useQuoteMutations.ts b/src/pages/quotes/components/useQuoteMutations.ts
index c267a75..ca9949f 100644
--- a/src/pages/quotes/components/useQuoteMutations.ts
+++ b/src/pages/quotes/components/useQuoteMutations.ts
@@ -1,83 +1,89 @@
-import { useMutation, useQueryClient } from "@tanstack/react-query"
-import { toast } from "sonner"
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
import {
updateQuoteMutation,
postEbillReqtopayMutation,
getQuoteOptions,
getEbillOptions,
-} from "@/generated/client/@tanstack/react-query.gen.ts"
-import type { OfferFormResult } from "./OfferFormDrawer.tsx"
-import Big from "big.js"
-import { getApiErrorMessage } from "@/lib/api-error"
+} from "@/generated/client/@tanstack/react-query.gen.ts";
+import type { OfferFormResult } from "./OfferFormDrawer.tsx";
+import Big from "big.js";
+import { getApiErrorMessage } from "@/lib/api-error";
export function useQuoteMutations(quoteId: string, billId: string) {
- const queryClient = useQueryClient()
+ const queryClient = useQueryClient();
const denyQuote = useMutation({
...updateQuoteMutation(),
onSettled: () => {
- toast.dismiss(`quote-${quoteId}-deny`)
+ toast.dismiss(`quote-${quoteId}-deny`);
},
onError: (error) => {
- toast.error(`Error while denying quote: ${getApiErrorMessage(error)}`)
- console.warn(error)
+ toast.error(`Error while denying quote: ${getApiErrorMessage(error)}`);
+ console.warn(error);
},
onSuccess: () => {
- toast.success("Quote has been denied.")
+ toast.success("Quote has been denied.");
void queryClient.invalidateQueries({
queryKey: getQuoteOptions({ path: { qid: quoteId } }).queryKey,
- })
+ });
},
- })
+ });
const offerQuote = useMutation({
...updateQuoteMutation(),
onSettled: () => {
- toast.dismiss(`quote-${quoteId}-offer`)
+ toast.dismiss(`quote-${quoteId}-offer`);
},
onError: (error) => {
- toast.error(`Error while offering quote: ${getApiErrorMessage(error)}`)
- console.warn(error)
+ toast.error(`Error while offering quote: ${getApiErrorMessage(error)}`);
+ console.warn(error);
},
onSuccess: () => {
- toast.success("Quote has been offered.")
+ toast.success("Quote has been offered.");
void queryClient.invalidateQueries({
queryKey: getQuoteOptions({ path: { qid: quoteId } }).queryKey,
- })
+ });
},
- })
+ });
const requestToPayMutation = useMutation({
...postEbillReqtopayMutation(),
onMutate: () => {
- toast.loading("Requesting to pay…", { id: `quote-${quoteId}-request-to-pay` })
+ toast.loading("Requesting to pay…", {
+ id: `quote-${quoteId}-request-to-pay`,
+ });
},
onSettled: () => {
- toast.dismiss(`quote-${quoteId}-request-to-pay`)
+ toast.dismiss(`quote-${quoteId}-request-to-pay`);
},
onError: (error) => {
- toast.error(`Error while requesting to pay: ${getApiErrorMessage(error)}`)
- console.warn(error)
+ toast.error(
+ `Error while requesting to pay: ${getApiErrorMessage(error)}`,
+ );
+ console.warn(error);
},
onSuccess: () => {
- toast.success("Payment request has been created.")
+ toast.success("Payment request has been created.");
void queryClient.invalidateQueries({
queryKey: getEbillOptions({ path: { bid: billId } }).queryKey,
- })
+ });
},
- })
+ });
const handleDenyQuote = () => {
- toast.loading("Denying quote…", { id: `quote-${quoteId}-deny` })
+ toast.loading("Denying quote…", { id: `quote-${quoteId}-deny` });
denyQuote.mutate({
path: { qid: quoteId },
body: { action: "Deny" },
- })
- }
+ });
+ };
const handleOfferQuote = (result: OfferFormResult) => {
- toast.loading("Offering quote…", { id: `quote-${quoteId}-offer` })
- const net_amount = result.discount.net.value.round(0, Big.roundDown).toNumber()
+ toast.loading("Offering quote…", { id: `quote-${quoteId}-offer` });
+ const net_amount = result.discount.net.value
+ .round(0, Big.roundDown)
+ .toNumber();
offerQuote.mutate({
path: { qid: quoteId },
@@ -86,8 +92,8 @@ export function useQuoteMutations(quoteId: string, billId: string) {
discounted: net_amount,
ttl: result.ttl.ttl.toISOString(),
},
- })
- }
+ });
+ };
const handleRequestToPay = (billSum: number, deadline: Date) => {
requestToPayMutation.mutate({
@@ -96,8 +102,8 @@ export function useQuoteMutations(quoteId: string, billId: string) {
amount: billSum,
deadline: deadline.toISOString(),
},
- })
- }
+ });
+ };
return {
denyQuote,
@@ -106,5 +112,5 @@ export function useQuoteMutations(quoteId: string, billId: string) {
handleDenyQuote,
handleOfferQuote,
handleRequestToPay,
- }
+ };
}
diff --git a/src/pages/settings/SettingsPage.tsx b/src/pages/settings/SettingsPage.tsx
index 0901ebc..ed8f3ab 100644
--- a/src/pages/settings/SettingsPage.tsx
+++ b/src/pages/settings/SettingsPage.tsx
@@ -1,14 +1,14 @@
-import { Breadcrumbs } from "@/components/Breadcrumbs"
-import { PageTitle } from "@/components/PageTitle"
-import { Skeleton } from "@/components/ui/skeleton"
-import { Suspense } from "react"
+import { Breadcrumbs } from "@/components/Breadcrumbs";
+import { PageTitle } from "@/components/PageTitle";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Suspense } from "react";
function Loader() {
return (
- )
+ );
}
function PageBody() {
@@ -16,7 +16,7 @@ function PageBody() {
<>
>
- )
+ );
}
export default function SettingsPage() {
@@ -29,5 +29,5 @@ export default function SettingsPage() {
>
- )
+ );
}
diff --git a/src/utils/dates.test.ts b/src/utils/dates.test.ts
index f43a2d2..7925bf6 100644
--- a/src/utils/dates.test.ts
+++ b/src/utils/dates.test.ts
@@ -1,55 +1,61 @@
-import { beforeEach, describe, expect, it, vi } from "vitest"
-import { daysBetween, formatDate, getDefaultDeadline, humanReadableDuration, humanReadableDurationDays } from "./dates"
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import {
+ daysBetween,
+ formatDate,
+ getDefaultDeadline,
+ humanReadableDuration,
+ humanReadableDurationDays,
+} from "./dates";
describe("dates utils", () => {
beforeEach(() => {
- vi.useRealTimers()
- })
+ vi.useRealTimers();
+ });
it("calculates day differences in UTC calendar days", () => {
- const start = new Date("2026-02-18T23:59:59.000Z")
- const end = new Date("2026-02-20T00:00:01.000Z")
- expect(daysBetween(start, end)).toBe(2)
- })
+ const start = new Date("2026-02-18T23:59:59.000Z");
+ const end = new Date("2026-02-20T00:00:01.000Z");
+ expect(daysBetween(start, end)).toBe(2);
+ });
it("formats date in day-month-year (UTC)", () => {
- const date = new Date("2026-02-20T10:11:12.000Z")
- expect(formatDate("en-US", date)).toBe("20-Feb-26")
- })
+ const date = new Date("2026-02-20T10:11:12.000Z");
+ expect(formatDate("en-US", date)).toBe("20-Feb-26");
+ });
it("returns relative day label", () => {
- const from = new Date("2026-02-20T00:00:00.000Z")
- const until = new Date("2026-02-18T00:00:00.000Z")
- expect(humanReadableDurationDays("en", from, until)).toBe("in 2 days")
- })
+ const from = new Date("2026-02-20T00:00:00.000Z");
+ const until = new Date("2026-02-18T00:00:00.000Z");
+ expect(humanReadableDurationDays("en", from, until)).toBe("in 2 days");
+ });
it("returns relative hours label for hour-scale differences", () => {
- const from = new Date("2026-02-20T12:00:00.000Z")
- const until = new Date("2026-02-20T09:00:00.000Z")
- expect(humanReadableDuration("en", from, until)).toBe("in 3 hours")
- })
+ const from = new Date("2026-02-20T12:00:00.000Z");
+ const until = new Date("2026-02-20T09:00:00.000Z");
+ expect(humanReadableDuration("en", from, until)).toBe("in 3 hours");
+ });
it("uses maturity -2 days when maturity is in the future", () => {
- vi.useFakeTimers()
- vi.setSystemTime(new Date("2026-02-18T12:00:00.000Z"))
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date("2026-02-18T12:00:00.000Z"));
- const deadline = getDefaultDeadline("2026-02-25")
- expect(deadline.toISOString()).toBe("2026-02-23T23:59:59.999Z")
- })
+ const deadline = getDefaultDeadline("2026-02-25");
+ expect(deadline.toISOString()).toBe("2026-02-23T23:59:59.999Z");
+ });
it("uses now +2 days when maturity is in the past", () => {
- vi.useFakeTimers()
- vi.setSystemTime(new Date("2026-02-18T12:00:00.000Z"))
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date("2026-02-18T12:00:00.000Z"));
- const deadline = getDefaultDeadline("2026-02-10")
- expect(deadline.toISOString()).toBe("2026-02-20T23:59:59.999Z")
- })
+ const deadline = getDefaultDeadline("2026-02-10");
+ expect(deadline.toISOString()).toBe("2026-02-20T23:59:59.999Z");
+ });
it("uses now +2 days when maturity is missing", () => {
- vi.useFakeTimers()
- vi.setSystemTime(new Date("2026-02-18T12:00:00.000Z"))
+ vi.useFakeTimers();
+ vi.setSystemTime(new Date("2026-02-18T12:00:00.000Z"));
- const deadline = getDefaultDeadline()
- expect(deadline.toISOString()).toBe("2026-02-20T23:59:59.999Z")
- })
-})
+ const deadline = getDefaultDeadline();
+ expect(deadline.toISOString()).toBe("2026-02-20T23:59:59.999Z");
+ });
+});
diff --git a/src/utils/dates.ts b/src/utils/dates.ts
index cad526f..1f8310d 100644
--- a/src/utils/dates.ts
+++ b/src/utils/dates.ts
@@ -1,55 +1,94 @@
-import { differenceInCalendarYears, differenceInMinutes, subDays, addDays } from "date-fns"
-import { differenceInCalendarDays, differenceInCalendarMonths, differenceInHours, differenceInSeconds } from "date-fns"
+import {
+ differenceInCalendarYears,
+ differenceInMinutes,
+ subDays,
+ addDays,
+} from "date-fns";
+import {
+ differenceInCalendarDays,
+ differenceInCalendarMonths,
+ differenceInHours,
+ differenceInSeconds,
+} from "date-fns";
-const UTC_TIME_ZONE = "UTC"
-const MS_PER_DAY = 24 * 60 * 60 * 1000
+const UTC_TIME_ZONE = "UTC";
+const MS_PER_DAY = 24 * 60 * 60 * 1000;
export const daysBetween = (startDate: Date, endDate: Date): number => {
- const startUtc = Date.UTC(startDate.getUTCFullYear(), startDate.getUTCMonth(), startDate.getUTCDate())
- const endUtc = Date.UTC(endDate.getUTCFullYear(), endDate.getUTCMonth(), endDate.getUTCDate())
- return Math.floor((endUtc - startUtc) / MS_PER_DAY)
-}
+ const startUtc = Date.UTC(
+ startDate.getUTCFullYear(),
+ startDate.getUTCMonth(),
+ startDate.getUTCDate(),
+ );
+ const endUtc = Date.UTC(
+ endDate.getUTCFullYear(),
+ endDate.getUTCMonth(),
+ endDate.getUTCDate(),
+ );
+ return Math.floor((endUtc - startUtc) / MS_PER_DAY);
+};
-export function humanReadableDuration(locale: string, from: Date, until = new Date(Date.now())) {
- const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" })
+export function humanReadableDuration(
+ locale: string,
+ from: Date,
+ until = new Date(Date.now()),
+) {
+ const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, {
+ numeric: "auto",
+ });
- const diffYears = differenceInCalendarYears(from, until)
+ const diffYears = differenceInCalendarYears(from, until);
if (Math.abs(diffYears) >= 1) {
- return relativeTimeFormatter.format(diffYears, "years")
+ return relativeTimeFormatter.format(diffYears, "years");
}
- const diffMonths = differenceInCalendarMonths(from, until)
+ const diffMonths = differenceInCalendarMonths(from, until);
if (Math.abs(diffMonths) >= 1) {
- return relativeTimeFormatter.format(diffMonths, "months")
+ return relativeTimeFormatter.format(diffMonths, "months");
}
- const diffDays = differenceInCalendarDays(from, until)
+ const diffDays = differenceInCalendarDays(from, until);
if (Math.abs(diffDays) >= 1) {
- return relativeTimeFormatter.format(diffDays, "days")
+ return relativeTimeFormatter.format(diffDays, "days");
}
- const diffHours = differenceInHours(from, until)
+ const diffHours = differenceInHours(from, until);
if (Math.abs(diffHours) > 1) {
- return relativeTimeFormatter.format(diffHours, "hours")
+ return relativeTimeFormatter.format(diffHours, "hours");
}
- const diffMinutes = differenceInMinutes(from, until)
+ const diffMinutes = differenceInMinutes(from, until);
if (Math.abs(diffMinutes) > 1) {
- return relativeTimeFormatter.format(diffMinutes, "minutes")
+ return relativeTimeFormatter.format(diffMinutes, "minutes");
}
- const diffSeconds = differenceInSeconds(from, until)
- return relativeTimeFormatter.format(diffSeconds, "seconds")
+ const diffSeconds = differenceInSeconds(from, until);
+ return relativeTimeFormatter.format(diffSeconds, "seconds");
}
-export function humanReadableDurationDays(locale: string, from: Date, until = new Date(Date.now())) {
- const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" })
+export function humanReadableDurationDays(
+ locale: string,
+ from: Date,
+ until = new Date(Date.now()),
+) {
+ const relativeTimeFormatter = new Intl.RelativeTimeFormat(locale, {
+ numeric: "auto",
+ });
- const diffDays = differenceInCalendarDays(from, until)
- return relativeTimeFormatter.format(diffDays, "day")
+ const diffDays = differenceInCalendarDays(from, until);
+ return relativeTimeFormatter.format(diffDays, "day");
}
export const formatDate = (locale: string, date: Date): string => {
- const year = new Intl.DateTimeFormat(locale, { year: "2-digit", timeZone: UTC_TIME_ZONE }).format(date)
- const month = new Intl.DateTimeFormat(locale, { month: "short", timeZone: UTC_TIME_ZONE }).format(date)
- const day = new Intl.DateTimeFormat(locale, { day: "2-digit", timeZone: UTC_TIME_ZONE }).format(date)
- return `${day}-${month}-${year}`
-}
+ const year = new Intl.DateTimeFormat(locale, {
+ year: "2-digit",
+ timeZone: UTC_TIME_ZONE,
+ }).format(date);
+ const month = new Intl.DateTimeFormat(locale, {
+ month: "short",
+ timeZone: UTC_TIME_ZONE,
+ }).format(date);
+ const day = new Intl.DateTimeFormat(locale, {
+ day: "2-digit",
+ timeZone: UTC_TIME_ZONE,
+ }).format(date);
+ return `${day}-${month}-${year}`;
+};
export const formatDateLong = (date: Date, locale: string): string => {
return new Intl.DateTimeFormat(locale, {
@@ -57,8 +96,8 @@ export const formatDateLong = (date: Date, locale: string): string => {
month: "long",
day: "numeric",
timeZone: UTC_TIME_ZONE,
- }).format(date)
-}
+ }).format(date);
+};
export const formatDateShort = (date: Date, locale: string): string => {
return new Intl.DateTimeFormat(locale, {
@@ -66,24 +105,30 @@ export const formatDateShort = (date: Date, locale: string): string => {
month: "short",
day: "numeric",
timeZone: UTC_TIME_ZONE,
- }).format(date)
-}
+ }).format(date);
+};
export const formatMonthLong = (date: Date, locale: string): string => {
- return new Intl.DateTimeFormat(locale, { month: "long", timeZone: UTC_TIME_ZONE }).format(date)
-}
+ return new Intl.DateTimeFormat(locale, {
+ month: "long",
+ timeZone: UTC_TIME_ZONE,
+ }).format(date);
+};
export const formatMonthYear = (date: Date, locale: string): string => {
return new Intl.DateTimeFormat(locale, {
year: "numeric",
month: "long",
timeZone: UTC_TIME_ZONE,
- }).format(date)
-}
+ }).format(date);
+};
export const formatYearNumeric = (date: Date, locale: string): string => {
- return new Intl.DateTimeFormat(locale, { year: "numeric", timeZone: UTC_TIME_ZONE }).format(date)
-}
+ return new Intl.DateTimeFormat(locale, {
+ year: "numeric",
+ timeZone: UTC_TIME_ZONE,
+ }).format(date);
+};
/**
* Calculate a smart default deadline based on maturity date.
@@ -92,20 +137,20 @@ export const formatYearNumeric = (date: Date, locale: string): string => {
* or if maturity is in the past or no maturity date provided, returns end of day UTC for today + 2 days.
*/
export const getDefaultDeadline = (maturityDate?: string | null): Date => {
- let deadline: Date
- const now = new Date()
+ let deadline: Date;
+ const now = new Date();
if (maturityDate) {
- const maturity = new Date(maturityDate)
+ const maturity = new Date(maturityDate);
if (maturity > now) {
- deadline = subDays(maturity, 2)
+ deadline = subDays(maturity, 2);
} else {
- deadline = addDays(now, 2)
+ deadline = addDays(now, 2);
}
} else {
- deadline = addDays(now, 2)
+ deadline = addDays(now, 2);
}
- deadline.setUTCHours(23, 59, 59, 999)
- return deadline
-}
+ deadline.setUTCHours(23, 59, 59, 999);
+ return deadline;
+};
diff --git a/src/utils/dev.ts b/src/utils/dev.ts
index 706f779..699acb4 100644
--- a/src/utils/dev.ts
+++ b/src/utils/dev.ts
@@ -1,5 +1,5 @@
export function getDeterministicColor(seed?: string): string {
- if (!seed) return "#64748b" // Default gray
+ if (!seed) return "#64748b"; // Default gray
const colorPalette = [
"#ef4444",
@@ -19,13 +19,13 @@ export function getDeterministicColor(seed?: string): string {
"#d946ef",
"#ec4899",
"#f43f5e",
- ]
+ ];
- let hash = 0
+ let hash = 0;
for (let i = 0; i < seed.length; i++) {
- hash = ((hash << 5) - hash + seed.charCodeAt(i)) & 0xffffffff
+ hash = ((hash << 5) - hash + seed.charCodeAt(i)) & 0xffffffff;
}
- const colorIndex = Math.abs(hash) % colorPalette.length
- return colorPalette[colorIndex]
+ const colorIndex = Math.abs(hash) % colorPalette.length;
+ return colorPalette[colorIndex];
}
diff --git a/src/utils/discount-util.test.ts b/src/utils/discount-util.test.ts
index 31556d1..a5851ac 100644
--- a/src/utils/discount-util.test.ts
+++ b/src/utils/discount-util.test.ts
@@ -1,90 +1,128 @@
-import { describe, it, expect } from "vitest"
-import Big from "big.js"
-import { daysBetween } from "@/utils/dates"
-import { Act360 } from "./discount-util"
+import { describe, it, expect } from "vitest";
+import Big from "big.js";
+import { daysBetween } from "@/utils/dates";
+import { Act360 } from "./discount-util";
describe("discount-util", () => {
describe("Act360", () => {
describe("netToGross", () => {
it("should calculate gross amount correctly (0)", () => {
- const startDate = new Date(2024, 11, 6)
- const endDate = new Date(2025, 2, 31)
- const netAmount = new Big("10.12")
- const discountRate = new Big("0.045")
+ const startDate = new Date(2024, 11, 6);
+ const endDate = new Date(2025, 2, 31);
+ const netAmount = new Big("10.12");
+ const discountRate = new Big("0.045");
- const days = daysBetween(startDate, endDate)
- const grossAmount = Act360.netToGross(netAmount, discountRate, days)
- expect(grossAmount).toStrictEqual(new Big("10.26759670259987317692"))
- expect(grossAmount!.toNumber()).toBe(10.267596702599873)
- })
+ const days = daysBetween(startDate, endDate);
+ const grossAmount = Act360.netToGross(netAmount, discountRate, days);
+ expect(grossAmount).toStrictEqual(new Big("10.26759670259987317692"));
+ expect(grossAmount!.toNumber()).toBe(10.267596702599873);
+ });
it("should calculate gross amount correctly (1)", () => {
- expect(Act360.netToGross(new Big("1"), new Big("1"), -1)).toStrictEqual(new Big("0.99722991689750692521"))
- expect(Act360.netToGross(new Big("1"), new Big("1"), 0)).toStrictEqual(new Big("1"))
- expect(Act360.netToGross(new Big("1"), new Big("1"), 1)).toStrictEqual(new Big("1.00278551532033426184"))
- expect(Act360.netToGross(new Big("1"), new Big("1"), 355)).toStrictEqual(new Big("71.99999999999999999424"))
- expect(Act360.netToGross(new Big("1"), new Big("1"), 360)).toStrictEqual(undefined)
- expect(Act360.netToGross(new Big("1"), new Big("1"), 365)).toStrictEqual(new Big("-71.99999999999999999424"))
- })
+ expect(Act360.netToGross(new Big("1"), new Big("1"), -1)).toStrictEqual(
+ new Big("0.99722991689750692521"),
+ );
+ expect(Act360.netToGross(new Big("1"), new Big("1"), 0)).toStrictEqual(
+ new Big("1"),
+ );
+ expect(Act360.netToGross(new Big("1"), new Big("1"), 1)).toStrictEqual(
+ new Big("1.00278551532033426184"),
+ );
+ expect(
+ Act360.netToGross(new Big("1"), new Big("1"), 355),
+ ).toStrictEqual(new Big("71.99999999999999999424"));
+ expect(
+ Act360.netToGross(new Big("1"), new Big("1"), 360),
+ ).toStrictEqual(undefined);
+ expect(
+ Act360.netToGross(new Big("1"), new Big("1"), 365),
+ ).toStrictEqual(new Big("-71.99999999999999999424"));
+ });
it("should calculate gross amount correctly (2)", () => {
- expect(Act360.netToGross(new Big(1), new Big("0.9863"), 365)).toStrictEqual(new Big("719999.999999999424"))
- expect(Act360.netToGross(new Big(1), new Big("0.9864"), 365)).toStrictEqual(new Big("-10000"))
- expect(Act360.netToGross(new Big(1), new Big("0.9865"), 365)).toStrictEqual(
- new Big("-4965.51724137931031743163"),
- )
- })
+ expect(
+ Act360.netToGross(new Big(1), new Big("0.9863"), 365),
+ ).toStrictEqual(new Big("719999.999999999424"));
+ expect(
+ Act360.netToGross(new Big(1), new Big("0.9864"), 365),
+ ).toStrictEqual(new Big("-10000"));
+ expect(
+ Act360.netToGross(new Big(1), new Big("0.9865"), 365),
+ ).toStrictEqual(new Big("-4965.51724137931031743163"));
+ });
it("should calculate gross amount correctly (step-by-step)", () => {
- const startDate = new Date(2024, 11, 6)
- const endDate = new Date(2025, 2, 31)
- const netAmount = new Big("10.12")
- const discountRate = new Big("0.045")
+ const startDate = new Date(2024, 11, 6);
+ const endDate = new Date(2025, 2, 31);
+ const netAmount = new Big("10.12");
+ const discountRate = new Big("0.045");
- const days = daysBetween(startDate, endDate)
- expect(days, "sanity check").toBe(115)
+ const days = daysBetween(startDate, endDate);
+ expect(days, "sanity check").toBe(115);
- const discountDays = discountRate.times(days).div(360)
- expect(discountDays.toNumber(), "sanity check").toBe(0.014375)
+ const discountDays = discountRate.times(days).div(360);
+ expect(discountDays.toNumber(), "sanity check").toBe(0.014375);
- const factor = new Big(1).minus(discountDays)
+ const factor = new Big(1).minus(discountDays);
- const grossAmount = netAmount.div(factor)
- expect(grossAmount).toStrictEqual(new Big("10.26759670259987317692"))
- expect(grossAmount.toNumber()).toBe(10.267596702599873)
+ const grossAmount = netAmount.div(factor);
+ expect(grossAmount).toStrictEqual(new Big("10.26759670259987317692"));
+ expect(grossAmount.toNumber()).toBe(10.267596702599873);
- const calcGrossAmount = Act360.netToGross(netAmount, discountRate, days)
- expect(calcGrossAmount).toStrictEqual(grossAmount)
- })
- })
+ const calcGrossAmount = Act360.netToGross(
+ netAmount,
+ discountRate,
+ days,
+ );
+ expect(calcGrossAmount).toStrictEqual(grossAmount);
+ });
+ });
describe("grossToNet", () => {
it("should calculate net amount correctly (0)", () => {
- const startDate = new Date(2024, 11, 6)
- const endDate = new Date(2025, 2, 31)
- const grossAmount = new Big("10.12")
- const discountRate = new Big("0.045")
+ const startDate = new Date(2024, 11, 6);
+ const endDate = new Date(2025, 2, 31);
+ const grossAmount = new Big("10.12");
+ const discountRate = new Big("0.045");
- const days = daysBetween(startDate, endDate)
- const netAmount = Act360.grossToNet(grossAmount, discountRate, days)
- expect(netAmount).toStrictEqual(new Big("9.974525"))
- expect(netAmount.toNumber()).toBe(9.974525)
- })
+ const days = daysBetween(startDate, endDate);
+ const netAmount = Act360.grossToNet(grossAmount, discountRate, days);
+ expect(netAmount).toStrictEqual(new Big("9.974525"));
+ expect(netAmount.toNumber()).toBe(9.974525);
+ });
it("should calculate net amount correctly (1)", () => {
- expect(Act360.grossToNet(new Big("1"), new Big("1"), -1)).toStrictEqual(new Big("1.00277777777777777778"))
- expect(Act360.grossToNet(new Big("1"), new Big("1"), 0)).toStrictEqual(new Big("1"))
- expect(Act360.grossToNet(new Big("1"), new Big("1"), 1)).toStrictEqual(new Big("0.99722222222222222222"))
- expect(Act360.grossToNet(new Big("1"), new Big("1"), 355)).toStrictEqual(new Big("0.01388888888888888889"))
- expect(Act360.grossToNet(new Big("1"), new Big("1"), 360)).toStrictEqual(new Big("0"))
- expect(Act360.grossToNet(new Big("1"), new Big("1"), 365)).toStrictEqual(new Big("-0.01388888888888888889"))
- })
+ expect(Act360.grossToNet(new Big("1"), new Big("1"), -1)).toStrictEqual(
+ new Big("1.00277777777777777778"),
+ );
+ expect(Act360.grossToNet(new Big("1"), new Big("1"), 0)).toStrictEqual(
+ new Big("1"),
+ );
+ expect(Act360.grossToNet(new Big("1"), new Big("1"), 1)).toStrictEqual(
+ new Big("0.99722222222222222222"),
+ );
+ expect(
+ Act360.grossToNet(new Big("1"), new Big("1"), 355),
+ ).toStrictEqual(new Big("0.01388888888888888889"));
+ expect(
+ Act360.grossToNet(new Big("1"), new Big("1"), 360),
+ ).toStrictEqual(new Big("0"));
+ expect(
+ Act360.grossToNet(new Big("1"), new Big("1"), 365),
+ ).toStrictEqual(new Big("-0.01388888888888888889"));
+ });
it("should calculate net amount correctly (2)", () => {
- expect(Act360.grossToNet(new Big(1), new Big("0.9863"), 365)).toStrictEqual(new Big("0.00000138888888888889"))
- expect(Act360.grossToNet(new Big(1), new Big("0.9864"), 365)).toStrictEqual(new Big("-0.0001"))
- expect(Act360.grossToNet(new Big(1), new Big("0.9865"), 365)).toStrictEqual(new Big("-0.00020138888888888889"))
- })
- })
- })
-})
+ expect(
+ Act360.grossToNet(new Big(1), new Big("0.9863"), 365),
+ ).toStrictEqual(new Big("0.00000138888888888889"));
+ expect(
+ Act360.grossToNet(new Big(1), new Big("0.9864"), 365),
+ ).toStrictEqual(new Big("-0.0001"));
+ expect(
+ Act360.grossToNet(new Big(1), new Big("0.9865"), 365),
+ ).toStrictEqual(new Big("-0.00020138888888888889"));
+ });
+ });
+ });
+});
diff --git a/src/utils/discount-util.ts b/src/utils/discount-util.ts
index 9150d04..e353e5b 100644
--- a/src/utils/discount-util.ts
+++ b/src/utils/discount-util.ts
@@ -1,19 +1,23 @@
-import Big from "big.js"
+import Big from "big.js";
-const BIG_1 = new Big("1")
-const BIG_360 = new Big("360")
+const BIG_1 = new Big("1");
+const BIG_360 = new Big("360");
const factor = (discountRate: Big, days: number) => {
- const discountDays = discountRate.times(days).div(BIG_360)
- return BIG_1.minus(discountDays)
-}
+ const discountDays = discountRate.times(days).div(BIG_360);
+ return BIG_1.minus(discountDays);
+};
export const Act360 = {
- netToGross: (netAmount: Big, discountRate: Big, days: number): Big | undefined => {
- const divisor = factor(discountRate, days)
- return divisor.toNumber() !== 0 ? netAmount.div(divisor) : undefined
+ netToGross: (
+ netAmount: Big,
+ discountRate: Big,
+ days: number,
+ ): Big | undefined => {
+ const divisor = factor(discountRate, days);
+ return divisor.toNumber() !== 0 ? netAmount.div(divisor) : undefined;
},
grossToNet: (grossAmount: Big, discountRate: Big, days: number): Big => {
- return grossAmount.times(factor(discountRate, days))
+ return grossAmount.times(factor(discountRate, days));
},
-}
+};
diff --git a/src/utils/keyset.test.ts b/src/utils/keyset.test.ts
index 4253cf4..0c8b189 100644
--- a/src/utils/keyset.test.ts
+++ b/src/utils/keyset.test.ts
@@ -1,36 +1,40 @@
-import { describe, expect, it, vi } from "vitest"
-import { serializeKeysetId } from "./keyset"
+import { describe, expect, it, vi } from "vitest";
+import { serializeKeysetId } from "./keyset";
describe("serializeKeysetId", () => {
it("returns input as-is when id is already a string", () => {
- expect(serializeKeysetId("abc123")).toBe("abc123")
- })
+ expect(serializeKeysetId("abc123")).toBe("abc123");
+ });
it("serializes Version00 with V1 bytes", () => {
- const id = { version: "Version00", id: { V1: [0, 1, 255] } }
- expect(serializeKeysetId(id as never)).toBe("000001ff")
- })
+ const id = { version: "Version00", id: { V1: [0, 1, 255] } };
+ expect(serializeKeysetId(id as never)).toBe("000001ff");
+ });
it("serializes Version01 with V2 bytes", () => {
- const id = { version: "Version01", id: { V2: [10, 11, 12] } }
- expect(serializeKeysetId(id as never)).toBe("010a0b0c")
- })
+ const id = { version: "Version01", id: { V2: [10, 11, 12] } };
+ expect(serializeKeysetId(id as never)).toBe("010a0b0c");
+ });
it("returns empty string for malformed id payload", () => {
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined)
- const id = { version: "Version00" }
+ const errorSpy = vi
+ .spyOn(console, "error")
+ .mockImplementation(() => undefined);
+ const id = { version: "Version00" };
- expect(serializeKeysetId(id as never)).toBe("")
- expect(errorSpy).toHaveBeenCalled()
- errorSpy.mockRestore()
- })
+ expect(serializeKeysetId(id as never)).toBe("");
+ expect(errorSpy).toHaveBeenCalled();
+ errorSpy.mockRestore();
+ });
it("returns empty string when id bytes shape is invalid", () => {
- const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined)
- const id = { version: "Version00", id: { invalid: [1, 2, 3] } }
+ const errorSpy = vi
+ .spyOn(console, "error")
+ .mockImplementation(() => undefined);
+ const id = { version: "Version00", id: { invalid: [1, 2, 3] } };
- expect(serializeKeysetId(id as never)).toBe("")
- expect(errorSpy).toHaveBeenCalled()
- errorSpy.mockRestore()
- })
-})
+ expect(serializeKeysetId(id as never)).toBe("");
+ expect(errorSpy).toHaveBeenCalled();
+ errorSpy.mockRestore();
+ });
+});
diff --git a/src/utils/keyset.ts b/src/utils/keyset.ts
index 28f9a65..8ce3dbf 100644
--- a/src/utils/keyset.ts
+++ b/src/utils/keyset.ts
@@ -1,4 +1,4 @@
-import { Id } from "@/generated/client/types.gen"
+import { Id } from "@/generated/client/types.gen";
/**
* Serializes an Id object to a string format suitable for URLs.
@@ -8,31 +8,31 @@ import { Id } from "@/generated/client/types.gen"
export function serializeKeysetId(id: Id | string): string {
// If it's already a string, return it directly
if (typeof id === "string") {
- return id
+ return id;
}
// Handle the case where the id might be malformed
if (!id.id) {
- console.error("Invalid Id object:", id)
- return ""
+ console.error("Invalid Id object:", id);
+ return "";
}
- let bytes: number[]
+ let bytes: number[];
if ("V1" in id.id) {
- bytes = id.id.V1
+ bytes = id.id.V1;
} else if ("V2" in id.id) {
- bytes = id.id.V2
+ bytes = id.id.V2;
} else {
- console.error("Invalid IdBytes structure:", id.id)
- return ""
+ console.error("Invalid IdBytes structure:", id.id);
+ return "";
}
// Convert bytes to hex string
- const hexString = bytes.map((b) => b.toString(16).padStart(2, "0")).join("")
+ const hexString = bytes.map((b) => b.toString(16).padStart(2, "0")).join("");
// Prepend version info (00 for Version00, 01 for Version01)
- const versionPrefix = id.version === "Version00" ? "00" : "01"
+ const versionPrefix = id.version === "Version00" ? "00" : "01";
- return `${versionPrefix}${hexString}`
+ return `${versionPrefix}${hexString}`;
}
diff --git a/src/utils/local-storage.ts b/src/utils/local-storage.ts
index df628c1..06953a0 100644
--- a/src/utils/local-storage.ts
+++ b/src/utils/local-storage.ts
@@ -1,24 +1,24 @@
export function setItem(key: string, value: unknown) {
try {
- window.localStorage.setItem(key, JSON.stringify(value))
+ window.localStorage.setItem(key, JSON.stringify(value));
} catch (err) {
- console.error(err)
+ console.error(err);
}
}
export function getItem(key: string): T | undefined {
try {
- const data = window.localStorage.getItem(key)
- return data ? (JSON.parse(data) as T) : undefined
+ const data = window.localStorage.getItem(key);
+ return data ? (JSON.parse(data) as T) : undefined;
} catch (err) {
- console.error(err)
+ console.error(err);
}
}
export function removeItem(key: string) {
try {
- window.localStorage.removeItem(key)
+ window.localStorage.removeItem(key);
} catch (err) {
- console.error(err)
+ console.error(err);
}
}
diff --git a/src/utils/numbers.test.ts b/src/utils/numbers.test.ts
index 1d03258..c723500 100644
--- a/src/utils/numbers.test.ts
+++ b/src/utils/numbers.test.ts
@@ -1,34 +1,34 @@
-import { describe, it, expect } from "vitest"
-import { parseFloatSafe, parseIntSafe } from "./numbers"
+import { describe, it, expect } from "vitest";
+import { parseFloatSafe, parseIntSafe } from "./numbers";
describe("util", () => {
describe("parseFloatSafe", () => {
it("should safely parse floats", () => {
- expect(parseFloatSafe("")).toBe(undefined)
- expect(parseFloatSafe("NaN")).toBe(undefined)
- expect(parseFloatSafe("Infinity")).toBe(undefined)
- expect(parseFloatSafe(String(1 / 0))).toBe(undefined)
- expect(parseFloatSafe("foobar")).toBe(undefined)
- expect(parseFloatSafe("0")).toBe(0)
- expect(parseFloatSafe("1")).toBe(1)
- expect(parseFloatSafe("-1")).toBe(-1)
- expect(parseFloatSafe("1.23456789")).toBe(1.23456789)
- expect(parseFloatSafe("-1.23456789")).toBe(-1.23456789)
- })
- })
+ expect(parseFloatSafe("")).toBe(undefined);
+ expect(parseFloatSafe("NaN")).toBe(undefined);
+ expect(parseFloatSafe("Infinity")).toBe(undefined);
+ expect(parseFloatSafe(String(1 / 0))).toBe(undefined);
+ expect(parseFloatSafe("foobar")).toBe(undefined);
+ expect(parseFloatSafe("0")).toBe(0);
+ expect(parseFloatSafe("1")).toBe(1);
+ expect(parseFloatSafe("-1")).toBe(-1);
+ expect(parseFloatSafe("1.23456789")).toBe(1.23456789);
+ expect(parseFloatSafe("-1.23456789")).toBe(-1.23456789);
+ });
+ });
describe("parseIntSafe", () => {
it("should safely parse ints", () => {
- expect(parseIntSafe("")).toBe(undefined)
- expect(parseIntSafe("NaN")).toBe(undefined)
- expect(parseIntSafe("Infinity")).toBe(undefined)
- expect(parseIntSafe(String(1 / 0))).toBe(undefined)
- expect(parseIntSafe("foobar")).toBe(undefined)
- expect(parseIntSafe("0")).toBe(0)
- expect(parseIntSafe("1")).toBe(1)
- expect(parseIntSafe("-1")).toBe(-1)
- expect(parseIntSafe("1.23456789")).toBe(1)
- expect(parseIntSafe("-1.23456789")).toBe(-1)
- })
- })
-})
+ expect(parseIntSafe("")).toBe(undefined);
+ expect(parseIntSafe("NaN")).toBe(undefined);
+ expect(parseIntSafe("Infinity")).toBe(undefined);
+ expect(parseIntSafe(String(1 / 0))).toBe(undefined);
+ expect(parseIntSafe("foobar")).toBe(undefined);
+ expect(parseIntSafe("0")).toBe(0);
+ expect(parseIntSafe("1")).toBe(1);
+ expect(parseIntSafe("-1")).toBe(-1);
+ expect(parseIntSafe("1.23456789")).toBe(1);
+ expect(parseIntSafe("-1.23456789")).toBe(-1);
+ });
+ });
+});
diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts
index ed1ae06..8072448 100644
--- a/src/utils/numbers.ts
+++ b/src/utils/numbers.ts
@@ -1,11 +1,11 @@
export const parseFloatSafe = (str: string | undefined) => {
- if (str === undefined) return undefined
- const parsed = parseFloat(str)
- return isNaN(parsed) || !isFinite(parsed) ? undefined : parsed
-}
+ if (str === undefined) return undefined;
+ const parsed = parseFloat(str);
+ return isNaN(parsed) || !isFinite(parsed) ? undefined : parsed;
+};
export const parseIntSafe = (str: string | undefined) => {
- if (str === undefined) return undefined
- const parsed = parseInt(str, 10)
- return isNaN(parsed) || !isFinite(parsed) ? undefined : parsed
-}
+ if (str === undefined) return undefined;
+ const parsed = parseInt(str, 10);
+ return isNaN(parsed) || !isFinite(parsed) ? undefined : parsed;
+};
diff --git a/src/utils/qrCodeUtils.ts b/src/utils/qrCodeUtils.ts
index f168c6c..976453b 100644
--- a/src/utils/qrCodeUtils.ts
+++ b/src/utils/qrCodeUtils.ts
@@ -1,19 +1,19 @@
-export const QR_CODE_MAX_LENGTH = 2956
+export const QR_CODE_MAX_LENGTH = 2956;
export function canGenerateQRCode(text: string): boolean {
- return text.length <= QR_CODE_MAX_LENGTH
+ return text.length <= QR_CODE_MAX_LENGTH;
}
export async function canGenerateQRCodeAsync(text: string): Promise {
if (text.length > QR_CODE_MAX_LENGTH) {
- return false
+ return false;
}
try {
- const { toString } = await import("qrcode")
- await toString(text, { errorCorrectionLevel: "M" })
- return true
+ const { toString } = await import("qrcode");
+ await toString(text, { errorCorrectionLevel: "M" });
+ return true;
} catch {
- return false
+ return false;
}
}
diff --git a/src/utils/quote-status.test.ts b/src/utils/quote-status.test.ts
index 0cb21c5..3e91959 100644
--- a/src/utils/quote-status.test.ts
+++ b/src/utils/quote-status.test.ts
@@ -1,25 +1,25 @@
-import { describe, expect, it } from "vitest"
-import { getQuoteStatusVariant } from "./quote-status"
+import { describe, expect, it } from "vitest";
+import { getQuoteStatusVariant } from "./quote-status";
describe("getQuoteStatusVariant", () => {
it("maps offered and pending statuses to default", () => {
- expect(getQuoteStatusVariant("Offered")).toBe("default")
- expect(getQuoteStatusVariant("OfferExpired")).toBe("default")
- expect(getQuoteStatusVariant("Pending")).toBe("default")
- })
+ expect(getQuoteStatusVariant("Offered")).toBe("default");
+ expect(getQuoteStatusVariant("OfferExpired")).toBe("default");
+ expect(getQuoteStatusVariant("Pending")).toBe("default");
+ });
it("maps accepted and minting statuses to success", () => {
- expect(getQuoteStatusVariant("Accepted")).toBe("success")
- expect(getQuoteStatusVariant("Minting")).toBe("success")
- })
+ expect(getQuoteStatusVariant("Accepted")).toBe("success");
+ expect(getQuoteStatusVariant("Minting")).toBe("success");
+ });
it("maps denied-like statuses to destructive", () => {
- expect(getQuoteStatusVariant("Denied")).toBe("destructive")
- expect(getQuoteStatusVariant("Canceled")).toBe("destructive")
- expect(getQuoteStatusVariant("Rejected")).toBe("destructive")
- })
+ expect(getQuoteStatusVariant("Denied")).toBe("destructive");
+ expect(getQuoteStatusVariant("Canceled")).toBe("destructive");
+ expect(getQuoteStatusVariant("Rejected")).toBe("destructive");
+ });
it("falls back to outline for unknown values", () => {
- expect(getQuoteStatusVariant("UnknownStatus")).toBe("outline")
- })
-})
+ expect(getQuoteStatusVariant("UnknownStatus")).toBe("outline");
+ });
+});
diff --git a/src/utils/quote-status.ts b/src/utils/quote-status.ts
index 750d0e6..133b90d 100644
--- a/src/utils/quote-status.ts
+++ b/src/utils/quote-status.ts
@@ -1,21 +1,26 @@
-export type QuoteStatusVariant = "default" | "secondary" | "destructive" | "success" | "outline"
+export type QuoteStatusVariant =
+ | "default"
+ | "secondary"
+ | "destructive"
+ | "success"
+ | "outline";
export const getQuoteStatusVariant = (status: string): QuoteStatusVariant => {
switch (status) {
case "Offered":
case "OfferExpired":
- return "default"
+ return "default";
case "Pending":
- return "default"
+ return "default";
case "Accepted":
case "Minting":
case "MintingEnabled":
- return "success"
+ return "success";
case "Denied":
case "Canceled":
case "Rejected":
- return "destructive"
+ return "destructive";
default:
- return "outline"
+ return "outline";
}
-}
+};
diff --git a/src/utils/strings.ts b/src/utils/strings.ts
index 3935b0a..05f3586 100644
--- a/src/utils/strings.ts
+++ b/src/utils/strings.ts
@@ -1,15 +1,17 @@
export const truncateString = (str: string, maxLength: number): string =>
str.length <= maxLength
? str
- : str.slice(0, Math.floor((maxLength - 3) / 2)) + "…" + str.slice(-Math.floor((maxLength - 3) / 2))
+ : str.slice(0, Math.floor((maxLength - 3) / 2)) +
+ "…" +
+ str.slice(-Math.floor((maxLength - 3) / 2));
export const formatNumber = (locale: string, value: number): string => {
- return new Intl.NumberFormat(locale).format(value)
-}
+ return new Intl.NumberFormat(locale).format(value);
+};
export const getInitials = (name?: string): string => {
if (!name) {
- return "?"
+ return "?";
}
return name
@@ -18,25 +20,25 @@ export const getInitials = (name?: string): string => {
.map((n) => n[0])
.join("")
.toUpperCase()
- .slice(0, 2)
-}
+ .slice(0, 2);
+};
export const getDeterministicColor = (seed?: string): string => {
if (!seed) {
- return "#999999"
+ return "#999999";
}
- let hash = 0
+ let hash = 0;
for (let i = 0; i < seed.length; i++) {
- hash = seed.charCodeAt(i) + ((hash << 5) - hash)
+ hash = seed.charCodeAt(i) + ((hash << 5) - hash);
}
- const hue = Math.abs(hash % 360)
- const saturation = 65 + (Math.abs(hash) % 20)
- const lightness = 45 + (Math.abs(hash >> 8) % 15)
+ const hue = Math.abs(hash % 360);
+ const saturation = 65 + (Math.abs(hash) % 20);
+ const lightness = 45 + (Math.abs(hash >> 8) % 15);
- return `hsl(${hue}, ${saturation}%, ${lightness}%)`
-}
+ return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
+};
/**
* Formats a status label for display to users.
@@ -44,10 +46,10 @@ export const getDeterministicColor = (seed?: string): string => {
*/
export const formatStatusLabel = (status: string): string => {
if (status === "OfferExpired") {
- return "Offer expired"
+ return "Offer expired";
}
if (status === "MintingEnabled") {
- return "Minting enabled"
+ return "Minting enabled";
}
- return status
-}
+ return status;
+};
diff --git a/tsconfig.app.json b/tsconfig.app.json
index dbaaf9a..9fb89c8 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -28,9 +28,5 @@
"@/*": ["./src/*"]
}
},
- "include": [
- "src",
- "vitest-setup.ts",
- "openapi-ts.config.ts"
- ]
+ "include": ["src", "vitest-setup.ts", "openapi-ts.config.ts"]
}
diff --git a/vite.config.ts b/vite.config.ts
index e0a7e51..54cd21b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,8 +1,11 @@
-import path from "path"
-import { defineConfig as defineViteConfig, mergeConfig } from "vite"
-import { defineConfig as defineVitestConfig, configDefaults } from "vitest/config"
-import react from "@vitejs/plugin-react"
-import tailwindcss from "@tailwindcss/vite"
+import path from "path";
+import { defineConfig as defineViteConfig, mergeConfig } from "vite";
+import {
+ defineConfig as defineVitestConfig,
+ configDefaults,
+} from "vitest/config";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
const viteConfig = defineViteConfig({
@@ -12,13 +15,13 @@ const viteConfig = defineViteConfig({
"@": path.resolve(__dirname, "./src"),
},
},
-})
+});
const vitestConfig = defineVitestConfig({
test: {
environment: "jsdom",
include: ["**/*.test.{ts,tsx}"],
- setupFiles: ['./vitest-setup.ts'],
+ setupFiles: ["./vitest-setup.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "lcov"],
@@ -26,6 +29,6 @@ const vitestConfig = defineVitestConfig({
},
exclude: [...configDefaults.exclude],
},
-})
+});
-export default mergeConfig(viteConfig, vitestConfig)
+export default mergeConfig(viteConfig, vitestConfig);
diff --git a/vitest-setup.ts b/vitest-setup.ts
index d178cb9..32d5a5e 100644
--- a/vitest-setup.ts
+++ b/vitest-setup.ts
@@ -1,3 +1,4 @@
-import '@testing-library/jest-dom/vitest'
-
-;(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+import "@testing-library/jest-dom/vitest";
+(
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+).IS_REACT_ACT_ENVIRONMENT = true;