diff --git a/_base/next.config.mjs b/_base/next.config.mjs
index 825cdc79..9ed59c7b 100644
--- a/_base/next.config.mjs
+++ b/_base/next.config.mjs
@@ -19,7 +19,7 @@ const isDevelopment = process.env.NODE_ENV === "development";
const ContentSecurityPolicy = `
default-src 'none';
base-uri 'none';
- connect-src 'self';
+ connect-src 'self' https://maps.googleapis.com;
font-src 'self' fonts.gstatic.com;
form-action 'self';
frame-ancestors 'none';
diff --git a/consumer-issuing/.env.example b/consumer-issuing/.env.example
new file mode 100644
index 00000000..0a7021fb
--- /dev/null
+++ b/consumer-issuing/.env.example
@@ -0,0 +1,32 @@
+### Stripe configuration
+
+# You can get a test publishable key from the Stripe dashboard: https://dashboard.stripe.com/test/apikeys
+# It has the `NEXT_PUBLIC_` prefix so that it can be used in the client-side code
+# This demo app can support different regions by having separate platform accounts. You can create a
+# platform account for each region and use the corresponding publishable and secret keys here.
+# Not all regions/key pairs are required to be set up: you can provide either one of both of UK and EU.
+NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_UK="" # pk_test_...
+NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_EU="" # pk_test_...
+NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_US="" # pk_test_...
+STRIPE_SECRET_KEY_UK="" # sk_test_...
+STRIPE_SECRET_KEY_EU="" # sk_test_...
+STRIPE_SECRET_KEY_US="" # sk_test_...
+
+# The URL that will be redirected to after a user completes Connect Onboarding
+CONNECT_ONBOARDING_REDIRECT_URL="http://localhost:3000"
+
+### Auth configuration
+
+# You can generate this using `openssl rand -base64 32`
+NEXTAUTH_SECRET=""
+# Your NextAuth base URL
+NEXTAUTH_URL="http://localhost:3000"
+
+### Demo configuration
+
+NEXT_PUBLIC_DEMO_MODE="false"
+
+### Database configuration
+
+DATABASE_URL="postgresql://postgres@localhost/consumer_issuing"
+POSTGRES_URL=${DATABASE_URL}
diff --git a/consumer-issuing/.eslintrc.json b/consumer-issuing/.eslintrc.json
new file mode 100644
index 00000000..dc7c76c4
--- /dev/null
+++ b/consumer-issuing/.eslintrc.json
@@ -0,0 +1,25 @@
+// {
+// "parser": "@typescript-eslint/parser",
+// "plugins": ["@typescript-eslint", "prettier", "import", "unused-imports"],
+// "extends": [
+// "next/core-web-vitals",
+// "plugin:@typescript-eslint/recommended",
+// "plugin:prettier/recommended",
+// "plugin:import/recommended",
+// "plugin:import/typescript"
+// ],
+// "rules": {
+// "import/order": ["error", {
+// "newlines-between": "always",
+// "alphabetize": {
+// "order": "asc"
+// }
+// }],
+// "no-console": "warn",
+// "unused-imports/no-unused-imports": "error", // Autofix unused imports
+// "unused-imports/no-unused-vars": [
+// "warn",
+// { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
+// ] // Autofix unused vars, with options to ignore variables and arguments starting with _
+// }
+// }
diff --git a/consumer-issuing/.node-version b/consumer-issuing/.node-version
new file mode 100644
index 00000000..4a1f488b
--- /dev/null
+++ b/consumer-issuing/.node-version
@@ -0,0 +1 @@
+18.17.1
diff --git a/consumer-issuing/README.md b/consumer-issuing/README.md
new file mode 100644
index 00000000..2c7e4b8f
--- /dev/null
+++ b/consumer-issuing/README.md
@@ -0,0 +1,107 @@
+# Stripe Issuing: An Expense Management starter application
+
+This sample demonstrates a basic web application with expense management features built on Stripeβs Issuing APIs.
+
+
+
+
+
+## Features
+
+- Onboard and verify business customers π
+- Issue cards π³
+- Display full card numbers with PCI compliance π’
+- Test funding an Issuing balance π¦
+- Simulate test payments β‘
+- Review transactions π
+
+## Prerequisites
+
+- Activate Stripe Issuing in test mode through this link:
+- Obtain your Stripe API keys at
+
+## Deploy the sample app to the cloud
+
+Click the button below to get started on [Vercel](https://vercel.com/docs). During setup, you will be asked to provide values for some environment variables. See information about [required environment variables](#environment-variables) below to learn more about the API keys and secrets you'll need to provide.
+
+This starter app supports both UK and EU Issuing users: you can provide Stripe API keys for platforms based in _one or both_ of those regions. Most users will only issue in one region, so will need to provide only the `_UK` or `_EU` environment variables. Note that Vercel considers all environment variables for one-click deployments to be compulsory: use the string value `none` for the regional environment variables that you don't want to provide.
+
+[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fstripe-samples%2Fissuing-treasury%2Fexpense-management&env=NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_UK,STRIPE_SECRET_KEY_UK,NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_EU,STRIPE_SECRET_KEY_EU,NEXTAUTH_SECRET&project-name=expense-management&demo-title=Expense%20Management%20app&demo-description=A%20commercial%20pre-funded%20card&repository-name=expense-management&stores=%5B%7B%22type%22%3A%22postgres%22%7D%5D)
+
+## Local development
+
+You can also clone this repo and run it locally by following the steps below.
+
+### Clone the repo
+
+Clone this repo and then run these steps inside it.
+
+ git clone https://github.com/stripe-samples/issuing-treasury.git && cd issuing-treasury/expense-management
+
+### Node.js installation
+
+Install the required Node.js runtime. You can install it directly from the [Node.js website](https://nodejs.org/en/download/releases)
+but we recommend using a Node version manager. Most version managers can read the required version off of `.node-version`
+and install it.
+
+If you're using the `nodenv` Node version manager ([setup instructions for nodenv](https://github.com/nodenv/nodenv#installation)), use:
+
+ nodenv install
+
+If you're using the popular `nvm` Node version manager ([setup instructions for Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating)), use:
+
+ nvm install
+
+### Dependency installation
+
+Once Node.js is installed and activated, install the application's dependencies using:
+
+ npm install
+
+### Environment variables file setup
+
+Replicate `.env.example` as `.env` using:
+
+ cp .env.example .env
+
+Update `.env` to reflect:
+
+- **NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY**: Your Stripe publishable test mode [API key](https://dashboard.stripe.com/test/apikeys) (starts with `pk_test_...`).
+- **STRIPE_SECRET_KEY**: Your Stripe secret test mode [API key](https://dashboard.stripe.com/test/apikeys) (starts with `sk_test_...`).
+- **NEXTAUTH_SECRET**: For JWT encryption by NextAuth.js ([learn more](https://next-auth.js.org/configuration/options#nextauth_secret)). Use `openssl rand -base64 32` to obtain a new one.
+- **NEXTAUTH_URL**: Your application URL, for local use you can keep the default "".
+- **CONNECT_ONBOARDING_REDIRECT_URL**: Your application URL, for local use you can keep the default "".
+
+### Database setup
+
+On Mac, follow these instructions to install Postgres:
+
+ brew install postgresql@14
+ createuser -s postgres
+ createdb "$(whoami)"
+
+You'll find more about the why you need the `createuser` step [here](https://stackoverflow.com/a/15309551).
+
+Next, create the database with:
+
+ npx prisma migrate dev
+
+If it errors out (perhaps due to permission issue running the Prisma CLI), simply run the included script:
+
+ ./db/setup-database.postgres.sh
+
+This script creates a local Postgres `expense_management` database.
+
+### Application launch
+
+After necessary setups, launch the application with `npm run dev`.
+
+*Note: This application serves as an example and should not proceed to production deployment as it is.*
+
+## Customizing your UI with the Devias Material UI theme
+
+This sample uses the [free Devias UI kit](https://github.com/devias-io/material-kit-react) under an MIT license, which seamlessly integrates with Material UI and React.
+
+You can easily customize aspects of the theme like the color palette by modifying the code in `/src/theme`.
+
+To build a full-featured, production-ready application we recommend the [Devias Pro](https://material-kit-pro-react.devias.io/) version, which offers additional layouts, advanced React components, pre-built dashboards, and essential TypeScript support that ensures your code remains clean, robust, and scalable.
diff --git a/consumer-issuing/db/setup-database.postgres.sh b/consumer-issuing/db/setup-database.postgres.sh
new file mode 100755
index 00000000..80048d9f
--- /dev/null
+++ b/consumer-issuing/db/setup-database.postgres.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Ideally you should be able to use `npx prisma migrate dev` to migrate your Postgres database but if that fails for any
+# reason, you can use this script to get started quickly.
+
+DB_NAME="expense_management"
+
+# Check if the database exists
+db_exists=$(psql -tAc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'")
+
+if [ "$db_exists" != "1" ]; then
+ # Create the database
+ echo "Creating database..."
+ psql -c "CREATE DATABASE $DB_NAME;"
+else
+ echo "Database already exists, skipping creation..."
+fi
+
+# Connect to the database and create the users table if it doesn't exist
+echo "Creating users table..."
+psql -d $DB_NAME -c "
+CREATE TABLE IF NOT EXISTS users (
+ id SERIAL PRIMARY KEY,
+ email TEXT UNIQUE NOT NULL,
+ password TEXT NOT NULL,
+ account_id TEXT NOT NULL,
+ country TEXT NOT NULL,
+ last_login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+"
+
+echo "Setup complete!"
diff --git a/consumer-issuing/next-env.d.ts b/consumer-issuing/next-env.d.ts
new file mode 100644
index 00000000..a4a7b3f5
--- /dev/null
+++ b/consumer-issuing/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
diff --git a/consumer-issuing/next.config.mjs b/consumer-issuing/next.config.mjs
new file mode 100644
index 00000000..825cdc79
--- /dev/null
+++ b/consumer-issuing/next.config.mjs
@@ -0,0 +1,52 @@
+const isDevelopment = process.env.NODE_ENV === "development";
+
+// We are setting the CSP headers as strictly as possible. There are a few notable exceptions being made:
+// - We are allowing 'unsafe-eval' in the script-src directive in development only because we are using Next.js'
+// built-in support for React Fast Refresh. This is a temporary exception until Next.js supports a better
+// solution for Fast Refresh.
+// - We are allowing 'unsafe-inline' in the style-src directive. This is a temporary exception until Next.js
+// supports a better solution.
+// - We are allowing fonts.gstatic.com and fonts.googleapis.com in the font-src directive. We could host the
+// fonts ourselves, but it's easier to use Google Fonts and the risk is minimal. Feel free to host the fonts
+// yourself if you prefer and tighten up the font-src and style-src directives.
+// - We are allowing js.stripe.com in the frame-src directive. This is required for Stripe Issuing Elements to work.
+//
+// Learn more about CSP: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+// Learn more about Next.js security headers: https://nextjs.org/docs/pages/api-reference/next-config-js/headers#content-security-policy
+//
+// Also follow [this discussion](https://github.com/vercel/next.js/discussions/41473) about making Next.js have a
+// strict CSP implementation which would make it easier to tighten these directives with a `nonce`.
+const ContentSecurityPolicy = `
+ default-src 'none';
+ base-uri 'none';
+ connect-src 'self';
+ font-src 'self' fonts.gstatic.com;
+ form-action 'self';
+ frame-ancestors 'none';
+ frame-src js.stripe.com;
+ img-src 'self';
+ script-src 'self' js.stripe.com ${isDevelopment ? "'unsafe-eval'" : ""};
+ style-src 'self' 'unsafe-inline' fonts.googleapis.com;
+ upgrade-insecure-requests;
+`;
+
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+ swcMinify: true,
+ async headers() {
+ return [
+ {
+ source: "/:path*",
+ headers: [
+ {
+ key: "Content-Security-Policy",
+ value: ContentSecurityPolicy.replace(/\s{2,}/g, " ").trim(),
+ },
+ ],
+ },
+ ];
+ },
+};
+
+export default nextConfig;
diff --git a/consumer-issuing/package-lock.json b/consumer-issuing/package-lock.json
new file mode 100644
index 00000000..07ff3f6f
--- /dev/null
+++ b/consumer-issuing/package-lock.json
@@ -0,0 +1,8021 @@
+{
+ "name": "consumer-issuing",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "consumer-issuing",
+ "version": "0.0.0",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@emotion/cache": "11.11.0",
+ "@emotion/react": "^11.11.3",
+ "@emotion/server": "11.11.0",
+ "@emotion/styled": "^11.11.0",
+ "@faker-js/faker": "^8.4.1",
+ "@heroicons/react": "^2.1.1",
+ "@mui/icons-material": "^5.15.7",
+ "@mui/material": "^5.15.7",
+ "@next-auth/prisma-adapter": "^1.0.7",
+ "@prisma/client": "^5.9.1",
+ "@react-google-maps/api": "^2.20.6",
+ "@stripe/connect-js": "^3.3.22-preview-2",
+ "@stripe/react-connect-js": "^3.3.25-preview-1",
+ "@stripe/react-stripe-js": "^2.4.0",
+ "@stripe/stripe-js": "^2.4.0",
+ "@types/uuid": "^10.0.0",
+ "@xstate/react": "^4.0.3",
+ "apexcharts": "3.45.2",
+ "babel-runtime": "^6.26.0",
+ "bcrypt": "^5.1.1",
+ "country-locale-map": "^1.9.0",
+ "date-fns": "3.3.1",
+ "formik": "^2.4.5",
+ "jsonwebtoken": "^9.0.2",
+ "libphonenumber-js": "^1.10.55",
+ "next": "^14.1.0",
+ "next-auth": "^4.24.5",
+ "nprogress": "^0.2.0",
+ "pretty-print-json": "^3.0.4",
+ "qs": "^6.11.2",
+ "react": "^18.3.0",
+ "react-apexcharts": "1.4.1",
+ "react-dom": "18.2.0",
+ "sharp": "^0.33.2",
+ "simplebar-react": "^3.2.4",
+ "stripe": "^14.25.0",
+ "uuid": "^11.1.0",
+ "xstate": "^5.13.0",
+ "yup": "^1.3.3"
+ },
+ "devDependencies": {
+ "@types/bcrypt": "5.0.2",
+ "@types/jsonwebtoken": "^9.0.5",
+ "@types/nprogress": "^0.2.3",
+ "@types/react": "^18.3.0",
+ "@typescript-eslint/eslint-plugin": "^6.20.0",
+ "@typescript-eslint/parser": "^6.20.0",
+ "autoprefixer": "^10.4.17",
+ "eslint": "^8.56.0",
+ "eslint-config-next": "^14.1.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-prettier": "^5.1.3",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-unused-imports": "^3.0.0",
+ "prettier": "^3.2.5",
+ "prisma": "^5.9.1",
+ "typescript": "^5.3.3"
+ },
+ "engines": {
+ "node": "18"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.26.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
+ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.25.9",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz",
+ "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
+ "dependencies": {
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+ "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+ "dependencies": {
+ "@babel/traverse": "^7.25.9",
+ "@babel/types": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.25.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
+ "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+ "dependencies": {
+ "@babel/types": "^7.27.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
+ "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
+ "dependencies": {
+ "regenerator-runtime": "^0.14.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
+ "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/parser": "^7.27.0",
+ "@babel/types": "^7.27.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz",
+ "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "@babel/generator": "^7.27.0",
+ "@babel/parser": "^7.27.0",
+ "@babel/template": "^7.27.0",
+ "@babel/types": "^7.27.0",
+ "debug": "^4.3.1",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
+ "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.25.9",
+ "@babel/helper-validator-identifier": "^7.25.9"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
+ "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.0.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
+ "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz",
+ "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+ "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.3.3",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
+ "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1",
+ "@emotion/sheet": "^1.2.2",
+ "@emotion/utils": "^1.2.1",
+ "@emotion/weak-memoize": "^0.3.1",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz",
+ "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0"
+ }
+ },
+ "node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
+ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/react/node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/react/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
+ "node_modules/@emotion/react/node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
+ "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.2",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/serialize/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
+ "node_modules/@emotion/server": {
+ "version": "11.11.0",
+ "resolved": "https://registry.npmjs.org/@emotion/server/-/server-11.11.0.tgz",
+ "integrity": "sha512-6q89fj2z8VBTx9w93kJ5n51hsmtYuFPtZgnc1L8VzRx9ti4EU6EyvF6Nn1H1x3vcCQCF7u2dB2lY4AYJwUW4PA==",
+ "dependencies": {
+ "@emotion/utils": "^1.2.1",
+ "html-tokenize": "^2.0.0",
+ "multipipe": "^1.0.2",
+ "through": "^2.3.8"
+ },
+ "peerDependencies": {
+ "@emotion/css": "^11.0.0-rc.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/css": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg=="
+ },
+ "node_modules/@emotion/styled": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz",
+ "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.0.0-rc.0",
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+ "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
+ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA=="
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
+ "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.1.tgz",
+ "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@faker-js/faker": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
+ "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fakerjs"
+ }
+ ],
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0",
+ "npm": ">=6.14.13"
+ }
+ },
+ "node_modules/@googlemaps/js-api-loader": {
+ "version": "1.16.8",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz",
+ "integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ=="
+ },
+ "node_modules/@googlemaps/markerclusterer": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
+ "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "supercluster": "^8.0.1"
+ }
+ },
+ "node_modules/@heroicons/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+ "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+ "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+ "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+ "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+ "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+ "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+ "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+ "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+ "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+ "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+ "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.0.5"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+ "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+ "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+ "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+ "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+ "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+ "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+ "cpu": [
+ "wasm32"
+ ],
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.2.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+ "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+ "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.25",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mapbox/node-pre-gyp": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
+ "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
+ "dependencies": {
+ "detect-libc": "^2.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "make-dir": "^3.1.0",
+ "node-fetch": "^2.6.7",
+ "nopt": "^5.0.0",
+ "npmlog": "^5.0.1",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.11"
+ },
+ "bin": {
+ "node-pre-gyp": "bin/node-pre-gyp"
+ }
+ },
+ "node_modules/@mui/core-downloads-tracker": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.17.1.tgz",
+ "integrity": "sha512-OcZj+cs6EfUD39IoPBOgN61zf1XFVY+imsGoBDwXeSq2UHJZE3N59zzBOVjclck91Ne3e9gudONOeILvHCIhUA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ }
+ },
+ "node_modules/@mui/icons-material": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.17.1.tgz",
+ "integrity": "sha512-CN86LocjkunFGG0yPlO4bgqHkNGgaEOEc3X/jG5Bzm401qYw79/SaLrofA7yAKCCXAGdIGnLoMHohc3+ubs95A==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^5.0.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/material": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.17.1.tgz",
+ "integrity": "sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/core-downloads-tracker": "^5.17.1",
+ "@mui/system": "^5.17.1",
+ "@mui/types": "~7.2.15",
+ "@mui/utils": "^5.17.1",
+ "@popperjs/core": "^2.11.8",
+ "@types/react-transition-group": "^4.4.10",
+ "clsx": "^2.1.0",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.0.0",
+ "react-transition-group": "^4.4.5"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/private-theming": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz",
+ "integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/utils": "^5.17.1",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/styled-engine": {
+ "version": "5.16.14",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.14.tgz",
+ "integrity": "sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@emotion/cache": "^11.13.5",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.4.1",
+ "@emotion/styled": "^11.3.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/styled-engine/node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@mui/styled-engine/node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ=="
+ },
+ "node_modules/@mui/styled-engine/node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="
+ },
+ "node_modules/@mui/system": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.17.1.tgz",
+ "integrity": "sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/private-theming": "^5.17.1",
+ "@mui/styled-engine": "^5.16.14",
+ "@mui/types": "~7.2.15",
+ "@mui/utils": "^5.17.1",
+ "clsx": "^2.1.0",
+ "csstype": "^3.1.3",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.5.0",
+ "@emotion/styled": "^11.3.0",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/react": {
+ "optional": true
+ },
+ "@emotion/styled": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/types": {
+ "version": "7.2.24",
+ "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz",
+ "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==",
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@mui/utils": {
+ "version": "5.17.1",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz",
+ "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==",
+ "dependencies": {
+ "@babel/runtime": "^7.23.9",
+ "@mui/types": "~7.2.15",
+ "@types/prop-types": "^15.7.12",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "react-is": "^19.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz",
+ "integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.0",
+ "@emnapi/runtime": "^1.4.0",
+ "@tybys/wasm-util": "^0.9.0"
+ }
+ },
+ "node_modules/@next-auth/prisma-adapter": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@next-auth/prisma-adapter/-/prisma-adapter-1.0.7.tgz",
+ "integrity": "sha512-Cdko4KfcmKjsyHFrWwZ//lfLUbcLqlyFqjd/nYE2m3aZ7tjMNUjpks47iw7NTCnXf+5UWz5Ypyt1dSs1EP5QJw==",
+ "peerDependencies": {
+ "@prisma/client": ">=2.26.0 || >=3",
+ "next-auth": "^4"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.28.tgz",
+ "integrity": "sha512-PAmWhJfJQlP+kxZwCjrVd9QnR5x0R3u0mTXTiZDgSd4h5LdXmjxCCWbN9kq6hkZBOax8Rm3xDW5HagWyJuT37g=="
+ },
+ "node_modules/@next/eslint-plugin-next": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.28.tgz",
+ "integrity": "sha512-GQUPA1bTZy5qZdPV5MOHB18465azzhg8xm5o2SqxMF+h1rWNjB43y6xmIPHG5OV2OiU3WxuINpusXom49DdaIQ==",
+ "dev": true,
+ "dependencies": {
+ "glob": "10.3.10"
+ }
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.28.tgz",
+ "integrity": "sha512-kzGChl9setxYWpk3H6fTZXXPFFjg7urptLq5o5ZgYezCrqlemKttwMT5iFyx/p1e/JeglTwDFRtb923gTJ3R1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.28.tgz",
+ "integrity": "sha512-z6FXYHDJlFOzVEOiiJ/4NG8aLCeayZdcRSMjPDysW297Up6r22xw6Ea9AOwQqbNsth8JNgIK8EkWz2IDwaLQcw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.28.tgz",
+ "integrity": "sha512-9ARHLEQXhAilNJ7rgQX8xs9aH3yJSj888ssSjJLeldiZKR4D7N08MfMqljk77fAwZsWwsrp8ohHsMvurvv9liQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.28.tgz",
+ "integrity": "sha512-p6gvatI1nX41KCizEe6JkF0FS/cEEF0u23vKDpl+WhPe/fCTBeGkEBh7iW2cUM0rvquPVwPWdiUR6Ebr/kQWxQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.28.tgz",
+ "integrity": "sha512-nsiSnz2wO6GwMAX2o0iucONlVL7dNgKUqt/mDTATGO2NY59EO/ZKnKEr80BJFhuA5UC1KZOMblJHWZoqIJddpA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.28.tgz",
+ "integrity": "sha512-+IuGQKoI3abrXFqx7GtlvNOpeExUH1mTIqCrh1LGFf8DnlUcTmOOCApEnPJUSLrSbzOdsF2ho2KhnQoO0I1RDw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.28.tgz",
+ "integrity": "sha512-l61WZ3nevt4BAnGksUVFKy2uJP5DPz2E0Ma/Oklvo3sGj9sw3q7vBWONFRgz+ICiHpW5mV+mBrkB3XEubMrKaA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.28.tgz",
+ "integrity": "sha512-+Kcp1T3jHZnJ9v9VTJ/yf1t/xmtFAc/Sge4v7mVc1z+NYfYzisi8kJ9AsY8itbgq+WgEwMtOpiLLJsUy2qnXZw==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.28.tgz",
+ "integrity": "sha512-1gCmpvyhz7DkB1srRItJTnmR2UwQPAUXXIg9r0/56g3O8etGmwlX68skKXJOp9EejW3hhv7nSQUJ2raFiz4MoA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nolyfill/is-core-module": {
+ "version": "1.0.39",
+ "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
+ "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.4.0"
+ }
+ },
+ "node_modules/@panva/hkdf": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
+ "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz",
+ "integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
+ "dev": true,
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "5.22.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz",
+ "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==",
+ "hasInstallScript": true,
+ "engines": {
+ "node": ">=16.13"
+ },
+ "peerDependencies": {
+ "prisma": "*"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "5.22.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
+ "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
+ "devOptional": true
+ },
+ "node_modules/@prisma/engines": {
+ "version": "5.22.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
+ "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/debug": "5.22.0",
+ "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
+ "@prisma/fetch-engine": "5.22.0",
+ "@prisma/get-platform": "5.22.0"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
+ "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
+ "devOptional": true
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "5.22.0",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
+ "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
+ "devOptional": true,
+ "dependencies": {
+ "@prisma/debug": "5.22.0",
+ "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
+ "@prisma/get-platform": "5.22.0"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "5.22.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
+ "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
+ "devOptional": true,
+ "dependencies": {
+ "@prisma/debug": "5.22.0"
+ }
+ },
+ "node_modules/@react-google-maps/api": {
+ "version": "2.20.6",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.20.6.tgz",
+ "integrity": "sha512-frxkSHWbd36ayyxrEVopSCDSgJUT1tVKXvQld2IyzU3UnDuqqNA3AZE4/fCdqQb2/zBQx3nrWnZB1wBXDcrjcw==",
+ "dependencies": {
+ "@googlemaps/js-api-loader": "1.16.8",
+ "@googlemaps/markerclusterer": "2.5.3",
+ "@react-google-maps/infobox": "2.20.0",
+ "@react-google-maps/marker-clusterer": "2.20.0",
+ "@types/google.maps": "3.58.1",
+ "invariant": "2.2.4"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/@react-google-maps/infobox": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.20.0.tgz",
+ "integrity": "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ=="
+ },
+ "node_modules/@react-google-maps/marker-clusterer": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.20.0.tgz",
+ "integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw=="
+ },
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true
+ },
+ "node_modules/@rushstack/eslint-patch": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz",
+ "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
+ "dev": true
+ },
+ "node_modules/@stripe/connect-js": {
+ "version": "3.3.22-preview-2",
+ "resolved": "https://registry.npmjs.org/@stripe/connect-js/-/connect-js-3.3.22-preview-2.tgz",
+ "integrity": "sha512-KYHRwInYRXklXwQZmu3YVhN3xlpBscZw1Fyhgz1+Zs8uORYNkt2BJr09GQabR9PpEktLttv0HaILMS1aks0gOQ=="
+ },
+ "node_modules/@stripe/react-connect-js": {
+ "version": "3.3.25-preview-1",
+ "resolved": "https://registry.npmjs.org/@stripe/react-connect-js/-/react-connect-js-3.3.25-preview-1.tgz",
+ "integrity": "sha512-DdNTRTYe7XQODoFzWus6nSOlGJPkPgZ6FEcmwACydYya5Nbg5ddsdoDq1AyyuxHN5UKCbAzDCrlTl8b1B7AZbQ==",
+ "peerDependencies": {
+ "@stripe/connect-js": ">=3.3.22-preview-2",
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@stripe/react-stripe-js": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.9.0.tgz",
+ "integrity": "sha512-+/j2g6qKAKuWSurhgRMfdlIdKM+nVVJCy/wl0US2Ccodlqx0WqfIIBhUkeONkCG+V/b+bZzcj4QVa3E/rXtT4Q==",
+ "dependencies": {
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "@stripe/stripe-js": "^1.44.1 || ^2.0.0 || ^3.0.0 || ^4.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/@stripe/stripe-js": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.4.0.tgz",
+ "integrity": "sha512-WFkQx1mbs2b5+7looI9IV1BLa3bIApuN3ehp9FP58xGg7KL9hCHDECgW3BwO9l9L+xBPVAD7Yjn1EhGe6EDTeA=="
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
+ "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
+ "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/bcrypt": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
+ "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/google.maps": {
+ "version": "3.58.1",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
+ "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ=="
+ },
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
+ "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
+ "dependencies": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true
+ },
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz",
+ "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/ms": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "22.14.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
+ "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/nprogress": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.3.tgz",
+ "integrity": "sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==",
+ "dev": true
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.14",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
+ "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.20",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz",
+ "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
+ "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/semver": {
+ "version": "7.7.0",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz",
+ "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==",
+ "dev": true
+ },
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
+ "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.5.1",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/type-utils": "6.21.0",
+ "@typescript-eslint/utils": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.4",
+ "natural-compare": "^1.4.0",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha",
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
+ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
+ "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz",
+ "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "@typescript-eslint/utils": "6.21.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
+ "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
+ "dev": true,
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
+ "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/visitor-keys": "6.21.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "9.0.3",
+ "semver": "^7.5.4",
+ "ts-api-utils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
+ "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@types/json-schema": "^7.0.12",
+ "@types/semver": "^7.5.0",
+ "@typescript-eslint/scope-manager": "6.21.0",
+ "@typescript-eslint/types": "6.21.0",
+ "@typescript-eslint/typescript-estree": "6.21.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
+ "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "6.21.0",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^16.0.0 || >=18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.6.3.tgz",
+ "integrity": "sha512-+BbDAtwT4AVUyGIfC6SimaA6Mi/tEJCf5OYV5XQg7WIOW0vyD15aVgDLvsQscIZxgz42xB6DDqR7Kv6NBQJrEg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.6.3.tgz",
+ "integrity": "sha512-q6qMXI8wT0u0GUns/L26kYHdX2du4yEhwxrXjPj/egvysI8XqcTyjnbWQm3NSJPw0Un2wvKPh0WuoTSJEZgbqw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.6.3.tgz",
+ "integrity": "sha512-/7xs7QNNW17VZrFBf+2C95G72rA5c0YGtR18pvWrzM2tVPLrTsKnLl32hi3CG7F6cwwYRy7h61BIkMHh7qaZkw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.6.3.tgz",
+ "integrity": "sha512-2xv5cUQCt+eYuq5tPF4AHStpzE8i8qdYnhitpvDv9vxzOZ5a0sdzgA8WHYgFe15dP469YOSivenMMdpuRcgE9Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.6.3.tgz",
+ "integrity": "sha512-4KaZxKIeFt/jAOD/zuBOLb5yyZk/XG9FKf5IXpDP21NcYxeus/os6w+NCK7wjSJKbOpHZhwfkAYLkfujkAOFkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.6.3.tgz",
+ "integrity": "sha512-dJoZsZoWwvfS+khk0jkX6KnLL1T2vbRfsxinOR3PghpRKmMTnasEVAxmrXLQFNKqVKZV/mU7gHzWhiBMhbq3bw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.6.3.tgz",
+ "integrity": "sha512-2Y6JcAY9e557rD6O53Zmeblrfu48vQfl5CrrKjt0/2J1Op/pKX3WI8TOh0gs5T4qX9uJDqdte11SNUssckdfUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.6.3.tgz",
+ "integrity": "sha512-kvcEe+j0De/DEfTNkte2xtmwSL4/GMesArcqmSgRqoOaGknUYY3whJ/3GygYKNMe82vvao4PaQkBlCrxhi88wQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.6.3.tgz",
+ "integrity": "sha512-fruY8swKre2H0J96h8HE+kN3iUnDR3VDd2wxBn4BxDw+5g7GOHBz5x1533l9mqAqHI4b2dMBECI4RtQdMOiBeQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.6.3.tgz",
+ "integrity": "sha512-1w0eaSxm9e69TEj9eArZDPQ7mL2VL6Bb4AXeLOdQoe5SNQpZaL6RlwGm7ss9xErwC7c9Hvob/ZZF7i8xYT55zg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.6.3.tgz",
+ "integrity": "sha512-ymUqs8AQyHTQQ50aN7EcMV47gKh5yKg8a0+SWSuDZEl6eGEOKn590D/iMDydS5KoWbMTy6/pBipS4vsPUEjYVw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.6.3.tgz",
+ "integrity": "sha512-LSfz1cguLZD+c00aTVbtrqX1x1sIR38M2lLYW3CZTGfippkg56Hf8kejHPA8H26OwB71c9/W78BCbgcdnEW+jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.6.3.tgz",
+ "integrity": "sha512-gehKZDmNDS2QTxefwPBLi0RJgOQ0dIoD/osCcNboDb3+ZKcbSMBaF3+4R5vj+XdV0QBdZg3vXwdwZswfEkQOcA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.9"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.6.3.tgz",
+ "integrity": "sha512-CzTmpDxwkoYl69stmlJzcVWITQEC6Vs8ASMZMEMbFO+q1Dw0GtpRjAA6X76zGcLOADDwzugx1vpT6YXarrhpTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.6.3.tgz",
+ "integrity": "sha512-j+n1gWkfu4Q/octUHXU1p1IOrh+B27vpA7ec81RB6nXCml5u7F0B7SrCZU+HqajxjVqgEQEYOcRCb1yzfwfsWw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.6.3.tgz",
+ "integrity": "sha512-n33drkd84G5Mu2BkUGawZXmm+IFPuRv7GpODfwEBs/CzZq2+BIZyAZmb03H9IgNbd7xaohZbtZ4/9Gb0xo5ssw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@xstate/react": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/@xstate/react/-/react-4.1.3.tgz",
+ "integrity": "sha512-zhE+ZfrcCR87bu71Rkh5Z5ruZBivR/7uD/dkelzJqjQdI45IZc9DqTI8lL4Cg5+VN2p5k86KxDsusqW1kW11Tg==",
+ "dependencies": {
+ "use-isomorphic-layout-effect": "^1.1.2",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "xstate": "^5.18.2"
+ },
+ "peerDependenciesMeta": {
+ "xstate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@yr/monotone-cubic-spline": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz",
+ "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA=="
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+ },
+ "node_modules/acorn": {
+ "version": "8.14.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
+ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "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-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/apexcharts": {
+ "version": "3.45.2",
+ "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.45.2.tgz",
+ "integrity": "sha512-PpuM4sJWy70sUh5U1IFn1m1p45MdHSChLUNnqEoUUUHSU2IHZugFrsVNhov1S8Q0cvfdrCRCvdBtHGSs6PSAWQ==",
+ "dependencies": {
+ "@yr/monotone-cubic-spline": "^1.0.3",
+ "svg.draggable.js": "^2.2.2",
+ "svg.easing.js": "^2.0.0",
+ "svg.filter.js": "^2.0.2",
+ "svg.pathmorphing.js": "^0.1.3",
+ "svg.resize.js": "^1.4.3",
+ "svg.select.js": "^3.0.1"
+ }
+ },
+ "node_modules/aproba": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
+ "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
+ "deprecated": "This package is no longer supported.",
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/are-we-there-yet/node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz",
+ "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
+ "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-shim-unscopables": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/ast-types-flow": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
+ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
+ "dev": true
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.10.3",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz",
+ "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
+ "dependencies": {
+ "core-js": "^2.4.0",
+ "regenerator-runtime": "^0.11.0"
+ }
+ },
+ "node_modules/babel-runtime/node_modules/regenerator-runtime": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/bcrypt": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
+ "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@mapbox/node-pre-gyp": "^1.0.11",
+ "node-addon-api": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.24.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz",
+ "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001688",
+ "electron-to-chromium": "^1.5.73",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
+ "node_modules/buffer-from": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz",
+ "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg=="
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001715",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz",
+ "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+ "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+ "dependencies": {
+ "color-convert": "^2.0.1",
+ "color-string": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=12.5.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/color-string": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+ "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+ "dependencies": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/core-js": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
+ "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
+ "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
+ "hasInstallScript": true
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/country-locale-map": {
+ "version": "1.9.9",
+ "resolved": "https://registry.npmjs.org/country-locale-map/-/country-locale-map-1.9.9.tgz",
+ "integrity": "sha512-CVoWx/z1FG8mwop2Hzx2T6kAMTJCTuTbDKn8/3Dbb5wWU4I6jxIVvmxDVGG2MZCduZyz2XKtpC0SDvyww/AlSw==",
+ "dependencies": {
+ "fuzzball": "^2.1.2"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz",
+ "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
+ },
+ "node_modules/detect-libc": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+ "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/duplexer2": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
+ "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==",
+ "dependencies": {
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "node_modules/duplexer2/node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+ },
+ "node_modules/duplexer2/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/duplexer2/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/duplexer2/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.140",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.140.tgz",
+ "integrity": "sha512-o82Rj+ONp4Ip7Cl1r7lrqx/pXhbp/lh9DpKcMNscFJdh8ebyRofnc7Sh01B4jx403RI0oqTBvlZ7OBIZLMr2+Q==",
+ "dev": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.23.9",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
+ "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.0",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-regex": "^1.2.1",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.0",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.3",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.18"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
+ "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.0.3",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.6",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "iterator.prototype": "^1.1.4",
+ "safe-array-concat": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
+ "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-next": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.28.tgz",
+ "integrity": "sha512-UxJMRQ4uaEdLp3mVQoIbRIlEF0S2rTlyZhI/2yEMVdAWmgFfPY4iJZ68jCbhLvXMnKeHMkmqTGjEhFH5Vm9h+A==",
+ "dev": true,
+ "dependencies": {
+ "@next/eslint-plugin-next": "14.2.28",
+ "@rushstack/eslint-patch": "^1.3.3",
+ "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-import-resolver-typescript": "^3.5.2",
+ "eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705"
+ },
+ "peerDependencies": {
+ "eslint": "^7.23.0 || ^8.0.0",
+ "typescript": ">=3.3.1"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+ "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+ "dev": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz",
+ "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==",
+ "dev": true,
+ "dependencies": {
+ "@nolyfill/is-core-module": "1.0.39",
+ "debug": "^4.4.0",
+ "get-tsconfig": "^4.10.0",
+ "is-bun-module": "^2.0.0",
+ "stable-hash": "^0.0.5",
+ "tinyglobby": "^0.2.13",
+ "unrs-resolver": "^1.6.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-import-resolver-typescript"
+ },
+ "peerDependencies": {
+ "eslint": "*",
+ "eslint-plugin-import": "*",
+ "eslint-plugin-import-x": "*"
+ },
+ "peerDependenciesMeta": {
+ "eslint-plugin-import": {
+ "optional": true
+ },
+ "eslint-plugin-import-x": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz",
+ "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.31.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz",
+ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
+ "dev": true,
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.8",
+ "array.prototype.findlastindex": "^1.2.5",
+ "array.prototype.flat": "^1.3.2",
+ "array.prototype.flatmap": "^1.3.2",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.12.0",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.15.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.0",
+ "semver": "^6.3.1",
+ "string.prototype.trimend": "^1.0.8",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
+ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
+ "dev": true,
+ "dependencies": {
+ "aria-query": "^5.3.2",
+ "array-includes": "^3.1.8",
+ "array.prototype.flatmap": "^1.3.2",
+ "ast-types-flow": "^0.0.8",
+ "axe-core": "^4.10.0",
+ "axobject-query": "^4.1.0",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^3.3.5",
+ "language-tags": "^1.0.9",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.includes": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
+ "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
+ "dev": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.11.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.37.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
+ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.3",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.2.1",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.9",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.12",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.0.0-canary-7118f5dd7-20230705",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz",
+ "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eslint-plugin-react/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==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-plugin-react/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,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/eslint-plugin-unused-imports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz",
+ "integrity": "sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==",
+ "dev": true,
+ "dependencies": {
+ "eslint-rule-composer": "^0.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/eslint-plugin": "6 - 7",
+ "eslint": "8"
+ },
+ "peerDependenciesMeta": {
+ "@typescript-eslint/eslint-plugin": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-rule-composer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz",
+ "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/eslint/node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true
+ },
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/formik": {
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz",
+ "integrity": "sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://opencollective.com/formik"
+ }
+ ],
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fs-minipass/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/fuzzball": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/fuzzball/-/fuzzball-2.2.2.tgz",
+ "integrity": "sha512-NCJZdEur2qTZ6/dYjkvLnNOnDxN4JG1rOWSRJS2ser4cFGVqFNBu2JjudxU2kZrHqfKg1zmtHii/JmWLaEeDHw==",
+ "dependencies": {
+ "heap": ">=0.2.0",
+ "lodash": "^4.17.21",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/gauge": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
+ "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
+ "deprecated": "This package is no longer supported.",
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.2",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.1",
+ "object-assign": "^4.1.1",
+ "signal-exit": "^3.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/gauge/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=="
+ },
+ "node_modules/gauge/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ },
+ "node_modules/gauge/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==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz",
+ "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==",
+ "dev": true,
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.3.10",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
+ "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
+ "dev": true,
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^2.3.5",
+ "minimatch": "^9.0.1",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
+ "path-scurry": "^1.10.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/heap": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz",
+ "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg=="
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/html-tokenize": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-2.0.1.tgz",
+ "integrity": "sha512-QY6S+hZ0f5m1WT8WffYN+Hg+xm/w5I8XeUcAq/ZYP5wVC8xbKi4Whhru3FtrAebD5EhBW8rmFzkDI6eCAuFe2w==",
+ "dependencies": {
+ "buffer-from": "~0.1.1",
+ "inherits": "~2.0.1",
+ "minimist": "~1.2.5",
+ "readable-stream": "~1.0.27-1",
+ "through2": "~0.4.1"
+ },
+ "bin": {
+ "html-tokenize": "bin/cmd.js"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "dev": true,
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bun-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
+ "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
+ "dev": true,
+ "dependencies": {
+ "semver": "^7.7.1"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.0",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "dev": true,
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
+ "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "get-proto": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
+ "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
+ "dev": true,
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jose": {
+ "version": "4.15.9",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
+ "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
+ },
+ "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
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+ "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
+ "dev": true
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
+ "dev": true,
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/libphonenumber-js": {
+ "version": "1.12.7",
+ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.7.tgz",
+ "integrity": "sha512-0nYZSNj/QEikyhcM5RZFXGlCB/mr4PVamnT1C2sKBnDDTYndrvbybYjvg+PMqAndQHlLbwQ3socolnL3WWTUFA=="
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
+ "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+ "dependencies": {
+ "semver": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
+ "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minizlib/node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/multipipe": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-1.0.2.tgz",
+ "integrity": "sha512-6uiC9OvY71vzSGX8lZvSqscE7ft9nPupJ8fMjrCNRAUy2LREUW42UL+V/NTrogr6rFgRydUrCX4ZitfpSNkSCQ==",
+ "dependencies": {
+ "duplexer2": "^0.1.2",
+ "object-assign": "^4.1.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/napi-postinstall": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.1.5.tgz",
+ "integrity": "sha512-HI5bHONOUYqV+FJvueOSgjRxHTLB25a3xIv59ugAxFe7xRNbW96hyYbMbsKzl+QvFV9mN/SrtHwiU+vYhMwA7Q==",
+ "dev": true,
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/next": {
+ "version": "14.2.28",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.28.tgz",
+ "integrity": "sha512-QLEIP/kYXynIxtcKB6vNjtWLVs3Y4Sb+EClTC/CSVzdLD1gIuItccpu/n1lhmduffI32iPGEK2cLLxxt28qgYA==",
+ "dependencies": {
+ "@next/env": "14.2.28",
+ "@swc/helpers": "0.5.5",
+ "busboy": "1.6.0",
+ "caniuse-lite": "^1.0.30001579",
+ "graceful-fs": "^4.2.11",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.1"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=18.17.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "14.2.28",
+ "@next/swc-darwin-x64": "14.2.28",
+ "@next/swc-linux-arm64-gnu": "14.2.28",
+ "@next/swc-linux-arm64-musl": "14.2.28",
+ "@next/swc-linux-x64-gnu": "14.2.28",
+ "@next/swc-linux-x64-musl": "14.2.28",
+ "@next/swc-win32-arm64-msvc": "14.2.28",
+ "@next/swc-win32-ia32-msvc": "14.2.28",
+ "@next/swc-win32-x64-msvc": "14.2.28"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-auth": {
+ "version": "4.24.11",
+ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz",
+ "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==",
+ "dependencies": {
+ "@babel/runtime": "^7.20.13",
+ "@panva/hkdf": "^1.0.2",
+ "cookie": "^0.7.0",
+ "jose": "^4.15.5",
+ "oauth": "^0.9.15",
+ "openid-client": "^5.4.0",
+ "preact": "^10.6.3",
+ "preact-render-to-string": "^5.1.19",
+ "uuid": "^8.3.2"
+ },
+ "peerDependencies": {
+ "@auth/core": "0.34.2",
+ "next": "^12.2.5 || ^13 || ^14 || ^15",
+ "nodemailer": "^6.6.5",
+ "react": "^17.0.2 || ^18 || ^19",
+ "react-dom": "^17.0.2 || ^18 || ^19"
+ },
+ "peerDependenciesMeta": {
+ "@auth/core": {
+ "optional": true
+ },
+ "nodemailer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next-auth/node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-addon-api": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
+ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true
+ },
+ "node_modules/nopt": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
+ "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npmlog": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
+ "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
+ "deprecated": "This package is no longer supported.",
+ "dependencies": {
+ "are-we-there-yet": "^2.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^3.0.0",
+ "set-blocking": "^2.0.0"
+ }
+ },
+ "node_modules/nprogress": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",
+ "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
+ },
+ "node_modules/oauth": {
+ "version": "0.9.15",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
+ "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
+ "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz",
+ "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
+ "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/oidc-token-hash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz",
+ "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==",
+ "engines": {
+ "node": "^10.13.0 || >=12.0.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/openid-client": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz",
+ "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==",
+ "dependencies": {
+ "jose": "^4.15.9",
+ "lru-cache": "^6.0.0",
+ "object-hash": "^2.2.0",
+ "oidc-token-hash": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/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==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.3",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+ "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "peer": true,
+ "dependencies": {
+ "nanoid": "^3.3.8",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true
+ },
+ "node_modules/preact": {
+ "version": "10.26.5",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.26.5.tgz",
+ "integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/preact-render-to-string": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
+ "integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
+ "dependencies": {
+ "pretty-format": "^3.8.0"
+ },
+ "peerDependencies": {
+ "preact": ">=10"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
+ "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
+ "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
+ },
+ "node_modules/pretty-print-json": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pretty-print-json/-/pretty-print-json-3.0.4.tgz",
+ "integrity": "sha512-sVupP4x7magteGomyHFUiL8trOVVo45BP74gVo6IiRhQFp3qtmlqCdFt/Tjkim1+/Rr+P/wKl4p67d1BQ8h9bw=="
+ },
+ "node_modules/prisma": {
+ "version": "5.22.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
+ "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@prisma/engines": "5.22.0"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": ">=16.13"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.3"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/property-expr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
+ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-apexcharts": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz",
+ "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==",
+ "dependencies": {
+ "prop-types": "^15.8.1"
+ },
+ "peerDependencies": {
+ "apexcharts": "^3.41.0",
+ "react": ">=0.13"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
+ "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "react": "^18.2.0"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
+ },
+ "node_modules/react-is": {
+ "version": "19.1.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
+ "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg=="
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "1.0.34",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/rimraf/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-array-concat/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
+ "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "dev": true,
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+ },
+ "node_modules/sharp": {
+ "version": "0.33.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+ "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+ "hasInstallScript": true,
+ "dependencies": {
+ "color": "^4.2.3",
+ "detect-libc": "^2.0.3",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.33.5",
+ "@img/sharp-darwin-x64": "0.33.5",
+ "@img/sharp-libvips-darwin-arm64": "1.0.4",
+ "@img/sharp-libvips-darwin-x64": "1.0.4",
+ "@img/sharp-libvips-linux-arm": "1.0.5",
+ "@img/sharp-libvips-linux-arm64": "1.0.4",
+ "@img/sharp-libvips-linux-s390x": "1.0.4",
+ "@img/sharp-libvips-linux-x64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+ "@img/sharp-linux-arm": "0.33.5",
+ "@img/sharp-linux-arm64": "0.33.5",
+ "@img/sharp-linux-s390x": "0.33.5",
+ "@img/sharp-linux-x64": "0.33.5",
+ "@img/sharp-linuxmusl-arm64": "0.33.5",
+ "@img/sharp-linuxmusl-x64": "0.33.5",
+ "@img/sharp-wasm32": "0.33.5",
+ "@img/sharp-win32-ia32": "0.33.5",
+ "@img/sharp-win32-x64": "0.33.5"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+ "dependencies": {
+ "is-arrayish": "^0.3.1"
+ }
+ },
+ "node_modules/simple-swizzle/node_modules/is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
+ },
+ "node_modules/simplebar-core": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/simplebar-core/-/simplebar-core-1.3.0.tgz",
+ "integrity": "sha512-LpWl3w0caz0bl322E68qsrRPpIn+rWBGAaEJ0lUJA7Xpr2sw92AkIhg6VWj988IefLXYh50ILatfAnbNoCFrlA==",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ }
+ },
+ "node_modules/simplebar-react": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/simplebar-react/-/simplebar-react-3.3.0.tgz",
+ "integrity": "sha512-sxzy+xRuU41He4tT4QLGYutchtOuye/xxVeq7xhyOiwMiHNK1ZpvbOTyy+7P0i7gfpXLGTJ8Bep8+4Mhdgtz/g==",
+ "dependencies": {
+ "simplebar-core": "^1.3.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stable-hash": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
+ "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
+ "dev": true
+ },
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "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,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/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
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/string.prototype.includes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
+ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "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,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stripe": {
+ "version": "14.25.0",
+ "resolved": "https://registry.npmjs.org/stripe/-/stripe-14.25.0.tgz",
+ "integrity": "sha512-wQS3GNMofCXwH8TSje8E1SE8zr6ODiGtHQgPtO95p9Mb4FhKC9jvXR2NUTpZ9ZINlckJcFidCmaTFV4P6vsb9g==",
+ "dependencies": {
+ "@types/node": ">=8.1.0",
+ "qs": "^6.11.0"
+ },
+ "engines": {
+ "node": ">=12.*"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
+ "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
+ },
+ "node_modules/supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "dependencies": {
+ "kdbush": "^4.0.2"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/svg.draggable.js": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
+ "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
+ "dependencies": {
+ "svg.js": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/svg.easing.js": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
+ "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==",
+ "dependencies": {
+ "svg.js": ">=2.3.x"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/svg.filter.js": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
+ "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==",
+ "dependencies": {
+ "svg.js": "^2.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/svg.js": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
+ "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
+ },
+ "node_modules/svg.pathmorphing.js": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
+ "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
+ "dependencies": {
+ "svg.js": "^2.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/svg.resize.js": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
+ "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
+ "dependencies": {
+ "svg.js": "^2.6.5",
+ "svg.select.js": "^2.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/svg.resize.js/node_modules/svg.select.js": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
+ "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
+ "dependencies": {
+ "svg.js": "^2.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/svg.select.js": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
+ "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
+ "dependencies": {
+ "svg.js": "^2.6.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/synckit": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz",
+ "integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==",
+ "dev": true,
+ "dependencies": {
+ "@pkgr/core": "^0.2.3",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/tar": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
+ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^5.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/tar/node_modules/minipass": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+ "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="
+ },
+ "node_modules/through2": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz",
+ "integrity": "sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==",
+ "dependencies": {
+ "readable-stream": "~1.0.17",
+ "xtend": "~2.1.1"
+ }
+ },
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="
+ },
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
+ "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+ "dev": true,
+ "dependencies": {
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+ "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+ "dev": true,
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "dev": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.8.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
+ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
+ },
+ "node_modules/unrs-resolver": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.6.3.tgz",
+ "integrity": "sha512-mYNIMmxlDcaepmUTNrBu2tEB/bRkLBUeAhke8XOnXYqSu/9dUk4cdFiJG1N4d5Q7Fii+9MpgavkxJpnXPqNhHw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "napi-postinstall": "^0.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/JounQin"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-darwin-arm64": "1.6.3",
+ "@unrs/resolver-binding-darwin-x64": "1.6.3",
+ "@unrs/resolver-binding-freebsd-x64": "1.6.3",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.6.3",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.6.3",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.6.3",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.6.3",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.6.3",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.6.3",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.6.3",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.6.3",
+ "@unrs/resolver-binding-linux-x64-musl": "1.6.3",
+ "@unrs/resolver-binding-wasm32-wasi": "1.6.3",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.6.3",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.6.3",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.6.3"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz",
+ "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
+ "node_modules/uuid": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
+ "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "bin": {
+ "uuid": "dist/esm/bin/uuid"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "dev": true,
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.19",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
+ "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/wide-align/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=="
+ },
+ "node_modules/wide-align/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==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/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
+ },
+ "node_modules/wrap-ansi-cjs/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,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/xstate": {
+ "version": "5.19.2",
+ "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.19.2.tgz",
+ "integrity": "sha512-B8fL2aP0ogn5aviAXFzI5oZseAMqN00fg/TeDa3ZtatyDcViYLIfuQl4y8qmHCiKZgGEzmnTyNtNQL9oeJE2gw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/xstate"
+ }
+ },
+ "node_modules/xtend": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",
+ "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==",
+ "dependencies": {
+ "object-keys": "~0.4.0"
+ },
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/xtend/node_modules/object-keys": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz",
+ "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw=="
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yup": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz",
+ "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
+ }
+ }
+}
diff --git a/consumer-issuing/package.json b/consumer-issuing/package.json
new file mode 100644
index 00000000..370485b1
--- /dev/null
+++ b/consumer-issuing/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "consumer-issuing",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint",
+ "vercel-build": "prisma generate && prisma migrate deploy && next build",
+ "postinstall": "prisma generate"
+ },
+ "dependencies": {
+ "@emotion/cache": "11.11.0",
+ "@emotion/react": "^11.11.3",
+ "@emotion/server": "11.11.0",
+ "@emotion/styled": "^11.11.0",
+ "@faker-js/faker": "^8.4.1",
+ "@heroicons/react": "^2.1.1",
+ "@mui/icons-material": "^5.15.7",
+ "@mui/material": "^5.15.7",
+ "@next-auth/prisma-adapter": "^1.0.7",
+ "@prisma/client": "^5.9.1",
+ "@react-google-maps/api": "^2.20.6",
+ "@stripe/connect-js": "^3.3.22-preview-2",
+ "@stripe/react-connect-js": "^3.3.25-preview-1",
+ "@stripe/react-stripe-js": "^2.4.0",
+ "@stripe/stripe-js": "^2.4.0",
+ "@types/uuid": "^10.0.0",
+ "@xstate/react": "^4.0.3",
+ "apexcharts": "3.45.2",
+ "babel-runtime": "^6.26.0",
+ "bcrypt": "^5.1.1",
+ "country-locale-map": "^1.9.0",
+ "date-fns": "3.3.1",
+ "formik": "^2.4.5",
+ "jsonwebtoken": "^9.0.2",
+ "libphonenumber-js": "^1.10.55",
+ "next": "^14.1.0",
+ "next-auth": "^4.24.5",
+ "nprogress": "^0.2.0",
+ "pretty-print-json": "^3.0.4",
+ "qs": "^6.11.2",
+ "react": "^18.3.0",
+ "react-apexcharts": "1.4.1",
+ "react-dom": "18.2.0",
+ "sharp": "^0.33.2",
+ "simplebar-react": "^3.2.4",
+ "stripe": "^14.25.0",
+ "uuid": "^11.1.0",
+ "xstate": "^5.13.0",
+ "yup": "^1.3.3"
+ },
+ "devDependencies": {
+ "@types/bcrypt": "5.0.2",
+ "@types/jsonwebtoken": "^9.0.5",
+ "@types/nprogress": "^0.2.3",
+ "@types/react": "^18.3.0",
+ "@typescript-eslint/eslint-plugin": "^6.20.0",
+ "@typescript-eslint/parser": "^6.20.0",
+ "autoprefixer": "^10.4.17",
+ "eslint": "^8.56.0",
+ "eslint-config-next": "^14.1.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-import-resolver-typescript": "^3.6.1",
+ "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-prettier": "^5.1.3",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-unused-imports": "^3.0.0",
+ "prettier": "^3.2.5",
+ "prisma": "^5.9.1",
+ "typescript": "^5.3.3"
+ },
+ "engines": {
+ "node": "18"
+ }
+}
diff --git a/consumer-issuing/prisma/migrations/20240130150918_initial/README.md b/consumer-issuing/prisma/migrations/20240130150918_initial/README.md
new file mode 100644
index 00000000..38832d12
--- /dev/null
+++ b/consumer-issuing/prisma/migrations/20240130150918_initial/README.md
@@ -0,0 +1,5 @@
+# Migration `20240130150918_initial`
+
+Created:
+
+- `users` table
diff --git a/consumer-issuing/prisma/migrations/20240130150918_initial/migration.sql b/consumer-issuing/prisma/migrations/20240130150918_initial/migration.sql
new file mode 100644
index 00000000..4cffea3f
--- /dev/null
+++ b/consumer-issuing/prisma/migrations/20240130150918_initial/migration.sql
@@ -0,0 +1,12 @@
+CREATE TABLE "users" (
+ "id" SERIAL NOT NULL,
+ "email" TEXT NOT NULL UNIQUE,
+ "password" TEXT NOT NULL,
+ "account_id" TEXT NOT NULL,
+ "last_login_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "country" TEXT NOT NULL,
+
+ CONSTRAINT "users_pkey" PRIMARY KEY ("id")
+);
diff --git a/consumer-issuing/prisma/migrations/20250506032959_add_api_request_logs/migration.sql b/consumer-issuing/prisma/migrations/20250506032959_add_api_request_logs/migration.sql
new file mode 100644
index 00000000..4d810a61
--- /dev/null
+++ b/consumer-issuing/prisma/migrations/20250506032959_add_api_request_logs/migration.sql
@@ -0,0 +1,15 @@
+-- CreateTable
+CREATE TABLE "api_request_logs" (
+ "id" SERIAL NOT NULL,
+ "user_id" INTEGER NOT NULL,
+ "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "request_url" TEXT NOT NULL,
+ "request_method" TEXT NOT NULL,
+ "request_body" TEXT,
+ "response_body" TEXT,
+
+ CONSTRAINT "api_request_logs_pkey" PRIMARY KEY ("id")
+);
+
+-- AddForeignKey
+ALTER TABLE "api_request_logs" ADD CONSTRAINT "api_request_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/consumer-issuing/prisma/migrations/migration_lock.toml b/consumer-issuing/prisma/migrations/migration_lock.toml
new file mode 100644
index 00000000..fbffa92c
--- /dev/null
+++ b/consumer-issuing/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (i.e. Git)
+provider = "postgresql"
\ No newline at end of file
diff --git a/consumer-issuing/prisma/schema.prisma b/consumer-issuing/prisma/schema.prisma
new file mode 100644
index 00000000..6b3163c5
--- /dev/null
+++ b/consumer-issuing/prisma/schema.prisma
@@ -0,0 +1,38 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("POSTGRES_URL")
+}
+
+model User {
+ id Int @id @default(autoincrement())
+ email String @unique
+ password String
+ accountId String @map("account_id")
+ country String @map("country")
+ lastLoginAt DateTime @default(now()) @map("last_login_at")
+ createdAt DateTime @default(now()) @map("created_at")
+ updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
+ apiRequestLogs ApiRequestLog[] @relation("UserToApiRequestLog")
+
+ @@map("users")
+}
+
+model ApiRequestLog {
+ id Int @id @default(autoincrement())
+ userId Int @map("user_id")
+ created DateTime @default(now())
+ requestUrl String @map("request_url")
+ requestMethod String @map("request_method")
+ requestBody String? @map("request_body")
+ responseBody String? @map("response_body")
+ user User @relation("UserToApiRequestLog", fields: [userId], references: [id])
+
+ @@map("api_request_logs")
+}
diff --git a/consumer-issuing/public/assets/avatars/avatar-anika-visser.png b/consumer-issuing/public/assets/avatars/avatar-anika-visser.png
new file mode 100644
index 00000000..5c7db9cb
Binary files /dev/null and b/consumer-issuing/public/assets/avatars/avatar-anika-visser.png differ
diff --git a/consumer-issuing/public/assets/cards/card-background.png b/consumer-issuing/public/assets/cards/card-background.png
new file mode 100644
index 00000000..4ee08733
Binary files /dev/null and b/consumer-issuing/public/assets/cards/card-background.png differ
diff --git a/consumer-issuing/public/assets/cards/chip.svg b/consumer-issuing/public/assets/cards/chip.svg
new file mode 100644
index 00000000..b669422b
--- /dev/null
+++ b/consumer-issuing/public/assets/cards/chip.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/consumer-issuing/public/assets/cards/contactless.svg b/consumer-issuing/public/assets/cards/contactless.svg
new file mode 100644
index 00000000..a60d8f9e
--- /dev/null
+++ b/consumer-issuing/public/assets/cards/contactless.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/consumer-issuing/public/assets/furever_logo.png b/consumer-issuing/public/assets/furever_logo.png
new file mode 100644
index 00000000..1d0db7f9
Binary files /dev/null and b/consumer-issuing/public/assets/furever_logo.png differ
diff --git a/consumer-issuing/public/assets/issuing-credit-cards.png b/consumer-issuing/public/assets/issuing-credit-cards.png
new file mode 100644
index 00000000..2d449f5e
Binary files /dev/null and b/consumer-issuing/public/assets/issuing-credit-cards.png differ
diff --git a/consumer-issuing/public/assets/issuing-only-screenshot.jpeg b/consumer-issuing/public/assets/issuing-only-screenshot.jpeg
new file mode 100644
index 00000000..3f621fc1
Binary files /dev/null and b/consumer-issuing/public/assets/issuing-only-screenshot.jpeg differ
diff --git a/consumer-issuing/public/assets/issuing-treasury-sample-app-screenshot.png b/consumer-issuing/public/assets/issuing-treasury-sample-app-screenshot.png
new file mode 100644
index 00000000..b9105030
Binary files /dev/null and b/consumer-issuing/public/assets/issuing-treasury-sample-app-screenshot.png differ
diff --git a/consumer-issuing/public/assets/issuing-treasury-sample-app.png b/consumer-issuing/public/assets/issuing-treasury-sample-app.png
new file mode 100644
index 00000000..c9d3cb2e
Binary files /dev/null and b/consumer-issuing/public/assets/issuing-treasury-sample-app.png differ
diff --git a/consumer-issuing/public/assets/llama_party_hat_transparent_MG.png b/consumer-issuing/public/assets/llama_party_hat_transparent_MG.png
new file mode 100644
index 00000000..4abe1f6e
Binary files /dev/null and b/consumer-issuing/public/assets/llama_party_hat_transparent_MG.png differ
diff --git a/consumer-issuing/public/assets/llama_party_hat_transparent_MG.xcf b/consumer-issuing/public/assets/llama_party_hat_transparent_MG.xcf
new file mode 100644
index 00000000..72c76b9b
Binary files /dev/null and b/consumer-issuing/public/assets/llama_party_hat_transparent_MG.xcf differ
diff --git a/consumer-issuing/public/assets/logos/logo-mastercard.svg b/consumer-issuing/public/assets/logos/logo-mastercard.svg
new file mode 100644
index 00000000..65e9268a
--- /dev/null
+++ b/consumer-issuing/public/assets/logos/logo-mastercard.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/consumer-issuing/public/assets/logos/logo-visa.svg b/consumer-issuing/public/assets/logos/logo-visa.svg
new file mode 100644
index 00000000..e15dfd88
--- /dev/null
+++ b/consumer-issuing/public/assets/logos/logo-visa.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/consumer-issuing/public/assets/rocket-rides-logo.png b/consumer-issuing/public/assets/rocket-rides-logo.png
new file mode 100644
index 00000000..ded28ecb
Binary files /dev/null and b/consumer-issuing/public/assets/rocket-rides-logo.png differ
diff --git a/consumer-issuing/public/favicon-16x16.png b/consumer-issuing/public/favicon-16x16.png
new file mode 100644
index 00000000..a06f2236
Binary files /dev/null and b/consumer-issuing/public/favicon-16x16.png differ
diff --git a/consumer-issuing/public/favicon-32x32.png b/consumer-issuing/public/favicon-32x32.png
new file mode 100644
index 00000000..f2c73e50
Binary files /dev/null and b/consumer-issuing/public/favicon-32x32.png differ
diff --git a/consumer-issuing/public/favicon.ico b/consumer-issuing/public/favicon.ico
new file mode 100644
index 00000000..4ab4f23e
Binary files /dev/null and b/consumer-issuing/public/favicon.ico differ
diff --git a/consumer-issuing/public/manifest.json b/consumer-issuing/public/manifest.json
new file mode 100644
index 00000000..cacfcfbb
--- /dev/null
+++ b/consumer-issuing/public/manifest.json
@@ -0,0 +1,15 @@
+{
+ "short_name": "Devias Kit",
+ "name": "Devias Kit",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/consumer-issuing/src/components/chart.tsx b/consumer-issuing/src/components/chart.tsx
new file mode 100644
index 00000000..6cab3d56
--- /dev/null
+++ b/consumer-issuing/src/components/chart.tsx
@@ -0,0 +1,9 @@
+import { styled } from "@mui/material/styles";
+import dynamic from "next/dynamic";
+
+const ApexChart = dynamic(() => import("react-apexcharts"), {
+ ssr: false,
+ loading: () => null,
+});
+
+export const Chart = styled(ApexChart)``;
diff --git a/consumer-issuing/src/components/currency-icon.tsx b/consumer-issuing/src/components/currency-icon.tsx
new file mode 100644
index 00000000..d413f74a
--- /dev/null
+++ b/consumer-issuing/src/components/currency-icon.tsx
@@ -0,0 +1,24 @@
+import {
+ CurrencyDollarIcon,
+ CurrencyEuroIcon,
+ CurrencyPoundIcon,
+} from "@heroicons/react/24/solid";
+import { SvgIcon } from "@mui/material";
+
+import { Currency } from "src/utils/account-management-helpers";
+
+const currencyIconMap: Record = {
+ [Currency.USD]: ,
+ [Currency.GBP]: ,
+ [Currency.EUR]: ,
+};
+
+const CurrencyIcon = (props: { currency: Currency }) => {
+ const { currency } = props;
+
+ const icon = currencyIconMap[currency];
+
+ return {icon} ;
+};
+
+export default CurrencyIcon;
diff --git a/consumer-issuing/src/components/embedded-components-switcher.tsx b/consumer-issuing/src/components/embedded-components-switcher.tsx
new file mode 100644
index 00000000..f1790fdd
--- /dev/null
+++ b/consumer-issuing/src/components/embedded-components-switcher.tsx
@@ -0,0 +1,26 @@
+import { FormControlLabel, Link, Switch } from "@mui/material";
+
+export const EmbeddedComponentsSwitcher = ({
+ value,
+ onChange,
+}: {
+ value: boolean;
+ onChange: (event: React.ChangeEvent) => void;
+}) => {
+ return (
+ }
+ label={
+ <>
+ Use{" "}
+
+ embedded components
+
+ >
+ }
+ />
+ );
+};
diff --git a/consumer-issuing/src/components/floating-payment-panel.tsx b/consumer-issuing/src/components/floating-payment-panel.tsx
new file mode 100644
index 00000000..a31d410a
--- /dev/null
+++ b/consumer-issuing/src/components/floating-payment-panel.tsx
@@ -0,0 +1,62 @@
+import { XMarkIcon } from "@heroicons/react/24/solid";
+import {
+ Button,
+ DialogTitle,
+ Drawer,
+ IconButton,
+ SvgIcon,
+} from "@mui/material";
+import Box from "@mui/material/Box";
+import React, { ReactNode, useState } from "react";
+
+interface FloatingPaymentPanelProps {
+ title: string;
+ children: ReactNode;
+ buttonText: string;
+ buttonProps?: {
+ variant?: "text" | "outlined" | "contained";
+ color?: "primary" | "secondary" | "error" | "info" | "success" | "warning";
+ size?: "small" | "medium" | "large";
+ };
+}
+
+const FloatingPaymentPanel = ({
+ title,
+ children,
+ buttonText,
+ buttonProps = {},
+}: FloatingPaymentPanelProps) => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(true)}
+ >
+ {buttonText}
+
+ setOpen(false)}>
+
+ {title}
+ setOpen(false)}>
+
+
+
+
+
+
+ {children}
+
+
+ >
+ );
+};
+
+export default FloatingPaymentPanel;
\ No newline at end of file
diff --git a/consumer-issuing/src/components/floating-test-panel.tsx b/consumer-issuing/src/components/floating-test-panel.tsx
new file mode 100644
index 00000000..7cb518d7
--- /dev/null
+++ b/consumer-issuing/src/components/floating-test-panel.tsx
@@ -0,0 +1,72 @@
+import { PlusCircleIcon } from "@heroicons/react/20/solid";
+import { XMarkIcon } from "@heroicons/react/24/solid";
+import {
+ Button,
+ DialogTitle,
+ Drawer,
+ IconButton,
+ SvgIcon,
+} from "@mui/material";
+import Box from "@mui/material/Box";
+import useTheme from "@mui/system/useTheme";
+import React, { ReactNode } from "react";
+
+const FloatingTestPanel = ({
+ title,
+ children,
+}: {
+ title: string;
+ children: ReactNode;
+}) => {
+ const theme = useTheme();
+ const [open, setOpen] = React.useState(false);
+
+ return (
+
+
+
+
+ }
+ onClick={() => setOpen(true)}
+ >
+ Generate test data
+
+ setOpen(false)}>
+
+ {title}
+ setOpen(false)}>
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+export default FloatingTestPanel;
diff --git a/consumer-issuing/src/components/google-map.tsx b/consumer-issuing/src/components/google-map.tsx
new file mode 100644
index 00000000..a40405a5
--- /dev/null
+++ b/consumer-issuing/src/components/google-map.tsx
@@ -0,0 +1,36 @@
+import { Box } from "@mui/material";
+import { GoogleMap, Marker } from "@react-google-maps/api";
+
+const containerStyle = {
+ width: "100%",
+ height: "200px",
+};
+
+interface GoogleMapProps {
+ latitude: number;
+ longitude: number;
+}
+
+export const GoogleMapComponent = ({ latitude, longitude }: GoogleMapProps) => {
+ const center = {
+ lat: latitude,
+ lng: longitude,
+ };
+
+ return (
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/consumer-issuing/src/components/google-maps-provider.tsx b/consumer-issuing/src/components/google-maps-provider.tsx
new file mode 100644
index 00000000..82342fa2
--- /dev/null
+++ b/consumer-issuing/src/components/google-maps-provider.tsx
@@ -0,0 +1,14 @@
+import { LoadScript } from "@react-google-maps/api";
+import { ReactNode } from "react";
+
+interface GoogleMapsProviderProps {
+ children: ReactNode;
+}
+
+export const GoogleMapsProvider = ({ children }: GoogleMapsProviderProps) => {
+ return (
+
+ <>{children}>
+
+ );
+};
\ No newline at end of file
diff --git a/consumer-issuing/src/components/issuing/authorizations-table.tsx b/consumer-issuing/src/components/issuing/authorizations-table.tsx
new file mode 100644
index 00000000..4ae9b6fb
--- /dev/null
+++ b/consumer-issuing/src/components/issuing/authorizations-table.tsx
@@ -0,0 +1,195 @@
+import { ArrowRightIcon } from "@heroicons/react/20/solid";
+import {
+ Box,
+ Button,
+ Card,
+ Checkbox,
+ SvgIcon,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TablePagination,
+ TableRow,
+} from "@mui/material";
+import { useSession } from "next-auth/react";
+import React, { ChangeEvent } from "react";
+import Stripe from "stripe";
+
+import { SeverityPill } from "../severity-pill";
+
+import { Scrollbar } from "src/components/scrollbar";
+import { SeverityColor } from "src/types/severity-color";
+import {
+ capitalize,
+ formatCurrencyForCountry,
+ formatDateTime,
+} from "src/utils/format";
+
+const statusMap: Record = {
+ closed: "primary",
+ pending: "warning",
+ reversed: "error",
+};
+
+const AuthorizationsTable = ({
+ count = 0,
+ items = [],
+ onDeselectAll,
+ onDeselectOne,
+ onPageChange = () => ({}),
+ onRowsPerPageChange,
+ onSelectAll,
+ onSelectOne,
+ page = 0,
+ rowsPerPage = 0,
+ selected = [],
+}: {
+ count?: number;
+ items: Stripe.Issuing.Authorization[];
+ onDeselectAll: () => void;
+ onDeselectOne: (item: string) => void;
+ onPageChange: (
+ e: React.MouseEvent | null,
+ page: number,
+ ) => void;
+ onRowsPerPageChange: (e: ChangeEvent) => void;
+ onSelectAll: () => void;
+ onSelectOne: (item: string) => void;
+ page?: number;
+ rowsPerPage?: number;
+ selected?: string[];
+}) => {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ const selectedSome = selected.length > 0 && selected.length < items.length;
+ const selectedAll = items.length > 0 && selected.length === items.length;
+
+ return (
+
+
+
+
+
+
+
+ {
+ if (e.target.checked) {
+ onSelectAll?.();
+ } else {
+ onDeselectAll?.();
+ }
+ }}
+ />
+
+ Date
+ Amount
+ Name on card
+ Card last 4
+ {/* Approved? */}
+ Status
+ Merchant
+ Merchant category
+
+
+
+
+ {items.map((authorization) => {
+ const isSelected = selected.includes(authorization.id);
+ const category = authorization.merchant_data.category.replace(
+ /_/g,
+ " ",
+ );
+ return (
+
+
+ {
+ if (e.target.checked) {
+ onSelectOne?.(authorization.id);
+ } else {
+ onDeselectOne?.(authorization.id);
+ }
+ }}
+ />
+
+
+ {formatDateTime(authorization.created)}
+
+
+ {formatCurrencyForCountry(authorization.amount, country)}
+
+
+ {authorization.card.cardholder.name}
+
+ β’β’β’β’ {authorization.card.last4}
+
+
+ {authorization.approved ? "Approved" : "Declined"}
+
+
+
+
+ {capitalize(authorization.status)}
+
+
+
+ {authorization.merchant_data.name}
+
+
+ {category}
+
+
+
+
+
+ }
+ href={`/authorizations/${authorization.id}`}
+ >
+ Details
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ );
+};
+
+export default AuthorizationsTable;
diff --git a/consumer-issuing/src/components/logo.tsx b/consumer-issuing/src/components/logo.tsx
new file mode 100644
index 00000000..8d074738
--- /dev/null
+++ b/consumer-issuing/src/components/logo.tsx
@@ -0,0 +1,15 @@
+import Image from "next/image";
+
+export const Logo = () => {
+ return (
+
+ );
+};
diff --git a/consumer-issuing/src/components/scrollbar.tsx b/consumer-issuing/src/components/scrollbar.tsx
new file mode 100644
index 00000000..4414587d
--- /dev/null
+++ b/consumer-issuing/src/components/scrollbar.tsx
@@ -0,0 +1,4 @@
+import { styled } from "@mui/material/styles";
+import SimpleBar from "simplebar-react";
+
+export const Scrollbar = styled(SimpleBar)``;
diff --git a/consumer-issuing/src/components/severity-pill.tsx b/consumer-issuing/src/components/severity-pill.tsx
new file mode 100644
index 00000000..cde2655a
--- /dev/null
+++ b/consumer-issuing/src/components/severity-pill.tsx
@@ -0,0 +1,49 @@
+import { useTheme } from "@mui/material/styles";
+import { Box } from "@mui/system";
+
+import { SeverityColor } from "src/types/severity-color";
+
+export const SeverityPill = ({
+ color = "primary",
+ children,
+ ...other
+}: {
+ color?: SeverityColor;
+ children: React.ReactNode;
+}) => {
+ const theme = useTheme();
+
+ const colorValue =
+ theme.palette.mode === "dark"
+ ? theme.palette[color].main
+ : theme.palette[color].dark;
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/consumer-issuing/src/components/transaction-details-panel.tsx b/consumer-issuing/src/components/transaction-details-panel.tsx
new file mode 100644
index 00000000..07dd1706
--- /dev/null
+++ b/consumer-issuing/src/components/transaction-details-panel.tsx
@@ -0,0 +1,396 @@
+import { XMarkIcon } from "@heroicons/react/24/solid";
+import {
+ Box,
+ Button,
+ DialogTitle,
+ Drawer,
+ Grid,
+ IconButton,
+ Stack,
+ SvgIcon,
+ Typography,
+} from "@mui/material";
+import React, { ReactNode, useState } from "react";
+import Stripe from "stripe";
+import { useSession } from "next-auth/react";
+
+import { SeverityPill } from "src/components/severity-pill";
+import { SeverityColor } from "src/types/severity-color";
+import { SupportedCountry } from "src/utils/account-management-helpers";
+import {
+ capitalize,
+ formatCurrencyForCountry,
+ formatDateAndTime,
+ titleize,
+} from "src/utils/format";
+import { GoogleMapComponent } from "./google-map";
+
+interface TransactionDetailsPanelProps {
+ transaction: {
+ id: string;
+ amount: number;
+ created: number;
+ source: {
+ type: string;
+ id: string;
+ issuing_transaction?: string;
+ issuing_credit_repayment?: string;
+ issuing_credit_ledger_adjustment?: string;
+ };
+ auth?: Stripe.Issuing.Authorization & {
+ merchant_data?: {
+ name?: string;
+ category: string;
+ address?: {
+ line1?: string;
+ line2?: string;
+ city?: string;
+ state?: string;
+ postal_code?: string;
+ country?: string;
+ };
+ };
+ enriched_merchant_data?: {
+ merchant?: {
+ name?: string;
+ url?: string;
+ phone?: string;
+ location?: {
+ coordinates?: {
+ latitude: number;
+ longitude: number;
+ };
+ address?: {
+ line1: string;
+ line2: string;
+ city: string;
+ state: string;
+ postal_code: string;
+ country: string;
+ };
+ };
+ };
+ };
+ };
+ creditRepayment?: {
+ id: string;
+ status: string;
+ credit_statement_descriptor?: string;
+ instructed_by?: {
+ type: string;
+ };
+ };
+ creditLedgerAdjustment?: {
+ reason?: string;
+ reason_description?: string;
+ };
+ };
+ country: SupportedCountry;
+ buttonText: string;
+ buttonProps?: {
+ variant?: "text" | "outlined" | "contained";
+ color?: "primary" | "secondary" | "error" | "info" | "success" | "warning";
+ size?: "small" | "medium" | "large";
+ };
+}
+
+const statusMap: Record = {
+ closed: "primary",
+ pending: "warning",
+ reversed: "error",
+};
+
+const creditRepaymentStatusMap: Record = {
+ succeeded: "success",
+ pending: "warning",
+ failed: "error",
+};
+
+// Helper function to ensure URL has https:// prefix
+const ensureHttps = (url: string): string => {
+ if (!url) return url;
+ if (url.startsWith('http://') || url.startsWith('https://')) {
+ return url;
+ }
+ return `https://${url}`;
+};
+
+const TransactionDetailsPanel = ({
+ transaction,
+ country,
+ buttonText,
+ buttonProps = {},
+}: TransactionDetailsPanelProps) => {
+ const [open, setOpen] = useState(false);
+
+ return (
+ <>
+ setOpen(true)}>
+ {buttonText}
+
+ setOpen(false)}>
+
+ Transaction details
+ setOpen(false)}>
+
+
+
+
+
+
+
+
+ ID
+
+
+ {transaction.id}
+
+
+
+
+ Type
+
+
+ {titleize(transaction.source.type.replace(/_/g, " "))}
+
+
+
+ {transaction.source.issuing_transaction && (
+
+ Issuing Transaction ID
+
+
+ {transaction.source.issuing_transaction}
+
+
+
+ )}
+ {transaction.source.type === "issuing_credit_repayment" && (
+ <>
+
+ Credit Repayment ID
+
+
+ {transaction.source.issuing_credit_repayment}
+
+
+
+
+ Payment Type
+
+ {transaction.creditRepayment?.credit_statement_descriptor ? (
+
+
+ {transaction.creditRepayment.credit_statement_descriptor === "API Payment received" ? "On-Stripe" : "Off-Stripe"}
+
+
+ ({transaction.creditRepayment.credit_statement_descriptor === "API Payment received"
+ ? "API-instructed"
+ : "User-instructed"
+ })
+
+
+ ) : (
+
+ Unknown
+
+ )}
+
+
+
+ Status
+
+ {transaction.creditRepayment ? (
+
+ {capitalize(transaction.creditRepayment.status)}
+
+ ) : (
+
+ Unknown
+
+ )}
+
+
+ >
+ )}
+ {transaction.source.type === "issuing_credit_ledger_adjustment" && (
+ <>
+
+ Reason
+
+
+ {titleize(transaction.creditLedgerAdjustment?.reason?.replace(/_/g, " ") || "Unknown")}
+
+
+
+ {transaction.creditLedgerAdjustment?.reason_description && (
+
+ Description
+
+
+ {transaction.creditLedgerAdjustment.reason_description}
+
+
+
+ )}
+ >
+ )}
+ {transaction.auth && (
+
+ Issuing Authorization ID
+
+
+ {transaction.auth.id}
+
+
+
+ )}
+
+ Date / Time
+
+
+ {formatDateAndTime(transaction.created)}
+
+
+
+
+ Amount
+
+
+ {formatCurrencyForCountry(-transaction.amount, country)}
+
+
+
+ {transaction.auth && (
+ <>
+
+ Approval Status
+
+
+ {transaction.auth.approved ? "Approved" : "Declined"}
+
+
+
+
+ Authorization Lifecycle Status
+
+
+ {capitalize(transaction.auth.status)}
+
+
+
+
+ Merchant
+
+
+ {transaction.auth?.enriched_merchant_data?.merchant?.url ? (
+
+ {transaction.auth?.enriched_merchant_data?.merchant?.name || transaction.auth?.merchant_data?.name || "Unknown merchant"}
+
+ ) : (
+ transaction.auth?.enriched_merchant_data?.merchant?.name || transaction.auth?.merchant_data?.name || "Unknown merchant"
+ )}
+ {transaction.auth?.enriched_merchant_data?.merchant?.phone && (
+
+ ({transaction.auth.enriched_merchant_data.merchant.phone})
+
+ )}
+
+
+
+
+ Location
+
+
+ {transaction.auth?.enriched_merchant_data?.merchant?.location?.address ? (
+ [
+ transaction.auth.enriched_merchant_data.merchant.location.address.line1,
+ transaction.auth.enriched_merchant_data.merchant.location.address.line2,
+ transaction.auth.enriched_merchant_data.merchant.location.address.city,
+ transaction.auth.enriched_merchant_data.merchant.location.address.state,
+ transaction.auth.enriched_merchant_data.merchant.location.address.postal_code,
+ transaction.auth.enriched_merchant_data.merchant.location.address.country,
+ ].filter(Boolean).join(", ")
+ ) : (
+ [
+ transaction.auth?.merchant_data?.city,
+ transaction.auth?.merchant_data?.state,
+ transaction.auth?.merchant_data?.postal_code,
+ transaction.auth?.merchant_data?.country,
+ ].filter(Boolean).join(", ") || "Unknown location"
+ )}
+
+ {transaction.auth?.enriched_merchant_data?.merchant?.location?.coordinates?.latitude &&
+ transaction.auth?.enriched_merchant_data?.merchant?.location?.coordinates?.longitude && (
+
+ {`${transaction.auth.enriched_merchant_data.merchant.location.coordinates.latitude.toFixed(6)}, ${transaction.auth.enriched_merchant_data.merchant.location.coordinates.longitude.toFixed(6)}`}
+
+ )}
+
+
+ {transaction.auth?.enriched_merchant_data?.merchant?.location?.coordinates?.latitude &&
+ transaction.auth?.enriched_merchant_data?.merchant?.location?.coordinates?.longitude && (
+
+
+
+
+
+ )}
+
+ Merchant category
+
+
+ {titleize(
+ transaction.auth.merchant_data.category.replace(/_/g, " "),
+ )}
+
+
+
+ {!transaction.auth.approved && transaction.auth.request_history.at(-1)?.reason && (
+
+ Decline reason
+
+
+ {titleize(
+ transaction.auth.request_history.at(-1)?.reason?.replace(/_/g, " ") ?? "",
+ )}
+
+
+
+ )}
+ >
+ )}
+
+
+
+ >
+ );
+};
+
+export default TransactionDetailsPanel;
diff --git a/consumer-issuing/src/db.ts b/consumer-issuing/src/db.ts
new file mode 100644
index 00000000..ef41cd76
--- /dev/null
+++ b/consumer-issuing/src/db.ts
@@ -0,0 +1,9 @@
+import { PrismaClient } from "@prisma/client";
+
+const globalForPrisma = globalThis as unknown as {
+ prisma: PrismaClient | undefined;
+};
+
+export const prisma = globalForPrisma.prisma ?? new PrismaClient();
+
+if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
diff --git a/consumer-issuing/src/hooks/use-nprogress.ts b/consumer-issuing/src/hooks/use-nprogress.ts
new file mode 100644
index 00000000..e13bb7ee
--- /dev/null
+++ b/consumer-issuing/src/hooks/use-nprogress.ts
@@ -0,0 +1,17 @@
+import Router from "next/router";
+import nProgress from "nprogress";
+import { useEffect } from "react";
+
+export function useNProgress() {
+ useEffect(() => {
+ Router.events.on("routeChangeStart", nProgress.start);
+ Router.events.on("routeChangeError", nProgress.done);
+ Router.events.on("routeChangeComplete", nProgress.done);
+
+ return () => {
+ Router.events.off("routeChangeStart", nProgress.start);
+ Router.events.off("routeChangeError", nProgress.done);
+ Router.events.off("routeChangeComplete", nProgress.done);
+ };
+ }, []);
+}
diff --git a/consumer-issuing/src/hooks/use-popover.ts b/consumer-issuing/src/hooks/use-popover.ts
new file mode 100644
index 00000000..abd62c2d
--- /dev/null
+++ b/consumer-issuing/src/hooks/use-popover.ts
@@ -0,0 +1,26 @@
+import { useCallback, useRef, useState } from "react";
+
+export function usePopover() {
+ const anchorRef = useRef(null);
+ const [open, setOpen] = useState(false);
+
+ const handleOpen = useCallback(() => {
+ setOpen(true);
+ }, []);
+
+ const handleClose = useCallback(() => {
+ setOpen(false);
+ }, []);
+
+ const handleToggle = useCallback(() => {
+ setOpen((prevState) => !prevState);
+ }, []);
+
+ return {
+ anchorRef,
+ handleClose,
+ handleOpen,
+ handleToggle,
+ open,
+ };
+}
diff --git a/consumer-issuing/src/hooks/use-selection.ts b/consumer-issuing/src/hooks/use-selection.ts
new file mode 100644
index 00000000..794f403b
--- /dev/null
+++ b/consumer-issuing/src/hooks/use-selection.ts
@@ -0,0 +1,35 @@
+import { useCallback, useEffect, useState } from "react";
+
+export const useSelection = (items: T[] = []) => {
+ const [selected, setSelected] = useState([]);
+
+ useEffect(() => {
+ setSelected([]);
+ }, [items]);
+
+ const handleSelectAll = useCallback(() => {
+ setSelected([...items]);
+ }, [items]);
+
+ const handleSelectOne = useCallback((item: T) => {
+ setSelected((prevState) => [...prevState, item]);
+ }, []);
+
+ const handleDeselectAll = useCallback(() => {
+ setSelected([]);
+ }, []);
+
+ const handleDeselectOne = useCallback((item: T) => {
+ setSelected((prevState) => {
+ return prevState.filter((_item) => _item !== item);
+ });
+ }, []);
+
+ return {
+ handleDeselectAll,
+ handleDeselectOne,
+ handleSelectAll,
+ handleSelectOne,
+ selected,
+ };
+};
diff --git a/consumer-issuing/src/layouts/auth/layout.tsx b/consumer-issuing/src/layouts/auth/layout.tsx
new file mode 100644
index 00000000..d2dd0cff
--- /dev/null
+++ b/consumer-issuing/src/layouts/auth/layout.tsx
@@ -0,0 +1,200 @@
+import { XMarkIcon } from "@heroicons/react/24/solid";
+import {
+ Box,
+ Card,
+ CardContent,
+ Fade,
+ IconButton,
+ Link,
+ Stack,
+ SvgIcon,
+ Typography,
+ useTheme,
+} from "@mui/material";
+import NextLink from "next/link";
+import { ReactNode, useState } from "react";
+
+import { Logo } from "src/components/logo";
+import { isDemoMode } from "src/utils/demo-helpers";
+
+const Layout = ({ children }: { children: ReactNode }) => {
+ const theme = useTheme();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+ );
+};
+
+const TopLogoBar = () => (
+
+
+
+
+
+);
+
+const WelcomeMessage = () => {
+ return (
+
+
+ Llama Llama Credit
+
+
+ Using Stripe's banking-as-a-service APIs, businesses have the
+ flexibility to add financial capabilities to their product. As shown in
+ these{" "}
+
+ user stories
+
+ , our APIs serve business models ranging from consumer issuing, expense management,
+ corporate benefits, B2B payments, BNPL, and more. This demo explores one
+ platform focused use case that enables businesses to offer revolving credit cards to their consumers modelled as Connected Accounts.
+
+
+ View our{" "}
+
+ docs
+ {" "}
+ and{" "}
+
+ source code
+ {" "}
+ on GitHub.
+
+
+
+ Stripe Privacy Policy & Terms apply
+
+
+
+ );
+};
+
+const CookieBanner = () => {
+ const [acknowledgedCookieNotice, setAcknowledgedCookieNotice] = useState(
+ localStorage.getItem("acknowledgedCookieNotice") === "true" || false,
+ );
+
+ const handleAcknowledgingCookieNotice = () => {
+ setAcknowledgedCookieNotice(true);
+ localStorage.setItem("acknowledgedCookieNotice", "true");
+ };
+
+ return (
+ <>
+ {!acknowledgedCookieNotice && (
+
+
+
+
+
+ This site uses necessary cookies to enable required functions
+ and features, such as login and account management services.
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default Layout;
diff --git a/consumer-issuing/src/layouts/dashboard/account-popover.tsx b/consumer-issuing/src/layouts/dashboard/account-popover.tsx
new file mode 100644
index 00000000..78fae11f
--- /dev/null
+++ b/consumer-issuing/src/layouts/dashboard/account-popover.tsx
@@ -0,0 +1,108 @@
+import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
+import {
+ Box,
+ Divider,
+ MenuItem,
+ MenuList,
+ Link,
+ Popover,
+ SvgIcon,
+ Typography,
+} from "@mui/material";
+import { useRouter } from "next/router";
+import { signOut, useSession } from "next-auth/react";
+
+import { isDemoMode } from "src/utils/demo-helpers";
+
+export const AccountPopover = ({
+ anchorEl,
+ onClose,
+ open,
+}: {
+ anchorEl: Element | null;
+ onClose: () => void;
+ open: boolean;
+}) => {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { stripeAccount, email, businessName, country } = session;
+ const { accountId } = stripeAccount;
+ const router = useRouter();
+
+ const handleLogout = async () => {
+ onClose?.();
+ await signOut();
+ // await signOut({callbackUrl: '/auth/login'});
+ // window.location.href = '/api/auth/signout';
+ };
+
+ return (
+
+
+ Name
+
+ {businessName}
+
+ Country
+
+ {country}
+
+
+
+
+ Account
+
+ {email}
+
+ {(!isDemoMode() || router.query.debug) && (
+
+
+ {accountId}{" "}
+
+
+
+
+
+ )}
+
+
+ *": {
+ borderRadius: 1,
+ },
+ }}
+ >
+ Log out
+
+
+ );
+};
diff --git a/consumer-issuing/src/layouts/dashboard/config.tsx b/consumer-issuing/src/layouts/dashboard/config.tsx
new file mode 100644
index 00000000..c1d1adb1
--- /dev/null
+++ b/consumer-issuing/src/layouts/dashboard/config.tsx
@@ -0,0 +1,77 @@
+import {
+ CreditCardIcon,
+ BanknotesIcon,
+ ChartBarIcon,
+ UsersIcon,
+ WrenchScrewdriverIcon,
+ ShoppingBagIcon,
+ CogIcon,
+ CommandLineIcon,
+} from "@heroicons/react/24/solid";
+import { SvgIcon } from "@mui/material";
+
+export const items = [
+ {
+ title: "Overview",
+ path: "/",
+ icon: (
+
+
+
+ ),
+ },
+ {
+ title: "Cardholders",
+ path: "/cardholders",
+ icon: (
+
+
+
+ ),
+ },
+ {
+ title: "Cards",
+ path: "/cards",
+ icon: (
+
+
+
+ ),
+ },
+ {
+ title: "Card transactions",
+ path: "/authorizations",
+ icon: (
+
+
+
+ ),
+ },
+ // {
+ // title: "API Requests",
+ // path: "/api-requests",
+ // icon: (
+ //
+ //
+ //
+ // ),
+ // },
+ // {
+ // title: "Test data",
+ // path: "/test-data",
+ // icon: (
+ //
+ //
+ //
+ // ),
+ // },
+ {
+ title: "Settings",
+ path: "/settings",
+ icon: (
+
+
+
+ ),
+ },
+];
diff --git a/consumer-issuing/src/layouts/dashboard/layout.tsx b/consumer-issuing/src/layouts/dashboard/layout.tsx
new file mode 100644
index 00000000..70df2bf9
--- /dev/null
+++ b/consumer-issuing/src/layouts/dashboard/layout.tsx
@@ -0,0 +1,66 @@
+import { Box, Link, Typography } from "@mui/material";
+import { styled } from "@mui/material/styles";
+import Head from "next/head";
+import { usePathname } from "next/navigation";
+import { ReactNode, useCallback, useEffect, useState } from "react";
+import { useSession } from "next-auth/react";
+import { useRouter } from "next/router";
+
+import { TopNav } from "src/layouts/dashboard/top-nav";
+import { GoogleMapsProvider } from "src/components/google-maps-provider";
+
+const LayoutRoot = styled("div")(({ theme }) => ({
+ display: "flex",
+ flex: "1 1 auto",
+ maxWidth: "100%",
+}));
+
+const LayoutContainer = styled("div")({
+ display: "flex",
+ flex: "1 1 auto",
+ flexDirection: "column",
+ width: "100%",
+});
+
+const Layout = ({ children, hideTopNav = false }: { children: ReactNode, hideTopNav?: boolean }) => {
+ const pathname = usePathname();
+ const [openNav, setOpenNav] = useState(false);
+ const { data: session } = useSession();
+ const router = useRouter();
+
+ const handlePathnameChange = useCallback(() => {
+ if (openNav) {
+ setOpenNav(false);
+ }
+ }, [openNav]);
+
+ useEffect(
+ () => {
+ handlePathnameChange();
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [pathname],
+ );
+
+ if (!session) {
+ router.push("/auth/login");
+ return null;
+ }
+
+ return (
+ <>
+
+ Stripe BaaS platform demo
+
+
+
+ { !hideTopNav && setOpenNav(true)} /> }
+
+ {children}
+
+
+ >
+ );
+};
+
+export default Layout;
diff --git a/consumer-issuing/src/layouts/dashboard/side-nav-item.tsx b/consumer-issuing/src/layouts/dashboard/side-nav-item.tsx
new file mode 100644
index 00000000..7be9f1a0
--- /dev/null
+++ b/consumer-issuing/src/layouts/dashboard/side-nav-item.tsx
@@ -0,0 +1,90 @@
+import { Box, ButtonBase, Theme } from "@mui/material";
+import NextLink from "next/link";
+import { ReactNode } from "react";
+
+export const SideNavItem = ({
+ active = false,
+ external,
+ icon,
+ path,
+ title,
+}: {
+ active: boolean;
+ external?: boolean;
+ icon: ReactNode;
+ path: string;
+ title: string;
+}) => {
+ const linkProps = path
+ ? external
+ ? {
+ component: "a",
+ href: path,
+ target: "_blank",
+ }
+ : {
+ component: NextLink,
+ href: path,
+ }
+ : {};
+
+ return (
+
+
+ {icon && (
+
+ {icon}
+
+ )}
+ theme.typography.fontFamily,
+ fontSize: 14,
+ fontWeight: 600,
+ lineHeight: "24px",
+ whiteSpace: "nowrap",
+ ...(active && {
+ color: "common.white",
+ }),
+ }}
+ >
+ {title}
+
+
+
+ );
+};
diff --git a/consumer-issuing/src/layouts/dashboard/side-nav.tsx b/consumer-issuing/src/layouts/dashboard/side-nav.tsx
new file mode 100644
index 00000000..79f5734f
--- /dev/null
+++ b/consumer-issuing/src/layouts/dashboard/side-nav.tsx
@@ -0,0 +1,167 @@
+import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
+import {
+ Box,
+ Button,
+ Divider,
+ Drawer,
+ Stack,
+ SvgIcon,
+ Theme,
+ Typography,
+ useMediaQuery,
+} from "@mui/material";
+import NextLink from "next/link";
+import { usePathname } from "next/navigation";
+import { useSession } from "next-auth/react";
+
+import { Logo } from "src/components/logo";
+import { Scrollbar } from "src/components/scrollbar";
+import { items } from "src/layouts/dashboard/config";
+import { SideNavItem } from "src/layouts/dashboard/side-nav-item";
+
+export const SideNav = (props: { onClose: () => void; open: boolean }) => {
+ const { open, onClose } = props;
+ const pathname = usePathname();
+ const lgUp = useMediaQuery((theme: Theme) => theme.breakpoints.up("lg"));
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+
+ const content = (
+
+
+
+
+
+
+
+
+ {session.businessName}
+
+
+
+
+
+
+ {items.map((item) => {
+ const active = item.path ? pathname === item.path : false;
+
+ return (
+
+ );
+ })}
+
+
+
+ {/* FOR-DEMO-ONLY: You can remove this for an actual application */}
+
+
+ Llama Llama Credit
+
+
+ Data, financial activity and cards are fictitious and for testing
+ purposes only. You should not input personal information.
+
+
+
+
+ }
+ fullWidth
+ href="https://github.com/stripe-samples/issuing-treasury/tree/main/expense-management"
+ sx={{ mt: 2 }}
+ target="_blank"
+ variant="contained"
+ color="secondary"
+ >
+ View on GitHub
+
+
+
+
+ );
+
+ if (lgUp) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ return (
+ theme.zIndex.appBar + 100 }}
+ variant="temporary"
+ >
+ {content}
+
+ );
+};
diff --git a/consumer-issuing/src/layouts/dashboard/top-nav.tsx b/consumer-issuing/src/layouts/dashboard/top-nav.tsx
new file mode 100644
index 00000000..627772d7
--- /dev/null
+++ b/consumer-issuing/src/layouts/dashboard/top-nav.tsx
@@ -0,0 +1,135 @@
+import Bars3Icon from "@heroicons/react/24/solid/Bars3Icon";
+import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
+import {
+ Avatar,
+ Box,
+ Button,
+ IconButton,
+ Stack,
+ SvgIcon,
+ Theme,
+ Typography,
+ useMediaQuery,
+} from "@mui/material";
+import { useSession } from "next-auth/react";
+import NextLink from "next/link";
+import { usePathname } from "next/navigation";
+
+import { usePopover } from "src/hooks/use-popover";
+import { AccountPopover } from "src/layouts/dashboard/account-popover";
+import { Logo } from "src/components/logo";
+import { items } from "src/layouts/dashboard/config";
+import { isDemoMode } from "src/utils/demo-helpers";
+
+const SIDE_NAV_WIDTH = 280;
+const TOP_NAV_HEIGHT = 64;
+
+export const TopNav = ({ onNavOpen }: { onNavOpen: () => void }) => {
+ const lgUp = useMediaQuery((theme: Theme) => theme.breakpoints.up("lg"));
+ const accountPopover = usePopover();
+ const { data: session } = useSession();
+ const pathname = usePathname();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+
+ return (
+ <>
+ theme.zIndex.appBar,
+ }}
+ >
+
+
+
+ {!lgUp && (
+
+
+
+
+
+ )}
+
+
+
+
+ Welcome, {session.businessName} to Furever!
+
+
+
+
+
+
+ }
+ href="https://github.com/stripe-samples/issuing-treasury/tree/main/consumer-issuing"
+ target="_blank"
+ variant="contained"
+ color="secondary"
+ sx={{ color: 'white' }}
+ >
+ View on GitHub
+
+
+
+
+
+
+ {items.map((item) => {
+ const active = item.path ? pathname === item.path : false;
+ return (
+
+ {item.title}
+
+ );
+ })}
+
+
+
+
+
+ >
+ );
+};
diff --git a/consumer-issuing/src/middleware.ts b/consumer-issuing/src/middleware.ts
new file mode 100644
index 00000000..a69c53ca
--- /dev/null
+++ b/consumer-issuing/src/middleware.ts
@@ -0,0 +1,110 @@
+import { NextResponse } from "next/server";
+import { withAuth } from "next-auth/middleware";
+
+import { getPlatformStripeAccountForCountry } from "./utils/account-management-helpers";
+import { hasOutstandingRequirements } from "./utils/onboarding-helpers";
+
+export default withAuth(
+ // This will only be called once the user is authorized
+ async function middleware(req) {
+ const token = req.nextauth.token;
+
+ if (token == null) {
+ throw new Error("JWT is missing in the request");
+ }
+
+ if (token.accountId == undefined) {
+ throw new Error(
+ "Pre-onboarding auth check: Stripe accountId is missing in the token",
+ );
+ }
+
+ if (token.email == undefined) {
+ throw new Error(
+ "Pre-onboarding auth check: email field is missing in the token",
+ );
+ }
+
+ if (token.requiresOnboarding == undefined) {
+ throw new Error(
+ "Pre-onboarding auth check: requiresOnboarding field is missing in the token",
+ );
+ }
+ const accessingOnboarding =
+ req.nextUrl.pathname === "/onboard" ||
+ req.nextUrl.pathname === "/api/onboard";
+ const stripeAccount = {
+ accountId: token.accountId,
+ platform: getPlatformStripeAccountForCountry(token.country),
+ };
+ const requiresOnboarding =
+ token.requiresOnboarding &&
+ (await hasOutstandingRequirements(stripeAccount));
+ // If the user needs to onboard but they are trying to access other pages, redirect them to the onboarding page
+ if (requiresOnboarding && !accessingOnboarding) {
+ return NextResponse.redirect(new URL("/onboard", req.url));
+ }
+ // If the user doesn't need to onboard but they are trying to access the onboarding page, redirect them to the home page
+ else if (!requiresOnboarding && accessingOnboarding) {
+ return NextResponse.redirect(new URL("/", req.url));
+ }
+
+ const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
+ const cspHeader = `
+ default-src 'self' https://maps.googleapis.com;
+ script-src 'self' 'unsafe-inline' 'unsafe-eval' https://connect-js.stripe.com/v1.0/connect.js https://js.stripe.com https://maps.googleapis.com;
+ style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
+ img-src 'self' blob: data: https://maps.gstatic.com https://maps.googleapis.com;
+ font-src 'self' https://fonts.gstatic.com;
+ object-src 'none';
+ frame-src https://connect-js.stripe.com https://js.stripe.com https://maps.googleapis.com;
+ base-uri 'self';
+ form-action 'self';
+ frame-ancestors 'none';
+ upgrade-insecure-requests;
+ `;
+ // Replace newline characters and spaces
+ const contentSecurityPolicyHeaderValue = cspHeader
+ .replace(/\s{2,}/g, " ")
+ .trim();
+
+ const requestHeaders = new Headers(req.headers);
+ requestHeaders.set("x-nonce", nonce);
+
+ requestHeaders.set(
+ "Content-Security-Policy",
+ contentSecurityPolicyHeaderValue,
+ );
+
+ const response = NextResponse.next({
+ request: {
+ headers: requestHeaders,
+ },
+ });
+ response.headers.set(
+ "Content-Security-Policy",
+ contentSecurityPolicyHeaderValue,
+ );
+
+ return response;
+ },
+ {
+ pages: {
+ signIn: "/auth/login",
+ },
+ },
+);
+
+export const config = {
+ matcher: [
+ /*
+ * Match all request paths except for the ones starting with:
+ * - _next/static (static files)
+ * - _next/image (image optimization files)
+ * - favicon.ico (favicon file)
+ * - assets (images, fonts, etc.)
+ * - (auth|api)/register (registration page)
+ */
+ "/((?!_next/static|_next/image|favicon.ico|assets|auth/register|api/register).*)",
+ ],
+};
diff --git a/consumer-issuing/src/pages/_app.tsx b/consumer-issuing/src/pages/_app.tsx
new file mode 100644
index 00000000..fb6e896e
--- /dev/null
+++ b/consumer-issuing/src/pages/_app.tsx
@@ -0,0 +1,91 @@
+import { CacheProvider, EmotionCache } from "@emotion/react";
+import { CssBaseline } from "@mui/material";
+import { ThemeProvider } from "@mui/material/styles";
+import { NextComponentType, NextPageContext } from "next";
+import { AppProps } from "next/app";
+import Head from "next/head";
+import { SessionProvider, useSession } from "next-auth/react";
+import React, { ReactElement } from "react";
+
+import { useNProgress } from "src/hooks/use-nprogress";
+import { createTheme } from "src/theme";
+import createEmotionCache from "src/utils/create-emotion-cache";
+import "simplebar-react/dist/simplebar.min.css";
+
+// Client-side cache, shared for the whole session of the user in the browser.
+const clientSideEmotionCache = createEmotionCache();
+
+const SplashScreen = () => null;
+
+interface SampleAppProps extends AppProps {
+ Component: NextComponentType & {
+ getLayout?: (page: ReactElement) => ReactElement;
+ };
+ emotionCache?: EmotionCache;
+}
+
+export default function App({
+ Component,
+ emotionCache = clientSideEmotionCache,
+ pageProps: { session, ...pageProps },
+}: SampleAppProps) {
+ useNProgress();
+
+ const getLayout = Component.getLayout ?? ((page) => page);
+
+ const theme = createTheme();
+
+ return (
+
+
+ Stripe Consumer Issuing demo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {getLayout( )}
+
+
+
+ );
+}
+
+const AppContent = ({ children }: { children: ReactElement }) => {
+ const { status } = useSession();
+
+ return status === "loading" ? : children;
+};
diff --git a/consumer-issuing/src/pages/_document.tsx b/consumer-issuing/src/pages/_document.tsx
new file mode 100644
index 00000000..aba1dd9b
--- /dev/null
+++ b/consumer-issuing/src/pages/_document.tsx
@@ -0,0 +1,82 @@
+import { EmotionCache } from "@emotion/react";
+import createEmotionServer from "@emotion/server/create-instance";
+import { AppType } from "next/app";
+import Document, {
+ Head,
+ Html,
+ Main,
+ NextScript,
+ DocumentContext,
+} from "next/document";
+import { Children } from "react";
+
+import createEmotionCache from "src/utils/create-emotion-cache";
+
+const Favicon = () => (
+ <>
+
+
+
+
+ >
+);
+
+class CustomDocument extends Document {
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+CustomDocument.getInitialProps = async (ctx: DocumentContext) => {
+ const originalRenderPage = ctx.renderPage;
+ const cache = createEmotionCache();
+ const { extractCriticalToChunks } = createEmotionServer(cache);
+
+ ctx.renderPage = () =>
+ originalRenderPage({
+ enhanceApp:
+ (App: AppType | React.ComponentType<{ emotionCache: EmotionCache }>) =>
+ (props) => ,
+ });
+
+ const initialProps = await Document.getInitialProps(ctx);
+ const emotionStyles = extractCriticalToChunks(initialProps.html);
+ const emotionStyleTags = emotionStyles.styles.map((style) => (
+
+ ));
+
+ return {
+ ...initialProps,
+ styles: [...Children.toArray(initialProps.styles), ...emotionStyleTags],
+ };
+};
+
+export default CustomDocument;
diff --git a/consumer-issuing/src/pages/api-requests.tsx b/consumer-issuing/src/pages/api-requests.tsx
new file mode 100644
index 00000000..803ebd7a
--- /dev/null
+++ b/consumer-issuing/src/pages/api-requests.tsx
@@ -0,0 +1,90 @@
+import React, { ReactNode, useState, useRef } from 'react';
+import { Box, Container, Typography, Button } from "@mui/material";
+import { OverviewApiRequestLogs } from "src/sections/overview/overview-api-request-logs";
+import DashboardLayout from "src/layouts/dashboard/layout";
+
+const ApiRequests = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [isFetchingStatement, setIsFetchingStatement] = useState(false);
+ const logsRef = useRef<{ refresh: () => Promise }>(null);
+
+ const handleFetchCreditData = async () => {
+ setIsLoading(true);
+ try {
+ const response = await fetch("/api/fetch_credit_data", {
+ method: "POST",
+ });
+ const data = await response.json();
+ if (data.success) {
+ // Refresh the logs to show the new entries
+ await logsRef.current?.refresh();
+ } else {
+ console.error("Failed to fetch credit data:", data.error);
+ }
+ } catch (error) {
+ console.error("Error fetching credit data:", error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const handleFetchCurrentStatement = async () => {
+ setIsFetchingStatement(true);
+ try {
+ const response = await fetch("/api/get_current_statement");
+ const data = await response.json();
+ if (response.ok) {
+ await logsRef.current?.refresh();
+ } else {
+ console.error("Failed to fetch current statement:", data.message);
+ }
+ } catch (error) {
+ console.error("Error fetching current statement:", error);
+ } finally {
+ setIsFetchingStatement(false);
+ }
+ };
+
+ return (
+
+
+
+
+ API Requests
+
+
+
+ {isLoading ? "Fetching..." : "Fetch Credit Data"}
+
+
+ {isFetchingStatement ? "Fetching..." : "Fetch Current Statement"}
+
+
+
+
+
+
+ );
+};
+
+ApiRequests.getLayout = (page: ReactNode) => {page} ;
+
+export default ApiRequests;
\ No newline at end of file
diff --git a/consumer-issuing/src/pages/api/add_external_account.ts b/consumer-issuing/src/pages/api/add_external_account.ts
new file mode 100644
index 00000000..0924e95f
--- /dev/null
+++ b/consumer-issuing/src/pages/api/add_external_account.ts
@@ -0,0 +1,58 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse } from "src/types/api-response";
+import { StripeAccount } from "src/utils/account-management-helpers";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const TEST_GB_ACCOUNT_NUMBER = "00012345";
+const TEST_GB_SORT_CODE = "108800";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: addExternalAccount,
+ });
+
+const addExternalBankAccount = async (
+ stripeAccount: StripeAccount,
+ currency: string,
+) => {
+ const { platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ const token = await stripe.tokens.create(
+ {
+ bank_account: {
+ account_number: TEST_GB_ACCOUNT_NUMBER,
+ routing_number: TEST_GB_SORT_CODE,
+ country: "GB",
+ currency: currency,
+ },
+ },
+ undefined,
+ );
+
+ return token;
+};
+
+const addExternalAccount = async (
+ req: NextApiRequest,
+ res: NextApiResponse,
+) => {
+ const session = await getSessionForServerSide(req, res);
+ const { currency, stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ const token = await (async () => {
+ return await addExternalBankAccount(stripeAccount, currency);
+ })();
+
+ await stripe.accounts.createExternalAccount(accountId, {
+ external_account: token.id,
+ });
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/auth/[...nextauth].ts b/consumer-issuing/src/pages/api/auth/[...nextauth].ts
new file mode 100644
index 00000000..cf38ac58
--- /dev/null
+++ b/consumer-issuing/src/pages/api/auth/[...nextauth].ts
@@ -0,0 +1,131 @@
+import NextAuth, { User } from "next-auth";
+import type { NextAuthOptions } from "next-auth";
+import { Session } from "next-auth/core/types";
+import { JWT } from "next-auth/jwt";
+import CredentialsProvider from "next-auth/providers/credentials";
+
+import {
+ getPlatformStripeAccountForCountry,
+ SupportedCountry,
+} from "src/utils/account-management-helpers";
+import { authenticateUser } from "src/utils/authentication";
+import logger from "src/utils/logger";
+import { hasOutstandingRequirements } from "src/utils/onboarding-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+export const authOptions: NextAuthOptions = {
+ providers: [
+ CredentialsProvider({
+ name: "Credentials",
+ credentials: {
+ email: { label: "Email", type: "text" },
+ password: { label: "Password", type: "password" },
+ },
+ authorize: async (credentials) => {
+ if (!credentials?.email || !credentials?.password) {
+ return null;
+ }
+
+ return await authenticateUser(credentials.email, credentials.password);
+ },
+ }),
+ ],
+ session: {
+ strategy: "jwt",
+ },
+ pages: {
+ signIn: "/auth/login",
+ },
+ callbacks: {
+ // User session context setup flow step 2:
+ // This will return a token object that also gets saved in the client's browser cookie and then subsequently
+ // provided in every request.
+ jwt: async ({ token, user }: { token: JWT; user?: User }) => {
+ // If the `requiresOnboarding` field is set to anything other than `undefined`, it means we've already set it up.
+ // Otherwise, we wouldn't have been able to set the `requiresOnboarding` value as setting up the token is a
+ // prerequisite for checking and setting that field. So we're safe to use the information in the token here.
+ logger.debug("Inside JWT callback, token provided:", token);
+ logger.debug("Inside JWT callback, user provided:", user);
+ if (token.requiresOnboarding != undefined) {
+ const stripeAccount = {
+ accountId: token.accountId,
+ platform: getPlatformStripeAccountForCountry(token.country),
+ };
+
+ token.requiresOnboarding =
+ token.requiresOnboarding &&
+ (await hasOutstandingRequirements(stripeAccount));
+ }
+
+ logger.debug("User's email:", user?.email);
+ if (user?.email) {
+ return { ...token, ...user };
+ }
+
+ // If accountId is undefined, that means we're most likely not authenticated yet
+ if (token.accountId != undefined && token.businessName == undefined) {
+ const platform = getPlatformStripeAccountForCountry(token.country);
+ const stripe = stripeClient(platform);
+ const account = await stripe.accounts.retrieve(token.accountId);
+ const firstName = account.individual?.first_name;
+ const lastName = account.individual?.last_name;
+ const businessName = firstName && lastName ? `${firstName} ${lastName}` : undefined;
+ const country = account.country;
+ const currency = account.default_currency;
+
+ if (businessName != undefined) {
+ token.businessName = businessName;
+ }
+
+ if (country != undefined) {
+ token.country = country as unknown as SupportedCountry;
+ }
+
+ if (currency != undefined) {
+ token.currency = currency;
+ }
+ }
+
+ return token;
+ },
+ session: async ({ session, token }: { session: Session; token: JWT }) => {
+ if (token.email == undefined) {
+ throw new Error("Session callback: email is missing in the token");
+ }
+
+ if (token.accountId == undefined) {
+ throw new Error("Session callback: account ID is missing in the token");
+ }
+
+ if (token.requiresOnboarding == undefined) {
+ throw new Error(
+ "Session callback: requiresOnboarding is missing in the token",
+ );
+ }
+
+ if (token.country == undefined) {
+ throw new Error("Session callback: country is missing in the token");
+ }
+
+ if (token.currency == undefined) {
+ throw new Error("Session callback: currency is missing in the token");
+ }
+
+ const stripeAccount = {
+ accountId: token.accountId,
+ platform: getPlatformStripeAccountForCountry(token.country),
+ };
+
+ session.email = token.email;
+ session.stripeAccount = stripeAccount;
+ session.requiresOnboarding = token.requiresOnboarding;
+ session.businessName = "Mrudula"; //token.businessName;
+ session.country = token.country;
+ session.currency = token.currency;
+
+ return session;
+ },
+ },
+};
+
+export default NextAuth(authOptions);
diff --git a/consumer-issuing/src/pages/api/cardholders.ts b/consumer-issuing/src/pages/api/cardholders.ts
new file mode 100644
index 00000000..a8831775
--- /dev/null
+++ b/consumer-issuing/src/pages/api/cardholders.ts
@@ -0,0 +1,143 @@
+import { getUnixTime } from "date-fns/getUnixTime";
+import { parsePhoneNumber } from "libphonenumber-js";
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+import validationSchemas from "src/utils/validation-schemas";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: createCardholder,
+ PUT: updateCardholder,
+ });
+
+const createCardholder = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ const validationSchema = (() => {
+ // PSD2 requires most transactions on payment cards issued in the EU and UK
+ // to be authenticated in order to proceed. This is called Strong Customer
+ // Authentication[0], or SCA. Stripe typically uses 3D Secure[1] to authenticate
+ // transactions, which requires a phone number to send an OTP to via SMS.
+ // So phone numbers must be mandatorily collected for cardholders of cards
+ // issued by EU or UK Stripe Issuing users.
+ //
+ // [0] https://stripe.com/docs/strong-customer-authentication
+ // [1] https://stripe.com/docs/issuing/3d-secure
+ return validationSchemas.cardholder.withSCA;
+ })();
+
+ const {
+ firstName,
+ lastName,
+ email,
+ phoneNumber,
+ address1,
+ city,
+ state,
+ postalCode,
+ country,
+ accept,
+ } = req.body;
+
+ try {
+ await validationSchema.validate(
+ {
+ firstName,
+ lastName,
+ email,
+ phoneNumber,
+ address1,
+ city,
+ state,
+ postalCode,
+ country,
+ accept,
+ },
+ { abortEarly: false },
+ );
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+
+ const parsedPhoneNumber = parsePhoneNumber(phoneNumber, country);
+ const formattedPhoneNumber = parsedPhoneNumber.formatInternational();
+
+ const ip =
+ req.headers["x-real-ip"]?.toString() || req.connection.remoteAddress;
+ const stripe = stripeClient(platform);
+ await stripe.issuing.cardholders.create(
+ {
+ type: "individual",
+ name: firstName + " " + lastName,
+ email: email,
+ phone_number: formattedPhoneNumber.replaceAll(" ", ""),
+ individual: {
+ first_name: firstName,
+ last_name: lastName,
+ card_issuing: {
+ user_terms_acceptance: {
+ date: getUnixTime(new Date()),
+ ip: ip,
+ },
+ },
+ },
+ billing: {
+ address: {
+ city: city,
+ line1: address1,
+ state: state,
+ postal_code: postalCode,
+ country: country,
+ },
+ },
+ },
+ {
+ stripeAccount: accountId,
+ },
+ );
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+const updateCardholder = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ const ip =
+ req.headers["x-real-ip"]?.toString() || req.connection.remoteAddress;
+ const stripe = stripeClient(platform);
+ await stripe.issuing.cardholders.update(
+ req.body.cardholderId,
+ {
+ individual: {
+ first_name: req.body.firstName,
+ last_name: req.body.lastName,
+ card_issuing: {
+ user_terms_acceptance: {
+ date: getUnixTime(new Date()),
+ ip: ip,
+ },
+ },
+ },
+ },
+ {
+ stripeAccount: accountId,
+ },
+ );
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/cards/[cardId]/card-keys.ts b/consumer-issuing/src/pages/api/cards/[cardId]/card-keys.ts
new file mode 100644
index 00000000..fa09fd40
--- /dev/null
+++ b/consumer-issuing/src/pages/api/cards/[cardId]/card-keys.ts
@@ -0,0 +1,53 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import * as Yup from "yup";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: createCardKey,
+ });
+
+const createCardKey = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ const cardId = req.query.cardId?.toString() || "";
+ const nonce = req.body.nonce;
+
+ const validationSchema = Yup.object().shape({
+ nonce: Yup.string().required("nonce is required"),
+ });
+
+ try {
+ await validationSchema.validate({ nonce }, { abortEarly: false });
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+
+ const apiVersion = "2022-08-01";
+ const stripe = stripeClient(platform);
+ const ephemeralKey = await stripe.ephemeralKeys.create(
+ {
+ nonce: nonce,
+ issuing_card: cardId,
+ },
+ {
+ stripeAccount: accountId,
+ apiVersion: apiVersion,
+ },
+ );
+
+ res.status(200).json(apiResponse({ success: true, data: ephemeralKey }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/cards/[cardId]/switch-card-status.ts b/consumer-issuing/src/pages/api/cards/[cardId]/switch-card-status.ts
new file mode 100644
index 00000000..bf0c0839
--- /dev/null
+++ b/consumer-issuing/src/pages/api/cards/[cardId]/switch-card-status.ts
@@ -0,0 +1,49 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import * as Yup from "yup";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ PATCH: switchCardStatus,
+ });
+
+const switchCardStatus = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ const cardId = req.query.cardId?.toString() || "";
+ const { newStatus } = req.body;
+
+ const validationSchema = Yup.object().shape({
+ newStatus: Yup.string()
+ .oneOf(["active", "inactive"])
+ .required("New status to switch to is required"),
+ });
+
+ try {
+ await validationSchema.validate({ newStatus }, { abortEarly: false });
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+
+ const stripe = stripeClient(platform);
+ await stripe.issuing.cards.update(
+ cardId,
+ { status: newStatus },
+ { stripeAccount: accountId },
+ );
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/cards/index.ts b/consumer-issuing/src/pages/api/cards/index.ts
new file mode 100644
index 00000000..692929a6
--- /dev/null
+++ b/consumer-issuing/src/pages/api/cards/index.ts
@@ -0,0 +1,88 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import Stripe from "stripe";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+import validationSchemas from "src/utils/validation-schemas";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: createCard,
+ });
+
+const createCard = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount, currency } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ const { cardholderid, card_type } = req.body;
+ const cardholder = await stripe.issuing.cardholders.retrieve(cardholderid, {
+ stripeAccount: accountId,
+ });
+
+ try {
+ const billingAddress = cardholder.billing.address;
+ await validationSchemas.card.validate(
+ { ...billingAddress },
+ { abortEarly: false },
+ );
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+
+ let cardOptions: Partial = {
+ cardholder: cardholderid,
+ currency: currency,
+ };
+
+ if (card_type == "physical") {
+ const shippingAddress = {
+ line1: cardholder.billing.address.line1 || "",
+ ...(cardholder.billing.address.line2 != null &&
+ cardholder.billing.address.line2 !== "" && {
+ line2: cardholder.billing.address.line2,
+ }),
+ city: cardholder.billing.address.city || "",
+ state: cardholder.billing.address.state || "",
+ postal_code: cardholder.billing.address.postal_code || "",
+ country: cardholder.billing.address.country || "",
+ };
+
+ cardOptions = {
+ ...cardOptions,
+ shipping: {
+ name: cardholder.name,
+ service: "standard",
+ type: "individual",
+ address: shippingAddress,
+ },
+ type: "physical",
+ status: "inactive",
+ };
+ } else {
+ cardOptions = {
+ ...cardOptions,
+ type: "virtual",
+ status: "active",
+ };
+ }
+
+ await stripe.issuing.cards.create(
+ cardOptions as Stripe.Issuing.CardCreateParams,
+ {
+ stripeAccount: accountId,
+ },
+ );
+
+ res.redirect("/cards");
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/create_authorization.ts b/consumer-issuing/src/pages/api/create_authorization.ts
new file mode 100644
index 00000000..f118bd25
--- /dev/null
+++ b/consumer-issuing/src/pages/api/create_authorization.ts
@@ -0,0 +1,61 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import { getBalance } from "src/utils/stripe-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: simulateAuthorization,
+ });
+
+const simulateAuthorization = async (
+ req: NextApiRequest,
+ res: NextApiResponse,
+) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount, currency } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ // A user must have sufficient funds in order to authorize a transaction
+ // on an Issuing card. For Embedded Finance users, these funds will be
+ // stored in a Treasury Financial Account, whereas other users who do not
+ // use Treasury will maintain an Issuing Balance. Here, we determine where
+ // to check for funds, which should illustrate where money comes from to
+ // fund Issuing transactions.
+ const balance = await (async () => {
+ const responseBalance = await getBalance(stripeAccount, session);
+ return responseBalance.balance.issuing?.available[0].amount || 0;
+ })();
+
+ if (balance < 1000) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: "Insufficient funds to create a test purchase.",
+ },
+ }),
+ );
+ }
+
+ const authorization = await stripe.testHelpers.issuing.authorizations.create(
+ {
+ amount: 1000,
+ currency: currency,
+ card: req.body.cardId,
+ },
+ { stripeAccount: accountId },
+ );
+
+ await stripe.testHelpers.issuing.authorizations.capture(authorization.id, {
+ stripeAccount: accountId,
+ });
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/create_credit_repayment.ts b/consumer-issuing/src/pages/api/create_credit_repayment.ts
new file mode 100644
index 00000000..e811ecc7
--- /dev/null
+++ b/consumer-issuing/src/pages/api/create_credit_repayment.ts
@@ -0,0 +1,106 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+import stripeClient from "src/utils/stripe-loader";
+import { logApiRequest } from "src/utils/api-logger";
+import { v4 as uuidv4 } from 'uuid';
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: createCreditRepayment,
+ });
+
+const createCreditRepayment = async (
+ req: NextApiRequest,
+ res: NextApiResponse,
+) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ try {
+ const { amount, currency = "usd", payment_type = "user_instructed", payment_method_id, customer_id } = req.body;
+
+ let requestBody: URLSearchParams;
+
+ if (payment_type === "api_instructed") {
+ // Validate required parameters for API-instructed payments
+ if (!payment_method_id || !customer_id) {
+ throw new Error("payment_method_id and customer_id are required for API-instructed payments");
+ }
+
+ requestBody = new URLSearchParams({
+ account: accountId,
+ customer: customer_id,
+ "instructed_by[type]": "credit_repayments_api",
+ "instructed_by[credit_repayments_api][payment_method]": payment_method_id,
+ "amount[value]": amount.toString(),
+ "amount[currency]": currency,
+ credit_statement_descriptor: "API Payment received",
+ });
+ } else {
+ // User-instructed payment (existing functionality)
+ requestBody = new URLSearchParams({
+ account: accountId,
+ "instructed_by[type]": "user",
+ "instructed_by[user][payment_method_type]": "paper_check",
+ "instructed_by[user][payment_reference]": uuidv4(),
+ "amount[value]": amount.toString(),
+ "amount[currency]": currency,
+ credit_statement_descriptor: "User Payment received",
+ });
+ }
+
+ // Make a direct API call to the credit repayments endpoint
+ const response = await fetch("https://api.stripe.com/v1/issuing/credit_repayments", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ },
+ body: requestBody,
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ // Log the failed API request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_repayments",
+ "POST",
+ Object.fromEntries(requestBody),
+ { error: data, status: response.status }
+ );
+
+ throw new Error(data.error?.message || "Failed to create credit repayment");
+ }
+
+ // Log the successful API request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_repayments",
+ "POST",
+ Object.fromEntries(requestBody),
+ data
+ );
+
+ return res.status(200).json(apiResponse({ success: true, data }));
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: error instanceof Error ? error.message : "Unknown error occurred",
+ },
+ }),
+ );
+ }
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/create_issuingtopup.tsx b/consumer-issuing/src/pages/api/create_issuingtopup.tsx
new file mode 100644
index 00000000..8e0d3877
--- /dev/null
+++ b/consumer-issuing/src/pages/api/create_issuingtopup.tsx
@@ -0,0 +1,40 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: simulateIssuingBalanceFunding,
+ });
+
+const simulateIssuingBalanceFunding = async (
+ req: NextApiRequest,
+ res: NextApiResponse,
+) => {
+ const session = await getSessionForServerSide(req, res);
+ const { currency, stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ const data = {
+ amount: "50000",
+ currency: currency as string,
+ };
+
+ //test_helpers topup issuing balance - not supported in the Node.js libary
+ await fetch("https://api.stripe.com/v1/test_helpers/issuing/fund_balance", {
+ method: "POST",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ },
+ body: new URLSearchParams(data),
+ });
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/create_paymentlink.ts b/consumer-issuing/src/pages/api/create_paymentlink.ts
new file mode 100644
index 00000000..4c3e2e49
--- /dev/null
+++ b/consumer-issuing/src/pages/api/create_paymentlink.ts
@@ -0,0 +1,68 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: createPaymentLink,
+ });
+
+const createPaymentLink = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount, currency } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ const prices = await stripe.prices.list(
+ {
+ limit: 1,
+ active: true,
+ type: "one_time",
+ },
+ {
+ stripeAccount: accountId,
+ },
+ );
+
+ const price =
+ prices.data.length < 1
+ ? await stripe.prices.create(
+ {
+ unit_amount: 1000,
+ currency: currency,
+ product_data: {
+ name: "Some Product",
+ },
+ },
+ {
+ stripeAccount: accountId,
+ },
+ )
+ : prices.data[0];
+
+ const paymentLink = await stripe.paymentLinks.create(
+ {
+ line_items: [
+ {
+ price: price.id,
+ quantity: 1,
+ adjustable_quantity: { enabled: true },
+ },
+ ],
+ },
+ {
+ stripeAccount: accountId,
+ },
+ );
+
+ return res
+ .status(200)
+ .json(
+ apiResponse({ success: true, data: { paymentLink: paymentLink.url } }),
+ );
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/create_payout.ts b/consumer-issuing/src/pages/api/create_payout.ts
new file mode 100644
index 00000000..11aea958
--- /dev/null
+++ b/consumer-issuing/src/pages/api/create_payout.ts
@@ -0,0 +1,61 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import * as Yup from "yup";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: createPayout,
+ });
+
+const createPayout = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount, currency } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ const balance = await stripe.balance.retrieve({
+ stripeAccount: accountId,
+ });
+ const availableBalance = balance.available[0].amount;
+
+ const validationSchema = Yup.object({
+ availableBalance: Yup.number()
+ .required("Available balance is required")
+ .moreThan(
+ 0,
+ "Available balance must be greater than $0.00 in order to do a payout",
+ ),
+ });
+
+ try {
+ await validationSchema.validate(
+ {
+ availableBalance,
+ },
+ { abortEarly: false },
+ );
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+
+ await stripe.payouts.create(
+ {
+ amount: availableBalance,
+ currency: currency,
+ },
+ { stripeAccount: accountId },
+ );
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/delete-account.ts b/consumer-issuing/src/pages/api/delete-account.ts
new file mode 100644
index 00000000..8a6b4ae7
--- /dev/null
+++ b/consumer-issuing/src/pages/api/delete-account.ts
@@ -0,0 +1,55 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { prisma } from "src/db";
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: deleteAccount,
+ });
+
+const deleteAccount = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount, user } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ // Try to delete the Stripe account, but continue even if it doesn't exist
+ try {
+ await stripe.accounts.del(accountId);
+ } catch (error) {
+ // If the error is not a "No such account" error, rethrow it
+ // if (!(error instanceof Error) || !error.message.includes('No such account')) {
+ // throw error;
+ // }
+ // Otherwise, log the error and continue with user deletion
+ console.log(`Stripe account ${accountId} not found, continuing with user deletion`);
+ }
+
+ if (user?.email == undefined) {
+ throw new Error("User email is undefined");
+ }
+
+ // First delete all API request logs for this user
+ await prisma.apiRequestLog.deleteMany({
+ where: {
+ user: {
+ email: user.email
+ }
+ }
+ });
+
+ // Then delete the user
+ await prisma.user.delete({
+ where: {
+ email: user?.email,
+ },
+ });
+
+ return res.status(200).json(apiResponse({ success: true }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/fetch_credit_data.ts b/consumer-issuing/src/pages/api/fetch_credit_data.ts
new file mode 100644
index 00000000..00b59414
--- /dev/null
+++ b/consumer-issuing/src/pages/api/fetch_credit_data.ts
@@ -0,0 +1,89 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+import { logApiRequest } from "src/utils/api-logger";
+import { apiResponse } from "src/types/api-response";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) => {
+ if (req.method !== "POST") {
+ return res.status(405).json({ message: "Method not allowed" });
+ }
+
+ try {
+ const session = await getSessionForServerSide(req, res);
+ if (!session) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const { email, stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ // Fetch credit policy
+ const creditPolicyResponse = await fetch(
+ "https://api.stripe.com/v1/issuing/credit_policy",
+ {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1",
+ },
+ }
+ );
+
+ const creditPolicyData = await creditPolicyResponse.json();
+
+ // Log credit policy request
+ await logApiRequest(
+ email,
+ "https://api.stripe.com/v1/issuing/credit_policy",
+ "GET",
+ null,
+ creditPolicyData
+ );
+
+ // Fetch credit underwriting records
+ const underwritingResponse = await fetch(
+ "https://api.stripe.com/v1/issuing/credit_underwriting_records",
+ {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1",
+ },
+ }
+ );
+
+ const underwritingData = await underwritingResponse.json();
+
+ // Log underwriting records request
+ await logApiRequest(
+ email,
+ "https://api.stripe.com/v1/issuing/credit_underwriting_records",
+ "GET",
+ null,
+ underwritingData
+ );
+
+ return res.status(200).json(
+ apiResponse({
+ success: true,
+ data: {
+ creditPolicy: creditPolicyData,
+ underwritingRecords: underwritingData,
+ },
+ })
+ );
+ } catch (error) {
+ console.error("Error fetching credit data:", error);
+ return res.status(500).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ })
+ );
+ }
+};
+
+export default handler;
\ No newline at end of file
diff --git a/consumer-issuing/src/pages/api/get_api_request_logs.ts b/consumer-issuing/src/pages/api/get_api_request_logs.ts
new file mode 100644
index 00000000..626068e1
--- /dev/null
+++ b/consumer-issuing/src/pages/api/get_api_request_logs.ts
@@ -0,0 +1,48 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import { getServerSession } from "next-auth";
+import { prisma } from "src/db";
+import { authOptions } from "src/pages/api/auth/[...nextauth]";
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ if (req.method !== "GET") {
+ return res.status(405).json({ message: "Method not allowed" });
+ }
+
+ try {
+ const session = await getServerSession(req, res, authOptions);
+ if (!session) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+
+ const { email } = session;
+ if (!email) {
+ return res.status(401).json({ message: "User not found" });
+ }
+
+ const user = await prisma.user.findUnique({
+ where: { email }
+ });
+
+ if (!user) {
+ return res.status(401).json({ message: "User not found" });
+ }
+
+ const logs = await prisma.apiRequestLog.findMany({
+ where: {
+ userId: user.id
+ },
+ orderBy: {
+ created: 'desc'
+ },
+ take: 100 // Limit to last 100 logs
+ });
+
+ res.status(200).json({ logs });
+ } catch (error) {
+ console.error("Error fetching API request logs:", error);
+ res.status(500).json({ message: "Error fetching API request logs" });
+ }
+}
\ No newline at end of file
diff --git a/consumer-issuing/src/pages/api/get_credit_repayment.ts b/consumer-issuing/src/pages/api/get_credit_repayment.ts
new file mode 100644
index 00000000..ffcdc611
--- /dev/null
+++ b/consumer-issuing/src/pages/api/get_credit_repayment.ts
@@ -0,0 +1,48 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import { getServerSession } from "next-auth";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+import { authOptions } from "src/pages/api/auth/[...nextauth]";
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ if (req.method !== "GET") {
+ return res.status(405).json({ message: "Method not allowed" });
+ }
+
+ try {
+ const session = await getServerSession(req, res, authOptions);
+ if (!session) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+ const { id } = req.query;
+
+ if (!id) {
+ return res.status(400).json({ message: "Credit repayment ID is required" });
+ }
+
+ const response = await fetch(
+ `https://api.stripe.com/v1/issuing/credit_repayments/${id}`,
+ {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ }
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error("Failed to fetch credit repayment details");
+ }
+
+ const data = await response.json();
+ res.status(200).json(data);
+ } catch (error) {
+ console.error("Error fetching credit repayment details:", error);
+ res.status(500).json({ message: "Error fetching credit repayment details" });
+ }
+}
\ No newline at end of file
diff --git a/consumer-issuing/src/pages/api/get_current_statement.ts b/consumer-issuing/src/pages/api/get_current_statement.ts
new file mode 100644
index 00000000..34e9f4ad
--- /dev/null
+++ b/consumer-issuing/src/pages/api/get_current_statement.ts
@@ -0,0 +1,58 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import { getServerSession } from "next-auth";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+import { authOptions } from "src/pages/api/auth/[...nextauth]";
+import { logApiRequest } from "src/utils/api-logger";
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ if (req.method !== "GET") {
+ return res.status(405).json({ message: "Method not allowed" });
+ }
+
+ try {
+ const session = await getServerSession(req, res, authOptions);
+ if (!session) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ // Get the current credit statement from the credit_statements API
+ const statementResponse = await fetch(
+ `https://api.stripe.com/v1/issuing/credit_statements/current`,
+ {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ }
+ }
+ );
+
+ const statementData = await statementResponse.json();
+
+ // Log the API request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_statements/current",
+ "GET",
+ null,
+ statementData
+ );
+
+ if (!statementResponse.ok) {
+ return res.status(statementResponse.status).json({
+ message: statementData.error?.message || "Failed to fetch current credit statement"
+ });
+ }
+
+ res.status(200).json({ statement: statementData });
+ } catch (error) {
+ console.error("Error fetching current credit statement:", error);
+ res.status(500).json({ message: "Error fetching current credit statement" });
+ }
+}
\ No newline at end of file
diff --git a/consumer-issuing/src/pages/api/get_statements.ts b/consumer-issuing/src/pages/api/get_statements.ts
new file mode 100644
index 00000000..a46d8b07
--- /dev/null
+++ b/consumer-issuing/src/pages/api/get_statements.ts
@@ -0,0 +1,102 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import { getServerSession } from "next-auth";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+import { authOptions } from "src/pages/api/auth/[...nextauth]";
+import { logApiRequest } from "src/utils/api-logger";
+
+export default async function handler(
+ req: NextApiRequest,
+ res: NextApiResponse
+) {
+ if (req.method !== "GET") {
+ return res.status(405).json({ message: "Method not allowed" });
+ }
+
+ try {
+ const session = await getServerSession(req, res, authOptions);
+ if (!session) {
+ return res.status(401).json({ message: "Unauthorized" });
+ }
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+
+ // Get credit statements from the credit_statements API
+ const statementsResponse = await fetch(
+ `https://api.stripe.com/v1/issuing/credit_statements`,
+ {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ }
+ }
+ );
+
+ if (!statementsResponse.ok) {
+ const errorText = await statementsResponse.text();
+ console.error("Credit statements API error:", {
+ status: statementsResponse.status,
+ statusText: statementsResponse.statusText,
+ error: errorText
+ });
+
+ // Log the failed API request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_statements",
+ "GET",
+ null,
+ { error: errorText, status: statementsResponse.status }
+ );
+
+ // If credit statements are not available, return empty array
+ if (statementsResponse.status === 404 || statementsResponse.status === 403) {
+ console.log("Credit statements not available, returning empty array");
+ return res.status(200).json({ statements: [] });
+ }
+
+ throw new Error(`Failed to fetch credit statements: ${statementsResponse.status} ${statementsResponse.statusText}`);
+ }
+
+ const statementsData = await statementsResponse.json();
+
+ // Log the successful API request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_statements",
+ "GET",
+ null,
+ statementsData
+ );
+
+ // Handle case where no statements are returned
+ if (!statementsData.data || !Array.isArray(statementsData.data)) {
+ console.log("No statements data found, returning empty array");
+ return res.status(200).json({ statements: [] });
+ }
+
+ // Sort statements by credit_period_ends_at in descending order (most recent first)
+ // and limit to 3 most recent, handling null values
+ const sortedStatements = statementsData.data
+ .sort((a: any, b: any) => {
+ // Put statements with null dates at the end
+ if (a.credit_period_ends_at === null && b.credit_period_ends_at === null) return 0;
+ if (a.credit_period_ends_at === null) return 1;
+ if (b.credit_period_ends_at === null) return -1;
+ return b.credit_period_ends_at - a.credit_period_ends_at;
+ })
+ .slice(0, 3);
+
+ // Map statements to include the statement_url directly
+ const statementsWithUrls = sortedStatements.map((statement: any) => ({
+ ...statement,
+ url: statement.statement_url || null
+ }));
+
+ res.status(200).json({ statements: statementsWithUrls });
+ } catch (error) {
+ console.error("Error fetching credit statements:", error);
+ res.status(500).json({ message: "Error fetching credit statements" });
+ }
+}
diff --git a/consumer-issuing/src/pages/api/get_verified_payment_methods.ts b/consumer-issuing/src/pages/api/get_verified_payment_methods.ts
new file mode 100644
index 00000000..b49e2807
--- /dev/null
+++ b/consumer-issuing/src/pages/api/get_verified_payment_methods.ts
@@ -0,0 +1,82 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse } from "src/types/api-response";
+import { handlerMapping } from "src/utils/api-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ GET: getVerifiedPaymentMethods,
+ });
+
+const getVerifiedPaymentMethods = async (
+ req: NextApiRequest,
+ res: NextApiResponse,
+) => {
+ const session = await getSessionForServerSide(req, res);
+ const { stripeAccount } = session;
+ const { platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+
+ try {
+ // Get all customers
+ const customers = await stripe.customers.list({
+ limit: 100, // Adjust as needed
+ });
+
+ const verifiedPaymentMethods = [];
+
+ // For each customer, get their payment methods and filter for verified US bank accounts
+ for (const customer of customers.data) {
+ try {
+ const paymentMethods = await stripe.paymentMethods.list({
+ customer: customer.id,
+ type: 'us_bank_account',
+ });
+
+ // Filter for payment methods (for demo, we'll include all US bank account payment methods)
+ // In a real application, you would have proper verification status checking
+ const bankAccountPaymentMethods = paymentMethods.data.filter(pm =>
+ pm.us_bank_account !== null
+ );
+
+ if (bankAccountPaymentMethods.length > 0) {
+ verifiedPaymentMethods.push({
+ customer: {
+ id: customer.id,
+ name: customer.name,
+ email: customer.email,
+ },
+ paymentMethods: bankAccountPaymentMethods.map(pm => ({
+ id: pm.id,
+ bank_name: pm.us_bank_account?.bank_name,
+ last4: pm.us_bank_account?.last4,
+ account_type: pm.us_bank_account?.account_type,
+ account_holder_type: pm.us_bank_account?.account_holder_type,
+ }))
+ });
+ }
+ } catch (error) {
+ // Skip customers that have errors when fetching payment methods
+ console.log(`Error fetching payment methods for customer ${customer.id}:`, error);
+ }
+ }
+
+ return res.status(200).json(apiResponse({
+ success: true,
+ data: verifiedPaymentMethods
+ }));
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: error instanceof Error ? error.message : "Unknown error occurred",
+ },
+ }),
+ );
+ }
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/onboard.ts b/consumer-issuing/src/pages/api/onboard.ts
new file mode 100644
index 00000000..bab37248
--- /dev/null
+++ b/consumer-issuing/src/pages/api/onboard.ts
@@ -0,0 +1,472 @@
+import { NextApiRequest, NextApiResponse } from "next";
+import Stripe from "stripe";
+import { getPlatform } from "src/utils/platform";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+import stripeClient from "src/utils/stripe-loader";
+import validationSchemas from "src/utils/validation-schemas";
+import { logApiRequest } from "src/utils/api-logger";
+
+import { apiResponse } from "src/types/api-response";
+import {
+ CountryConfigMap,
+ SupportedCountry,
+} from "src/utils/account-management-helpers";
+import { handlerMapping } from "src/utils/api-helpers";
+import {
+ getFiscalYearEnd,
+ isDemoMode,
+ TOS_ACCEPTANCE,
+} from "src/utils/demo-helpers";
+import { createAccountOnboardingUrl } from "src/utils/onboarding-helpers";
+import { getSessionForServerSide } from "src/utils/session-helpers";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: onboard,
+ });
+
+const onboard = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSessionForServerSide(req, res);
+ const { email, stripeAccount, country } = session;
+ const { accountId, platform } = stripeAccount;
+
+ const {
+ businessName,
+ skipOnboarding,
+ }: { businessName: string; skipOnboarding?: boolean } = req.body;
+
+ let validationSchema;
+ if (isDemoMode()) {
+ validationSchema = validationSchemas.business.withOnbardingSkip;
+ } else {
+ validationSchema = validationSchemas.business.default;
+ }
+
+ try {
+ await validationSchema.validate(
+ { businessName, skipOnboarding },
+ { abortEarly: false },
+ );
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+
+ const [firstName, ...lastNameParts] = businessName.split(" ");
+ const lastName = lastNameParts.join(" ");
+
+ const onboardingData: Stripe.AccountUpdateParams = {
+ business_profile: { name: businessName },
+ // TODO: Only update the fields during the demo that are outstanding to speed things up
+ // FOR-DEMO-ONLY: We're using fake data for illustrative purposes in this demo. The fake data will be used to bypass
+ // showing the Stripe Connect Onboarding forms. In a real application, you would not do this so that you can collect
+ // the real KYC data from your users.
+ ...(isDemoMode() && {
+ business_type: "individual",
+ business_profile: {
+ name: businessName,
+ // Merchant category code for "computer software stores" (https://fs.fldfs.com/iwpapps/pcard/docs/MCCs.pdf)
+ mcc: "5734",
+ product_description: "Some demo product",
+ url: "https://some-company.com",
+ annual_revenue: {
+ amount: 0,
+ currency: CountryConfigMap[country].currency,
+ fiscal_year_end: getFiscalYearEnd(),
+ },
+ estimated_worker_count: 1,
+ },
+ company: {
+ name: businessName,
+ // Fake business TIN: https://stripe.com/docs/connect/testing#test-business-tax-ids
+ tax_id: "000000000",
+ },
+ individual: {
+ address: {
+ // This value causes the address to be verified in testmode: https://stripe.com/docs/connect/testing#test-verification-addresses
+ line1: "354 Oyster Point Blvd",
+ city: "South San Francisco",
+ state: "CA",
+ postal_code: "94080",
+ country: "US",
+ },
+ // These values together cause the DOB to be verified in testmode: https://stripe.com/docs/connect/testing#test-dobs
+ dob: {
+ day: 1,
+ month: 1,
+ year: 1901,
+ },
+ email: email,
+ first_name: firstName,
+ last_name: lastName,
+ // Fake phone number: https://docs.stripe.com/connect/testing#using-oauth
+ phone: "000-000-0000",
+ },
+ ...(skipOnboarding && { tos_acceptance: TOS_ACCEPTANCE }),
+ // Faking Terms of Service acceptances
+ settings: {
+ card_issuing: {
+ tos_acceptance: TOS_ACCEPTANCE,
+ },
+ },
+ }),
+ };
+
+ const stripe = stripeClient(platform);
+ await stripe.accounts.update(accountId, onboardingData);
+
+ // Create Issuing Program for the account
+ // console.log("Creating issuing program with params:", {
+ // platform_program: process.env.PLATFORM_PROGRAM,
+ // is_default: "true"
+ // });
+
+ const createProgramResponse = await fetch("https://api.stripe.com/v1/issuing/programs", {
+ method: "POST",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2025-03-31.basil;issuing_program_beta=v2"
+ },
+ body: new URLSearchParams({
+ platform_program: process.env.PLATFORM_PROGRAM || "",
+ is_default: "true"
+ }),
+ });
+
+ // console.log("Issuing program creation response status:", createProgramResponse.status);
+ const programResponseText = await createProgramResponse.text();
+ // console.log("Issuing program creation response body:", programResponseText);
+
+ // Log the issuing program creation request
+ await logApiRequest(
+ email,
+ "https://api.stripe.com/v1/issuing/programs",
+ "POST",
+ {
+ platform_program: process.env.PLATFORM_PROGRAM || "",
+ is_default: true
+ },
+ JSON.parse(programResponseText)
+ );
+
+ if (!createProgramResponse.ok) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Failed to create issuing program" },
+ }),
+ );
+ }
+
+ // Create Credit Underwriting Record from the account
+ const underwritingParams = {
+ "application[purpose]": "credit_line_opening",
+ "application[submitted_at]": Math.floor(Date.now() / 1000).toString(),
+ "credit_user[name]": businessName,
+ "credit_user[email]": email
+ };
+
+ // console.log("Creating underwriting record with params:", underwritingParams);
+
+ const createUnderwritingResponse = await fetch("https://api.stripe.com/v1/issuing/credit_underwriting_records/create_from_application", {
+ method: "POST",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ },
+ body: new URLSearchParams(underwritingParams),
+ });
+
+ // console.log("Underwriting record creation response status:", createUnderwritingResponse.status);
+
+ let underwritingRecord;
+ try {
+ underwritingRecord = await createUnderwritingResponse.json();
+ // console.log("Underwriting record creation response:", underwritingRecord);
+
+ // Log the underwriting record creation request
+ await logApiRequest(
+ email,
+ "https://api.stripe.com/v1/issuing/credit_underwriting_records/create_from_application",
+ "POST",
+ underwritingParams,
+ underwritingRecord
+ );
+ } catch (error) {
+ console.error("Error parsing underwriting record response:", error);
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Failed to parse credit underwriting record response" },
+ }),
+ );
+ }
+
+ if (!createUnderwritingResponse.ok) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Failed to create credit underwriting record from application" },
+ }),
+ );
+ }
+
+ if (!underwritingRecord || !underwritingRecord.id) {
+ console.error("Invalid underwriting record response:", underwritingRecord);
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Invalid credit underwriting record response" },
+ }),
+ );
+ }
+
+ // Report the underwriting decision
+ const decisionParams = {
+ decided_at: Math.floor(Date.now() / 1000).toString(),
+ "decision[type]": "credit_limit_approved",
+ "decision[credit_limit_approved][amount]": "2000"
+ };
+
+ const reportDecisionResponse = await fetch(`https://api.stripe.com/v1/issuing/credit_underwriting_records/${underwritingRecord.id}/report_decision`, {
+ method: "POST",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ },
+ body: new URLSearchParams(decisionParams),
+ });
+
+ const decisionResponse = await reportDecisionResponse.json();
+
+ // Log the underwriting decision request
+ await logApiRequest(
+ email,
+ `https://api.stripe.com/v1/issuing/credit_underwriting_records/${underwritingRecord.id}/report_decision`,
+ "POST",
+ decisionParams,
+ decisionResponse
+ );
+
+ if (!reportDecisionResponse.ok) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Failed to report credit underwriting decision" },
+ }),
+ );
+ }
+
+ // Check capability status before activating Credit Policy
+ // console.log("Checking capability status for card_issuing_consumer_revolving_credit_card_celtic");
+
+ const POLL_INTERVAL = 2000; // 2 seconds
+ const MAX_ATTEMPTS = 30; // 1 minute total
+ let attempts = 0;
+ let capabilityStatus;
+
+ while (attempts < MAX_ATTEMPTS) {
+ const capabilityResponse = await fetch(`https://api.stripe.com/v1/accounts/${accountId}/capabilities/card_issuing_consumer_revolving_credit_card_celtic`, {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1;issuing_program_beta=v2"
+ }
+ });
+
+ console.log(`Capability check attempt ${attempts + 1}/${MAX_ATTEMPTS} response status:`, capabilityResponse.status);
+
+ try {
+ capabilityStatus = await capabilityResponse.json();
+ // console.log("Capability status:", capabilityStatus);
+
+ if (capabilityStatus.status === "active") {
+ console.log("Capability is now active, proceeding with Credit Policy activation");
+ break;
+ }
+
+ if (!capabilityResponse.ok) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: "Failed to check capability status",
+ details: capabilityStatus?.error?.message || "Unknown error"
+ },
+ }),
+ );
+ }
+ } catch (error) {
+ console.error("Error parsing capability status response:", error);
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Failed to parse capability status response" },
+ }),
+ );
+ }
+
+ attempts++;
+ if (attempts < MAX_ATTEMPTS) {
+ // console.log(`Capability not yet active, waiting ${POLL_INTERVAL}ms before next check...`);
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
+ }
+ }
+
+ if (attempts >= MAX_ATTEMPTS) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: "Capability did not become active within the expected time",
+ details: "Timed out waiting for capability to become active"
+ },
+ }),
+ );
+ }
+
+ // Poll for Credit Policy presence
+ // console.log("Checking for Credit Policy presence");
+ attempts = 0;
+ let creditPolicy;
+
+ while (attempts < MAX_ATTEMPTS) {
+ const creditPolicyResponse = await fetch(`https://api.stripe.com/v1/issuing/credit_policy`, {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1;issuing_program_beta=v2"
+ }
+ });
+
+ // console.log(`Credit Policy check attempt ${attempts + 1}/${MAX_ATTEMPTS} response status:`, creditPolicyResponse.status);
+
+ try {
+ const response = await creditPolicyResponse.json();
+ // console.log("Credit Policy response:", response);
+
+ if (response.object === "issuing.credit_policy") {
+ console.log("Credit Policy is present, proceeding with activation");
+ creditPolicy = response;
+ break;
+ }
+
+ // console.log("No Credit Policy found yet, will retry...");
+ } catch (error) {
+ console.error("Error parsing Credit Policy response:", error);
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Failed to parse Credit Policy response" },
+ }),
+ );
+ }
+
+ attempts++;
+ if (attempts < MAX_ATTEMPTS) {
+ // console.log(`Credit Policy not yet present, waiting ${POLL_INTERVAL}ms before next check...`);
+ await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
+ }
+ }
+
+ if (attempts >= MAX_ATTEMPTS) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: "Credit Policy did not become available within the expected time",
+ details: "Timed out waiting for Credit Policy to become available"
+ },
+ }),
+ );
+ }
+
+ // Activate the Credit Policy for the account
+ const creditPolicyParams = {
+ status: "active",
+ credit_limit_amount: "2000",
+ credit_limit_currency: "usd",
+ credit_period_interval: "day",
+ credit_period_interval_count: "1",
+ days_until_due: "0"
+ };
+
+ // console.log("Activating credit policy with params:", creditPolicyParams);
+
+ const creditPolicyResponse = await fetch("https://api.stripe.com/v1/issuing/credit_policy", {
+ method: "POST",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ },
+ body: new URLSearchParams(creditPolicyParams),
+ });
+
+ // console.log("Credit policy activation response status:", creditPolicyResponse.status);
+ let creditPolicyResponseBody;
+ try {
+ creditPolicyResponseBody = await creditPolicyResponse.json();
+ // console.log("Credit policy activation response:", creditPolicyResponseBody);
+
+ // Log the credit policy activation request
+ await logApiRequest(
+ email,
+ "https://api.stripe.com/v1/issuing/credit_policy",
+ "POST",
+ creditPolicyParams,
+ creditPolicyResponseBody
+ );
+ } catch (error) {
+ console.error("Error parsing credit policy response:", error);
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Failed to parse credit policy response" },
+ }),
+ );
+ }
+
+ if (!creditPolicyResponse.ok) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: "Failed to activate credit policy",
+ details: creditPolicyResponseBody?.error?.message || "Unknown error"
+ },
+ }),
+ );
+ }
+
+ // FOR-DEMO-ONLY: We're going to check if the user wants to skip the onboarding process. If they do, we'll redirect to
+ // the home page. In a real application, you would not allow this bypass so that you can collect the real KYC data
+ // from your users.
+ if (isDemoMode() && skipOnboarding) {
+ return res
+ .status(200)
+ .json(apiResponse({ success: true, data: { redirectUrl: "/" } }));
+ }
+
+ // This is the Connect Onboarding URL that will be used to collect KYC information from the user
+ const onboardingUrl = await createAccountOnboardingUrl(stripeAccount);
+
+ return res
+ .status(200)
+ .json(apiResponse({ success: true, data: { redirectUrl: onboardingUrl } }));
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/api/register.ts b/consumer-issuing/src/pages/api/register.ts
new file mode 100644
index 00000000..2b5d7c1a
--- /dev/null
+++ b/consumer-issuing/src/pages/api/register.ts
@@ -0,0 +1,220 @@
+import bcrypt from "bcrypt";
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { prisma } from "src/db";
+import { apiResponse } from "src/types/api-response";
+import {
+ getPlatformStripeAccountForCountry,
+ SupportedCountry,
+} from "src/utils/account-management-helpers";
+import { handlerMapping } from "src/utils/api-helpers";
+import { isDemoMode } from "src/utils/demo-helpers";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+import validationSchemas from "src/utils/validation-schemas";
+import { logApiRequest } from "src/utils/api-logger";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) =>
+ handlerMapping(req, res, {
+ POST: register,
+ });
+
+const register = async (req: NextApiRequest, res: NextApiResponse) => {
+ const { email, password, country: rawCountry } = req.body;
+
+ try {
+ await validationSchemas.user.validate(
+ { email, password, country: rawCountry },
+ { abortEarly: false },
+ );
+ } catch (error) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+
+ const country = rawCountry as SupportedCountry;
+
+ // Check if user exists
+ const user = await prisma.user.findFirst({ where: { email } });
+ if (user) {
+ return res.status(400).json(
+ apiResponse({
+ success: false,
+ error: { message: "Account already exists" },
+ }),
+ );
+ }
+
+ // Create a Connected Account using raw HTTP request
+ const platform = getPlatformStripeAccountForCountry(country);
+ const stripeSecretKey = getStripeSecretKey(platform);
+
+ if (!stripeSecretKey) {
+ return res.status(500).json(
+ apiResponse({
+ success: false,
+ error: { message: "Stripe secret key not configured" },
+ }),
+ );
+ }
+
+ const accountPayload = {
+ controller: {
+ stripe_dashboard: {
+ type: "none",
+ },
+ fees: {
+ payer: "application",
+ },
+ requirement_collection: "application",
+ losses: {
+ payments: "application",
+ },
+ },
+ country: country,
+ email: email,
+ ...(isDemoMode() && {
+ business_type: "individual",
+ individual: {
+ id_number: "000000000",
+ },
+ }),
+ capabilities: {
+ transfers: { requested: true },
+ card_payments: { requested: true },
+ //card_issuing_consumer_revolving_credit_card_celtic: { requested: true },
+ },
+ };
+
+ try {
+ const formData = new URLSearchParams();
+
+ // Add simple fields
+ formData.append('country', country);
+ formData.append('email', email);
+
+ // Add nested objects
+ formData.append('controller[stripe_dashboard][type]', 'none');
+ formData.append('controller[fees][payer]', 'application');
+ formData.append('controller[requirement_collection]', 'application');
+ formData.append('controller[losses][payments]', 'application');
+
+ // Add capabilities
+ formData.append('capabilities[transfers][requested]', 'true');
+ formData.append('capabilities[card_payments][requested]', 'true');
+ formData.append('capabilities[card_issuing_consumer_revolving_credit_card_celtic][requested]', 'true');
+
+ // Add demo mode fields if needed
+ if (isDemoMode()) {
+ formData.append('business_type', 'individual');
+ formData.append('individual[id_number]', '000000000');
+ }
+
+ // console.log('Creating a new account: POST https://api.stripe.com/v1/accounts');
+ // console.log('Body:');
+ // console.log(JSON.stringify({
+ // country,
+ // email,
+ // controller: {
+ // stripe_dashboard: {
+ // type: 'none'
+ // },
+ // fees: {
+ // payer: 'application'
+ // },
+ // requirement_collection: 'application',
+ // losses: {
+ // payments: 'application'
+ // }
+ // },
+ // capabilities: {
+ // transfers: { requested: true },
+ // card_payments: { requested: true },
+ // card_issuing_consumer_revolving_credit_card_celtic: { requested: true }
+ // },
+ // ...(isDemoMode() && {
+ // business_type: 'individual',
+ // individual: {
+ // id_number: '000000000'
+ // }
+ // })
+ // }, null, 2));
+
+ const response = await fetch("https://api.stripe.com/v1/accounts", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ Authorization: `Bearer ${stripeSecretKey}`,
+ "Stripe-Version": "2024-04-10;issuing_program_beta=v2",
+ },
+ body: formData,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error?.message || "Failed to create Stripe account");
+ }
+
+ const account = await response.json();
+
+ // Create the user
+ const hashedPassword = await bcrypt.hash(password, 10);
+ await prisma.user.create({
+ data: {
+ email: email,
+ password: hashedPassword,
+ accountId: account.id,
+ country: country.toString(),
+ },
+ });
+
+ // Log the API request after user is created
+ await logApiRequest(
+ email,
+ "https://api.stripe.com/v1/accounts",
+ "POST",
+ {
+ country,
+ email,
+ controller: {
+ stripe_dashboard: {
+ type: 'none'
+ },
+ fees: {
+ payer: 'application'
+ },
+ requirement_collection: 'application',
+ losses: {
+ payments: 'application'
+ }
+ },
+ capabilities: {
+ transfers: { requested: true },
+ card_payments: { requested: true },
+ card_issuing_consumer_revolving_credit_card_celtic: { requested: true }
+ },
+ ...(isDemoMode() && {
+ business_type: 'individual',
+ individual: {
+ id_number: '000000000'
+ }
+ })
+ },
+ account
+ );
+
+ return res.status(200).json(apiResponse({ success: true }));
+ } catch (error) {
+ return res.status(500).json(
+ apiResponse({
+ success: false,
+ error: { message: (error as Error).message },
+ }),
+ );
+ }
+};
+
+export default handler;
diff --git a/consumer-issuing/src/pages/auth/login.tsx b/consumer-issuing/src/pages/auth/login.tsx
new file mode 100644
index 00000000..83a587c0
--- /dev/null
+++ b/consumer-issuing/src/pages/auth/login.tsx
@@ -0,0 +1,143 @@
+import {
+ Alert,
+ Button,
+ Link,
+ Stack,
+ TextField,
+ Typography,
+} from "@mui/material";
+import { Field, Form, Formik, FormikHelpers } from "formik";
+import { GetServerSidePropsContext } from "next";
+import NextLink from "next/link";
+import router from "next/router";
+import { signIn } from "next-auth/react";
+import { ReactNode, useState } from "react";
+import * as Yup from "yup";
+
+import AuthLayout from "src/layouts/auth/layout";
+import { isDemoMode } from "src/utils/demo-helpers";
+import { getSessionForLoginOrRegisterServerSideProps } from "src/utils/session-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForLoginOrRegisterServerSideProps(context);
+
+ if (session != null) {
+ return { redirect: { destination: "/", permanent: false } };
+ }
+
+ return { props: {} };
+};
+
+const validationSchema = Yup.object({
+ email: Yup.string().max(255).required("Email is required"),
+ password: Yup.string().max(255).required("Password is required"),
+});
+
+const Page = () => {
+ const { callbackUrl } = router.query;
+ const [isContinuingSuccessfully, setIsContinuingSuccessfully] =
+ useState(false);
+
+ const initialValues = {
+ email: "",
+ password: "",
+ submit: null,
+ };
+
+ const handleSubmit = async (
+ values: typeof initialValues,
+ { setStatus, setErrors }: FormikHelpers,
+ ) => {
+ try {
+ setIsContinuingSuccessfully(true);
+ const response = await signIn("credentials", {
+ email: values.email,
+ password: values.password,
+ redirect: false,
+ });
+
+ if (response?.ok) {
+ window.location.href = (callbackUrl ?? "/") as string;
+ } else if (response?.error === "CredentialsSignin") {
+ if (isDemoMode()) {
+ throw new Error(
+ "Incorrect email or password. Demo accounts inactive for 6 months are deleted.",
+ );
+ } else {
+ throw new Error("Incorrect email or password.");
+ }
+ } else {
+ throw new Error("Something went wrong");
+ }
+ } catch (err) {
+ setStatus({ success: false });
+ setErrors({ submit: (err as Error).message });
+ setIsContinuingSuccessfully(false);
+ }
+ };
+
+ return (
+ <>
+
+ Login
+
+ Don't have an account?
+
+ Register
+
+
+
+
+ {({ errors, touched }) => (
+
+ )}
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/auth/register.tsx b/consumer-issuing/src/pages/auth/register.tsx
new file mode 100644
index 00000000..61ca76d6
--- /dev/null
+++ b/consumer-issuing/src/pages/auth/register.tsx
@@ -0,0 +1,240 @@
+import {
+ Alert,
+ Button,
+ Link,
+ Stack,
+ TextField,
+ Typography,
+ MenuItem,
+} from "@mui/material";
+import { Field, Form, Formik, FormikHelpers } from "formik";
+import { GetServerSidePropsContext } from "next";
+import NextLink from "next/link";
+import { signIn } from "next-auth/react";
+import { ReactNode, useState } from "react";
+
+import AuthLayout from "src/layouts/auth/layout";
+// import { COUNTRIES } from "src/types/constants";
+import {
+ PlatformStripeAccount,
+ enabledPlatforms,
+} from "src/utils/account-management-helpers";
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+import { isDemoMode } from "src/utils/demo-helpers";
+import { getSessionForLoginOrRegisterServerSideProps } from "src/utils/session-helpers";
+import validationSchemas from "src/utils/validation-schemas";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForLoginOrRegisterServerSideProps(context);
+
+ if (session != null) {
+ return { redirect: { destination: "/", permanent: false } };
+ }
+
+ const {
+ [PlatformStripeAccount.UK]: enableUK,
+ [PlatformStripeAccount.EU]: enableEU,
+ [PlatformStripeAccount.US]: enableUS,
+ } = enabledPlatforms();
+
+ return {
+ props: {
+ enableUK,
+ enableEU,
+ enableUS,
+ },
+ };
+};
+
+const Page = ({
+ enableUK,
+ enableEU,
+ enableUS,
+}: {
+ enableUK: boolean;
+ enableEU: boolean;
+ enableUS: boolean;
+}) => {
+ const [isContinuingSuccessfully, setIsContinuingSuccessfully] =
+ useState(false);
+
+ const initialValues = {
+ email: "",
+ password: "",
+ // TODO: See if we can improve the way we handle errors from the backend
+ submit: null,
+ ...{
+ country: "US",
+ },
+ };
+
+ const handleSubmit = async (
+ values: typeof initialValues,
+ { setErrors }: FormikHelpers,
+ ) => {
+ setIsContinuingSuccessfully(true);
+ const response = await postApi("/api/register", {
+ email: values.email,
+ password: values.password,
+ country: values.country,
+ });
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ const signInResponse = await signIn("credentials", {
+ email: values.email,
+ password: values.password,
+ callbackUrl: "/",
+ });
+ if (signInResponse?.error) {
+ throw new Error("Something went wrong");
+ }
+ },
+ onError: (error) => {
+ setErrors({ submit: (error as Error).message });
+ setIsContinuingSuccessfully(false);
+ },
+ });
+ };
+
+ return (
+ <>
+
+
+ Create
+ {isDemoMode() ? " a demo account" : " an account"}
+
+
+ Already have an account?
+
+ Log in
+
+
+
+
+ {({ errors, touched, isValid, dirty }) => (
+
+ )}
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/authorizations.tsx b/consumer-issuing/src/pages/authorizations.tsx
new file mode 100644
index 00000000..af7ba2a8
--- /dev/null
+++ b/consumer-issuing/src/pages/authorizations.tsx
@@ -0,0 +1,140 @@
+import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
+import {
+ Box,
+ Container,
+ Stack,
+ Typography,
+ SvgIcon,
+ Card,
+ InputAdornment,
+ OutlinedInput,
+} from "@mui/material";
+import { GetServerSidePropsContext } from "next";
+import React, {
+ ChangeEvent,
+ ReactNode,
+ useCallback,
+ useMemo,
+ useState,
+} from "react";
+import Stripe from "stripe";
+
+import AuthorizationsTable from "src/components/issuing/authorizations-table";
+import { useSelection } from "src/hooks/use-selection";
+import DashboardLayout from "src/layouts/dashboard/layout";
+import { applyPagination } from "src/utils/apply-pagination";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import { getAuthorizations } from "src/utils/stripe-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const { stripeAccount } = session;
+ const responseAuthorizations = await getAuthorizations(stripeAccount);
+
+ return {
+ props: {
+ authorizations: responseAuthorizations.authorizations.data,
+ },
+ };
+};
+
+function useAuthorizations(
+ authorizations: Stripe.Issuing.Authorization[],
+ page: number,
+ rowsPerPage: number,
+): Stripe.Issuing.Authorization[] {
+ return useMemo(() => {
+ return applyPagination(authorizations, page, rowsPerPage);
+ }, [authorizations, page, rowsPerPage]);
+}
+
+function useAuthorizationIds(
+ authorizations: Stripe.Issuing.Authorization[],
+): string[] {
+ return useMemo(() => {
+ return authorizations.map((authorization) => authorization.id);
+ }, [authorizations]);
+}
+
+const Page = ({
+ authorizations: allAuthorizations,
+}: {
+ authorizations: Stripe.Issuing.Authorization[];
+}) => {
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const authorizations = useAuthorizations(
+ allAuthorizations,
+ page,
+ rowsPerPage,
+ );
+ const authorizationsIds = useAuthorizationIds(authorizations);
+ const authorizationsSelection = useSelection(authorizationsIds);
+
+ const handlePageChange = useCallback(
+ (e: React.MouseEvent | null, page: number) => {
+ setPage(page);
+ },
+ [],
+ );
+
+ const handleRowsPerPageChange = useCallback(
+ (e: ChangeEvent) => {
+ const rows = parseInt(e.target.value);
+ setRowsPerPage(rows);
+ },
+ [],
+ );
+
+ return (
+ <>
+
+
+
+ Card transactions
+
+
+
+
+
+
+ }
+ sx={{ maxWidth: 500 }}
+ />
+
+
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/authorizations/[authorizationId].tsx b/consumer-issuing/src/pages/authorizations/[authorizationId].tsx
new file mode 100644
index 00000000..aa1172a1
--- /dev/null
+++ b/consumer-issuing/src/pages/authorizations/[authorizationId].tsx
@@ -0,0 +1,173 @@
+import {
+ Box,
+ Card,
+ CardContent,
+ Container,
+ Grid,
+ Stack,
+ Typography,
+} from "@mui/material";
+import { GetServerSidePropsContext } from "next";
+import React, { ReactNode } from "react";
+import Stripe from "stripe";
+
+import { SeverityPill } from "src/components/severity-pill";
+import DashboardLayout from "src/layouts/dashboard/layout";
+import { SeverityColor } from "src/types/severity-color";
+import { SupportedCountry } from "src/utils/account-management-helpers";
+import {
+ capitalize,
+ formatCurrencyForCountry,
+ formatDateAndTime,
+ titleize,
+} from "src/utils/format";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import { getAuthorizationDetails } from "src/utils/stripe-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const authorizationId = context?.params?.authorizationId?.toString();
+ if (authorizationId === undefined) {
+ throw new Error("authorizationId must be provided");
+ }
+ const { stripeAccount, country } = session;
+ const result = await getAuthorizationDetails(stripeAccount, authorizationId);
+
+ return {
+ props: {
+ authorization: result.authorization,
+ country,
+ },
+ };
+};
+
+const statusMap: Record = {
+ closed: "primary",
+ pending: "warning",
+ reversed: "error",
+};
+
+const Page = ({
+ authorization,
+ country,
+}: {
+ authorization: Stripe.Issuing.Authorization;
+ country: SupportedCountry;
+}) => {
+ const declineReason = authorization.request_history.at(-1)?.reason;
+
+ return (
+
+
+ Authorization details
+
+
+
+
+ ID
+
+
+ {authorization.id}
+
+
+
+
+ Card
+
+
+ β’β’β’β’ {authorization.card.last4}
+
+
+
+
+ Location
+
+
+ {authorization.merchant_data.city +
+ ", " +
+ authorization.merchant_data.state +
+ ", " +
+ authorization.merchant_data.postal_code +
+ ", " +
+ authorization.merchant_data.country}
+
+
+
+
+ Date / Time
+
+
+ {formatDateAndTime(authorization.created)}
+
+
+
+
+ Amount
+
+
+ {formatCurrencyForCountry(authorization.amount, country)}
+
+
+
+
+ Status
+
+
+
+ {authorization.approved ? "Approved" : "Declined"}
+
+
+ {capitalize(authorization.status)}
+
+
+
+
+
+ Merchant
+
+
+ {authorization.merchant_data.name}
+
+
+
+
+ Merchant category
+
+
+ {titleize(
+ authorization.merchant_data.category.replace(/_/g, " "),
+ )}
+
+
+
+ {!authorization.approved && declineReason && (
+
+ Decline reason
+
+
+ {titleize(declineReason.replace(/_/g, " "))}
+
+
+
+ )}
+
+
+
+
+
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/cardholders.tsx b/consumer-issuing/src/pages/cardholders.tsx
new file mode 100644
index 00000000..78fcd0e1
--- /dev/null
+++ b/consumer-issuing/src/pages/cardholders.tsx
@@ -0,0 +1,115 @@
+import { Box, Container, Stack, Typography } from "@mui/material";
+import { GetServerSidePropsContext } from "next";
+import React, {
+ ChangeEvent,
+ ReactNode,
+ useCallback,
+ useMemo,
+ useState,
+} from "react";
+import Stripe from "stripe";
+
+import { useSelection } from "src/hooks/use-selection";
+import DashboardLayout from "src/layouts/dashboard/layout";
+import CardholderCreateWidget from "src/sections/cardholders/cardholder-create-widget";
+import { CardholdersSearch } from "src/sections/cardholders/cardholders-search";
+import CardholdersTable from "src/sections/cardholders/cardholders-table";
+import { applyPagination } from "src/utils/apply-pagination";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import { getCardholders } from "src/utils/stripe-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const { stripeAccount } = session;
+ const responseCardholders = await getCardholders(stripeAccount);
+
+ return {
+ props: {
+ cardholders: responseCardholders.cardholders.data,
+ },
+ };
+};
+
+function useCardholders(
+ cardholders: Stripe.Issuing.Cardholder[],
+ page: number,
+ rowsPerPage: number,
+): Stripe.Issuing.Cardholder[] {
+ return useMemo(() => {
+ return applyPagination(cardholders, page, rowsPerPage);
+ }, [cardholders, page, rowsPerPage]);
+}
+
+function useCardholderIds(cardholders: Stripe.Issuing.Cardholder[]): string[] {
+ return useMemo(() => {
+ return cardholders.map((cardholder) => cardholder.id);
+ }, [cardholders]);
+}
+
+const Page = ({
+ cardholders: allCardholders,
+}: {
+ cardholders: Stripe.Issuing.Cardholder[];
+}) => {
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const cardholders = useCardholders(allCardholders, page, rowsPerPage);
+ const cardholdersIds = useCardholderIds(cardholders);
+ const cardholdersSelection = useSelection(cardholdersIds);
+
+ const handlePageChange = useCallback(
+ (e: React.MouseEvent | null, page: number) => {
+ setPage(page);
+ },
+ [],
+ );
+
+ const handleRowsPerPageChange = useCallback(
+ (e: ChangeEvent) => {
+ const rows = parseInt(e.target.value);
+ setRowsPerPage(rows);
+ },
+ [],
+ );
+
+ return (
+ <>
+
+
+
+
+ Cardholders
+
+
+
+
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/cards.tsx b/consumer-issuing/src/pages/cards.tsx
new file mode 100644
index 00000000..af69b3ea
--- /dev/null
+++ b/consumer-issuing/src/pages/cards.tsx
@@ -0,0 +1,183 @@
+import { Box, Container, Grid, Stack, Typography } from "@mui/material";
+import { loadConnectAndInitialize } from "@stripe/connect-js/pure";
+import {
+ ConnectComponentsProvider,
+ ConnectIssuingCardsList,
+} from "@stripe/react-connect-js";
+import { GetServerSidePropsContext } from "next";
+import React, {
+ ChangeEvent,
+ ReactNode,
+ useCallback,
+ useMemo,
+ useState,
+} from "react";
+import Stripe from "stripe";
+
+import { EmbeddedComponentsSwitcher } from "src/components/embedded-components-switcher";
+import { useSelection } from "src/hooks/use-selection";
+import DashboardLayout from "src/layouts/dashboard/layout";
+import { CardsSearch } from "src/sections/cards/cards-search";
+import CardsTable from "src/sections/cards/cards-table";
+import { postApi } from "src/utils/api-helpers";
+import { applyPagination } from "src/utils/apply-pagination";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import { getCards } from "src/utils/stripe-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const { stripeAccount } = session;
+ const { accountId } = stripeAccount;
+ const responseCards = await getCards(stripeAccount);
+
+ return {
+ props: {
+ cards: responseCards.cards.data,
+ account: accountId,
+ },
+ };
+};
+
+function useCards(
+ cards: Stripe.Issuing.Card[],
+ page: number,
+ rowsPerPage: number,
+): Stripe.Issuing.Card[] {
+ return useMemo(() => {
+ return applyPagination(cards, page, rowsPerPage);
+ }, [cards, page, rowsPerPage]);
+}
+
+function useCardIds(cards: Stripe.Issuing.Card[]): string[] {
+ return useMemo(() => {
+ return cards.map((card) => card.id);
+ }, [cards]);
+}
+
+const Page = ({ cards: allCards }: { cards: Stripe.Issuing.Card[] }) => {
+ const [page, setPage] = useState(0);
+ const [rowsPerPage, setRowsPerPage] = useState(5);
+ const cards = useCards(allCards, page, rowsPerPage);
+ const cardsIds = useCardIds(cards);
+ const cardsSelection = useSelection(cardsIds);
+
+ const [useEmbeddedComponents, setUseEmbeddedComponents] =
+ React.useState(false);
+
+ const [stripeConnectInstance] = React.useState(() => {
+ const fetchClientSecret = async () => {
+ // Fetch the AccountSession client secret
+ const response = await postApi("/api/create_account_session", {
+ method: "POST",
+ });
+ if (!response.ok) {
+ return undefined;
+ } else {
+ const { client_secret: clientSecret } = await response.json();
+ return clientSecret;
+ }
+ };
+ return loadConnectAndInitialize({
+ // This is your test publishable API key.
+ publishableKey: process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string,
+ fetchClientSecret: fetchClientSecret,
+ appearance: {
+ overlays: "dialog",
+ variables: {
+ colorPrimary: "#625afa",
+ },
+ },
+ });
+ });
+
+ const handlePageChange = useCallback(
+ (e: React.MouseEvent | null, page: number) => {
+ setPage(page);
+ },
+ [],
+ );
+
+ const handleRowsPerPageChange = useCallback(
+ (e: ChangeEvent) => {
+ const rows = parseInt(e.target.value);
+ setRowsPerPage(rows);
+ },
+ [],
+ );
+
+ return (
+ <>
+
+
+
+
+ Cards
+ {/* */}
+
+ {useEmbeddedComponents ? (
+ <>
+
+
+
+
+
+
+
+ setUseEmbeddedComponents(!useEmbeddedComponents)
+ }
+ />
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+ setUseEmbeddedComponents(!useEmbeddedComponents)
+ }
+ />
+
+ >
+ )}
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/cards/[cardId].tsx b/consumer-issuing/src/pages/cards/[cardId].tsx
new file mode 100644
index 00000000..c5eff6ed
--- /dev/null
+++ b/consumer-issuing/src/pages/cards/[cardId].tsx
@@ -0,0 +1,167 @@
+import {
+ Avatar,
+ Box,
+ Card,
+ CardContent,
+ Container,
+ Grid,
+ Stack,
+ Typography,
+} from "@mui/material";
+import { Elements } from "@stripe/react-stripe-js";
+import { loadStripe } from "@stripe/stripe-js";
+import { GetServerSidePropsContext } from "next";
+import React, { ReactNode } from "react";
+import Stripe from "stripe";
+
+import CurrencyIcon from "src/components/currency-icon";
+import FloatingTestPanel from "src/components/floating-test-panel";
+import DashboardLayout from "src/layouts/dashboard/layout";
+import CardDetails from "src/sections/[cardId]/card-details";
+import CardIllustration from "src/sections/[cardId]/card-illustration";
+import LatestCardAuthorizations from "src/sections/[cardId]/latest-card-authorizations";
+import TestDataCreateAuthorization from "src/sections/test-data/test-data-create-authorization";
+import {
+ CountryConfigMap,
+ StripeAccount,
+ SupportedCountry,
+} from "src/utils/account-management-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import { getStripePublishableKey } from "src/utils/stripe-authentication";
+import { getCardDetails } from "src/utils/stripe-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const cardId = context?.params?.cardId?.toString();
+ if (cardId === undefined) {
+ throw new Error("cardId must be provided");
+ }
+ const { stripeAccount, country } = session;
+ const cardTransactions = await getCardDetails(stripeAccount, cardId);
+
+ return {
+ props: {
+ authorizations: cardTransactions.card_authorizations,
+ currentSpend: cardTransactions.current_spend,
+ stripeAccount: stripeAccount,
+ cardId: context?.params?.cardId,
+ card: cardTransactions.card_details,
+ country: country,
+ },
+ };
+};
+
+const Page = ({
+ authorizations,
+ currentSpend,
+ stripeAccount,
+ cardId,
+ card,
+ country,
+}: {
+ authorizations: Stripe.Issuing.Authorization[];
+ currentSpend: number;
+ stripeAccount: StripeAccount;
+ cardId: string;
+ card: Stripe.Issuing.Card;
+ country: SupportedCountry;
+}) => {
+ const { accountId, platform } = stripeAccount;
+ const stripePublishableKey = getStripePublishableKey(platform);
+
+ if (!stripePublishableKey) {
+ throw new Error("NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY must be defined");
+ }
+
+ const stripePromise = loadStripe(stripePublishableKey, {
+ stripeAccount: accountId,
+ });
+
+ const spendingLimit = card.spending_controls.spending_limits?.[0];
+ const spendingLimitDisplay =
+ spendingLimit != undefined
+ ? `${formatCurrencyForCountry(spendingLimit.amount, country)} ${spendingLimit.interval}`
+ : "No spending limit set";
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Current spend
+
+
+ {formatCurrencyForCountry(currentSpend, country)}
+
+
+ Spending limit:
+
+
+ {spendingLimitDisplay}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/index.tsx b/consumer-issuing/src/pages/index.tsx
new file mode 100644
index 00000000..c72bc536
--- /dev/null
+++ b/consumer-issuing/src/pages/index.tsx
@@ -0,0 +1,140 @@
+import Box from "@mui/material/Box";
+import Container from "@mui/material/Container";
+import Grid from "@mui/material/Grid";
+import { GetServerSidePropsContext } from "next";
+import React, { ReactNode } from "react";
+import Stripe from "stripe";
+
+import FloatingTestPanel from "src/components/floating-test-panel";
+import DashboardLayout from "src/layouts/dashboard/layout";
+import { OverviewBalanceFundsFlowChart } from "src/sections/overview/overview-balance-funds-flow-chart";
+import { OverviewCreditStatement } from "src/sections/overview/overview-credit-limit";
+import { OverviewIssuingBalance } from "src/sections/overview/overview-issuing-balance";
+import { OverviewLatestBalanceTransactions } from "src/sections/overview/overview-latest-balance-transactions";
+import { OverviewAvailableBalance } from "src/sections/overview/overview-payments-balance";
+import TestDataTopUpIssuingBalance from "src/sections/test-data/test-data-create-issuing-topup";
+import { BalanceChartData } from "src/types/chart-data";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import { getBalance, getCreditLedgerEntries } from "src/utils/stripe-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const { stripeAccount, currency } = session;
+
+ let issuingBalance = null;
+ let availableBalance = null;
+ let creditLedgerEntries = null;
+ let balanceFundsFlowChartData = null;
+
+ const responseCreditLedgerEntries = await getCreditLedgerEntries(
+ stripeAccount,
+ currency,
+ session
+ );
+ creditLedgerEntries = responseCreditLedgerEntries.balanceTransactions;
+ balanceFundsFlowChartData =
+ responseCreditLedgerEntries.balanceFundsFlowChartData;
+
+ const responseAccountBalance = await getBalance(stripeAccount, session);
+ issuingBalance = responseAccountBalance.balance.issuing;
+ availableBalance = responseAccountBalance.balance;
+
+ return {
+ props: {
+ issuingBalance,
+ availableBalance,
+ creditLedgerEntries,
+ balanceFundsFlowChartData,
+ },
+ };
+};
+
+const IssuingBalanceStuff = ({
+ issuingBalance,
+ availableBalance,
+ balanceFundsFlowChartData,
+ creditLedgerEntries,
+}: {
+ issuingBalance: Stripe.Balance.Issuing;
+ availableBalance: Stripe.Balance;
+ creditLedgerEntries: Stripe.BalanceTransaction[];
+ balanceFundsFlowChartData: BalanceChartData;
+}) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const Page = ({
+ issuingBalance,
+ availableBalance,
+ creditLedgerEntries,
+ balanceFundsFlowChartData,
+}: {
+ issuingBalance: Stripe.Balance.Issuing;
+ availableBalance: Stripe.Balance;
+ creditLedgerEntries: Stripe.BalanceTransaction[];
+ balanceFundsFlowChartData: BalanceChartData;
+}) => {
+ const BalanceWidget = (() => {
+ return IssuingBalanceStuff({
+ issuingBalance,
+ availableBalance,
+ creditLedgerEntries,
+ balanceFundsFlowChartData,
+ });
+ })();
+
+ const TestDataGenerationPanel = (() => {
+ return (
+
+
+
+ );
+ })();
+
+ return (
+ <>
+
+ {BalanceWidget}
+
+
+ {TestDataGenerationPanel}
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/onboard.tsx b/consumer-issuing/src/pages/onboard.tsx
new file mode 100644
index 00000000..bed933bc
--- /dev/null
+++ b/consumer-issuing/src/pages/onboard.tsx
@@ -0,0 +1,268 @@
+import {
+ Stack,
+ Typography,
+ Button,
+ Checkbox,
+ FormControlLabel,
+ Alert,
+ TextField,
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ DialogActions,
+ Chip,
+ Divider,
+} from "@mui/material";
+import { Formik, Form, Field, FormikHelpers } from "formik";
+import { signOut } from "next-auth/react";
+import React, { ChangeEvent, ReactNode, useState } from "react";
+
+import AuthLayout from "src/layouts/auth/layout";
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+import { isDemoMode } from "src/utils/demo-helpers";
+import validationSchemas from "src/utils/validation-schemas";
+
+const Page = () => {
+ const [isContinuingSuccessfully, setIsContinuingSuccessfully] =
+ useState(false);
+ const [showConnectOnboardingGuide, setShowConnectOnboardingGuide] =
+ useState(false);
+ const [isLoggingOut, setIsLoggingOut] = useState(false);
+
+ const initialValues = {
+ businessName: "",
+ ...(isDemoMode() && {
+ // FOR-DEMO-ONLY: We're using a fake business name here but you should modify this line and collect a real business
+ // name from the user
+ businessName: `Jenny Rosen`,
+ skipOnboarding: true,
+ }),
+ // TODO: See if we can improve the way we handle errors from the backend
+ submit: null,
+ };
+
+ type OnboardResponse = {
+ redirectUrl: string;
+ };
+
+ const handleSubmit = async (
+ values: typeof initialValues,
+ { setErrors }: FormikHelpers,
+ ) => {
+ setIsContinuingSuccessfully(true);
+ const response = await postApi("/api/onboard", {
+ businessName: values.businessName,
+ ...(isDemoMode() && { skipOnboarding: values.skipOnboarding }),
+ });
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ if (result.data && "redirectUrl" in result.data) {
+ const data = result.data as OnboardResponse;
+ const redirectUrl = data.redirectUrl;
+ window.location.replace(redirectUrl);
+ } else {
+ throw new Error("Something went wrong");
+ }
+ },
+ onError: (error) => {
+ setErrors({ submit: (error as Error).message });
+ setIsContinuingSuccessfully(false);
+ },
+ });
+ };
+
+ const handleLogout = async (e: React.MouseEvent) => {
+ e.preventDefault();
+ await signOut();
+ setIsLoggingOut(true);
+ };
+
+ return (
+ <>
+
+ Enter your name
+ {isDemoMode() && (
+
+ <>
+ We'll create a demo account for this business. Accounts are
+ deleted after 6 months of inactivity.
+ >
+
+ )}
+
+
+ {({ errors, touched, values, isValid, setFieldValue }) => {
+ const submitButtonText = values.skipOnboarding
+ ? isContinuingSuccessfully
+ ? "Entering demo..."
+ : "Enter demo"
+ : isContinuingSuccessfully
+ ? "Continuing..."
+ : "Continue";
+
+ return (
+
+ );
+ }}
+
+
+ Log out
+
+ >
+ );
+};
+
+const ConnectOnboardingGuideDialog = ({
+ showConnectOnboardingGuide,
+ setShowConnectOnboardingGuide,
+}: {
+ showConnectOnboardingGuide: boolean;
+ setShowConnectOnboardingGuide: (show: boolean) => void;
+}) => (
+ setShowConnectOnboardingGuide(false)}
+ >
+
+ Use these test values in Stripe's hosted onboarding form
+
+
+
+ {/* Hello World */}
+
+
+ To complete onboarding, you'll be redirected to Stripe's
+ hosted onboarding form.
+
+
+ We've prefilled the account data for this demo. If you don't
+ follow these steps, you'll get an error.
+
+
+
+
+ On the "Let's get started" screen, enter:
+
+
+
+
+ Mobile Number:{" "}
+
+
+
+
+
+ Email:{" "}
+
+
+
+
+
+
+
+ At "Verify your mobile number", click{" "}
+
+
+
+
+
+ At "Verify your identity", click{" "}
+
+
+
+
+
+
+
+
+ {
+ setShowConnectOnboardingGuide(false);
+ }}
+ >
+ Confirm
+
+
+
+);
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/settings.tsx b/consumer-issuing/src/pages/settings.tsx
new file mode 100644
index 00000000..4ed53f5a
--- /dev/null
+++ b/consumer-issuing/src/pages/settings.tsx
@@ -0,0 +1,30 @@
+import { Box, Container, Stack, Typography } from "@mui/material";
+import React, { ReactNode } from "react";
+
+import DashboardLayout from "src/layouts/dashboard/layout";
+import SettingsDeleteAccount from "src/sections/settings/settings-delete-account";
+
+const Page = () => {
+ return (
+ <>
+
+
+
+ Settings
+
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/test-data.tsx b/consumer-issuing/src/pages/test-data.tsx
new file mode 100644
index 00000000..653c376c
--- /dev/null
+++ b/consumer-issuing/src/pages/test-data.tsx
@@ -0,0 +1,79 @@
+import { Box, Container, Grid } from "@mui/material";
+import { GetServerSidePropsContext } from "next";
+import React, { ReactNode } from "react";
+
+import DashboardLayout from "src/layouts/dashboard/layout";
+import TestDataCreatePaymentLink from "src/sections/test-data/test-data-create-payment-link";
+import TestDataCreatePayoutsToBank from "src/sections/test-data/test-data-create-payout-to-bank";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const { stripeAccount } = session;
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ const responseAccount = await stripe.accounts.retrieve(accountId);
+
+ const hasExternalAccount =
+ responseAccount.external_accounts?.data[0] != undefined;
+
+ const responseBalance = await stripe.balance.retrieve({
+ stripeAccount: accountId,
+ });
+
+ const availableBalance = responseBalance.available[0].amount;
+
+ return {
+ props: {
+ hasExternalAccount,
+ availableBalance,
+ },
+ };
+};
+
+const Page = ({
+ hasExternalAccount,
+ availableBalance,
+}: {
+ hasExternalAccount: boolean;
+ availableBalance: number;
+}) => {
+ const PayoutsWidget = (() => {
+ return (
+
+ );
+ })();
+
+ return (
+ <>
+
+
+
+
+
+
+
+ {PayoutsWidget}
+
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/pages/top-ups.tsx b/consumer-issuing/src/pages/top-ups.tsx
new file mode 100644
index 00000000..821ecd66
--- /dev/null
+++ b/consumer-issuing/src/pages/top-ups.tsx
@@ -0,0 +1,229 @@
+import BusinessIcon from "@mui/icons-material/Business";
+import ForkRightIcon from "@mui/icons-material/ForkRight";
+import LanguageIcon from "@mui/icons-material/Language";
+import NumbersIcon from "@mui/icons-material/Numbers";
+import {
+ Box,
+ Card,
+ CardContent,
+ Container,
+ List,
+ ListItem,
+ ListItemIcon,
+ ListItemText,
+ Stack,
+ Typography,
+} from "@mui/material";
+import { GetServerSidePropsContext } from "next";
+import React, { ReactNode } from "react";
+import Stripe from "stripe";
+
+import FloatingTestPanel from "src/components/floating-test-panel";
+import DashboardLayout from "src/layouts/dashboard/layout";
+import TestDataTopUpIssuingBalance from "src/sections/test-data/test-data-create-issuing-topup";
+import { SupportedCountry } from "src/utils/account-management-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+import { getSessionForServerSideProps } from "src/utils/session-helpers";
+import {
+ FinancialAddress,
+ FundingInstructions,
+ createFundingInstructions,
+ getBalance,
+} from "src/utils/stripe-helpers";
+
+export const getServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getSessionForServerSideProps(context);
+ const { stripeAccount, country, currency } = session;
+
+ // To top up an Issuing balance, you (or your user) will send a bank
+ // transfer to a Stripe-owned account. Each user will have their own
+ // unique destination to which transfers can be made, and any received
+ // transfers will be credited to that user's Issuing balance.
+ // Create or retrieve Funding Instructions[0] to get the correct
+ // destination bank account details for a user.
+ //
+ // [0] https://stripe.com/docs/api/issuing/funding_instructions
+ const fundingInstructions = await createFundingInstructions(
+ stripeAccount,
+ country.toString(),
+ currency,
+ );
+
+ const response = await getBalance(stripeAccount, session);
+ const availableBalance = response.balance.issuing?.available[0];
+
+ return {
+ props: { fundingInstructions, availableBalance, country },
+ };
+};
+
+// When creating Funding Instructions, Stripe will assign a destination
+// bank account in an appropriate country for the user. Topups must be
+// sent as either FPS/BACS or SEPA credit transfers, depending on the
+// banking rails used in the country the bank account is created in.
+const FPSTransferTopupInstructions = ({
+ financialAddress,
+}: {
+ financialAddress: FinancialAddress;
+}) => {
+ const sortCode = financialAddress.sort_code;
+
+ if (!sortCode) {
+ return <>>;
+ }
+
+ return (
+ <>
+
+ Send an FPS or BACS credit transfer to the following bank account to top
+ up your Issuing balance.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+const SEPATransferTopupInstructions = ({
+ financialAddress,
+}: {
+ financialAddress: FinancialAddress;
+}) => {
+ const iban = financialAddress.iban;
+
+ if (!iban) {
+ return <>>;
+ }
+
+ return (
+ <>
+
+ Send an SEPA credit transfer to the following bank account to top up
+ your Issuing balance.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+const Page = ({
+ fundingInstructions,
+ availableBalance,
+ country,
+}: {
+ fundingInstructions: FundingInstructions;
+ availableBalance: Stripe.Balance.Available;
+ country: SupportedCountry;
+}) => {
+ return (
+ <>
+
+
+
+ Top up your Issuing balance
+
+
+
+
+
+ Your Issuing balance is currently{" "}
+
+ {formatCurrencyForCountry(
+ availableBalance.amount,
+ country,
+ )}
+
+
+
+ {fundingInstructions.bank_transfer.type ==
+ "gb_bank_transfer" ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+Page.getLayout = (page: ReactNode) => {page} ;
+
+export default Page;
diff --git a/consumer-issuing/src/sections/[cardId]/card-details.tsx b/consumer-issuing/src/sections/[cardId]/card-details.tsx
new file mode 100644
index 00000000..7319f170
--- /dev/null
+++ b/consumer-issuing/src/sections/[cardId]/card-details.tsx
@@ -0,0 +1,90 @@
+import { Typography, Box, Stack } from "@mui/material";
+import React from "react";
+import Stripe from "stripe";
+
+import { SeverityPill } from "src/components/severity-pill";
+import CardStatusSwitcher from "src/sections/[cardId]/card-status-switcher";
+import { SeverityColor } from "src/types/severity-color";
+import { capitalize, formatDateTime } from "src/utils/format";
+
+const statusMap: Record = {
+ active: "success",
+ canceled: "error",
+ inactive: "warning",
+};
+
+const CardDetails = ({ card }: { card: Stripe.Issuing.Card }) => {
+ return (
+
+
+
+
+
+ Card Type
+
+ {capitalize(card.type)}
+
+
+
+ Card Created
+
+
+ {formatDateTime(card.created)}
+
+
+
+
+
+
+
+ Card Currency
+
+
+ {card.currency.toUpperCase()}
+
+
+
+
+ Status:
+
+
+
+ {capitalize(card.status)}
+
+
+
+
+
+
+ Billing Address
+
+
+
+ {card.cardholder.billing.address.line1}
+
+
+ {card.cardholder.billing.address.line2}
+
+
+ {card.cardholder.billing.address.city} -{" "}
+ {card.cardholder.billing.address.state}{" "}
+ {card.cardholder.billing.address.postal_code}
+
+
+
+
+
+ );
+};
+
+export default CardDetails;
diff --git a/consumer-issuing/src/sections/[cardId]/card-illustration.tsx b/consumer-issuing/src/sections/[cardId]/card-illustration.tsx
new file mode 100644
index 00000000..23f24343
--- /dev/null
+++ b/consumer-issuing/src/sections/[cardId]/card-illustration.tsx
@@ -0,0 +1,203 @@
+import { Slide, Stack, useTheme } from "@mui/material";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import { useStripe, useElements } from "@stripe/react-stripe-js";
+import Image from "next/image";
+import { useEffect, useState } from "react";
+import Stripe from "stripe";
+
+import { extractJsonFromResponse, postApi } from "src/utils/api-helpers";
+
+const brandIcon: Record = {
+ Mastercard: "/assets/logos/logo-mastercard.svg",
+ VISA: "/assets/logos/logo-visa.svg",
+};
+
+const CardIllustration = ({
+ cardId,
+ card,
+ brand,
+}: {
+ cardId: string;
+ card: Stripe.Issuing.Card;
+ brand: string;
+}) => {
+ const stripe = useStripe();
+ const elements = useElements();
+ const theme = useTheme();
+ const [issuingElementsMounted, setIssuingElementsMounted] = useState(false);
+
+ useEffect(
+ () => {
+ const renderCard = async () => {
+ if (
+ !stripe ||
+ !elements ||
+ // Only retrieve the card details it's a virtual card. Sensitive physical card details are accessible using
+ // Issuing Elements only in testmode for development use which would work here for this demo but not in
+ // production. In order to be as close as possible to the production experience, we're only retrieving the card
+ // details for virtual cards.
+ card.type === "physical"
+ ) {
+ return;
+ }
+
+ const elementsStyle = {
+ base: {
+ color: "white",
+ fontFamily: theme.typography.fontFamily,
+ fontSize: "16px",
+ fontWeight: 700,
+ },
+ };
+
+ const nonceResult = await stripe.createEphemeralKeyNonce({
+ issuingCard: cardId,
+ });
+
+ const response = await postApi(`/api/cards/${cardId}/card-keys`, {
+ nonce: nonceResult.nonce,
+ });
+ const result = await extractJsonFromResponse<{
+ ephemeralKey: string;
+ secret: string;
+ }>(response);
+ if (result.data == undefined) {
+ throw new Error("Something went wrong");
+ }
+
+ const ephemeralKeyResult = result.data;
+
+ // Populate the raw card details
+ const cardNumberStyle = {
+ base: {
+ ...elementsStyle.base,
+ ...{
+ fontSize: "24px",
+ letterSpacing: "0.1em",
+ backgroundClip: "text",
+ textFillColor: "transparent",
+ },
+ },
+ };
+ elements
+ .create("issuingCardNumberDisplay", {
+ issuingCard: cardId,
+ ephemeralKeySecret: ephemeralKeyResult.secret,
+ nonce: nonceResult.nonce,
+ style: cardNumberStyle,
+ })
+ .mount("#card-number");
+
+ elements
+ .create("issuingCardExpiryDisplay", {
+ issuingCard: cardId,
+ ephemeralKeySecret: ephemeralKeyResult.secret,
+ nonce: nonceResult.nonce,
+ style: elementsStyle,
+ })
+ .mount("#card-expiry");
+
+ elements
+ .create("issuingCardCvcDisplay", {
+ issuingCard: cardId,
+ ephemeralKeySecret: ephemeralKeyResult.secret,
+ nonce: nonceResult.nonce,
+ style: elementsStyle,
+ })
+ .mount("#card-cvc");
+ };
+
+ setIssuingElementsMounted(true);
+
+ renderCard();
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [stripe, elements],
+ );
+
+ return (
+
+
+
+
+
+
+ {/*
+ Setting the height of 30px for the card number element so that the card doesn't get resized when the element
+ info is being mounted.
+ */}
+
+
+
+
+
+
+ Cardholder name
+
+
+ {card.cardholder.name}
+
+
+ {card.type === "virtual" && (
+ <>
+
+
+ Expiry date
+
+
+
+
+
+ CVC
+
+
+
+ >
+ )}
+
+
+
+
+
+
+ );
+};
+
+export default CardIllustration;
diff --git a/consumer-issuing/src/sections/[cardId]/card-status-switcher.tsx b/consumer-issuing/src/sections/[cardId]/card-status-switcher.tsx
new file mode 100644
index 00000000..a7352f0b
--- /dev/null
+++ b/consumer-issuing/src/sections/[cardId]/card-status-switcher.tsx
@@ -0,0 +1,111 @@
+import {
+ Button,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+} from "@mui/material";
+import { useRouter } from "next/router";
+import React, { useState } from "react";
+import Stripe from "stripe";
+
+import {
+ extractJsonFromResponse,
+ handleResult,
+ patchApi,
+} from "src/utils/api-helpers";
+
+function CardStatusSwitcher({
+ cardId,
+ cardStatus,
+}: {
+ cardId: string;
+ cardStatus: Stripe.Issuing.Card.Status;
+}) {
+ const [submitting, setSubmitting] = useState(false);
+ const [showErrorAlert, setShowErrorAlert] = useState(false);
+ const [errorAlertText, setErrorAlertText] = useState("");
+ const router = useRouter();
+
+ const handleSwitchCardStatus = async (
+ e: React.MouseEvent,
+ ) => {
+ e.preventDefault();
+ setSubmitting(true);
+ const newStatus = cardStatus === "active" ? "inactive" : "active";
+ const response = await patchApi(`/api/cards/${cardId}/switch-card-status`, {
+ newStatus: newStatus,
+ });
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ await router.push(`/cards/${cardId}`);
+ },
+ onError: (error) => {
+ setErrorAlertText(`Error: ${error.message}`);
+ setShowErrorAlert(true);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ const handleErrorAlertClose = async (
+ e: React.MouseEvent,
+ ) => {
+ e.preventDefault();
+ setShowErrorAlert(false);
+ setSubmitting(false);
+ };
+
+ return (
+ <>
+ {cardStatus != "canceled" ? (
+ <>
+ {cardStatus == "inactive" ? (
+
+ {submitting ? "Activating Card..." : "Activate Card"}
+
+ ) : (
+
+ {submitting ? "Deactivating Card..." : "Deactivate Card"}
+
+ )}
+
+
+ Changing Card Status Error
+
+
+
+ {errorAlertText}
+
+
+
+
+ Close
+
+
+
+ >
+ ) : null}
+ >
+ );
+}
+export default CardStatusSwitcher;
diff --git a/consumer-issuing/src/sections/[cardId]/latest-card-authorizations.tsx b/consumer-issuing/src/sections/[cardId]/latest-card-authorizations.tsx
new file mode 100644
index 00000000..a8783688
--- /dev/null
+++ b/consumer-issuing/src/sections/[cardId]/latest-card-authorizations.tsx
@@ -0,0 +1,153 @@
+import { ArrowRightIcon } from "@heroicons/react/20/solid";
+import {
+ Card,
+ CardHeader,
+ Box,
+ Table,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableBody,
+ Typography,
+ Divider,
+ Button,
+ SvgIcon,
+} from "@mui/material";
+import { useSession } from "next-auth/react";
+import React from "react";
+import Stripe from "stripe";
+
+import { Scrollbar } from "src/components/scrollbar";
+import { SeverityPill } from "src/components/severity-pill";
+import { SeverityColor } from "src/types/severity-color";
+import {
+ capitalize,
+ formatCurrencyForCountry,
+ formatDateTime,
+} from "src/utils/format";
+
+const statusMap: Record = {
+ closed: "primary",
+ pending: "warning",
+ reversed: "error",
+};
+
+const LatestCardAuthorizations = ({
+ authorizations,
+ sx,
+}: {
+ authorizations: Stripe.Issuing.Authorization[];
+ sx?: object;
+}) => {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ return (
+ <>
+
+
+
+
+ {authorizations.length > 0 ? (
+
+
+
+ Date
+ Amount
+ {/* Approved? */}
+ Status
+ Merchant
+ Merchant Category
+
+
+
+
+ {authorizations.map((authorization) => {
+ const category =
+ authorization.merchant_data.category.replace(/_/g, " ");
+ return (
+
+
+ {formatDateTime(authorization.created)}
+
+
+ {formatCurrencyForCountry(
+ authorization.amount,
+ country,
+ )}
+
+
+
+ {authorization.approved ? "Approved" : "Declined"}
+
+
+
+
+ {capitalize(authorization.status)}
+
+
+
+
+ {authorization.merchant_data.name}
+
+
+
+ {category}
+
+
+
+
+
+ }
+ href={`/authorizations/${authorization.id}`}
+ >
+ Details
+
+
+
+ );
+ })}
+
+
+ ) : (
+ <>
+
+
+
+ There are no issuing authorizations for this card.
+
+
+ >
+ )}
+
+
+
+ >
+ );
+};
+
+export default LatestCardAuthorizations;
diff --git a/consumer-issuing/src/sections/cardholders/cardholder-create-widget.tsx b/consumer-issuing/src/sections/cardholders/cardholder-create-widget.tsx
new file mode 100644
index 00000000..2fb287f6
--- /dev/null
+++ b/consumer-issuing/src/sections/cardholders/cardholder-create-widget.tsx
@@ -0,0 +1,353 @@
+import { Faker } from "@faker-js/faker";
+import {
+ Alert,
+ Button,
+ Checkbox,
+ CheckboxProps,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Divider,
+ FormControl,
+ FormControlLabel,
+ FormHelperText,
+ Grid,
+ Link,
+ MenuItem,
+ TextField,
+ Typography,
+} from "@mui/material";
+import { Formik, Form, Field, FormikProps, FormikValues } from "formik";
+import { useRouter } from "next/router";
+import { useSession } from "next-auth/react";
+import React, { RefObject, useRef } from "react";
+
+import { getSupportedCountryConfigsInRegion } from "src/utils/account-management-helpers";
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+import {
+ getFakeAddressByCountry,
+ getFakePhoneByCountry,
+ LocalizedFakerMap,
+} from "src/utils/demo-helpers";
+import validationSchemas from "src/utils/validation-schemas";
+
+const CreateCardholderForm = ({
+ formRef,
+ onCreate,
+}: {
+ formRef: RefObject>;
+ onCreate: () => void;
+}) => {
+ const router = useRouter();
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ const validationSchema = (() => {
+ // PSD2 requires most transactions on payment cards issued in the EU and UK
+ // to be authenticated in order to proceed. This is called Strong Customer
+ // Authentication[0], or SCA. Stripe typically uses 3D Secure[1] to authenticate
+ // transactions, which requires a phone number to send an OTP to via SMS.
+ // So phone numbers must be mandatorily collected for cardholders of cards
+ // issued by EU or UK Stripe Issuing users.
+ //
+ // [0] https://stripe.com/docs/strong-customer-authentication
+ // [1] https://stripe.com/docs/issuing/3d-secure
+ return validationSchemas.cardholder.withSCA;
+ })();
+
+ const initialValues = {
+ firstName: "",
+ lastName: "",
+ email: "",
+ phoneNumber: "",
+ address1: "",
+ city: "",
+ state: "",
+ postalCode: "",
+ country: country,
+ accept: false,
+ };
+
+ const [errorText, setErrorText] = React.useState("");
+
+ // Get all the countries that are supported by the platform Stripe Account
+ const countryConfigs = getSupportedCountryConfigsInRegion(country);
+
+ const handleSubmit = async (
+ values: FormikValues,
+ { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
+ ) => {
+ const response = await postApi("api/cardholders", values);
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ await router.push("/cardholders");
+ onCreate();
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ // For SCA in EU/UK
+ const phoneNumberRequired = () => {
+ return true;
+ };
+
+ return (
+
+ {({ errors, touched }) => (
+
+ )}
+
+ );
+};
+
+const CardholderCreateWidget = () => {
+ const [showModal, setShowModal] = React.useState(false);
+
+ const formRef = useRef>(null);
+
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ const handleAutofill = () => {
+ const form = formRef.current;
+ if (form) {
+ const faker = LocalizedFakerMap[country] as Faker;
+
+ const generateNamesWithMaxLength = (maxLength: number) => {
+ let firstName, lastName;
+ do {
+ firstName = faker.person.firstName();
+ lastName = faker.person.lastName();
+ } while (firstName.length + lastName.length >= maxLength);
+ return { firstName, lastName };
+ };
+ const { firstName, lastName } = generateNamesWithMaxLength(24);
+
+ const fakeAddress = getFakeAddressByCountry(country);
+
+ form.setValues({
+ firstName: firstName,
+ lastName: lastName,
+ email: faker.internet.email().toLowerCase(),
+ phoneNumber: getFakePhoneByCountry(country),
+ address1: fakeAddress.address1,
+ city: fakeAddress.city,
+ state: fakeAddress.state,
+ postalCode: fakeAddress.postalCode,
+ country: country,
+ accept: true,
+ });
+ }
+ };
+
+ const handleSubmit = async () => {
+ const form = formRef.current;
+ if (form) {
+ form.submitForm();
+ }
+ };
+
+ const handleCreate = () => {
+ setShowModal(false);
+ };
+
+ return (
+
+
setShowModal(true)} variant="contained">
+ Create a new cardholder
+
+
setShowModal(false)}
+ aria-labelledby="modal-modal-title"
+ aria-describedby="modal-modal-description"
+ id="new-cardholder-modal"
+ >
+ Add new cardholder
+
+
+
+
+
+
+ Autofill with test data
+
+ {formRef.current?.isSubmitting
+ ? "Adding cardholder..."
+ : "Add cardholder"}
+
+
+
+
+ );
+};
+
+export default CardholderCreateWidget;
diff --git a/consumer-issuing/src/sections/cardholders/cardholder-update-widget.tsx b/consumer-issuing/src/sections/cardholders/cardholder-update-widget.tsx
new file mode 100644
index 00000000..4dbe930b
--- /dev/null
+++ b/consumer-issuing/src/sections/cardholders/cardholder-update-widget.tsx
@@ -0,0 +1,133 @@
+import {
+ Button,
+ Checkbox,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Divider,
+ FormControlLabel,
+ Grid,
+ Link,
+ Typography,
+} from "@mui/material";
+import { Formik, Form, Field, FormikValues } from "formik";
+import { useRouter } from "next/router";
+import React from "react";
+import * as Yup from "yup";
+
+import {
+ extractJsonFromResponse,
+ handleResult,
+ putApi,
+} from "src/utils/api-helpers";
+
+const validationSchema = Yup.object({
+ accept: Yup.boolean().oneOf(
+ [true],
+ "You must accept the terms and privacy policy",
+ ),
+});
+
+const initialValues = {
+ accept: false,
+};
+
+const CardholderUpdateWidget = ({ cardholderId }: { cardholderId: string }) => {
+ const router = useRouter();
+
+ const [showModal, setShowModal] = React.useState(false);
+ const [errorText, setErrorText] = React.useState("");
+
+ const handleSubmit = async (
+ values: FormikValues,
+ { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
+ ) => {
+ const response = await putApi("api/cardholders", {
+ cardholderId: cardholderId,
+ });
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ await router.push("/cardholders");
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ return (
+
+
setShowModal(true)} variant="contained">
+ Update
+
+
setShowModal(false)}
+ aria-labelledby="modal-modal-title"
+ aria-describedby="modal-modal-description"
+ >
+ Update Cardholder
+
+
+ {({ isSubmitting }) => (
+
+ )}
+
+
+
+ );
+};
+
+export default CardholderUpdateWidget;
diff --git a/consumer-issuing/src/sections/cardholders/cardholders-search.tsx b/consumer-issuing/src/sections/cardholders/cardholders-search.tsx
new file mode 100644
index 00000000..e73856b8
--- /dev/null
+++ b/consumer-issuing/src/sections/cardholders/cardholders-search.tsx
@@ -0,0 +1,20 @@
+import MagnifyingGlassIcon from "@heroicons/react/24/solid/MagnifyingGlassIcon";
+import { Card, InputAdornment, OutlinedInput, SvgIcon } from "@mui/material";
+
+export const CardholdersSearch = () => (
+
+
+
+
+
+
+ }
+ sx={{ maxWidth: 500 }}
+ />
+
+);
diff --git a/consumer-issuing/src/sections/cardholders/cardholders-table.tsx b/consumer-issuing/src/sections/cardholders/cardholders-table.tsx
new file mode 100644
index 00000000..6a09a381
--- /dev/null
+++ b/consumer-issuing/src/sections/cardholders/cardholders-table.tsx
@@ -0,0 +1,159 @@
+import {
+ Box,
+ Button,
+ Card,
+ Checkbox,
+ MenuItem,
+ Select,
+ Stack,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TablePagination,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import React, { ChangeEvent } from "react";
+import Stripe from "stripe";
+
+import { Scrollbar } from "src/components/scrollbar";
+import CardholderUpdateWidget from "src/sections/cardholders/cardholder-update-widget";
+import { formatDateTime } from "src/utils/format";
+
+const CardholdersTable = ({
+ count = 0,
+ items = [],
+ onDeselectAll,
+ onDeselectOne,
+ onPageChange = () => ({}),
+ onRowsPerPageChange,
+ onSelectAll,
+ onSelectOne,
+ page = 0,
+ rowsPerPage = 0,
+ selected = [],
+}: {
+ count?: number;
+ items: Stripe.Issuing.Cardholder[];
+ onDeselectAll: () => void;
+ onDeselectOne: (item: string) => void;
+ onPageChange: (
+ e: React.MouseEvent | null,
+ page: number,
+ ) => void;
+ onRowsPerPageChange: (e: ChangeEvent) => void;
+ onSelectAll: () => void;
+ onSelectOne: (item: string) => void;
+ page?: number;
+ rowsPerPage?: number;
+ selected?: string[];
+}) => {
+ const selectedSome = selected.length > 0 && selected.length < items.length;
+ const selectedAll = items.length > 0 && selected.length === items.length;
+
+ return (
+
+
+
+
+
+
+
+ {
+ if (e.target.checked) {
+ onSelectAll?.();
+ } else {
+ onDeselectAll?.();
+ }
+ }}
+ />
+
+ Name
+ Email
+ Created
+
+
+
+
+ {items.map((cardholder) => {
+ const isSelected = selected.includes(cardholder.id);
+ return (
+
+
+ {
+ if (e.target.checked) {
+ onSelectOne?.(cardholder.id);
+ } else {
+ onDeselectOne?.(cardholder.id);
+ }
+ }}
+ />
+
+
+
+
+ {cardholder.name}
+
+
+
+ {cardholder.email}
+ {formatDateTime(cardholder.created)}
+
+ {cardholder.individual ? (
+
+ ) : (
+
+ )}
+
+
+ );
+ })}
+
+
+
+
+
+
+ );
+};
+
+export default CardholdersTable;
diff --git a/consumer-issuing/src/sections/cards/cards-search.tsx b/consumer-issuing/src/sections/cards/cards-search.tsx
new file mode 100644
index 00000000..9414de4c
--- /dev/null
+++ b/consumer-issuing/src/sections/cards/cards-search.tsx
@@ -0,0 +1,20 @@
+import MagnifyingGlassIcon from "@heroicons/react/24/solid/MagnifyingGlassIcon";
+import { Card, InputAdornment, OutlinedInput, SvgIcon } from "@mui/material";
+
+export const CardsSearch = () => (
+
+
+
+
+
+
+ }
+ sx={{ maxWidth: 500 }}
+ />
+
+);
diff --git a/consumer-issuing/src/sections/cards/cards-table.tsx b/consumer-issuing/src/sections/cards/cards-table.tsx
new file mode 100644
index 00000000..6ecff1ce
--- /dev/null
+++ b/consumer-issuing/src/sections/cards/cards-table.tsx
@@ -0,0 +1,157 @@
+import { ArrowRightIcon } from "@heroicons/react/24/solid";
+import {
+ Box,
+ Button,
+ Card,
+ Checkbox,
+ Stack,
+ SvgIcon,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TablePagination,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import React, { ChangeEvent } from "react";
+import Stripe from "stripe";
+
+import { Scrollbar } from "src/components/scrollbar";
+import { SeverityPill } from "src/components/severity-pill";
+import { SeverityColor } from "src/types/severity-color";
+import { formatDateTime } from "src/utils/format";
+
+const statusMap: Record = {
+ virtual: "info",
+ physical: "warning",
+};
+
+const CardsTable = ({
+ count = 0,
+ items = [],
+ onDeselectAll,
+ onDeselectOne,
+ onPageChange = () => ({}),
+ onRowsPerPageChange,
+ onSelectAll,
+ onSelectOne,
+ page = 0,
+ rowsPerPage = 0,
+ selected = [],
+}: {
+ count?: number;
+ items: Stripe.Issuing.Card[];
+ onDeselectAll: () => void;
+ onDeselectOne: (item: string) => void;
+ onPageChange: (
+ e: React.MouseEvent | null,
+ page: number,
+ ) => void;
+ onRowsPerPageChange: (e: ChangeEvent) => void;
+ onSelectAll: () => void;
+ onSelectOne: (item: string) => void;
+ page?: number;
+ rowsPerPage?: number;
+ selected?: string[];
+}) => {
+ const selectedSome = selected.length > 0 && selected.length < items.length;
+ const selectedAll = items.length > 0 && selected.length === items.length;
+
+ return (
+
+
+
+
+
+
+
+ {
+ if (event.target.checked) {
+ onSelectAll?.();
+ } else {
+ onDeselectAll?.();
+ }
+ }}
+ />
+
+ Cardholder Name
+ Type
+ Card Last 4
+ Created
+
+
+
+
+ {items.map((card) => {
+ const isSelected = selected.includes(card.id);
+ return (
+
+
+ {
+ if (event.target.checked) {
+ onSelectOne?.(card.id);
+ } else {
+ onDeselectOne?.(card.id);
+ }
+ }}
+ />
+
+
+
+
+ {card.cardholder.name}
+
+
+
+
+
+ {card.type}
+
+
+ {card.last4}
+ {formatDateTime(card.created)}
+
+
+
+
+ }
+ href={`/cards/${card.id}`}
+ >
+ Details
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ );
+};
+
+export default CardsTable;
diff --git a/consumer-issuing/src/sections/financial-account/send-money-wizard-dialog.tsx b/consumer-issuing/src/sections/financial-account/send-money-wizard-dialog.tsx
new file mode 100644
index 00000000..e122266a
--- /dev/null
+++ b/consumer-issuing/src/sections/financial-account/send-money-wizard-dialog.tsx
@@ -0,0 +1,872 @@
+import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
+import {
+ Dialog,
+ DialogTitle,
+ Divider,
+ Button,
+ DialogContent,
+ DialogActions,
+ Typography,
+ FormControlLabel,
+ FormLabel,
+ Radio,
+ RadioGroup,
+ FormHelperText,
+ TextField,
+ Grid,
+ Box,
+ FormControl,
+ MenuItem,
+ Select,
+ Link,
+ Alert,
+ SvgIcon,
+} from "@mui/material";
+import { useMachine } from "@xstate/react";
+import {
+ Formik,
+ Form,
+ Field,
+ FormikProps,
+ FormikValues,
+ ErrorMessage,
+} from "formik";
+import React, { ChangeEvent, RefObject, useRef, useState } from "react";
+import * as Yup from "yup";
+
+import stateMachine from "src/sections/financial-account/send-money-wizard-state-machine";
+import NetworkType from "src/types/network-type";
+import TransactionResult from "src/types/transaction-result";
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+
+const DEFAULT_TRANSACTION_RESULT = TransactionResult.POSTED;
+
+type DestinationAddress = {
+ name: string;
+ address1?: string;
+ city?: string;
+ state?: string;
+ postalCode?: string;
+ routingNumber: string;
+ accountNumber: string;
+ amount: number;
+ accountNotes?: string;
+};
+
+const SelectingNetworkForm = ({
+ formRef,
+ onFormSubmit,
+ network,
+ setNetwork,
+ destinationAddress,
+ setDestinationAddress,
+}: {
+ formRef: RefObject>;
+ onFormSubmit: () => void;
+ network: string;
+ setNetwork: (network: NetworkType) => void;
+ destinationAddress: DestinationAddress | null;
+ setDestinationAddress: (destinationAddress: DestinationAddress) => void;
+}) => {
+ const initialValues = { network };
+ const validationSchema = Yup.object().shape({
+ network: Yup.string()
+ .oneOf(Object.values(NetworkType))
+ .required("Please select a network"),
+ });
+
+ const handleNetworkChange = (
+ e: ChangeEvent,
+ setFieldValue: (field: string, value: string) => void,
+ ) => {
+ const selectedNetwork = e.target.value;
+ if (destinationAddress != null && selectedNetwork != network) {
+ // The selection was changed by the user so clear any previous destination address that's not
+ // applicable
+ if (selectedNetwork == NetworkType.ACH) {
+ setDestinationAddress({
+ ...destinationAddress,
+ address1: "",
+ city: "",
+ state: "",
+ postalCode: "",
+ });
+ }
+ }
+ setFieldValue("network", selectedNetwork);
+ };
+
+ return (
+ {
+ setNetwork(values.network);
+ onFormSubmit();
+ }}
+ >
+ {({ errors, setFieldValue }) => (
+
+ )}
+
+ );
+};
+
+const CollectingDestinationAddressForm = ({
+ formRef,
+ onFormSubmit,
+ network,
+ destinationAddress,
+ setDestinationAddress,
+}: {
+ formRef: RefObject>;
+ onFormSubmit: () => void;
+ network: string;
+ destinationAddress: DestinationAddress | null;
+ setDestinationAddress: (destinationAddress: DestinationAddress) => void;
+}) => {
+ let initialValues = destinationAddress || {
+ name: "",
+ routingNumber: "",
+ accountNumber: "",
+ amount: "",
+ accountNotes: "",
+ };
+
+ const requiredFieldsValidationSchema = {
+ name: Yup.string().required("Name is required"),
+ routingNumber: Yup.string().required("Routing Number is required"),
+ accountNumber: Yup.string().required("Account Number is required"),
+ amount: Yup.number().required("Amount is required").min(0),
+ };
+
+ let validationSchema = Yup.object().shape(requiredFieldsValidationSchema);
+
+ if (network === NetworkType.US_DOMESTIC_WIRE) {
+ const conditionalInitialValues = destinationAddress
+ ? {
+ address1: destinationAddress.address1,
+ city: destinationAddress.city,
+ state: destinationAddress.state,
+ postalCode: destinationAddress.postalCode,
+ }
+ : {
+ address1: "",
+ city: "",
+ state: "",
+ postalCode: "",
+ };
+
+ initialValues = { ...initialValues, ...conditionalInitialValues };
+
+ const conditionalAddressValidationSchema = Yup.object().shape({
+ address1: Yup.string().required("Street address is required"),
+ city: Yup.string().required("City is required"),
+ state: Yup.string().required("State is required"),
+ postalCode: Yup.string().required("Postal code is required"),
+ });
+
+ validationSchema = validationSchema.concat(
+ conditionalAddressValidationSchema,
+ );
+ }
+
+ const handleSubmit = async (
+ values: FormikValues,
+ { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
+ ) => {
+ const destinationAddress: DestinationAddress = {
+ name: values.name,
+ routingNumber: values.routingNumber,
+ accountNumber: values.accountNumber,
+ amount: values.amount,
+ accountNotes: values.accountNotes,
+ };
+
+ if (network === NetworkType.US_DOMESTIC_WIRE) {
+ destinationAddress.address1 = values.address1;
+ destinationAddress.city = values.city;
+ destinationAddress.state = values.state;
+ destinationAddress.postalCode = values.postalCode;
+ }
+
+ setDestinationAddress(destinationAddress);
+ onFormSubmit();
+ setSubmitting(false);
+ };
+
+ return (
+
+ {({ errors, touched }) => (
+
+ )}
+
+ );
+};
+
+const ConfirmingTransferForm = ({
+ formRef,
+ onFormSubmit,
+ network,
+ destinationAddress,
+ transactionResult,
+ setTransactionResult,
+ sendingErrorText,
+}: {
+ formRef: RefObject>;
+ onFormSubmit: () => void;
+ network: string;
+ destinationAddress: DestinationAddress | null;
+ transactionResult: TransactionResult;
+ setTransactionResult: (transactionResult: TransactionResult) => void;
+ sendingErrorText: string;
+}) => {
+ if (!destinationAddress) {
+ throw new Error("Destination address is required");
+ }
+
+ const transactionResultOptions: { [key: string]: string } = {};
+ for (const key in TransactionResult) {
+ const value = TransactionResult[key as keyof typeof TransactionResult];
+ transactionResultOptions[key] = value;
+ }
+
+ const initialValues = { transactionResult };
+
+ const validationSchema = Yup.object().shape({
+ transactionResult: Yup.string()
+ .oneOf(Object.values(TransactionResult))
+ .required("Transaction result is required"),
+ });
+
+ const handleSubmit = async (
+ values: FormikValues,
+ { setSubmitting }: { setSubmitting: (isSubmitting: boolean) => void },
+ ) => {
+ setTransactionResult(values.transactionResult);
+ onFormSubmit();
+ setSubmitting(false);
+ };
+
+ return (
+
+ {() => (
+
+ )}
+
+ );
+};
+
+const NotifyingCompletionForm = ({
+ network,
+ destinationAddress,
+ transactionResult,
+}: {
+ network: string;
+ destinationAddress: DestinationAddress | null;
+ transactionResult: TransactionResult;
+}) => {
+ if (!destinationAddress) {
+ throw new Error("Destination address is required");
+ }
+
+ return (
+
+
+
+
+ Successfully sent an outbound payment to {destinationAddress.name}{" "}
+ with an expected result of {transactionResult}.
+
+
+
+
+ Name
+
+
+ {destinationAddress.name}
+
+
+
+ {network === NetworkType.US_DOMESTIC_WIRE && (
+ <>
+
+ Street address
+
+
+ {destinationAddress.address1}
+
+
+
+
+ City
+
+
+ {destinationAddress.city}
+
+
+
+
+ State
+
+
+ {destinationAddress.state}
+
+
+
+
+ ZIP / Postal code
+
+
+ {destinationAddress.postalCode}
+
+
+
+ >
+ )}
+
+ Routing Number
+
+
+ {destinationAddress.routingNumber}
+
+
+
+
+ Account Number
+
+
+ {destinationAddress.accountNumber}
+
+
+
+
+ Amount
+
+
+ {destinationAddress.amount}
+
+
+
+ {destinationAddress.accountNotes != "" && (
+
+ Account Notes
+
+
+ {destinationAddress.accountNotes}
+
+
+
+ )}
+
+ Transaction Result
+
+
+ {transactionResult}
+
+
+
+
+ );
+};
+
+const SendMoneyWizardDialog = () => {
+ const [showModal, setShowModal] = React.useState(false);
+ const [current, send] = useMachine(stateMachine);
+
+ const [network, setNetwork] = useState("");
+ const [destinationAddress, setDestinationAddress] =
+ useState(null);
+ const [transactionResult, setTransactionResult] = useState(
+ DEFAULT_TRANSACTION_RESULT,
+ );
+
+ const handleReset = () => {
+ setNetwork("");
+ setDestinationAddress(null);
+ setTransactionResult(DEFAULT_TRANSACTION_RESULT);
+ setSendingErrorText("");
+ send({ type: "RESET" });
+ };
+
+ const handleOpen = () => {
+ handleReset();
+ setShowModal(true);
+ };
+ const handleClose = () => setShowModal(false);
+
+ const handleNext = () => {
+ send({ type: "NEXT" });
+ };
+
+ const handleBack = () => {
+ send({ type: "BACK" });
+ };
+
+ const selectingNetworkFormRef = useRef>(null);
+ const handleSubmitSelectingNetworkForm = () => {
+ const form = selectingNetworkFormRef.current;
+ if (form) {
+ form.submitForm();
+ }
+ };
+
+ const collectingDestinationAddressFormRef =
+ useRef>(null);
+ const handleSubmitCollectingDestinationAddressForm = () => {
+ const form = collectingDestinationAddressFormRef.current;
+ if (form) {
+ form.submitForm();
+ }
+ };
+
+ const confirmingTransferFormRef = useRef>(null);
+ const handleSubmitConfirmingTransferForm = () => {
+ const form = confirmingTransferFormRef.current;
+ if (form) {
+ form.submitForm();
+ }
+ };
+
+ const [isSending, setIsSending] = useState(false);
+ const [sendingErrorText, setSendingErrorText] = useState("");
+ const handleSendTransfer = async () => {
+ try {
+ setSendingErrorText("");
+ setIsSending(true);
+ if (destinationAddress === null) {
+ throw new Error(
+ "Destination address is null. Check to make sure it's set before submitting.",
+ );
+ }
+
+ const body = {
+ name: destinationAddress.name,
+ routing_number: destinationAddress.routingNumber,
+ account_number: destinationAddress.accountNumber,
+ network: network,
+ amount: destinationAddress.amount,
+ line1: destinationAddress.address1,
+ city: destinationAddress.city,
+ state: destinationAddress.state,
+ postalCode: destinationAddress.postalCode,
+ notes: destinationAddress.accountNotes,
+ transaction_result: transactionResult,
+ };
+
+ const response = await postApi("/api/send_money", body);
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: () => {
+ send({ type: "COMPLETE" });
+ },
+ onError: (error) => {
+ setSendingErrorText((error as Error).message);
+ },
+ });
+ } catch (error) {
+ setSendingErrorText((error as Error).message);
+ } finally {
+ setIsSending(false);
+ }
+ };
+
+ return (
+ <>
+
+ Send money
+
+
+ Send money
+
+
+ {current.matches("selectingNetwork") && (
+
+ )}
+ {current.matches("collectingDestinationAddress") && (
+
+ )}
+ {current.matches("confirmingTransfer") && (
+
+ )}
+ {current.matches("notifyingCompletion") && (
+
+ )}
+
+
+
+ {current.matches("selectingNetwork") && (
+ <>
+
+ Next
+
+ >
+ )}
+ {current.matches("collectingDestinationAddress") && (
+ <>
+
+ Back
+
+
+ Next
+
+ >
+ )}
+ {current.matches("confirmingTransfer") && (
+ <>
+
+ Back
+
+
+ {isSending ? "Sending..." : "Send"}
+
+ >
+ )}
+ {current.matches("notifyingCompletion") && (
+ <>
+
+ Send Another
+
+ >
+ )}
+
+
+ >
+ );
+};
+
+export default SendMoneyWizardDialog;
diff --git a/consumer-issuing/src/sections/financial-account/send-money-wizard-state-machine.ts b/consumer-issuing/src/sections/financial-account/send-money-wizard-state-machine.ts
new file mode 100644
index 00000000..ae7f0a14
--- /dev/null
+++ b/consumer-issuing/src/sections/financial-account/send-money-wizard-state-machine.ts
@@ -0,0 +1,29 @@
+import { createMachine } from "xstate";
+
+const stateMachine = createMachine({
+ initial: "selectingNetwork",
+ states: {
+ selectingNetwork: {
+ on: { NEXT: "collectingDestinationAddress", RESET: "selectingNetwork" },
+ },
+ collectingDestinationAddress: {
+ on: {
+ NEXT: "confirmingTransfer",
+ BACK: "selectingNetwork",
+ RESET: "selectingNetwork",
+ },
+ },
+ confirmingTransfer: {
+ on: {
+ BACK: "collectingDestinationAddress",
+ RESET: "selectingNetwork",
+ COMPLETE: "notifyingCompletion",
+ },
+ },
+ notifyingCompletion: {
+ on: { RESET: "selectingNetwork" },
+ },
+ },
+});
+
+export default stateMachine;
diff --git a/consumer-issuing/src/sections/overview/balance-transaction-flow-details.tsx b/consumer-issuing/src/sections/overview/balance-transaction-flow-details.tsx
new file mode 100644
index 00000000..30fcacfc
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/balance-transaction-flow-details.tsx
@@ -0,0 +1,22 @@
+import { Stack, Typography } from "@mui/material";
+import Stripe from "stripe";
+
+const BalanceTransactionFlowDetails = ({
+ transaction,
+}: {
+ transaction: Stripe.BalanceTransaction;
+}) => {
+ const flowType = transaction.type;
+ const flowDetails = transaction;
+ const flowTypeFormatted = flowType.replace(/_/g, " ");
+
+ return flowDetails ? (
+
+ {flowTypeFormatted}
+
+ ) : (
+ {flowTypeFormatted}
+ );
+};
+
+export default BalanceTransactionFlowDetails;
diff --git a/consumer-issuing/src/sections/overview/overview-api-request-logs.tsx b/consumer-issuing/src/sections/overview/overview-api-request-logs.tsx
new file mode 100644
index 00000000..8587f81f
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/overview-api-request-logs.tsx
@@ -0,0 +1,233 @@
+import {
+ Box,
+ Card,
+ Divider,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ Typography,
+ Paper,
+} from "@mui/material";
+import { useEffect, useState, useImperativeHandle, forwardRef } from "react";
+import { format } from "date-fns";
+import { prettyPrintJson } from 'pretty-print-json';
+
+import { Scrollbar } from "src/components/scrollbar";
+
+// Add pretty-print-json styles
+const jsonStyles = `
+ .json-container {
+ font-family: 'Courier New', monospace;
+ font-size: 0.875rem;
+ line-height: 1.5;
+ }
+ .json-container .json-key {
+ color: #9cdcfe;
+ }
+ .json-container .json-string {
+ color: #ce9178;
+ }
+ .json-container .json-number {
+ color: #b5cea8;
+ }
+ .json-container .json-boolean {
+ color: #569cd6;
+ }
+ .json-container .json-null {
+ color: #569cd6;
+ }
+ .json-container .json-mark {
+ color: #808080;
+ }
+`;
+
+type ApiRequestLog = {
+ id: number;
+ created: string;
+ requestUrl: string;
+ requestMethod: string;
+ requestBody: string | null;
+ responseBody: string | null;
+};
+
+export const OverviewApiRequestLogs = forwardRef((props: {
+ sx?: object;
+}, ref) => {
+ const { sx } = props;
+ const [logs, setLogs] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [selectedLog, setSelectedLog] = useState(null);
+
+ const fetchLogs = async () => {
+ try {
+ const response = await fetch('/api/get_api_request_logs');
+ if (!response.ok) {
+ throw new Error('Failed to fetch API request logs');
+ }
+ const data = await response.json();
+ setLogs(data.logs);
+ } catch (err) {
+ setError('Failed to load API request logs');
+ console.error('Error fetching API request logs:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useImperativeHandle(ref, () => ({
+ refresh: fetchLogs
+ }));
+
+ useEffect(() => {
+ fetchLogs();
+ }, []);
+
+ const parseJson = (jsonString: string | null) => {
+ if (!jsonString) return null;
+ try {
+ return JSON.parse(jsonString);
+ } catch {
+ return null;
+ }
+ };
+
+ const renderJson = (jsonString: string | null) => {
+ const parsed = parseJson(jsonString);
+ if (!parsed) return jsonString || '-';
+ return (
+
+ );
+ };
+
+ return (
+ <>
+
+
+
+
+
+ {loading ? (
+
+ Loading logs...
+
+ ) : error ? (
+
+ {error}
+
+ ) : logs.length === 0 ? (
+
+ No API request logs available
+
+ ) : (
+
+
+
+ Time
+ Method
+ URL
+
+
+
+ {logs.map((log) => (
+ setSelectedLog(log)}
+ sx={{
+ cursor: 'pointer',
+ backgroundColor: selectedLog?.id === log.id ? 'action.selected' : 'inherit'
+ }}
+ >
+
+ {format(new Date(log.created), "MMM dd, yyyy 'at' h:mm a")}
+
+
+ {log.requestMethod}
+
+
+ {log.requestUrl}
+
+
+ ))}
+
+
+ )}
+
+
+
+
+ {selectedLog && (
+
+
+
+
+ Request Details
+
+ {selectedLog.requestBody && (
+ <>
+
+ Request Body
+
+
+ {renderJson(selectedLog.requestBody)}
+
+ >
+ )}
+
+
+ Response Body
+
+
+ {renderJson(selectedLog.responseBody)}
+
+
+
+
+ )}
+
+ >
+ );
+});
\ No newline at end of file
diff --git a/consumer-issuing/src/sections/overview/overview-balance-funds-flow-chart.tsx b/consumer-issuing/src/sections/overview/overview-balance-funds-flow-chart.tsx
new file mode 100644
index 00000000..a1d693fb
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/overview-balance-funds-flow-chart.tsx
@@ -0,0 +1,159 @@
+import ArrowPathIcon from "@heroicons/react/24/solid/ArrowPathIcon";
+import {
+ Button,
+ Card,
+ CardContent,
+ CardHeader,
+ SvgIcon,
+ useTheme,
+} from "@mui/material";
+import { useSession } from "next-auth/react";
+
+import { Chart } from "src/components/chart";
+import { BalanceChartData } from "src/types/chart-data";
+import { SupportedCountry } from "src/utils/account-management-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+
+const useChartOptions = (
+ balanceFundsFlowChartData: BalanceChartData,
+ country: SupportedCountry,
+) => {
+ const theme = useTheme();
+ const formatCurrency = (amountInMinorUnits: number) =>
+ formatCurrencyForCountry(amountInMinorUnits, country);
+
+ return {
+ chart: {
+ background: "transparent",
+ stacked: false,
+ toolbar: {
+ show: false,
+ },
+ },
+ colors: [theme.palette.primary.main, theme.palette.primary.light],
+ dataLabels: {
+ enabled: false,
+ },
+ fill: {
+ opacity: 1,
+ type: "solid",
+ },
+ grid: {
+ borderColor: theme.palette.divider,
+ strokeDashArray: 2,
+ xaxis: {
+ lines: {
+ show: false,
+ },
+ },
+ yaxis: {
+ lines: {
+ show: true,
+ },
+ },
+ padding: {
+ bottom: 32,
+ },
+ },
+ legend: {
+ fontSize: "16px",
+ itemMargin: {
+ horizontal: 16,
+ },
+ },
+ theme: {
+ mode: theme.palette.mode,
+ },
+ xaxis: {
+ axisBorder: {
+ color: theme.palette.divider,
+ show: true,
+ },
+ axisTicks: {
+ color: theme.palette.divider,
+ show: true,
+ },
+ categories: balanceFundsFlowChartData.balanceTransactionsDates,
+ labels: {
+ offsetY: 5,
+ style: {
+ fontSize: "16px",
+ colors: theme.palette.text.secondary,
+ },
+ },
+ tickAmount: Math.ceil(balanceFundsFlowChartData.balanceTransactionsDates.length / 7),
+ tickPlacement: 'between',
+ },
+ yaxis: {
+ labels: {
+ formatter: formatCurrency,
+ offsetX: -10,
+ style: {
+ fontSize: "16px",
+ colors: theme.palette.text.secondary,
+ },
+ },
+ },
+ tooltip: {
+ y: {
+ formatter: formatCurrency,
+ },
+ },
+ };
+};
+
+export const OverviewBalanceFundsFlowChart = ({
+ balanceFundsFlowChartData,
+ sx,
+}: {
+ balanceFundsFlowChartData: BalanceChartData;
+ sx?: object;
+}) => {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+ const chartOptions = useChartOptions(balanceFundsFlowChartData, country);
+
+ const chartSeries = [
+ {
+ name: "Funds in",
+ data: balanceFundsFlowChartData.balanceTransactionsFundsIn,
+ },
+ {
+ name: "Funds out",
+ data: balanceFundsFlowChartData.balanceTransactionsFundsOut,
+ },
+ ];
+
+ return (
+
+
+
+
+ }
+ >
+ Sync
+
+ }
+ title="Account funds flow"
+ />
+
+
+
+
+ );
+};
diff --git a/consumer-issuing/src/sections/overview/overview-credit-limit.tsx b/consumer-issuing/src/sections/overview/overview-credit-limit.tsx
new file mode 100644
index 00000000..23527a2a
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/overview-credit-limit.tsx
@@ -0,0 +1,168 @@
+import { Avatar, Card, CardContent, Link, Stack, Typography, Box } from "@mui/material";
+import { DocumentCurrencyDollarIcon } from "@heroicons/react/24/outline";
+import { useSession } from "next-auth/react";
+import { useEffect, useState } from "react";
+
+import { CountryConfigMap } from "src/utils/account-management-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+import FloatingPaymentPanel from "src/components/floating-payment-panel";
+import { TestDataMakePayment } from "src/sections/test-data/test-data-make-payment";
+
+type Statement = {
+ id: string;
+ credit_period_starts_at: number | null;
+ credit_period_ends_at: number | null;
+ url: string | null;
+ file?: string;
+ status?: string;
+};
+
+export const OverviewCreditStatement = (props: {
+ sx: object;
+ creditLimit: number;
+}) => {
+ const { sx, creditLimit } = props;
+ const [statements, setStatements] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ useEffect(() => {
+ const fetchStatements = async () => {
+ try {
+ const response = await fetch('/api/get_statements');
+ if (!response.ok) {
+ throw new Error('Failed to fetch statements');
+ }
+ const data = await response.json();
+ setStatements(data.statements);
+ } catch (err) {
+ setError('Failed to load statements');
+ console.error('Error fetching statements:', err);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchStatements();
+ }, []);
+
+ return (
+
+
+
+
+
+ Statements
+
+ {loading ? (
+ Loading statements...
+ ) : error ? (
+ {error}
+ ) : statements.length === 0 ? (
+ No statements available
+ ) : (
+
+ {statements.map((statement) => {
+ // Handle null dates for pending statements
+ if (!statement.credit_period_starts_at || !statement.credit_period_ends_at) {
+ const statementName = statement.status === 'pending'
+ ? "Current Statement (Pending)"
+ : "Statement (Processing)";
+
+ return statement.url ? (
+
+ {statementName}
+
+ ) : (
+
+ {statementName}
+
+ );
+ }
+
+ // Format the statement period as a readable name
+ const endDate = new Date(statement.credit_period_ends_at * 1000);
+ const statementDate = endDate.toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric'
+ });
+
+ const statementName = statementDate;
+
+ return statement.url ? (
+
+ {statementName}
+
+ ) : (
+
+ {statementName}
+
+ );
+ })}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/consumer-issuing/src/sections/overview/overview-issuing-balance.tsx b/consumer-issuing/src/sections/overview/overview-issuing-balance.tsx
new file mode 100644
index 00000000..dcc89790
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/overview-issuing-balance.tsx
@@ -0,0 +1,94 @@
+import { Avatar, Card, CardContent, Stack, Typography } from "@mui/material";
+import { useSession } from "next-auth/react";
+import Stripe from "stripe";
+
+import CurrencyIcon from "src/components/currency-icon";
+import { CountryConfigMap } from "src/utils/account-management-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+
+type IssuingBalance = {
+ available?: Array<{
+ amount: number;
+ currency: string;
+ }>;
+ credit_limit?: Array<{
+ amount: number;
+ currency: string;
+ }>;
+ total_balance?: Array<{
+ amount: number;
+ currency: string;
+ }>;
+};
+
+export const OverviewIssuingBalance = (props: {
+ sx: object;
+ balance: IssuingBalance;
+}) => {
+ const { sx, balance } = props;
+ const availableAmount = balance.available?.[0]?.amount ?? 0;
+ const creditLimitAmount = balance.credit_limit?.[0]?.amount ?? 0;
+ const totalBalanceAmount = balance.total_balance?.[0]?.amount ?? 0;
+
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ return (
+
+
+
+
+
+ Your Credit Account
+
+
+ {formatCurrencyForCountry(availableAmount, country)}
+
+
+
+
+
+ Available credit
+
+
+
+
+
+
+ Total balance
+
+
+ {formatCurrencyForCountry(totalBalanceAmount, country)}
+
+
+
+
+ Credit limit
+
+
+ {formatCurrencyForCountry(creditLimitAmount, country)}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/consumer-issuing/src/sections/overview/overview-latest-balance-transactions.tsx b/consumer-issuing/src/sections/overview/overview-latest-balance-transactions.tsx
new file mode 100644
index 00000000..4502ca65
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/overview-latest-balance-transactions.tsx
@@ -0,0 +1,200 @@
+import {
+ Box,
+ Card,
+ CardHeader,
+ Divider,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableRow,
+ Typography,
+} from "@mui/material";
+import { useSession } from "next-auth/react";
+import Stripe from "stripe";
+
+import { Scrollbar } from "src/components/scrollbar";
+import { SeverityPill } from "src/components/severity-pill";
+import TransactionDetailsPanel from "src/components/transaction-details-panel";
+import { formatCurrencyForCountry, formatDateTime, titleize } from "src/utils/format";
+import { SeverityColor } from "src/types/severity-color";
+
+const statusMap: Record = {
+ open: "warning",
+ posted: "success",
+ void: "error",
+};
+
+const typeMap: Record = {
+ issuing_authorization: "Authorization",
+ issuing_transaction: "Transaction",
+ issuing_credit_repayment: "Repayment",
+ issuing_credit_ledger_adjustment: "Adjustment"
+};
+
+const adjustmentReasonMap: Record = {
+ interest_charge: "Interest Charge",
+ interest_credit: "Interest Credit",
+ late_fee: "Late Fee",
+ annual_fee: "Annual Fee",
+ foreign_transaction_fee: "Foreign Transaction Fee",
+ cash_advance_fee: "Cash Advance Fee",
+ balance_transfer_fee: "Balance Transfer Fee",
+ returned_payment_fee: "Returned Payment Fee",
+ statement_credit: "Statement Credit",
+ other: "Other Adjustment",
+ platform_issued_debit_memo: "Platform Issued Debit Memo",
+};
+
+const getTransactionType = (entry: any): string => {
+ if (entry.source?.type === 'issuing_transaction' && entry.transaction?.type === 'refund') {
+ return 'Refund';
+ }
+ return entry.source?.type ? typeMap[entry.source.type] || entry.source.type : 'Unknown';
+};
+
+const getStatusInfo = (entry: any): { text: string; color: SeverityColor } => {
+ if (entry.source?.type === "issuing_credit_repayment") {
+ const status = entry.creditRepayment?.status || "processing";
+ switch (status) {
+ case "processing":
+ return {
+ text: "Processing",
+ color: "info"
+ };
+ case "succeeded":
+ return {
+ text: "Success",
+ color: "success"
+ };
+ case "failed":
+ return {
+ text: "Failed",
+ color: "error"
+ };
+ default:
+ return {
+ text: "Pending",
+ color: "info"
+ };
+ }
+ } else if (entry.source?.type === "issuing_transaction") {
+ return {
+ text: "Posted",
+ color: "success"
+ };
+ } else if (entry.source?.type === "issuing_credit_ledger_adjustment") {
+ return {
+ text: "Posted",
+ color: "success"
+ };
+ } else {
+ return {
+ text: "Pending",
+ color: "warning"
+ };
+ }
+};
+
+export const OverviewLatestBalanceTransactions = (props: {
+ creditLedgerEntries: any[]; // We'll need to define a proper type for credit ledger entries
+ sx?: object;
+}) => {
+ const { creditLedgerEntries = [], sx } = props;
+
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ return (
+
+
+
+
+ {creditLedgerEntries.length > 0 ? (
+
+
+
+ Date
+ Amount
+ Type
+ Status
+ Merchant
+ Merchant Category
+
+
+
+
+ {creditLedgerEntries.map((entry) => {
+ const category = entry.auth?.merchant_data?.category
+ ? titleize(entry.auth.merchant_data.category.replace(/_/g, " "))
+ : entry.source?.type === "issuing_credit_ledger_adjustment"
+ ? adjustmentReasonMap[entry.creditLedgerAdjustment?.reason] || titleize(entry.creditLedgerAdjustment?.reason?.replace(/_/g, " ") || "Unknown")
+ : "Unknown";
+ const isRepayment = entry.source?.type === "issuing_credit_repayment";
+ const isAdjustment = entry.source?.type === "issuing_credit_ledger_adjustment";
+ const statusInfo = getStatusInfo(entry);
+ return (
+
+
+ {formatDateTime(entry.created)}
+
+
+ {formatCurrencyForCountry(-entry.amount, country)}
+
+
+ {getTransactionType(entry)}
+
+
+
+ {statusInfo.text}
+
+
+
+ {!isRepayment && !isAdjustment && (entry.auth?.merchant_data?.name || "Unknown merchant")}
+
+
+ {!isRepayment && category}
+
+
+
+
+
+ );
+ })}
+
+
+ ) : (
+ <>
+
+
+
+ There are no transactions to show yet.
+
+
+ >
+ )}
+
+
+
+ );
+};
diff --git a/consumer-issuing/src/sections/overview/overview-payments-balance.tsx b/consumer-issuing/src/sections/overview/overview-payments-balance.tsx
new file mode 100644
index 00000000..14dd2d17
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/overview-payments-balance.tsx
@@ -0,0 +1,55 @@
+import { Avatar, Card, CardContent, Stack, Typography } from "@mui/material";
+import { useSession } from "next-auth/react";
+import Stripe from "stripe";
+
+import CurrencyIcon from "src/components/currency-icon";
+import { CountryConfigMap } from "src/utils/account-management-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+
+export const OverviewAvailableBalance = (props: {
+ sx: object;
+ balance: Stripe.Balance.Available;
+}) => {
+ const { sx, balance } = props;
+ const { amount: value } = balance;
+
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ return (
+
+
+
+
+
+ Payments Acquired Balance
+
+
+ {formatCurrencyForCountry(value, country)}
+
+
+ Available acquired balance
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/consumer-issuing/src/sections/overview/transaction-flow-details.tsx b/consumer-issuing/src/sections/overview/transaction-flow-details.tsx
new file mode 100644
index 00000000..daaa060e
--- /dev/null
+++ b/consumer-issuing/src/sections/overview/transaction-flow-details.tsx
@@ -0,0 +1,35 @@
+import { DocumentArrowDownIcon } from "@heroicons/react/24/outline";
+import { Stack, Typography, SvgIcon, Link } from "@mui/material";
+import Stripe from "stripe";
+
+type FlowDetailsWithRegulatoryReceiptUrl = T extends
+ | "other"
+ | "issuing_authorization"
+ ? never
+ : T;
+
+const TransactionFlowDetails = ({
+ transaction,
+}: {
+ transaction: Stripe.Treasury.Transaction;
+}) => {
+ const flowType =
+ transaction.flow_type as FlowDetailsWithRegulatoryReceiptUrl;
+ const flowDetails = transaction.flow_details?.[flowType];
+ const flowTypeFormatted = flowType.replace(/_/g, " ");
+
+ return flowDetails && flowDetails.hosted_regulatory_receipt_url ? (
+
+ {flowTypeFormatted}
+
+
+
+
+
+
+ ) : (
+ {flowTypeFormatted}
+ );
+};
+
+export default TransactionFlowDetails;
diff --git a/consumer-issuing/src/sections/settings/settings-delete-account.tsx b/consumer-issuing/src/sections/settings/settings-delete-account.tsx
new file mode 100644
index 00000000..c7521d7e
--- /dev/null
+++ b/consumer-issuing/src/sections/settings/settings-delete-account.tsx
@@ -0,0 +1,80 @@
+import {
+ Alert,
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ CardHeader,
+ Divider,
+ Stack,
+ Typography,
+} from "@mui/material";
+import { signOut } from "next-auth/react";
+import React, { useState } from "react";
+
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+
+function SettingsDeleteAccount() {
+ const [submitting, setSubmitting] = useState(false);
+ const [errorText, setErrorText] = useState("");
+
+ const deleteAccount = async () => {
+ setSubmitting(true);
+ const response = await postApi("api/delete-account");
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ await signOut();
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ return (
+
+
+
+
+
+
+ Deleting your account will permanently remove all of your data from
+ our platform. This action cannot be undone.
+
+
+ You'll be logged out and will need to register again if you
+ want to use our platform.
+
+ {errorText !== "" && (
+
+ {errorText}
+
+ )}
+
+
+
+
+
+ {submitting ? "Deleting..." : "Delete account"}
+
+
+
+ );
+}
+
+export default SettingsDeleteAccount;
diff --git a/consumer-issuing/src/sections/test-data/test-data-create-authorization.tsx b/consumer-issuing/src/sections/test-data/test-data-create-authorization.tsx
new file mode 100644
index 00000000..f0d56e19
--- /dev/null
+++ b/consumer-issuing/src/sections/test-data/test-data-create-authorization.tsx
@@ -0,0 +1,102 @@
+import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
+import {
+ Alert,
+ Box,
+ Button,
+ Link,
+ Stack,
+ SvgIcon,
+ Typography,
+} from "@mui/material";
+import { useRouter } from "next/router";
+import { useSession } from "next-auth/react";
+import React, { useState } from "react";
+
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+
+const TestDataCreateAuthorization = ({ cardId }: { cardId: string }) => {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+ const router = useRouter();
+ const [submitting, setSubmitting] = useState(false);
+ const [errorText, setErrorText] = useState("");
+
+ const simulateAuthorization = async () => {
+ setSubmitting(true);
+ const response = await postApi("/api/create_authorization", {
+ cardId: cardId,
+ });
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ await router.push(`/cards/${cardId}`);
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ return (
+ <>
+
+
+ A {formatCurrencyForCountry(1000, country)}{" "}
+
+ Authorization{" "}
+
+
+
+ {" "}
+ will be created each time you press the button.
+
+ {errorText !== "" && (
+
+ {errorText ===
+ "Error: Insufficient funds to create a test purchase." ? (
+
+ Insufficient funds to create a test purchase.{" "}
+
+ Add funds
+ {" "}
+ to your financial account first.
+
+ ) : (
+ errorText
+ )}
+
+ )}
+
+
+
+ {submitting ? "Simulating..." : "Simulate test purchase"}
+
+
+ >
+ );
+};
+
+export default TestDataCreateAuthorization;
diff --git a/consumer-issuing/src/sections/test-data/test-data-create-issuing-topup.tsx b/consumer-issuing/src/sections/test-data/test-data-create-issuing-topup.tsx
new file mode 100644
index 00000000..71911b41
--- /dev/null
+++ b/consumer-issuing/src/sections/test-data/test-data-create-issuing-topup.tsx
@@ -0,0 +1,87 @@
+import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
+import {
+ Alert,
+ Box,
+ Button,
+ Link,
+ Stack,
+ SvgIcon,
+ Typography,
+} from "@mui/material";
+import { useRouter } from "next/router";
+import { useSession } from "next-auth/react";
+import React, { useState } from "react";
+
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+
+const TestDataTopUpIssuingBalance = ({}) => {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+ const router = useRouter();
+ const [submitting, setSubmitting] = useState(false);
+ const [errorText, setErrorText] = useState("");
+
+ const simulateIssuingBalanceFunding = async () => {
+ setSubmitting(true);
+ const response = await postApi("api/create_issuingtopup");
+ const result = await extractJsonFromResponse(response);
+
+ handleResult({
+ result,
+ onSuccess: async () => {
+ await router.push("/");
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ return (
+ <>
+
+
+ Your issuing balance account will receive a{" "}
+ {formatCurrencyForCountry(50000, country)}{" "}
+
+ funding{" "}
+
+
+
+ {" "}
+ each time you press the button.
+
+ {errorText !== "" && {errorText} }
+
+
+
+ {submitting ? "Simulating..." : "Simulate issuing balance funding"}
+
+
+ >
+ );
+};
+
+export default TestDataTopUpIssuingBalance;
diff --git a/consumer-issuing/src/sections/test-data/test-data-create-payment-link.tsx b/consumer-issuing/src/sections/test-data/test-data-create-payment-link.tsx
new file mode 100644
index 00000000..cc440c8c
--- /dev/null
+++ b/consumer-issuing/src/sections/test-data/test-data-create-payment-link.tsx
@@ -0,0 +1,131 @@
+import {
+ Alert,
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ CardHeader,
+ Divider,
+ Link,
+ Stack,
+ Typography,
+} from "@mui/material";
+import { useRouter } from "next/router";
+import { useSession } from "next-auth/react";
+import React, { useState } from "react";
+
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+import { isDemoMode } from "src/utils/demo-helpers";
+
+function TestDataCreatePaymentLink() {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { stripeAccount } = session;
+ const { accountId } = stripeAccount;
+ const router = useRouter();
+
+ const [submitted, setSubmitted] = useState(false);
+ const [errorText, setErrorText] = useState("");
+ const [gotUrl, setGotUrl] = useState(false);
+ const [url, setUrl] = useState("");
+
+ const createPaymentLink = async () => {
+ setSubmitted(true);
+ const response = await postApi("api/create_paymentlink");
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ setGotUrl(true);
+ if (result.data && "paymentLink" in result.data) {
+ const data = result.data as {
+ paymentLink: string;
+ };
+ const paymentLink = data.paymentLink;
+ setUrl(paymentLink);
+ } else {
+ throw new Error("Something went wrong");
+ }
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitted(false);
+ },
+ });
+ };
+
+ return (
+
+
+
+
+
+ By pressing the Create PaymentLink button you will generate a{" "}
+
+ PaymentLink
+ {" "}
+ that you can use to receive a payment into your Connected
+ Account's Stripe Balance.
+
+
+ Use the test card number{" "}
+ 4000 0000 0000 0077 to
+ make the charge succeed. Funds are added directly to your available
+ balance, bypassing your pending balance.
+
+ {(!isDemoMode() || router.query.debug) && (
+
+ You can view the payments{" "}
+
+ here
+ {" "}
+ in the Stripe dashboard.
+
+ )}
+ {errorText !== "" && {errorText} }
+
+
+
+
+ {gotUrl ? (
+ window.open(url, "_blank")}
+ >
+ Go to PaymentLink
+
+ ) : (
+
+ {submitted ? "Creating..." : "Create PaymentLink"}
+
+ )}
+
+
+ );
+}
+
+export default TestDataCreatePaymentLink;
diff --git a/consumer-issuing/src/sections/test-data/test-data-create-payout-to-bank.tsx b/consumer-issuing/src/sections/test-data/test-data-create-payout-to-bank.tsx
new file mode 100644
index 00000000..a9fa1c79
--- /dev/null
+++ b/consumer-issuing/src/sections/test-data/test-data-create-payout-to-bank.tsx
@@ -0,0 +1,157 @@
+import {
+ Alert,
+ Button,
+ Card,
+ CardActions,
+ CardContent,
+ CardHeader,
+ Divider,
+ Link,
+ Stack,
+ Typography,
+} from "@mui/material";
+import { useSession } from "next-auth/react";
+import React, { useState } from "react";
+
+import {
+ extractJsonFromResponse,
+ handleResult,
+ postApi,
+} from "src/utils/api-helpers";
+import { formatCurrencyForCountry } from "src/utils/format";
+
+function TestDataCreatePayoutsToBank({
+ availableBalance: availableBalanceProp,
+ hasExternalAccount: hasExternalAccountProp,
+}: {
+ availableBalance: number;
+ hasExternalAccount: boolean;
+}) {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country } = session;
+
+ const [submitting, setSubmitting] = useState(false);
+ const [errorText, setErrorText] = useState("");
+ const [availableBalance, setAvailableBalance] =
+ useState(availableBalanceProp);
+ const [hasExternalAccount, setHasExternalAccount] = useState(
+ hasExternalAccountProp,
+ );
+
+ const createPayout = async () => {
+ setSubmitting(true);
+ const response = await postApi("api/create_payout");
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: async () => {
+ setAvailableBalance(0);
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ const addExternalAccount = async () => {
+ setSubmitting(true);
+ const response = await postApi("api/add_external_account");
+ const result = await extractJsonFromResponse(response);
+
+ handleResult({
+ result,
+ onSuccess: async () => {
+ setHasExternalAccount(true);
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ };
+
+ return (
+
+
+
+
+
+ In order to enable payouts, you need to attach a bank account as the
+ external account for your connected account.
+
+
+ {`If you haven't done it yet, by pressing the "Add bank account
+ as external account" button, a test bank account will be set as
+ an external account, and manual payouts will be enabled.`}
+
+
+ Platforms have the ability to set up automatic payouts with
+ different schedules. You can dive deep into this topic on{" "}
+
+ this page
+
+ .
+
+
+ Currently, your connected account has an{" "}
+
+ available balance
+ {" "}
+ of{" "}
+
+ {formatCurrencyForCountry(availableBalance, country)}
+
+ .
+
+ {errorText !== "" && (
+
+ {errorText}
+
+ )}
+
+
+
+
+ {hasExternalAccount ? (
+
+ {submitting ? "Creating..." : "Create payout"}
+
+ ) : (
+
+ {submitting ? "Adding..." : "Add bank account as external account"}
+
+ )}
+
+
+ );
+}
+
+export default TestDataCreatePayoutsToBank;
diff --git a/consumer-issuing/src/sections/test-data/test-data-make-payment.tsx b/consumer-issuing/src/sections/test-data/test-data-make-payment.tsx
new file mode 100644
index 00000000..e0075e8f
--- /dev/null
+++ b/consumer-issuing/src/sections/test-data/test-data-make-payment.tsx
@@ -0,0 +1,341 @@
+import {
+ Button,
+ InputAdornment,
+ Grid,
+ TextField,
+ Alert,
+ Box,
+ Typography,
+ FormControl,
+ FormControlLabel,
+ RadioGroup,
+ Radio,
+ FormLabel,
+ Chip,
+ Select,
+ MenuItem,
+ InputLabel,
+ CircularProgress
+} from "@mui/material";
+import { useSession } from "next-auth/react";
+import { useState, useEffect } from "react";
+
+import { formatCurrencyForCountry } from "src/utils/format";
+import { extractJsonFromResponse, handleResult, postApi } from "src/utils/api-helpers";
+import { CountryConfigMap } from "src/utils/account-management-helpers";
+
+interface VerifiedPaymentMethod {
+ customer: {
+ id: string;
+ name: string;
+ email: string;
+ };
+ paymentMethods: Array<{
+ id: string;
+ bank_name?: string;
+ last4?: string;
+ account_type?: string;
+ account_holder_type?: string;
+ }>;
+}
+
+export const TestDataMakePayment = () => {
+ const { data: session } = useSession();
+ if (session == undefined) {
+ throw new Error("Session is missing in the request");
+ }
+ const { country, stripeAccount } = session;
+ const [paymentAmount, setPaymentAmount] = useState("");
+ const [paymentType, setPaymentType] = useState("user_instructed");
+ const [selectedPaymentMethod, setSelectedPaymentMethod] = useState("");
+ const [selectedCustomer, setSelectedCustomer] = useState("");
+ const [verifiedPaymentMethods, setVerifiedPaymentMethods] = useState([]);
+ const [loadingPaymentMethods, setLoadingPaymentMethods] = useState(false);
+ const [errorText, setErrorText] = useState("");
+ const [successText, setSuccessText] = useState("");
+ const [submitting, setSubmitting] = useState(false);
+
+ // Fetch verified payment methods immediately when component mounts
+ useEffect(() => {
+ fetchVerifiedPaymentMethods();
+ }, []);
+
+ const fetchVerifiedPaymentMethods = async () => {
+ setLoadingPaymentMethods(true);
+ try {
+ const response = await fetch("/api/get_verified_payment_methods", {
+ method: "GET",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ });
+ const result = await extractJsonFromResponse(response);
+ if (result.success && result.data) {
+ setVerifiedPaymentMethods(result.data as VerifiedPaymentMethod[]);
+ } else {
+ setErrorText("Failed to load verified payment methods");
+ }
+ } catch (error) {
+ setErrorText("Error loading payment methods");
+ } finally {
+ setLoadingPaymentMethods(false);
+ }
+ };
+
+ const handlePaymentAmountChange = (event: React.ChangeEvent) => {
+ const value = event.target.value;
+ // Only allow numbers and decimal point
+ if (/^\d*\.?\d*$/.test(value)) {
+ setPaymentAmount(value);
+ // Clear success message when user starts typing
+ setSuccessText("");
+ }
+ };
+
+ const handlePaymentTypeChange = (event: React.ChangeEvent) => {
+ setPaymentType(event.target.value);
+ // Clear messages and selections when payment type changes
+ setSuccessText("");
+ setErrorText("");
+ setSelectedPaymentMethod("");
+ setSelectedCustomer("");
+ };
+
+ const handlePaymentMethodChange = (event: any) => {
+ const value = event.target.value;
+ setSelectedPaymentMethod(value);
+
+ // Find the customer for this payment method
+ for (const customerData of verifiedPaymentMethods) {
+ const paymentMethod = customerData.paymentMethods.find(pm => pm.id === value);
+ if (paymentMethod) {
+ setSelectedCustomer(customerData.customer.id);
+ break;
+ }
+ }
+ };
+
+ const handleKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === 'Enter') {
+ handleSubmit();
+ }
+ };
+
+ const handleSubmit = async () => {
+ if (!paymentAmount || parseFloat(paymentAmount) <= 0) {
+ setErrorText("Please enter a valid payment amount");
+ return;
+ }
+
+ if (paymentType === "api_instructed" && !selectedPaymentMethod) {
+ setErrorText("Please select a payment method for on-Stripe payments");
+ return;
+ }
+
+ setSubmitting(true);
+ setErrorText("");
+ setSuccessText("");
+
+ const amount = parseFloat(paymentAmount);
+ const amountInCents = Math.round(amount * 100);
+ const currency = CountryConfigMap[country]?.currency || country.toLowerCase();
+
+ // Capture these values before making API calls to ensure they're available for success message
+ const formattedAmount = formatCurrencyForCountry(amountInCents, country);
+ const paymentTypeLabel = paymentType === "api_instructed" ? "on-Stripe" : "off-Stripe";
+
+ const requestData: any = {
+ amount: amountInCents,
+ currency,
+ payment_type: paymentType,
+ account: stripeAccount.accountId,
+ };
+
+ if (paymentType === "api_instructed") {
+ requestData.payment_method_id = selectedPaymentMethod;
+ requestData.customer_id = selectedCustomer;
+ }
+
+ try {
+ const response = await postApi("/api/create_credit_repayment", requestData);
+
+ const result = await extractJsonFromResponse(response);
+ handleResult({
+ result,
+ onSuccess: () => {
+ setPaymentAmount("");
+ setSelectedPaymentMethod("");
+ setSelectedCustomer("");
+ setSuccessText(`Successfully processed ${paymentTypeLabel} payment of ${formattedAmount}`);
+ },
+ onError: (error) => {
+ setErrorText(`Error: ${error.message}`);
+ },
+ onFinally: () => {
+ setSubmitting(false);
+ },
+ });
+ } catch (error) {
+ setErrorText("An unexpected error occurred");
+ setSubmitting(false);
+ }
+ };
+
+ // Get all payment methods for the dropdown
+ const allPaymentMethods = verifiedPaymentMethods.flatMap(customerData =>
+ customerData.paymentMethods.map(pm => ({
+ ...pm,
+ customerName: customerData.customer.name || customerData.customer.email,
+ customerId: customerData.customer.id,
+ }))
+ );
+
+ return (
+
+
+
+
+
+ Payment Type
+
+
+
+ }
+ label={
+
+
+
+ }
+ />
+ }
+ label={
+
+
+
+ }
+ />
+
+
+
+
+
+
+ {paymentType === "api_instructed"
+ ? "On-Stripe payments are processed automatically through the API using a stored bank account and credited to the platform's Issuing balance."
+ : "Off-Stripe payments are externally-processed payments (e.g., paper check, external bank transfer) that do not affect the platform's balance."
+ }
+
+
+
+ {paymentType === "api_instructed" && (
+
+
+ Select Payment Method
+
+ {loadingPaymentMethods ? (
+
+
+
+ Loading payment methods...
+
+
+ ) : allPaymentMethods.length === 0 ? (
+
+
+
+ No verified bank accounts found
+
+
+
+ ) : (
+ allPaymentMethods.map((pm) => (
+
+
+
+ {pm.bank_name} ****{pm.last4} ({pm.account_type})
+
+
+ Account holder: {pm.customerName}
+
+
+
+ ))
+ )}
+
+
+
+ )}
+
+
+ Payment Amount
+
+
+ {formatCurrencyForCountry(0, country).split("0")[0]}
+
+ ),
+ }}
+ />
+
+
+ {errorText && (
+
+ {errorText}
+
+ )}
+ {successText && (
+
+ {successText}
+
+ )}
+
+
+ {submitting
+ ? "Processing..."
+ : `Make ${paymentType === "api_instructed" ? "On-Stripe" : "Off-Stripe"} Payment`
+ }
+
+
+
+ );
+};
diff --git a/consumer-issuing/src/theme/colors.ts b/consumer-issuing/src/theme/colors.ts
new file mode 100644
index 00000000..fc0a7561
--- /dev/null
+++ b/consumer-issuing/src/theme/colors.ts
@@ -0,0 +1,86 @@
+import { alpha } from "@mui/material";
+
+const withAlphas = (color: {
+ lightest: string;
+ light: string;
+ main: string;
+ dark: string;
+ darkest: string;
+ contrastText: string;
+}) => {
+ return {
+ ...color,
+ alpha4: alpha(color.main, 0.04),
+ alpha8: alpha(color.main, 0.08),
+ alpha12: alpha(color.main, 0.12),
+ alpha30: alpha(color.main, 0.3),
+ alpha50: alpha(color.main, 0.5),
+ };
+};
+
+export const neutral = {
+ 50: "#F8F9FA",
+ 100: "#F3F4F6",
+ 200: "#E5E7EB",
+ 300: "#D2D6DB",
+ 400: "#9DA4AE",
+ 500: "#6C737F",
+ 600: "#4D5761",
+ 700: "#2F3746",
+ 800: "#1C2536",
+ 900: "#111927",
+};
+
+export const indigo = withAlphas({
+ lightest: "#F5F7FF",
+ light: "#EBEEFE",
+ main: "#6366F1",
+ dark: "#4338CA",
+ darkest: "#312E81",
+ contrastText: "#FFFFFF",
+});
+
+export const secondary = withAlphas({
+ lightest: "#F8F9FA",
+ light: "#F3F4F6",
+ main: "#384250",
+ dark: "#1C2536",
+ darkest: "#111927",
+ contrastText: "#FFFFFF",
+});
+
+export const success = withAlphas({
+ lightest: "#F0FDF9",
+ light: "#3FC79A",
+ main: "#10B981",
+ dark: "#0B815A",
+ darkest: "#134E48",
+ contrastText: "#FFFFFF",
+});
+
+export const info = withAlphas({
+ lightest: "#ECFDFF",
+ light: "#CFF9FE",
+ main: "#06AED4",
+ dark: "#0E7090",
+ darkest: "#164C63",
+ contrastText: "#FFFFFF",
+});
+
+export const warning = withAlphas({
+ lightest: "#FFFAEB",
+ light: "#FEF0C7",
+ main: "#F79009",
+ dark: "#B54708",
+ darkest: "#7A2E0E",
+ contrastText: "#FFFFFF",
+});
+
+export const error = withAlphas({
+ lightest: "#FEF3F2",
+ light: "#FEE4E2",
+ main: "#F04438",
+ dark: "#B42318",
+ darkest: "#7A271A",
+ contrastText: "#FFFFFF",
+});
diff --git a/consumer-issuing/src/theme/create-components.ts b/consumer-issuing/src/theme/create-components.ts
new file mode 100644
index 00000000..a8338f18
--- /dev/null
+++ b/consumer-issuing/src/theme/create-components.ts
@@ -0,0 +1,308 @@
+import {
+ Components,
+ createTheme,
+ filledInputClasses,
+ inputLabelClasses,
+ outlinedInputClasses,
+ paperClasses,
+ tableCellClasses,
+ Theme,
+} from "@mui/material";
+
+import createPalette from "src/theme/create-palette";
+
+// Used only to create transitions
+const muiTheme = createTheme();
+
+export function createComponents({
+ palette,
+}: {
+ palette: ReturnType;
+}): Components> {
+ return {
+ MuiAvatar: {
+ styleOverrides: {
+ root: {
+ fontSize: 14,
+ fontWeight: 600,
+ letterSpacing: 0,
+ },
+ },
+ },
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ borderRadius: "12px",
+ textTransform: "none",
+ },
+ sizeSmall: {
+ padding: "6px 16px",
+ },
+ sizeMedium: {
+ padding: "8px 20px",
+ },
+ sizeLarge: {
+ padding: "11px 24px",
+ },
+ textSizeSmall: {
+ padding: "7px 12px",
+ },
+ textSizeMedium: {
+ padding: "9px 16px",
+ },
+ textSizeLarge: {
+ padding: "12px 16px",
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ borderRadius: 20,
+ [`&.${paperClasses.elevation1}`]: {
+ boxShadow:
+ "0px 5px 22px rgba(0, 0, 0, 0.04), 0px 0px 0px 0.5px rgba(0, 0, 0, 0.03)",
+ },
+ },
+ },
+ },
+ MuiCardContent: {
+ styleOverrides: {
+ root: {
+ padding: "32px 24px",
+ "&:last-child": {
+ paddingBottom: "32px",
+ },
+ },
+ },
+ },
+ MuiCardHeader: {
+ defaultProps: {
+ titleTypographyProps: {
+ variant: "h6",
+ },
+ subheaderTypographyProps: {
+ variant: "body2",
+ },
+ },
+ styleOverrides: {
+ root: {
+ padding: "32px 24px 16px",
+ },
+ },
+ },
+ MuiCssBaseline: {
+ styleOverrides: {
+ "*": {
+ boxSizing: "border-box",
+ },
+ html: {
+ MozOsxFontSmoothing: "grayscale",
+ WebkitFontSmoothing: "antialiased",
+ display: "flex",
+ flexDirection: "column",
+ minHeight: "100%",
+ width: "100%",
+ },
+ body: {
+ display: "flex",
+ flex: "1 1 auto",
+ flexDirection: "column",
+ minHeight: "100%",
+ width: "100%",
+ },
+ "#__next": {
+ display: "flex",
+ flex: "1 1 auto",
+ flexDirection: "column",
+ height: "100%",
+ width: "100%",
+ },
+ "#nprogress": {
+ pointerEvents: "none",
+ },
+ "#nprogress .bar": {
+ backgroundColor: palette.primary.main,
+ height: 3,
+ left: 0,
+ position: "fixed",
+ top: 0,
+ width: "100%",
+ zIndex: 2000,
+ },
+ },
+ },
+ MuiInputBase: {
+ styleOverrides: {
+ input: {
+ "&::placeholder": {
+ opacity: 1,
+ },
+ },
+ },
+ },
+ MuiInput: {
+ styleOverrides: {
+ input: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: "24px",
+ "&::placeholder": {
+ color: palette.text.secondary,
+ },
+ },
+ },
+ },
+ MuiFilledInput: {
+ styleOverrides: {
+ root: {
+ backgroundColor: "transparent",
+ borderRadius: 8,
+ borderStyle: "solid",
+ borderWidth: 1,
+ overflow: "hidden",
+ borderColor: palette.neutral[200],
+ transition: muiTheme.transitions.create([
+ "border-color",
+ "box-shadow",
+ ]),
+ "&:hover": {
+ backgroundColor: palette.action.hover,
+ },
+ "&:before": {
+ display: "none",
+ },
+ "&:after": {
+ display: "none",
+ },
+ [`&.${filledInputClasses.disabled}`]: {
+ backgroundColor: "transparent",
+ },
+ [`&.${filledInputClasses.focused}`]: {
+ backgroundColor: "transparent",
+ borderColor: palette.primary.main,
+ boxShadow: `${palette.primary.main} 0 0 0 2px`,
+ },
+ [`&.${filledInputClasses.error}`]: {
+ borderColor: palette.error.main,
+ boxShadow: `${palette.error.main} 0 0 0 2px`,
+ },
+ },
+ input: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: "24px",
+ },
+ },
+ },
+ MuiOutlinedInput: {
+ styleOverrides: {
+ root: {
+ "&:hover": {
+ backgroundColor: palette.action.hover,
+ [`& .${outlinedInputClasses.notchedOutline}`]: {
+ borderColor: palette.neutral[200],
+ },
+ },
+ [`&.${outlinedInputClasses.focused}`]: {
+ backgroundColor: "transparent",
+ [`& .${outlinedInputClasses.notchedOutline}`]: {
+ borderColor: palette.primary.main,
+ boxShadow: `${palette.primary.main} 0 0 0 2px`,
+ },
+ },
+ [`&.${filledInputClasses.error}`]: {
+ [`& .${outlinedInputClasses.notchedOutline}`]: {
+ borderColor: palette.error.main,
+ boxShadow: `${palette.error.main} 0 0 0 2px`,
+ },
+ },
+ },
+ input: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: "24px",
+ },
+ notchedOutline: {
+ borderColor: palette.neutral[200],
+ transition: muiTheme.transitions.create([
+ "border-color",
+ "box-shadow",
+ ]),
+ },
+ },
+ },
+ MuiFormLabel: {
+ styleOverrides: {
+ root: {
+ fontSize: 14,
+ fontWeight: 500,
+ [`&.${inputLabelClasses.filled}`]: {
+ transform: "translate(12px, 18px) scale(1)",
+ },
+ [`&.${inputLabelClasses.shrink}`]: {
+ [`&.${inputLabelClasses.standard}`]: {
+ transform: "translate(0, -1.5px) scale(0.85)",
+ },
+ [`&.${inputLabelClasses.filled}`]: {
+ transform: "translate(12px, 6px) scale(0.85)",
+ },
+ [`&.${inputLabelClasses.outlined}`]: {
+ transform: "translate(14px, -9px) scale(0.85)",
+ },
+ },
+ },
+ },
+ },
+ MuiTab: {
+ styleOverrides: {
+ root: {
+ fontSize: 14,
+ fontWeight: 500,
+ lineHeight: 1.71,
+ minWidth: "auto",
+ paddingLeft: 0,
+ paddingRight: 0,
+ textTransform: "none",
+ "& + &": {
+ marginLeft: 24,
+ },
+ },
+ },
+ },
+ MuiTableCell: {
+ styleOverrides: {
+ root: {
+ borderBottomColor: palette.divider,
+ padding: "15px 16px",
+ },
+ },
+ },
+ MuiTableHead: {
+ styleOverrides: {
+ root: {
+ borderBottom: "none",
+ [`& .${tableCellClasses.root}`]: {
+ borderBottom: "none",
+ backgroundColor: palette.neutral[50],
+ color: palette.neutral[700],
+ fontSize: 12,
+ fontWeight: 600,
+ lineHeight: 1,
+ letterSpacing: 0.5,
+ textTransform: "uppercase",
+ },
+ [`& .${tableCellClasses.paddingCheckbox}`]: {
+ paddingTop: 4,
+ paddingBottom: 4,
+ },
+ },
+ },
+ },
+ MuiTextField: {
+ defaultProps: {
+ variant: "filled",
+ },
+ },
+ };
+}
diff --git a/consumer-issuing/src/theme/create-palette.ts b/consumer-issuing/src/theme/create-palette.ts
new file mode 100644
index 00000000..a00bc2dd
--- /dev/null
+++ b/consumer-issuing/src/theme/create-palette.ts
@@ -0,0 +1,42 @@
+import { alpha } from "@mui/material";
+import { common } from "@mui/material/colors";
+
+import {
+ error,
+ indigo,
+ info,
+ neutral,
+ success,
+ warning,
+ secondary,
+} from "src/theme/colors";
+
+export default function createPalette() {
+ return {
+ action: {
+ active: neutral[500],
+ disabled: alpha(neutral[900], 0.38),
+ disabledBackground: alpha(neutral[900], 0.12),
+ focus: alpha(neutral[900], 0.16),
+ hover: alpha(neutral[900], 0.04),
+ selected: alpha(neutral[900], 0.12),
+ },
+ background: {
+ default: common.white,
+ paper: common.white,
+ },
+ divider: "#F2F4F7",
+ error,
+ info,
+ neutral,
+ primary: indigo,
+ secondary,
+ success,
+ text: {
+ primary: neutral[900],
+ secondary: neutral[500],
+ disabled: alpha(neutral[900], 0.38),
+ },
+ warning,
+ };
+}
diff --git a/consumer-issuing/src/theme/create-shadows.ts b/consumer-issuing/src/theme/create-shadows.ts
new file mode 100644
index 00000000..63d2c0bc
--- /dev/null
+++ b/consumer-issuing/src/theme/create-shadows.ts
@@ -0,0 +1,31 @@
+import { Shadows } from "@mui/material";
+
+export const createShadows = (): Shadows => {
+ return [
+ "none",
+ "0px 1px 2px rgba(0, 0, 0, 0.08)",
+ "0px 1px 5px rgba(0, 0, 0, 0.08)",
+ "0px 1px 8px rgba(0, 0, 0, 0.08)",
+ "0px 1px 10px rgba(0, 0, 0, 0.08)",
+ "0px 1px 14px rgba(0, 0, 0, 0.08)",
+ "0px 1px 18px rgba(0, 0, 0, 0.08)",
+ "0px 2px 16px rgba(0, 0, 0, 0.08)",
+ "0px 3px 14px rgba(0, 0, 0, 0.08)",
+ "0px 3px 16px rgba(0, 0, 0, 0.08)",
+ "0px 4px 18px rgba(0, 0, 0, 0.08)",
+ "0px 4px 20px rgba(0, 0, 0, 0.08)",
+ "0px 5px 22px rgba(0, 0, 0, 0.08)",
+ "0px 5px 24px rgba(0, 0, 0, 0.08)",
+ "0px 5px 26px rgba(0, 0, 0, 0.08)",
+ "0px 6px 28px rgba(0, 0, 0, 0.08)",
+ "0px 6px 30px rgba(0, 0, 0, 0.08)",
+ "0px 6px 32px rgba(0, 0, 0, 0.08)",
+ "0px 7px 34px rgba(0, 0, 0, 0.08)",
+ "0px 7px 36px rgba(0, 0, 0, 0.08)",
+ "0px 8px 38px rgba(0, 0, 0, 0.08)",
+ "0px 8px 40px rgba(0, 0, 0, 0.08)",
+ "0px 8px 42px rgba(0, 0, 0, 0.08)",
+ "0px 9px 44px rgba(0, 0, 0, 0.08)",
+ "0px 9px 46px rgba(0, 0, 0, 0.08)",
+ ];
+};
diff --git a/consumer-issuing/src/theme/create-typography.ts b/consumer-issuing/src/theme/create-typography.ts
new file mode 100644
index 00000000..5b31692d
--- /dev/null
+++ b/consumer-issuing/src/theme/create-typography.ts
@@ -0,0 +1,79 @@
+import { TypographyOptions } from "@mui/material/styles/createTypography";
+
+export const createTypography = (): TypographyOptions => {
+ return {
+ fontFamily:
+ '"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"',
+ body1: {
+ fontSize: "1rem",
+ fontWeight: 400,
+ lineHeight: 1.5,
+ },
+ body2: {
+ fontSize: "0.875rem",
+ fontWeight: 400,
+ lineHeight: 1.57,
+ },
+ button: {
+ fontWeight: 600,
+ },
+ caption: {
+ fontSize: "0.75rem",
+ fontWeight: 500,
+ lineHeight: 1.66,
+ },
+ subtitle1: {
+ fontSize: "1rem",
+ fontWeight: 500,
+ lineHeight: 1.57,
+ },
+ subtitle2: {
+ fontSize: "0.875rem",
+ fontWeight: 500,
+ lineHeight: 1.57,
+ },
+ overline: {
+ fontSize: "0.75rem",
+ fontWeight: 600,
+ letterSpacing: "0.5px",
+ lineHeight: 2.5,
+ textTransform: "uppercase",
+ },
+ h1: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "3.5rem",
+ lineHeight: 1.2,
+ },
+ h2: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "3rem",
+ lineHeight: 1.2,
+ },
+ h3: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "2.25rem",
+ lineHeight: 1.2,
+ },
+ h4: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "2rem",
+ lineHeight: 1.2,
+ },
+ h5: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "1.5rem",
+ lineHeight: 1.2,
+ },
+ h6: {
+ fontFamily: "'Plus Jakarta Sans', sans-serif",
+ fontWeight: 700,
+ fontSize: "1.125rem",
+ lineHeight: 1.2,
+ },
+ };
+};
diff --git a/consumer-issuing/src/theme/index.ts b/consumer-issuing/src/theme/index.ts
new file mode 100644
index 00000000..3e4d5a95
--- /dev/null
+++ b/consumer-issuing/src/theme/index.ts
@@ -0,0 +1,32 @@
+import { createTheme as createMuiTheme } from "@mui/material";
+
+import { createComponents } from "src/theme/create-components";
+import createPalette from "src/theme/create-palette";
+import { createShadows } from "src/theme/create-shadows";
+import { createTypography } from "src/theme/create-typography";
+
+export function createTheme() {
+ const palette = createPalette();
+ const components = createComponents({ palette });
+ const shadows = createShadows();
+ const typography = createTypography();
+
+ return createMuiTheme({
+ breakpoints: {
+ values: {
+ xs: 0,
+ sm: 600,
+ md: 900,
+ lg: 1200,
+ xl: 1440,
+ },
+ },
+ components,
+ palette,
+ shadows,
+ shape: {
+ borderRadius: 8,
+ },
+ typography,
+ });
+}
diff --git a/consumer-issuing/src/types/api-response.ts b/consumer-issuing/src/types/api-response.ts
new file mode 100644
index 00000000..56c7c521
--- /dev/null
+++ b/consumer-issuing/src/types/api-response.ts
@@ -0,0 +1,10 @@
+export type ApiResponse = {
+ success: boolean;
+ data?: TData;
+ error?: {
+ message: string;
+ details?: string;
+ };
+};
+
+export const apiResponse = (data: ApiResponse) => data;
diff --git a/consumer-issuing/src/types/chart-data.ts b/consumer-issuing/src/types/chart-data.ts
new file mode 100644
index 00000000..281126ed
--- /dev/null
+++ b/consumer-issuing/src/types/chart-data.ts
@@ -0,0 +1,6 @@
+export interface BalanceChartData {
+ currency: string;
+ balanceTransactionsDates: string[];
+ balanceTransactionsFundsIn: number[];
+ balanceTransactionsFundsOut: number[];
+}
diff --git a/consumer-issuing/src/types/constants.ts b/consumer-issuing/src/types/constants.ts
new file mode 100644
index 00000000..90880d74
--- /dev/null
+++ b/consumer-issuing/src/types/constants.ts
@@ -0,0 +1,24 @@
+export const COUNTRIES = [
+ ["AT", "Austria"],
+ ["BE", "Belgium"],
+ ["HR", "Croatia"],
+ ["CY", "Cyprus"],
+ ["EE", "Estonia"],
+ ["FI", "Finland"],
+ ["FR", "France"],
+ ["DE", "Germany"],
+ ["GR", "Greece"],
+ ["IE", "Ireland"],
+ ["IT", "Italy"],
+ ["LV", "Latvia"],
+ ["LT", "Lithuania"],
+ ["LU", "Luxembourg"],
+ ["MT", "Malta"],
+ ["NL", "Netherlands"],
+ ["PT", "Portugal"],
+ ["SK", "Slovakia"],
+ ["SI", "Slovenia"],
+ ["ES", "Spain"],
+ ["GB", "United Kingdom"],
+ ["US", "United States"],
+];
diff --git a/consumer-issuing/src/types/mui.d.ts b/consumer-issuing/src/types/mui.d.ts
new file mode 100644
index 00000000..aaf36c99
--- /dev/null
+++ b/consumer-issuing/src/types/mui.d.ts
@@ -0,0 +1,42 @@
+import { Color } from "@mui/material";
+import * as React from "react";
+
+declare module "@mui/material/Alert" {
+ export interface AlertPropsColorOverrides {
+ primary: true;
+ }
+
+ export interface AlertPropsVariantOverrides {
+ standard: true;
+ }
+
+ export interface AlertPropsColor {
+ primary?: true;
+ }
+
+ export interface AlertPropsVariant {
+ standard?: true;
+ }
+
+ export interface AlertProps
+ extends Omit, "color" | "variant"> {
+ color?: Color | keyof AlertPropsColorOverrides;
+ variant?: keyof AlertPropsVariantOverrides;
+ }
+}
+
+declare module "@mui/material/styles" {
+ export interface PaletteColor {
+ lightest: string;
+ light: string;
+ main: string;
+ dark: string;
+ darkest: string;
+ contrastText: string;
+ alpha4: string;
+ alpha8: string;
+ alpha12: string;
+ alpha30: string;
+ alpha50: string;
+ }
+}
diff --git a/consumer-issuing/src/types/network-type.tsx b/consumer-issuing/src/types/network-type.tsx
new file mode 100644
index 00000000..d0f2c586
--- /dev/null
+++ b/consumer-issuing/src/types/network-type.tsx
@@ -0,0 +1,6 @@
+enum NetworkType {
+ ACH = "ach",
+ US_DOMESTIC_WIRE = "us_domestic_wire",
+}
+
+export default NetworkType;
diff --git a/consumer-issuing/src/types/next-auth.d.ts b/consumer-issuing/src/types/next-auth.d.ts
new file mode 100644
index 00000000..dd014507
--- /dev/null
+++ b/consumer-issuing/src/types/next-auth.d.ts
@@ -0,0 +1,31 @@
+// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports
+import NextAuth, { DefaultSession, DefaultUser } from "next-auth";
+import { DefaultJWT } from "next-auth/jwt";
+
+import { SupportedCountry } from "src/utils/account-management-helpers";
+
+declare module "next-auth" {
+ /**
+ * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
+ */
+ interface Session extends DefaultSession {
+ email: string;
+ accountId: string;
+ requiresOnboarding: boolean;
+ businessName: string;
+ country: SupportedCountry;
+ currency: string;
+ stripeAccount: StripeAccount;
+ }
+}
+
+declare module "next-auth/jwt" {
+ /** Returned by the `jwt` callback and `getToken`, when using JWT sessions */
+ interface JWT extends DefaultJWT {
+ accountId: string;
+ requiresOnboarding: boolean;
+ businessName: string;
+ country: SupportedCountry;
+ currency: string;
+ }
+}
diff --git a/consumer-issuing/src/types/severity-color.ts b/consumer-issuing/src/types/severity-color.ts
new file mode 100644
index 00000000..b1a5ba81
--- /dev/null
+++ b/consumer-issuing/src/types/severity-color.ts
@@ -0,0 +1,7 @@
+export type SeverityColor =
+ | "primary"
+ | "secondary"
+ | "error"
+ | "info"
+ | "warning"
+ | "success";
diff --git a/consumer-issuing/src/types/transaction-result.tsx b/consumer-issuing/src/types/transaction-result.tsx
new file mode 100644
index 00000000..2e1b0b52
--- /dev/null
+++ b/consumer-issuing/src/types/transaction-result.tsx
@@ -0,0 +1,9 @@
+enum TransactionResult {
+ POSTED = "posted",
+ PROCESSING = "processing",
+ // TODO: Handle the return status of the transaction result
+ // RETURNED = "return",
+ FAILED = "fail",
+}
+
+export default TransactionResult;
diff --git a/consumer-issuing/src/utils/account-management-helpers.ts b/consumer-issuing/src/utils/account-management-helpers.ts
new file mode 100644
index 00000000..c5fd5d0e
--- /dev/null
+++ b/consumer-issuing/src/utils/account-management-helpers.ts
@@ -0,0 +1,249 @@
+export enum SupportedCountry {
+ AT = "AT",
+ BE = "BE",
+ CY = "CY",
+ DE = "DE",
+ EE = "EE",
+ ES = "ES",
+ FI = "FI",
+ FR = "FR",
+ GR = "GR",
+ HR = "HR",
+ IE = "IE",
+ IT = "IT",
+ LT = "LT",
+ LU = "LU",
+ LV = "LV",
+ MT = "MT",
+ NL = "NL",
+ PT = "PT",
+ SI = "SI",
+ SK = "SK",
+ UK = "GB",
+ US = "US",
+}
+
+export enum Currency {
+ USD = "usd",
+ GBP = "gbp",
+ EUR = "eur",
+}
+
+export enum PlatformStripeAccount {
+ UK,
+ EU,
+ US,
+}
+
+export type StripeAccount = {
+ accountId: string;
+ platform: PlatformStripeAccount;
+};
+
+type CountryConfig = {
+ code: string;
+ name: string;
+ currency: Currency;
+ platform: PlatformStripeAccount;
+ locale: string;
+};
+
+export const CountryConfigMap: Record = {
+ [SupportedCountry.AT]: {
+ code: "AT",
+ name: "Austria",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "de-AT",
+ },
+ [SupportedCountry.BE]: {
+ code: "BE",
+ name: "Belgium",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "fr-BE",
+ },
+ [SupportedCountry.CY]: {
+ code: "CY",
+ name: "Cyprus",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "el-CY",
+ },
+ [SupportedCountry.DE]: {
+ code: "DE",
+ name: "Germany",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "de-DE",
+ },
+ [SupportedCountry.EE]: {
+ code: "EE",
+ name: "Estonia",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "et-EE",
+ },
+ [SupportedCountry.ES]: {
+ code: "ES",
+ name: "Spain",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "es-ES",
+ },
+ [SupportedCountry.FI]: {
+ code: "FI",
+ name: "Finland",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "fi-FI",
+ },
+ [SupportedCountry.FR]: {
+ code: "FR",
+ name: "France",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "fr-FR",
+ },
+ [SupportedCountry.GR]: {
+ code: "GR",
+ name: "Greece",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "el-GR",
+ },
+ [SupportedCountry.HR]: {
+ code: "HR",
+ name: "Croatia",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "hr-HR",
+ },
+ [SupportedCountry.IE]: {
+ code: "IE",
+ name: "Ireland",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "en-IE",
+ },
+ [SupportedCountry.IT]: {
+ code: "IT",
+ name: "Italy",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "it-IT",
+ },
+ [SupportedCountry.LT]: {
+ code: "LT",
+ name: "Lithuania",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "lt-LT",
+ },
+ [SupportedCountry.LU]: {
+ code: "LU",
+ name: "Luxembourg",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "lb-LU",
+ },
+ [SupportedCountry.LV]: {
+ code: "LV",
+ name: "Latvia",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "lv-LV",
+ },
+ [SupportedCountry.MT]: {
+ code: "MT",
+ name: "Malta",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "mt-MT",
+ },
+ [SupportedCountry.NL]: {
+ code: "NL",
+ name: "Netherlands",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "nl-NL",
+ },
+ [SupportedCountry.PT]: {
+ code: "PT",
+ name: "Portugal",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "pt-PT",
+ },
+ [SupportedCountry.SI]: {
+ code: "SI",
+ name: "Slovenia",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "sl-SI",
+ },
+ [SupportedCountry.SK]: {
+ code: "SK",
+ name: "Slovakia",
+ currency: Currency.EUR,
+ platform: PlatformStripeAccount.EU,
+ locale: "sk-SK",
+ },
+ [SupportedCountry.UK]: {
+ code: "GB",
+ name: "United Kingdom",
+ currency: Currency.GBP,
+ platform: PlatformStripeAccount.UK,
+ locale: "en-GB",
+ },
+ [SupportedCountry.US]: {
+ code: "US",
+ name: "United States",
+ currency: Currency.USD,
+ platform: PlatformStripeAccount.US,
+ locale: "en-US",
+ },
+};
+
+export const isSupportedCountry = (
+ country: unknown,
+): country is SupportedCountry => {
+ return Object.values(SupportedCountry).includes(country as SupportedCountry);
+};
+
+export const getPlatformStripeAccountForCountry = (
+ country: SupportedCountry,
+): PlatformStripeAccount => {
+ return CountryConfigMap[country].platform;
+};
+
+export const getSupportedCountryConfigsInRegion = (
+ country: SupportedCountry,
+): CountryConfig[] => {
+ const platform = getPlatformStripeAccountForCountry(country);
+ const countryConfigs = Object.values(CountryConfigMap).filter(
+ (config) => config.platform === platform,
+ );
+ return countryConfigs;
+};
+
+const keyPresent = (key: string | undefined): boolean =>
+ !!key && key.length > 0 && key != "none";
+
+export const enabledPlatforms = () => {
+ const {
+ [PlatformStripeAccount.UK]: enableUK,
+ [PlatformStripeAccount.EU]: enableEU,
+ [PlatformStripeAccount.US]: enableUS,
+ } = {
+ [PlatformStripeAccount.UK]: keyPresent(process.env.STRIPE_SECRET_KEY_UK),
+ [PlatformStripeAccount.EU]: keyPresent(process.env.STRIPE_SECRET_KEY_EU),
+ [PlatformStripeAccount.US]: keyPresent(process.env.STRIPE_SECRET_KEY_US),
+ };
+
+ return {
+ [PlatformStripeAccount.UK]: enableUK,
+ [PlatformStripeAccount.EU]: enableEU,
+ [PlatformStripeAccount.US]: enableUS,
+ };
+};
diff --git a/consumer-issuing/src/utils/api-helpers.ts b/consumer-issuing/src/utils/api-helpers.ts
new file mode 100644
index 00000000..ff2b9544
--- /dev/null
+++ b/consumer-issuing/src/utils/api-helpers.ts
@@ -0,0 +1,106 @@
+import { NextApiRequest, NextApiResponse } from "next";
+
+import { apiResponse, ApiResponse } from "src/types/api-response";
+
+export const postApi = async (path: string, body?: object) => {
+ return await fetchApi("POST", path, body);
+};
+
+export const putApi = async (path: string, body?: object) => {
+ return await fetchApi("PUT", path, body);
+};
+
+export const patchApi = async (path: string, body?: object) => {
+ return await fetchApi("PATCH", path, body);
+};
+
+const fetchApi = async (method: HttpMethod, path: string, body?: object) => {
+ return await fetch(path, {
+ method,
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json; charset=utf-8",
+ },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+};
+
+export const extractJsonFromResponse = async (
+ response: Response,
+): Promise> => {
+ const contentType = response.headers.get("content-type");
+ if (contentType && contentType.includes("application/json")) {
+ const data = await response.json();
+ return data as ApiResponse;
+ } else {
+ throw new Error("Something went wrong");
+ }
+};
+
+export const handleResult = async ({
+ result,
+ onSuccess,
+ onError,
+ onFinally,
+}: {
+ result: ApiResponse;
+ onSuccess: () => Promise | void;
+ onError: (error: { message: string; details?: string | undefined }) => void;
+ onFinally?: () => void;
+}) => {
+ try {
+ if (result.success) {
+ await onSuccess();
+ } else {
+ if (result.error == undefined) {
+ throw new Error("Something went wrong");
+ }
+ onError(result.error);
+ }
+ } finally {
+ if (onFinally) {
+ onFinally();
+ }
+ }
+};
+
+type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
+
+type HandlerMapping = {
+ [key in HttpMethod]?: (req: NextApiRequest, res: NextApiResponse) => void;
+};
+
+export const handlerMapping = (
+ req: NextApiRequest,
+ res: NextApiResponse,
+ mapping: HandlerMapping,
+) => {
+ try {
+ if (req.method == undefined) {
+ return res
+ .status(400)
+ .json(
+ apiResponse({ success: false, error: { message: "Bad Request" } }),
+ );
+ }
+ const handler = mapping[req.method as HttpMethod];
+ if (handler == undefined) {
+ return res
+ .status(400)
+ .json(
+ apiResponse({ success: false, error: { message: "Bad Request" } }),
+ );
+ }
+ return handler(req, res);
+ } catch (error) {
+ return res.status(500).json(
+ apiResponse({
+ success: false,
+ error: {
+ message: (error as Error).message,
+ details: (error as Error).stack,
+ },
+ }),
+ );
+ }
+};
diff --git a/consumer-issuing/src/utils/api-logger.ts b/consumer-issuing/src/utils/api-logger.ts
new file mode 100644
index 00000000..9422c80b
--- /dev/null
+++ b/consumer-issuing/src/utils/api-logger.ts
@@ -0,0 +1,34 @@
+import { prisma } from "src/db";
+
+export const logApiRequest = async (
+ email: string,
+ requestUrl: string,
+ requestMethod: string,
+ requestBody?: any,
+ responseBody?: any
+) => {
+ if (!prisma) {
+ console.error("Prisma client is not initialized");
+ return;
+ }
+
+// console.log("Logging API request for user:", email);
+ try {
+ await prisma.apiRequestLog.create({
+ data: {
+ user: {
+ connect: {
+ email: email,
+ },
+ },
+ requestUrl,
+ requestMethod,
+ requestBody: requestBody ? JSON.stringify(requestBody) : null,
+ responseBody: responseBody ? JSON.stringify(responseBody) : null,
+ },
+ });
+ } catch (error) {
+ // Log the error but don't throw it to prevent breaking the main functionality
+ console.error("Error logging API request:", error);
+ }
+};
\ No newline at end of file
diff --git a/consumer-issuing/src/utils/apply-pagination.ts b/consumer-issuing/src/utils/apply-pagination.ts
new file mode 100644
index 00000000..3078e787
--- /dev/null
+++ b/consumer-issuing/src/utils/apply-pagination.ts
@@ -0,0 +1,7 @@
+export function applyPagination(
+ documents: T[],
+ page: number,
+ rowsPerPage: number,
+): T[] {
+ return documents.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
+}
diff --git a/consumer-issuing/src/utils/authentication.ts b/consumer-issuing/src/utils/authentication.ts
new file mode 100644
index 00000000..35f42669
--- /dev/null
+++ b/consumer-issuing/src/utils/authentication.ts
@@ -0,0 +1,52 @@
+import bcrypt from "bcrypt";
+
+import { prisma } from "src/db";
+import {
+ getPlatformStripeAccountForCountry,
+ isSupportedCountry,
+ SupportedCountry,
+} from "src/utils/account-management-helpers";
+import { hasOutstandingRequirements } from "src/utils/onboarding-helpers";
+
+export const authenticateUser = async (email: string, password: string) => {
+ const userToAuthenticate = await prisma.user.findFirst({
+ where: { email },
+ });
+
+ const passwordMatch = await bcrypt.compare(
+ password,
+ userToAuthenticate?.password || "",
+ );
+
+ if (userToAuthenticate && passwordMatch) {
+ const user = await prisma.user.update({
+ where: { id: userToAuthenticate.id },
+ data: { lastLoginAt: new Date() },
+ });
+
+ if (!isSupportedCountry(user.country)) {
+ throw new Error(`Unsupported country ${user.country}`);
+ }
+
+ const stripeAccount = {
+ accountId: user.accountId,
+ platform: getPlatformStripeAccountForCountry(
+ user.country as unknown as SupportedCountry,
+ ),
+ };
+
+ const requiresOnboarding = await hasOutstandingRequirements(stripeAccount);
+
+ // User session context setup flow step 1:
+ // This is the object that will be available as `user` in the `jwt` callback in [...nextauth].ts
+ return {
+ id: user.id.toString(),
+ email: user.email,
+ accountId: user.accountId,
+ country: user.country,
+ requiresOnboarding: requiresOnboarding,
+ };
+ }
+
+ return null;
+};
diff --git a/consumer-issuing/src/utils/create-emotion-cache.ts b/consumer-issuing/src/utils/create-emotion-cache.ts
new file mode 100644
index 00000000..9285b9ef
--- /dev/null
+++ b/consumer-issuing/src/utils/create-emotion-cache.ts
@@ -0,0 +1,5 @@
+import createCache from "@emotion/cache";
+
+export default function createEmotionCache() {
+ return createCache({ key: "css", prepend: true });
+}
diff --git a/consumer-issuing/src/utils/demo-helpers.ts b/consumer-issuing/src/utils/demo-helpers.ts
new file mode 100644
index 00000000..cf4021fd
--- /dev/null
+++ b/consumer-issuing/src/utils/demo-helpers.ts
@@ -0,0 +1,115 @@
+import {
+ Faker,
+ faker,
+ fakerDE_AT,
+ fakerFR_BE,
+ fakerEL,
+ fakerDE,
+ fakerES,
+ fakerFI,
+ fakerFR,
+ fakerHR,
+ fakerEN_IE,
+ fakerIT,
+ fakerLV,
+ fakerNL,
+ fakerPT_PT,
+ fakerSK,
+ fakerEN_GB,
+ fakerEN_US,
+} from "@faker-js/faker";
+
+import { SupportedCountry } from "./account-management-helpers";
+
+// This helper function is used to determine if the app is running in demo mode which we deploy at baas.stripe.dev
+// To make it easier for you transition to a real production app, we've made it so that you can delete this code, all
+// references to it, and logic guarded by it and the app will still work. The only thing that will change is that your
+// app will start to behave like a real production app. For example, the Stripe Connect Onboarding forms will require
+// filling out by your users.
+export const isDemoMode = () => {
+ return process.env.NEXT_PUBLIC_DEMO_MODE === "true";
+};
+
+export const TOS_ACCEPTANCE = { date: 1691518261, ip: "127.0.0.1" };
+
+export const LocalizedFakerMap: Record = {
+ [SupportedCountry.AT]: fakerDE_AT,
+ [SupportedCountry.BE]: fakerFR_BE,
+ [SupportedCountry.CY]: fakerEL,
+ [SupportedCountry.DE]: fakerDE,
+ [SupportedCountry.EE]: fakerEN_US,
+ [SupportedCountry.ES]: fakerES,
+ [SupportedCountry.FI]: fakerFI,
+ [SupportedCountry.FR]: fakerFR,
+ [SupportedCountry.GR]: fakerEL,
+ [SupportedCountry.HR]: fakerHR,
+ [SupportedCountry.IE]: fakerEN_IE,
+ [SupportedCountry.IT]: fakerIT,
+ [SupportedCountry.LT]: fakerEN_US,
+ [SupportedCountry.LU]: fakerEN_US,
+ [SupportedCountry.LV]: fakerLV,
+ [SupportedCountry.MT]: fakerEN_US,
+ [SupportedCountry.NL]: fakerNL,
+ [SupportedCountry.PT]: fakerPT_PT,
+ [SupportedCountry.SI]: fakerEN_US,
+ [SupportedCountry.SK]: fakerSK,
+ [SupportedCountry.UK]: fakerEN_GB,
+ [SupportedCountry.US]: fakerEN_US,
+};
+
+type FakeAddress = {
+ address1: string;
+ city: string;
+ state: string;
+ postalCode: string;
+};
+
+export const getFakePhoneByCountry = (country: SupportedCountry): string => {
+ const faker = LocalizedFakerMap[country] as Faker;
+
+ switch (country) {
+ case SupportedCountry.UK:
+ return faker.helpers.fromRegExp("+44 7[0-9]{9}"); // UK phone number format
+ case SupportedCountry.US:
+ return faker.helpers.fromRegExp("+1 [0-9]{3} [0-9]{3} [0-9]{4}"); // US phone number format
+ default:
+ throw new Error(
+ `Fake phone number generation not implemented for country: ${country}`,
+ );
+ }
+};
+
+export const getFakeAddressByCountry = (
+ country: SupportedCountry,
+): FakeAddress => {
+ const faker = LocalizedFakerMap[country] as Faker;
+
+ switch (country) {
+ case SupportedCountry.UK:
+ return {
+ address1: faker.location.streetAddress(),
+ city: faker.location.city(),
+ state: faker.location.county(),
+ postalCode: faker.location.zipCode(),
+ };
+ case SupportedCountry.US:
+ return {
+ address1: faker.location.streetAddress(),
+ city: faker.location.city(),
+ state: faker.location.state({ abbreviated: true }),
+ postalCode: faker.location.zipCode(),
+ };
+ default:
+ throw new Error(
+ `Fake address generation not implemented for country: ${country}`,
+ );
+ }
+};
+
+/**
+ * Returns the fiscal year end date dynamically based on the current year.
+ */
+export const getFiscalYearEnd = (): string => {
+ const lastYear = new Date().getFullYear() - 1;
+ return `${lastYear}-12-31`;
+};
diff --git a/consumer-issuing/src/utils/format.ts b/consumer-issuing/src/utils/format.ts
new file mode 100644
index 00000000..6a718ccf
--- /dev/null
+++ b/consumer-issuing/src/utils/format.ts
@@ -0,0 +1,38 @@
+import { format, fromUnixTime } from "date-fns";
+
+import {
+ CountryConfigMap,
+ SupportedCountry,
+} from "src/utils/account-management-helpers";
+
+export const formatCurrencyForCountry = (
+ amountInMinorUnits: number,
+ country: SupportedCountry,
+) => {
+ const countryConfig = CountryConfigMap[country];
+ const currencyFormatter = new Intl.NumberFormat(countryConfig.locale, {
+ style: "currency",
+ currency: countryConfig.currency.toString(),
+ minimumFractionDigits: 2,
+ });
+ // Convert amount from minor units to major units (e.g., cents to dollars)
+ return currencyFormatter.format(amountInMinorUnits / 100);
+};
+
+export const formatDateTime = (secondsSinceEpoch: number) => {
+ return format(fromUnixTime(secondsSinceEpoch), "MMM dd, yyyy");
+};
+
+export const formatDateAndTime = (secondsSinceEpoch: number) => {
+ return format(fromUnixTime(secondsSinceEpoch), "MMM dd, yyyy 'at' h:mm a");
+};
+
+export const capitalize = (value: string) => {
+ return value.charAt(0).toUpperCase() + value.slice(1);
+};
+
+export const titleize = (str: string) => {
+ return str
+ .toLowerCase()
+ .replace(/(?:^|\s|-)\w/g, (match) => match.toUpperCase());
+};
diff --git a/consumer-issuing/src/utils/logger.ts b/consumer-issuing/src/utils/logger.ts
new file mode 100644
index 00000000..962f8e9d
--- /dev/null
+++ b/consumer-issuing/src/utils/logger.ts
@@ -0,0 +1,60 @@
+type LogLevel = "debug" | "info" | "warn" | "error";
+const logLevels: Record = {
+ debug: 0,
+ info: 1,
+ warn: 2,
+ error: 3,
+};
+
+// Determine the current log level from the environment variable or default to "info"
+const currentLogLevel: LogLevel = (process.env.LOG_LEVEL as LogLevel) || "info";
+
+// Check if a message at the given level should be logged
+const shouldLog = (level: LogLevel): boolean => {
+ return logLevels[level] >= logLevels[currentLogLevel];
+};
+
+// ANSI escape codes for colors and styles
+const colors = {
+ reset: "\x1b[0m",
+ bright: "\x1b[1m", // Makes text bold/bright
+ dim: "\x1b[2m",
+
+ // Foreground (text) colors
+ fg: {
+ red: "\x1b[31m",
+ green: "\x1b[32m",
+ yellow: "\x1b[33m",
+ blue: "\x1b[34m",
+ // Add more colors as needed
+ },
+};
+
+const logger = {
+ debug: (...args: unknown[]) => {
+ if (shouldLog("debug")) {
+ // eslint-disable-next-line no-console
+ console.debug(colors.bright, colors.fg.blue, ...args, colors.reset);
+ }
+ },
+ info: (...args: unknown[]) => {
+ if (shouldLog("info")) {
+ // eslint-disable-next-line no-console
+ console.info(colors.fg.green, ...args, colors.reset);
+ }
+ },
+ warn: (...args: unknown[]) => {
+ if (shouldLog("warn")) {
+ // eslint-disable-next-line no-console
+ console.warn(colors.fg.yellow, ...args, colors.reset);
+ }
+ },
+ error: (...args: unknown[]) => {
+ if (shouldLog("error")) {
+ // eslint-disable-next-line no-console
+ console.error(colors.fg.red, ...args, colors.reset);
+ }
+ },
+};
+
+export default logger;
diff --git a/consumer-issuing/src/utils/onboarding-helpers.ts b/consumer-issuing/src/utils/onboarding-helpers.ts
new file mode 100644
index 00000000..45114d84
--- /dev/null
+++ b/consumer-issuing/src/utils/onboarding-helpers.ts
@@ -0,0 +1,51 @@
+import logger from "./logger";
+
+import { StripeAccount } from "src/utils/account-management-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+const IGNORE_REQUIREMENTS = ["external_account"];
+
+export const hasOutstandingRequirements = async (
+ stripeAccount: StripeAccount,
+) => {
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ const account = await stripe.accounts.retrieve(accountId);
+
+ const outstandingRequirements = account?.requirements?.currently_due?.filter(
+ (requirement) => !IGNORE_REQUIREMENTS.includes(requirement),
+ );
+
+ logger.debug("Outstanding requirements check:", outstandingRequirements);
+
+ const result = (outstandingRequirements?.length ?? 0) > 0;
+
+ return result;
+};
+
+export async function createAccountOnboardingUrl(stripeAccount: StripeAccount) {
+ if (
+ process.env.CONNECT_ONBOARDING_REDIRECT_URL == undefined &&
+ process.env.VERCEL_URL == undefined
+ ) {
+ throw new Error("CONNECT_ONBOARDING_REDIRECT_URL is not set");
+ }
+
+ const { accountId, platform } = stripeAccount;
+ let connectOnboardingRedirectUrl;
+
+ if (process.env.VERCEL_URL) {
+ connectOnboardingRedirectUrl = `https://${process.env.VERCEL_URL}`;
+ } else {
+ connectOnboardingRedirectUrl = process.env.CONNECT_ONBOARDING_REDIRECT_URL;
+ }
+
+ const stripe = stripeClient(platform);
+ const { url } = await stripe.accountLinks.create({
+ type: "account_onboarding",
+ account: accountId,
+ refresh_url: connectOnboardingRedirectUrl + "/onboard",
+ return_url: connectOnboardingRedirectUrl + "/",
+ });
+ return url;
+}
diff --git a/consumer-issuing/src/utils/platform-stripe-account-helpers.ts b/consumer-issuing/src/utils/platform-stripe-account-helpers.ts
new file mode 100644
index 00000000..a81fcc63
--- /dev/null
+++ b/consumer-issuing/src/utils/platform-stripe-account-helpers.ts
@@ -0,0 +1,96 @@
+enum SupportedCountry {
+ AT = "AT",
+ BE = "BE",
+ CY = "CY",
+ DE = "DE",
+ EE = "EE",
+ ES = "ES",
+ FI = "FI",
+ FR = "FR",
+ GB = "GB",
+ GR = "GR",
+ HR = "HR",
+ IE = "IE",
+ IT = "IT",
+ LT = "LT",
+ LU = "LU",
+ LV = "LV",
+ MT = "MT",
+ NL = "NL",
+ PT = "PT",
+ SI = "SI",
+ SK = "SK",
+ US = "US",
+}
+
+enum PlatformStripeAccount {
+ UK = "UK",
+ EU = "EU",
+ US = "US",
+}
+
+const countryToPlatformStripeAccountMap: Record<
+ SupportedCountry,
+ PlatformStripeAccount
+> = {
+ [SupportedCountry.AT]: PlatformStripeAccount.EU,
+ [SupportedCountry.BE]: PlatformStripeAccount.EU,
+ [SupportedCountry.CY]: PlatformStripeAccount.EU,
+ [SupportedCountry.DE]: PlatformStripeAccount.EU,
+ [SupportedCountry.EE]: PlatformStripeAccount.EU,
+ [SupportedCountry.ES]: PlatformStripeAccount.EU,
+ [SupportedCountry.FI]: PlatformStripeAccount.EU,
+ [SupportedCountry.FR]: PlatformStripeAccount.EU,
+ [SupportedCountry.GB]: PlatformStripeAccount.UK,
+ [SupportedCountry.GR]: PlatformStripeAccount.EU,
+ [SupportedCountry.HR]: PlatformStripeAccount.EU,
+ [SupportedCountry.IE]: PlatformStripeAccount.EU,
+ [SupportedCountry.IT]: PlatformStripeAccount.EU,
+ [SupportedCountry.LT]: PlatformStripeAccount.EU,
+ [SupportedCountry.LU]: PlatformStripeAccount.EU,
+ [SupportedCountry.LV]: PlatformStripeAccount.EU,
+ [SupportedCountry.MT]: PlatformStripeAccount.EU,
+ [SupportedCountry.NL]: PlatformStripeAccount.EU,
+ [SupportedCountry.PT]: PlatformStripeAccount.EU,
+ [SupportedCountry.SI]: PlatformStripeAccount.EU,
+ [SupportedCountry.SK]: PlatformStripeAccount.EU,
+ [SupportedCountry.US]: PlatformStripeAccount.US,
+};
+
+const isSupportedCountry = (country: unknown): country is SupportedCountry => {
+ return Object.values(SupportedCountry).includes(country as SupportedCountry);
+};
+
+const getPlatformStripeAccountForCountry = (
+ country: string,
+): PlatformStripeAccount => {
+ if (isSupportedCountry(country)) {
+ return countryToPlatformStripeAccountMap[country];
+ } else {
+ throw new Error(`Invalid or unsupported country: ${country}`);
+ }
+};
+
+const keyPresent = (key: string | undefined): boolean =>
+ !!key && key.length > 0 && key != "none";
+
+const enabledPlatforms = () => {
+ const ukEnabled =
+ keyPresent(process.env.STRIPE_SECRET_KEY_UK) &&
+ keyPresent(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_UK);
+
+ const euEnabled =
+ keyPresent(process.env.STRIPE_SECRET_KEY_EU) &&
+ keyPresent(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_EU);
+
+ return {
+ [PlatformStripeAccount.UK]: ukEnabled,
+ [PlatformStripeAccount.EU]: euEnabled,
+ };
+};
+
+export {
+ PlatformStripeAccount,
+ getPlatformStripeAccountForCountry,
+ enabledPlatforms,
+};
diff --git a/consumer-issuing/src/utils/platform.ts b/consumer-issuing/src/utils/platform.ts
new file mode 100644
index 00000000..5913bc64
--- /dev/null
+++ b/consumer-issuing/src/utils/platform.ts
@@ -0,0 +1,43 @@
+enum Platform {
+ UK,
+ EU,
+ US,
+}
+
+const getPlatform = (country: string): Platform => {
+ switch (country) {
+ case "GB":
+ return Platform.UK;
+ case "EU":
+ return Platform.EU;
+ case "US":
+ return Platform.US;
+ default:
+ throw new Error(`Unsupported country ${country}`);
+ }
+};
+
+const keyPresent = (key: string | undefined): boolean =>
+ !!key && key.length > 0 && key != "none";
+
+const enabledPlatforms = () => {
+ const ukEnabled =
+ keyPresent(process.env.STRIPE_SECRET_KEY_UK) &&
+ keyPresent(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_UK);
+
+ const euEnabled =
+ keyPresent(process.env.STRIPE_SECRET_KEY_EU) &&
+ keyPresent(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_EU);
+
+ const usEnabled =
+ keyPresent(process.env.STRIPE_SECRET_KEY_US) &&
+ keyPresent(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_US);
+
+ return {
+ [Platform.UK]: ukEnabled,
+ [Platform.EU]: euEnabled,
+ [Platform.US]: usEnabled,
+ };
+};
+
+export { Platform, getPlatform, enabledPlatforms };
diff --git a/consumer-issuing/src/utils/registration-mode-context.ts b/consumer-issuing/src/utils/registration-mode-context.ts
new file mode 100644
index 00000000..af47a916
--- /dev/null
+++ b/consumer-issuing/src/utils/registration-mode-context.ts
@@ -0,0 +1,14 @@
+import { createContext } from "react";
+
+enum RegistrationMode {
+ IssuingTreasury,
+ Issuing,
+}
+
+const RegistrationModeContext = createContext({
+ mode: RegistrationMode.IssuingTreasury,
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ setMode: (_: RegistrationMode) => {},
+});
+
+export { RegistrationMode, RegistrationModeContext };
diff --git a/consumer-issuing/src/utils/session-helpers.ts b/consumer-issuing/src/utils/session-helpers.ts
new file mode 100644
index 00000000..62bde9cd
--- /dev/null
+++ b/consumer-issuing/src/utils/session-helpers.ts
@@ -0,0 +1,41 @@
+import {
+ GetServerSidePropsContext,
+ NextApiRequest,
+ NextApiResponse,
+} from "next";
+import { getServerSession } from "next-auth/next";
+
+import { authOptions } from "src/pages/api/auth/[...nextauth]";
+
+export const getSessionForServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getServerSession(context.req, context.res, authOptions);
+
+ if (session == null) {
+ throw new Error("Session is missing in the request");
+ }
+
+ return session;
+};
+
+export const getSessionForLoginOrRegisterServerSideProps = async (
+ context: GetServerSidePropsContext,
+) => {
+ const session = await getServerSession(context.req, context.res, authOptions);
+
+ return session;
+};
+
+export const getSessionForServerSide = async (
+ req: NextApiRequest,
+ res: NextApiResponse,
+) => {
+ const session = await getServerSession(req, res, authOptions);
+
+ if (session == null) {
+ throw new Error("Session is missing in the request");
+ }
+
+ return session;
+};
diff --git a/consumer-issuing/src/utils/stripe-authentication.ts b/consumer-issuing/src/utils/stripe-authentication.ts
new file mode 100644
index 00000000..6815930b
--- /dev/null
+++ b/consumer-issuing/src/utils/stripe-authentication.ts
@@ -0,0 +1,45 @@
+import { PlatformStripeAccount } from "./account-management-helpers";
+
+const getStripeSecretKey = (platform: PlatformStripeAccount): string | null => {
+ let key;
+
+ switch (platform) {
+ case PlatformStripeAccount.UK:
+ key = process.env.STRIPE_SECRET_KEY_UK;
+ break;
+ case PlatformStripeAccount.EU:
+ key = process.env.STRIPE_SECRET_KEY_EU;
+ break;
+ case PlatformStripeAccount.US:
+ key = process.env.STRIPE_SECRET_KEY_US;
+ break;
+ default:
+ throw new Error(`Unsupported platform: ${platform}`);
+ }
+
+ return key || null;
+};
+
+const getStripePublishableKey = (
+ platform: PlatformStripeAccount,
+): string | null => {
+ let key;
+
+ switch (platform) {
+ case PlatformStripeAccount.UK:
+ key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_UK;
+ break;
+ case PlatformStripeAccount.EU:
+ key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_EU;
+ break;
+ case PlatformStripeAccount.US:
+ key = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY_US;
+ break;
+ default:
+ throw new Error(`Unsupported platform: ${platform}`);
+ }
+
+ return key || null;
+};
+
+export { getStripeSecretKey, getStripePublishableKey };
diff --git a/consumer-issuing/src/utils/stripe-helpers.ts b/consumer-issuing/src/utils/stripe-helpers.ts
new file mode 100644
index 00000000..054b92c5
--- /dev/null
+++ b/consumer-issuing/src/utils/stripe-helpers.ts
@@ -0,0 +1,506 @@
+import { format, addDays } from "date-fns";
+import Stripe from "stripe";
+import { Session } from "next-auth";
+
+import { getStripeSecretKey } from "./stripe-authentication";
+import { logApiRequest } from "./api-logger";
+
+import { BalanceChartData } from "src/types/chart-data";
+import { StripeAccount, PlatformStripeAccount } from "src/utils/account-management-helpers";
+import stripeClient from "src/utils/stripe-loader";
+
+type FundsFlowByDate = {
+ date: string;
+ fundsIn: number;
+ fundsOut: number;
+};
+
+const NUMBER_OF_DAYS = 90;
+const DATE_FORMAT = "MMM dd";
+
+export async function getCardholders(stripeAccount: StripeAccount) {
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ const cardholders = await stripe.issuing.cardholders.list(
+ { limit: 100 },
+ { stripeAccount: accountId },
+ );
+
+ return {
+ cardholders: cardholders,
+ };
+}
+
+export async function getCards(stripeAccount: StripeAccount) {
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ const cards = await stripe.issuing.cards.list(
+ { limit: 100 },
+ { stripeAccount: accountId },
+ );
+
+ return {
+ cards: cards,
+ };
+}
+
+export async function getCardDetails(
+ stripeAccount: StripeAccount,
+ cardId: string,
+) {
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ // Retrieve last 10 authorizations
+ const card_authorizations = await stripe.issuing.authorizations.list(
+ {
+ card: cardId,
+ limit: 10,
+ },
+ { stripeAccount: accountId },
+ );
+
+ // Calculate current spend
+ let current_spend = 0;
+ card_authorizations.data.forEach(function (
+ authorization: Stripe.Issuing.Authorization,
+ ) {
+ // Validate the authorization was approved before adding it to the total spend
+ if (authorization.approved == true) {
+ current_spend = current_spend + authorization.amount;
+ }
+ });
+
+ const card_details = await stripe.issuing.cards.retrieve(
+ cardId,
+ { expand: ["cardholder"] },
+ {
+ stripeAccount: accountId,
+ },
+ );
+
+ const cardTransactions = {
+ card_authorizations: [] as Stripe.Issuing.Authorization[],
+ current_spend: 0,
+ card_details: {} as Stripe.Issuing.Card,
+ };
+ cardTransactions["card_authorizations"] = card_authorizations.data;
+ cardTransactions["current_spend"] = current_spend;
+ cardTransactions["card_details"] = card_details;
+
+ return cardTransactions;
+}
+
+export async function getAuthorizations(stripeAccount: StripeAccount) {
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ const authorizations = await stripe.issuing.authorizations.list(
+ { limit: 10 },
+ { stripeAccount: accountId },
+ );
+
+ return {
+ authorizations,
+ };
+}
+
+export async function getAuthorizationDetails(
+ stripeAccount: StripeAccount,
+ authorizationId: string,
+) {
+ const { accountId, platform } = stripeAccount;
+ const stripe = stripeClient(platform);
+ const authorization = await stripe.issuing.authorizations.retrieve(
+ authorizationId,
+ { stripeAccount: accountId },
+ );
+
+ return {
+ authorization,
+ };
+}
+
+export async function getBalance(stripeAccount: StripeAccount, session: Session) {
+ const { accountId, platform } = stripeAccount;
+
+ // Get the credit ledger data using direct HTTP request
+ const creditLedgerResponse = await fetch("https://api.stripe.com/v1/issuing/credit_ledger", {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ },
+ });
+
+ // Log the API request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_ledger",
+ "GET",
+ null,
+ await creditLedgerResponse.clone().json()
+ );
+
+ if (!creditLedgerResponse.ok) {
+ return {
+ balance: {
+ issuing: {
+ available: [{
+ amount: 0,
+ currency: "usd"
+ }],
+ credit_limit: [{
+ amount: 0,
+ currency: "usd"
+ }],
+ total_balance: [{
+ amount: 0,
+ currency: "usd"
+ }]
+ }
+ }
+ };
+ }
+
+ const creditLedger = await creditLedgerResponse.json();
+ const totalBalance = (creditLedger.obligations?.unpaid || 0) + (creditLedger.obligations?.accruing || 0);
+
+ // Create a balance object that matches the expected structure
+ const balance = {
+ issuing: {
+ available: [{
+ amount: creditLedger.credit_available || 0,
+ currency: creditLedger.currency || "usd",
+ }],
+ credit_limit: [{
+ amount: creditLedger.credit_limit || 0,
+ currency: creditLedger.currency || "usd",
+ }],
+ total_balance: [{
+ amount: totalBalance,
+ currency: creditLedger.currency || "usd",
+ }]
+ },
+ };
+
+ return {
+ balance: balance,
+ };
+}
+
+export async function getCreditLedgerEntries(
+ stripeAccount: StripeAccount,
+ currency: string,
+ session: Session
+) {
+ const { accountId, platform } = stripeAccount;
+
+ // Calculate the start and end date for the last 7 days
+ const endDate = new Date();
+ const startDate = addDays(endDate, -NUMBER_OF_DAYS + 1);
+
+ // Make direct API call to credit ledger entries endpoint with limit parameter
+ const response = await fetch(
+ "https://api.stripe.com/v1/issuing/credit_ledger_entries?limit=100",
+ {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ },
+ }
+ );
+
+ // Log the API request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_ledger_entries",
+ "GET",
+ null,
+ await response.clone().json()
+ );
+
+ // await logApiRequest(
+ // stripeAccount.userId,
+ // "https://api.stripe.com/v1/issuing/credit_ledger_entries",
+ // "GET",
+ // null,
+ // await response.clone().json()
+ // );
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch credit ledger entries: ${response.statusText}`);
+ }
+
+ const creditLedgerEntries = await response.json();
+ // console.log('Retrieving Credit Ledger Data from https://api.stripe.com/v1/issuing/credit_ledger_entries:', JSON.stringify(creditLedgerEntries, null, 2));
+
+ // Include only credit ledger entries for issuing_credit_repayments with non-null funding_obligation
+ const filteredEntries = creditLedgerEntries.data.filter((entry: any) => {
+ if (entry.source?.type === "issuing_credit_repayment" && entry.source?.issuing_credit_repayment) {
+ return !!entry.funding_obligation;
+ }
+ return true;
+ });
+
+ const datesArray: string[] = Array.from(
+ { length: NUMBER_OF_DAYS },
+ (_, index) => {
+ const date = addDays(endDate, -index);
+ return format(date, DATE_FORMAT);
+ },
+ );
+
+ const transactionList: any[] = [];
+
+ // Group entries by source
+ const entriesBySource: { [source: string]: any[] } = {};
+ filteredEntries.forEach((entry: any) => {
+ const sourceKey = entry.source ? JSON.stringify(entry.source) : 'unknown';
+ if (!entriesBySource[sourceKey]) {
+ entriesBySource[sourceKey] = [];
+ }
+ entriesBySource[sourceKey].push(entry);
+ });
+
+ // Process each group of entries
+ Object.values(entriesBySource).forEach((entries) => {
+ // Calculate total amount for the group
+ const totalAmount = entries.reduce((sum, entry) => sum + entry.amount, 0);
+
+ // Skip groups with zero total, EXCEPT for credit repayments which should always be shown
+ // Failed credit repayments have offsetting entries that sum to 0
+ const isRepayment = entries[0]?.source?.type === "issuing_credit_repayment";
+ if (totalAmount === 0 && !isRepayment) {
+ return;
+ }
+
+ // Use the latest entry's date and metadata
+ const latestEntry = entries[entries.length - 1];
+
+ // For credit repayments, we'll use the actual repayment amount later when we fetch the details
+ // For other transactions, use the summed amount
+ const processedEntry = {
+ ...latestEntry,
+ amount: totalAmount,
+ isRepayment: isRepayment
+ };
+ transactionList.push(processedEntry);
+ });
+
+ // Fetch all transactions and authorizations in bulk
+ const stripe = stripeClient(platform);
+ const [transactionsResponse, authorizationsResponse, creditRepaymentsResponse] = await Promise.all([
+ stripe.issuing.transactions.list(
+ { limit: 100 },
+ { stripeAccount: accountId }
+ ),
+ stripe.issuing.authorizations.list(
+ { limit: 100 },
+ { stripeAccount: accountId }
+ ),
+ fetch("https://api.stripe.com/v1/issuing/credit_repayments?account=" + accountId + "&limit=100", {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ }
+ }).then(async res => {
+ const responseData = await res.clone().json();
+ // Log the credit repayments request
+ await logApiRequest(
+ session.email,
+ "https://api.stripe.com/v1/issuing/credit_repayments",
+ "GET",
+ null,
+ responseData
+ );
+ return responseData;
+ })
+ ]);
+
+ // Create lookup maps for quick access
+ const transactionsMap = new Map(
+ transactionsResponse.data.map(t => [t.id, t])
+ );
+ const authorizationsMap = new Map(
+ authorizationsResponse.data.map(a => [a.id, a])
+ );
+ const creditRepaymentsMap = new Map(
+ (creditRepaymentsResponse.data || []).map((r: any) => [r.id, r])
+ );
+
+ // Process transactions with the bulk-fetched data
+ for (const transaction of transactionList) {
+ if (transaction.source?.type === "issuing_authorization" && transaction.source?.issuing_authorization) {
+ const auth = authorizationsMap.get(transaction.source.issuing_authorization);
+ if (auth) {
+ transaction.auth = auth;
+ }
+ } else if (transaction.source?.type === "issuing_credit_repayment" && transaction.source?.issuing_credit_repayment) {
+ const creditRepayment = creditRepaymentsMap.get(transaction.source.issuing_credit_repayment);
+ if (creditRepayment) {
+ transaction.creditRepayment = creditRepayment;
+ }
+ } else if (transaction.source?.type === "issuing_transaction" && transaction.source?.issuing_transaction) {
+ const issuingTransaction = transactionsMap.get(transaction.source.issuing_transaction);
+ if (issuingTransaction) {
+ transaction.transaction = issuingTransaction;
+
+ // If the transaction has an authorization, get it from our map
+ if (issuingTransaction.authorization && typeof issuingTransaction.authorization === 'string') {
+ const auth = authorizationsMap.get(issuingTransaction.authorization);
+ if (auth) {
+ transaction.auth = auth;
+ }
+ }
+ }
+ } else if (transaction.source?.type === "issuing_credit_ledger_adjustment" && transaction.source?.issuing_credit_ledger_adjustment) {
+ try {
+ const response = await fetch(
+ `https://api.stripe.com/v1/issuing/credit_ledger_adjustments/${transaction.source.issuing_credit_ledger_adjustment}`,
+ {
+ method: "GET",
+ headers: {
+ "Stripe-Account": accountId,
+ Authorization: `Bearer ${getStripeSecretKey(platform)}`,
+ "Stripe-Version": "2024-04-10;issuing_credit_beta=v1;issuing_underwritten_credit_beta=v1"
+ }
+ }
+ );
+
+ // Log the credit ledger adjustment request
+ await logApiRequest(
+ session.email,
+ `https://api.stripe.com/v1/issuing/credit_ledger_adjustments/${transaction.source.issuing_credit_ledger_adjustment}`,
+ "GET",
+ null,
+ await response.clone().json()
+ );
+
+ if (response.ok) {
+ const creditLedgerAdjustment = await response.json();
+ transaction.creditLedgerAdjustment = creditLedgerAdjustment;
+ }
+ } catch (error) {
+ console.error('Failed to fetch credit ledger adjustment details:', error);
+ }
+ }
+ }
+
+ // Initialize funds flow data
+ const fundsFlowByDate: { [formattedDate: string]: FundsFlowByDate } =
+ datesArray.reduce(
+ (dates, formattedDate) => {
+ dates[formattedDate] = {
+ date: formattedDate,
+ fundsIn: 0,
+ fundsOut: 0,
+ };
+ return dates;
+ },
+ {} as { [formattedDate: string]: FundsFlowByDate },
+ );
+
+ // Process transactions to build funds flow data
+ transactionList.forEach((transaction) => {
+ const date = new Date(transaction.created * 1000);
+ const formattedDate = format(date, DATE_FORMAT);
+ const amount = Math.abs(transaction.amount);
+
+ if (fundsFlowByDate.hasOwnProperty(formattedDate)) {
+ if (transaction.amount > 0) {
+ fundsFlowByDate[formattedDate].fundsIn += amount;
+ } else {
+ fundsFlowByDate[formattedDate].fundsOut += amount;
+ }
+ }
+ });
+
+ const fundsInArray: number[] = datesArray.map(
+ (formattedDate) => fundsFlowByDate[formattedDate].fundsIn,
+ );
+ const fundsOutArray: number[] = datesArray.map(
+ (formattedDate) => fundsFlowByDate[formattedDate].fundsOut,
+ );
+
+ // Reverse the arrays
+ datesArray.reverse();
+ fundsInArray.reverse();
+ fundsOutArray.reverse();
+
+ const balanceTransactionsChart: BalanceChartData = {
+ currency: currency,
+ balanceTransactionsDates: datesArray,
+ balanceTransactionsFundsIn: fundsInArray,
+ balanceTransactionsFundsOut: fundsOutArray,
+ };
+ const result = {
+ balanceTransactions: transactionList,
+ balanceFundsFlowChartData: balanceTransactionsChart,
+ };
+
+ return result;
+}
+
+export type FinancialAddress = {
+ type: "iban" | "sort_code";
+ supported_networks: string[];
+ iban?: {
+ account_holder_name: string;
+ bic: string;
+ country: string;
+ iban: string;
+ };
+ sort_code?: {
+ account_holder_name: string;
+ account_number: string;
+ sort_code: string;
+ };
+};
+
+export type FundingInstructions = {
+ currency: string;
+ funding_type: string;
+ livemode: boolean;
+ object: string;
+ bank_transfer: {
+ country: string;
+ type: string;
+ financial_addresses: FinancialAddress[];
+ };
+};
+
+export async function createFundingInstructions(
+ stripeAccount: StripeAccount,
+ country: string,
+ currency: string,
+): Promise {
+ const { accountId, platform } = stripeAccount;
+ const bankTransferType =
+ country == "GB" ? "gb_bank_transfer" : "eu_bank_transfer";
+ const data = {
+ currency: currency as string,
+ funding_type: "bank_transfer",
+ "bank_transfer[type]": bankTransferType,
+ };
+
+ // using fetch, because this API is not yet supported in the Node.js libary
+ const response = await fetch(
+ "https://api.stripe.com/v1/issuing/funding_instructions",
+ {
+ method: "POST",
+ headers: {
+ "Stripe-Account": accountId,
+ "content-type": "application/x-www-form-urlencoded",
+ Authorization: "Bearer " + getStripeSecretKey(platform),
+ },
+ body: new URLSearchParams(data),
+ },
+ );
+ const responseBody = await response.json();
+ return responseBody;
+}
diff --git a/consumer-issuing/src/utils/stripe-loader.ts b/consumer-issuing/src/utils/stripe-loader.ts
new file mode 100644
index 00000000..441b7f68
--- /dev/null
+++ b/consumer-issuing/src/utils/stripe-loader.ts
@@ -0,0 +1,26 @@
+import { Stripe } from "stripe";
+
+import { PlatformStripeAccount } from "src/utils/account-management-helpers";
+import { getStripeSecretKey } from "src/utils/stripe-authentication";
+
+const API_VERSION = "2024-04-10";
+const APP_INFO_NAME = "Stripe Consumer Issuing Demo";
+
+const stripeClient = (platform: PlatformStripeAccount) => {
+ const stripeSecretKey = getStripeSecretKey(platform);
+
+ if (!stripeSecretKey) {
+ throw new Error(
+ "Cannot instantiate Stripe client. STRIPE_SECRET_KEY needs to be set in environment variables.",
+ );
+ }
+
+ return new Stripe(stripeSecretKey, {
+ // @ts-expect-error we are using a different API version
+ apiVersion: API_VERSION,
+ typescript: true,
+ appInfo: { name: APP_INFO_NAME },
+ });
+};
+
+export default stripeClient;
diff --git a/consumer-issuing/src/utils/validation-schemas.ts b/consumer-issuing/src/utils/validation-schemas.ts
new file mode 100644
index 00000000..1cc5630f
--- /dev/null
+++ b/consumer-issuing/src/utils/validation-schemas.ts
@@ -0,0 +1,85 @@
+import * as Yup from "yup";
+
+import { SupportedCountry } from "src/utils/account-management-helpers";
+
+const cardholderBase = Yup.object({
+ firstName: Yup.string().required("First name is required"),
+ lastName: Yup.string().required("Last name is required"),
+ email: Yup.string()
+ .email("Invalid email address")
+ .required("Email address is required"),
+ address1: Yup.string().required("Street address is required"),
+ city: Yup.string().required("City is required"),
+ state: Yup.string().required("State / Province is required"),
+ postalCode: Yup.string().required("ZIP / Postal code is required"),
+ country: Yup.string().required("Country is required"),
+ accept: Yup.boolean()
+ .required("The terms of service and privacy policy must be accepted.")
+ .oneOf([true], "The terms of service and privacy policy must be accepted."),
+}).test(
+ "name-length",
+ "The combined first and last names must be less than 24 characters",
+ function (value) {
+ const { firstName, lastName } = value;
+ return (firstName?.length || 0) + (lastName?.length || 0) < 24;
+ },
+);
+
+const cardholderWithSCA = cardholderBase.concat(
+ Yup.object({
+ phoneNumber: Yup.string().required("Phone number is required for SCA"),
+ }),
+);
+
+const supportedCountries = Object.values(SupportedCountry) as string[];
+
+const user = Yup.object().shape({
+ email: Yup.string()
+ .email("Must be a valid email")
+ .max(255)
+ .required("Email is required"),
+ password: Yup.string()
+ .max(255)
+ .required("Password is required"),
+ country: Yup.string()
+ .required("Country is required")
+ .max(2)
+ .oneOf(supportedCountries, "Country is not supported"),
+});
+
+const businessBase = Yup.object().shape({
+ businessName: Yup.string().max(255).required("Name is required"),
+});
+
+const businessWithOnboardingSkip = businessBase.concat(
+ Yup.object().shape({
+ skipOnboarding: Yup.boolean().required("Skip onboarding choice required"),
+ }),
+);
+
+const card = Yup.object().shape({
+ line1: Yup.string().required("Cardholder billing address is required"),
+ city: Yup.string().required("Cardholder billing address city is required"),
+ state: Yup.string().required("Cardholder billing address state is required"),
+ postal_code: Yup.string().required(
+ "Cardholder billing address postal code is required",
+ ),
+ country: Yup.string().required(
+ "Cardholder billing address country is required",
+ ),
+});
+
+const schemas = {
+ business: {
+ default: businessBase,
+ withOnbardingSkip: businessWithOnboardingSkip,
+ },
+ card,
+ cardholder: {
+ default: cardholderBase,
+ withSCA: cardholderWithSCA,
+ },
+ user,
+};
+
+export default schemas;
diff --git a/consumer-issuing/src/utils/validation_schemas.ts b/consumer-issuing/src/utils/validation_schemas.ts
new file mode 100644
index 00000000..e28b096e
--- /dev/null
+++ b/consumer-issuing/src/utils/validation_schemas.ts
@@ -0,0 +1,88 @@
+import * as Yup from "yup";
+
+const cardholderBase = Yup.object({
+ firstName: Yup.string().required("First name is required"),
+ lastName: Yup.string().required("Last name is required"),
+ email: Yup.string()
+ .email("Invalid email address")
+ .required("Email address is required"),
+ address1: Yup.string().required("Street address is required"),
+ city: Yup.string().required("City is required"),
+ state: Yup.string().required("State / Province is required"),
+ postalCode: Yup.string().required("ZIP / Postal code is required"),
+ country: Yup.string().required("Country is required"),
+ accept: Yup.boolean()
+ .required("The terms of service and privacy policy must be accepted.")
+ .oneOf([true], "The terms of service and privacy policy must be accepted."),
+}).test(
+ "name-length",
+ "The combined first and last names must be less than 24 characters",
+ function (value) {
+ const { firstName, lastName } = value;
+ return (firstName?.length || 0) + (lastName?.length || 0) < 24;
+ },
+);
+
+const cardholderWithSCA = cardholderBase.concat(
+ Yup.object({
+ phoneNumber: Yup.string().required("Phone number is required for SCA"),
+ }),
+);
+
+const getCharacterValidationError = (str: string) => {
+ return `Your password must have at least 1 ${str} character`;
+};
+
+const user = Yup.object().shape({
+ email: Yup.string()
+ .email("Must be a valid email")
+ .max(255)
+ .required("Email is required"),
+ password: Yup.string()
+ .max(255)
+ .required("Password is required")
+ // check minimum characters
+ .min(8, "Password must have at least 8 characters")
+ // different error messages for different requirements
+ .matches(/[0-9]/, getCharacterValidationError("digit"))
+ .matches(/[a-z]/, getCharacterValidationError("lowercase"))
+ .matches(/[A-Z]/, getCharacterValidationError("uppercase")),
+ country: Yup.string().max(2).required("Country is required"),
+});
+
+const businessBase = Yup.object().shape({
+ businessName: Yup.string().max(255).required("Name is required"),
+});
+
+const businessWithOnboardingSkip = businessBase.concat(
+ Yup.object().shape({
+ skipOnboarding: Yup.boolean().required("Skip onboarding choice required"),
+ }),
+);
+
+const card = Yup.object().shape({
+ line1: Yup.string().required("Cardholder billing address is required"),
+ city: Yup.string().required("Cardholder billing address city is required"),
+ state: Yup.string().required("Cardholder billing address state is required"),
+ postal_code: Yup.string().required(
+ "Cardholder billing address postal code is required",
+ ),
+ country: Yup.string().required(
+ "Cardholder billing address country is required",
+ ),
+});
+
+const schemas = {
+ business: {
+ default: businessBase,
+ withOnbardingSkip: businessWithOnboardingSkip,
+ },
+ card,
+ cardholder: {
+ default: cardholderBase,
+ withSCA: cardholderWithSCA,
+ },
+ user,
+};
+
+export default schemas;
diff --git a/consumer-issuing/tsconfig.json b/consumer-issuing/tsconfig.json
new file mode 100644
index 00000000..c3a7c6c7
--- /dev/null
+++ b/consumer-issuing/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./",
+ "target": "esnext",
+ "jsx": "preserve",
+ "module": "esnext",
+ "allowJs": true,
+ "outDir": "./dist",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "noEmitOnError": true,
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "noEmit": true,
+ "incremental": true,
+ "isolatedModules": true,
+ },
+ "exclude": ["dist", "node_modules"],
+ "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx"]
+}
diff --git a/package-lock.json b/package-lock.json
index 074f8b65..67513d03 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,10 @@
"./embedded-finance/",
"./expense-management/"
],
- "devDependencies": {},
+ "dependencies": {
+ "@react-google-maps/api": "^2.20.6",
+ "@vis.gl/react-google-maps": "^1.5.2"
+ },
"engines": {
"node": "18"
}
@@ -592,6 +595,20 @@
"npm": ">=6.14.13"
}
},
+ "node_modules/@googlemaps/js-api-loader": {
+ "version": "1.16.8",
+ "resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.8.tgz",
+ "integrity": "sha512-CROqqwfKotdO6EBjZO/gQGVTbeDps5V7Mt9+8+5Q+jTg5CRMi3Ii/L9PmV3USROrt2uWxtGzJHORmByxyo9pSQ=="
+ },
+ "node_modules/@googlemaps/markerclusterer": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/@googlemaps/markerclusterer/-/markerclusterer-2.5.3.tgz",
+ "integrity": "sha512-x7lX0R5yYOoiNectr10wLgCBasNcXFHiADIBdmn7jQllF2B5ENQw5XtZK+hIw4xnV0Df0xhN4LN98XqA5jaiOw==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "supercluster": "^8.0.1"
+ }
+ },
"node_modules/@heroicons/react": {
"version": "2.2.0",
"license": "MIT",
@@ -1273,6 +1290,33 @@
"@prisma/debug": "5.22.0"
}
},
+ "node_modules/@react-google-maps/api": {
+ "version": "2.20.6",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/api/-/api-2.20.6.tgz",
+ "integrity": "sha512-frxkSHWbd36ayyxrEVopSCDSgJUT1tVKXvQld2IyzU3UnDuqqNA3AZE4/fCdqQb2/zBQx3nrWnZB1wBXDcrjcw==",
+ "dependencies": {
+ "@googlemaps/js-api-loader": "1.16.8",
+ "@googlemaps/markerclusterer": "2.5.3",
+ "@react-google-maps/infobox": "2.20.0",
+ "@react-google-maps/marker-clusterer": "2.20.0",
+ "@types/google.maps": "3.58.1",
+ "invariant": "2.2.4"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/@react-google-maps/infobox": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/infobox/-/infobox-2.20.0.tgz",
+ "integrity": "sha512-03PJHjohhaVLkX6+NHhlr8CIlvUxWaXhryqDjyaZ8iIqqix/nV8GFdz9O3m5OsjtxtNho09F/15j14yV0nuyLQ=="
+ },
+ "node_modules/@react-google-maps/marker-clusterer": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/@react-google-maps/marker-clusterer/-/marker-clusterer-2.20.0.tgz",
+ "integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw=="
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"dev": true,
@@ -1332,6 +1376,11 @@
"@types/node": "*"
}
},
+ "node_modules/@types/google.maps": {
+ "version": "3.58.1",
+ "resolved": "https://registry.npmjs.org/@types/google.maps/-/google.maps-3.58.1.tgz",
+ "integrity": "sha512-X9QTSvGJ0nCfMzYOnaVs/k6/4L+7F5uCS+4iUmkLEls6J9S/Phv+m/i3mDeyc49ZBgwab3EFO1HEoBY7k98EGQ=="
+ },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.6",
"license": "MIT",
@@ -1596,6 +1645,19 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/@vis.gl/react-google-maps": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/@vis.gl/react-google-maps/-/react-google-maps-1.5.2.tgz",
+ "integrity": "sha512-0Ypmde7M73GgV4TgcaUTNKXsbcXWToPVuawMNrVg7htXmhpEfLARHwhtmP6N1da3od195ZKC8ShXzC6Vm+zYHQ==",
+ "dependencies": {
+ "@types/google.maps": "^3.54.10",
+ "fast-deep-equal": "^3.1.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": ">=16.8.0 || ^19.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/@xstate/react": {
"version": "4.1.3",
"license": "MIT",
@@ -3239,7 +3301,6 @@
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
- "dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
@@ -3832,6 +3893,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/is-array-buffer": {
"version": "3.0.4",
"dev": true,
@@ -4339,6 +4408,11 @@
"safe-buffer": "^5.0.1"
}
},
+ "node_modules/kdbush": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
+ "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA=="
+ },
"node_modules/keyv": {
"version": "4.5.4",
"dev": true,
@@ -5911,6 +5985,14 @@
"version": "4.2.0",
"license": "MIT"
},
+ "node_modules/supercluster": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-8.0.1.tgz",
+ "integrity": "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==",
+ "dependencies": {
+ "kdbush": "^4.0.2"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"dev": true,
diff --git a/package.json b/package.json
index 1a33f78c..b9ebc363 100644
--- a/package.json
+++ b/package.json
@@ -7,8 +7,10 @@
"lint": "npm run lint --workspaces",
"codegen": "./codegen.sh"
},
- "dependencies": {},
- "devDependencies": {},
+ "dependencies": {
+ "@react-google-maps/api": "^2.20.6",
+ "@vis.gl/react-google-maps": "^1.5.2"
+ },
"workspaces": [
"./embedded-finance/",
"./expense-management/"