diff --git a/favicon.png b/favicon.png
new file mode 100644
index 000000000..e69de29bb
diff --git a/web-citizen-reporting/.env.example b/web-citizen-reporting/.env.example
new file mode 100644
index 000000000..5492f5837
--- /dev/null
+++ b/web-citizen-reporting/.env.example
@@ -0,0 +1,3 @@
+VITE_API_URL=http://localhost:5000/api/
+VITE_ELECTION_ROUND_ID=uuid-here
+VITE_DEFAULT_LANGUAGE=EN
\ No newline at end of file
diff --git a/web-citizen-reporting/.gitignore b/web-citizen-reporting/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/web-citizen-reporting/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/web-citizen-reporting/README.md b/web-citizen-reporting/README.md
new file mode 100644
index 000000000..74872fd4a
--- /dev/null
+++ b/web-citizen-reporting/README.md
@@ -0,0 +1,50 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
+
+- Configure the top-level `parserOptions` property like this:
+
+```js
+export default tseslint.config({
+ languageOptions: {
+ // other options...
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+})
+```
+
+- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
+- Optionally add `...tseslint.configs.stylisticTypeChecked`
+- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
+
+```js
+// eslint.config.js
+import react from 'eslint-plugin-react'
+
+export default tseslint.config({
+ // Set the react version
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ // Add the react plugin
+ react,
+ },
+ rules: {
+ // other rules...
+ // Enable its recommended rules
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ },
+})
+```
diff --git a/web-citizen-reporting/eslint.config.js b/web-citizen-reporting/eslint.config.js
new file mode 100644
index 000000000..092408a9f
--- /dev/null
+++ b/web-citizen-reporting/eslint.config.js
@@ -0,0 +1,28 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+
+export default tseslint.config(
+ { ignores: ['dist'] },
+ {
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
+ files: ['**/*.{ts,tsx}'],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ plugins: {
+ 'react-hooks': reactHooks,
+ 'react-refresh': reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+ },
+)
diff --git a/web-citizen-reporting/index.html b/web-citizen-reporting/index.html
new file mode 100644
index 000000000..583d0f1f5
--- /dev/null
+++ b/web-citizen-reporting/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Vote Monitor: Citizen Reporting
+
+
+
+
+
+
diff --git a/web-citizen-reporting/package.json b/web-citizen-reporting/package.json
new file mode 100644
index 000000000..922959bb2
--- /dev/null
+++ b/web-citizen-reporting/package.json
@@ -0,0 +1,76 @@
+{
+ "name": "web-citizen-reporting",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@headlessui/react": "^2.2.0",
+ "@heroicons/react": "^2.2.0",
+ "@radix-ui/react-alert-dialog": "^1.1.6",
+ "@radix-ui/react-collapsible": "^1.1.3",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-label": "^2.1.1",
+ "@radix-ui/react-popover": "^1.1.5",
+ "@radix-ui/react-progress": "^1.1.2",
+ "@radix-ui/react-radio-group": "^1.2.2",
+ "@radix-ui/react-scroll-area": "^1.2.3",
+ "@radix-ui/react-select": "^2.1.6",
+ "@radix-ui/react-separator": "^1.1.2",
+ "@radix-ui/react-slider": "^1.2.3",
+ "@radix-ui/react-slot": "^1.1.1",
+ "@radix-ui/react-switch": "^1.1.3",
+ "@radix-ui/react-tabs": "^1.1.3",
+ "@radix-ui/react-toast": "^1.2.6",
+ "@radix-ui/react-toggle": "^1.1.2",
+ "@radix-ui/react-tooltip": "^1.1.8",
+ "@tailwindcss/typography": "^0.5.16",
+ "@tailwindcss/vite": "^4.0.3",
+ "@tanstack/react-query": "^5.66.0",
+ "@tanstack/react-query-devtools": "^5.66.0",
+ "@tanstack/react-router": "^1.99.6",
+ "@types/node": "^22.13.1",
+ "axios": "^1.7.9",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.0.4",
+ "date-fns": "^4.1.0",
+ "i18next": "^24.2.2",
+ "lucide-react": "^0.474.0",
+ "react": "^18.3.1",
+ "react-day-picker": "^9.5.1",
+ "react-dom": "^18.3.1",
+ "react-dropzone": "^14.3.5",
+ "react-hook-form": "^7.54.2",
+ "react-i18next": "^15.4.0",
+ "tailwind-merge": "^3.0.1",
+ "tailwindcss-animate": "^1.0.7",
+ "uuid": "^11.0.5",
+ "zod": "^3.24.1",
+ "zustand": "^5.0.3"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.19.0",
+ "@tailwindcss/postcss": "^4.0.3",
+ "@tanstack/router-devtools": "^1.99.6",
+ "@tanstack/router-plugin": "^1.99.6",
+ "@types/react": "^18.3.18",
+ "@types/react-dom": "^18.3.5",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.17.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.16",
+ "globals": "^15.14.0",
+ "postcss": "^8.5.1",
+ "tailwindcss": "^3.4.3",
+ "typescript": "~5.6.2",
+ "typescript-eslint": "^8.18.2",
+ "vite": "^6.0.5"
+ }
+}
diff --git a/web-citizen-reporting/pnpm-lock.yaml b/web-citizen-reporting/pnpm-lock.yaml
new file mode 100644
index 000000000..fc61c9b19
--- /dev/null
+++ b/web-citizen-reporting/pnpm-lock.yaml
@@ -0,0 +1,5352 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@headlessui/react':
+ specifier: ^2.2.0
+ version: 2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@heroicons/react':
+ specifier: ^2.2.0
+ version: 2.2.0(react@18.3.1)
+ '@radix-ui/react-alert-dialog':
+ specifier: ^1.1.6
+ version: 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-collapsible':
+ specifier: ^1.1.3
+ version: 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-dialog':
+ specifier: ^1.1.6
+ version: 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-label':
+ specifier: ^2.1.1
+ version: 2.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-popover':
+ specifier: ^1.1.5
+ version: 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-progress':
+ specifier: ^1.1.2
+ version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-radio-group':
+ specifier: ^1.2.2
+ version: 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-scroll-area':
+ specifier: ^1.2.3
+ version: 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-select':
+ specifier: ^2.1.6
+ version: 2.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-separator':
+ specifier: ^1.1.2
+ version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slider':
+ specifier: ^1.2.3
+ version: 1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot':
+ specifier: ^1.1.1
+ version: 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-switch':
+ specifier: ^1.1.3
+ version: 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-tabs':
+ specifier: ^1.1.3
+ version: 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-toast':
+ specifier: ^1.2.6
+ version: 1.2.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-toggle':
+ specifier: ^1.1.2
+ version: 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-tooltip':
+ specifier: ^1.1.8
+ version: 1.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tailwindcss/typography':
+ specifier: ^0.5.16
+ version: 0.5.16(tailwindcss@3.4.3)
+ '@tailwindcss/vite':
+ specifier: ^4.0.3
+ version: 4.0.3(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0))
+ '@tanstack/react-query':
+ specifier: ^5.66.0
+ version: 5.66.0(react@18.3.1)
+ '@tanstack/react-query-devtools':
+ specifier: ^5.66.0
+ version: 5.66.0(@tanstack/react-query@5.66.0(react@18.3.1))(react@18.3.1)
+ '@tanstack/react-router':
+ specifier: ^1.99.6
+ version: 1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@types/node':
+ specifier: ^22.13.1
+ version: 22.13.1
+ axios:
+ specifier: ^1.7.9
+ version: 1.7.9
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ cmdk:
+ specifier: ^1.0.4
+ version: 1.0.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ date-fns:
+ specifier: ^4.1.0
+ version: 4.1.0
+ i18next:
+ specifier: ^24.2.2
+ version: 24.2.2(typescript@5.6.3)
+ lucide-react:
+ specifier: ^0.474.0
+ version: 0.474.0(react@18.3.1)
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-day-picker:
+ specifier: ^9.5.1
+ version: 9.5.1(react@18.3.1)
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ react-dropzone:
+ specifier: ^14.3.5
+ version: 14.3.5(react@18.3.1)
+ react-hook-form:
+ specifier: ^7.54.2
+ version: 7.54.2(react@18.3.1)
+ react-i18next:
+ specifier: ^15.4.0
+ version: 15.4.0(i18next@24.2.2(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ tailwind-merge:
+ specifier: ^3.0.1
+ version: 3.0.1
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@3.4.3)
+ uuid:
+ specifier: ^11.0.5
+ version: 11.0.5
+ zod:
+ specifier: ^3.24.1
+ version: 3.24.1
+ zustand:
+ specifier: ^5.0.3
+ version: 5.0.3(@types/react@18.3.18)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1))
+ devDependencies:
+ '@eslint/js':
+ specifier: ^9.19.0
+ version: 9.19.0
+ '@tailwindcss/postcss':
+ specifier: ^4.0.3
+ version: 4.0.3
+ '@tanstack/router-devtools':
+ specifier: ^1.99.6
+ version: 1.99.6(@tanstack/react-router@1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tanstack/router-plugin':
+ specifier: ^1.99.6
+ version: 1.99.6(@tanstack/react-router@1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0))
+ '@types/react':
+ specifier: ^18.3.18
+ version: 18.3.18
+ '@types/react-dom':
+ specifier: ^18.3.5
+ version: 18.3.5(@types/react@18.3.18)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.4
+ version: 4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0))
+ autoprefixer:
+ specifier: ^10.4.20
+ version: 10.4.20(postcss@8.5.1)
+ eslint:
+ specifier: ^9.17.0
+ version: 9.19.0(jiti@2.4.2)
+ eslint-plugin-react-hooks:
+ specifier: ^5.0.0
+ version: 5.1.0(eslint@9.19.0(jiti@2.4.2))
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.16
+ version: 0.4.18(eslint@9.19.0(jiti@2.4.2))
+ globals:
+ specifier: ^15.14.0
+ version: 15.14.0
+ postcss:
+ specifier: ^8.5.1
+ version: 8.5.1
+ tailwindcss:
+ specifier: ^3.4.3
+ version: 3.4.3
+ typescript:
+ specifier: ~5.6.2
+ version: 5.6.3
+ typescript-eslint:
+ specifier: ^8.18.2
+ version: 8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ vite:
+ specifier: ^6.0.5
+ version: 6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0)
+
+packages:
+
+ '@alloc/quick-lru@5.2.0':
+ resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+ engines: {node: '>=10'}
+
+ '@ampproject/remapping@2.3.0':
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+
+ '@babel/code-frame@7.26.2':
+ resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.26.5':
+ resolution: {integrity: sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.26.7':
+ resolution: {integrity: sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.26.5':
+ resolution: {integrity: sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.26.5':
+ resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.25.9':
+ resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.26.0':
+ resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.26.5':
+ resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.25.9':
+ resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.25.9':
+ resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.25.9':
+ resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.26.7':
+ resolution: {integrity: sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.26.7':
+ resolution: {integrity: sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-syntax-jsx@7.25.9':
+ resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-typescript@7.25.9':
+ resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-self@7.25.9':
+ resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.25.9':
+ resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.26.7':
+ resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.25.9':
+ resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.26.7':
+ resolution: {integrity: sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.26.7':
+ resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==}
+ engines: {node: '>=6.9.0'}
+
+ '@date-fns/tz@1.2.0':
+ resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
+
+ '@esbuild/aix-ppc64@0.23.1':
+ resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.24.2':
+ resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.23.1':
+ resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm64@0.24.2':
+ resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.23.1':
+ resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.24.2':
+ resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.23.1':
+ resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/android-x64@0.24.2':
+ resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.23.1':
+ resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-arm64@0.24.2':
+ resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.23.1':
+ resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.24.2':
+ resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.23.1':
+ resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-arm64@0.24.2':
+ resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.23.1':
+ resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.24.2':
+ resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.23.1':
+ resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm64@0.24.2':
+ resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.23.1':
+ resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.24.2':
+ resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.23.1':
+ resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.24.2':
+ resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.23.1':
+ resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.24.2':
+ resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.23.1':
+ resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.24.2':
+ resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.23.1':
+ resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.24.2':
+ resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.23.1':
+ resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.24.2':
+ resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.23.1':
+ resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.24.2':
+ resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.23.1':
+ resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.24.2':
+ resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.24.2':
+ resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.24.2':
+ resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-arm64@0.24.2':
+ resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.23.1':
+ resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.24.2':
+ resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/sunos-x64@0.23.1':
+ resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/sunos-x64@0.24.2':
+ resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.23.1':
+ resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.24.2':
+ resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.23.1':
+ resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.24.2':
+ resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.23.1':
+ resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.24.2':
+ resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.4.1':
+ resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.1':
+ resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.19.2':
+ resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.10.0':
+ resolution: {integrity: sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.2.0':
+ resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.19.0':
+ resolution: {integrity: sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.6':
+ resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.2.5':
+ resolution: {integrity: sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@floating-ui/core@1.6.9':
+ resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
+
+ '@floating-ui/dom@1.6.13':
+ resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
+
+ '@floating-ui/react-dom@2.1.2':
+ resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/react@0.26.28':
+ resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.9':
+ resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+
+ '@headlessui/react@2.2.0':
+ resolution: {integrity: sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^18 || ^19 || ^19.0.0-rc
+
+ '@heroicons/react@2.2.0':
+ resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==}
+ peerDependencies:
+ react: '>= 16 || ^19.0.0-rc'
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.6':
+ resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.3.1':
+ resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+ engines: {node: '>=18.18'}
+
+ '@humanwhocodes/retry@0.4.1':
+ resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
+ engines: {node: '>=18.18'}
+
+ '@isaacs/cliui@8.0.2':
+ resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
+ engines: {node: '>=12'}
+
+ '@jridgewell/gen-mapping@0.3.8':
+ resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/set-array@1.2.1':
+ resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.0':
+ resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@pkgjs/parseargs@0.11.0':
+ resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
+ engines: {node: '>=14'}
+
+ '@radix-ui/number@1.1.0':
+ resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
+
+ '@radix-ui/primitive@1.1.1':
+ resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==}
+
+ '@radix-ui/react-alert-dialog@1.1.6':
+ resolution: {integrity: sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-arrow@1.1.1':
+ resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-arrow@1.1.2':
+ resolution: {integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collapsible@1.1.3':
+ resolution: {integrity: sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collection@1.1.1':
+ resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-collection@1.1.2':
+ resolution: {integrity: sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-compose-refs@1.1.1':
+ resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-context@1.1.1':
+ resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dialog@1.1.6':
+ resolution: {integrity: sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-direction@1.1.0':
+ resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.4':
+ resolution: {integrity: sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.5':
+ resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-focus-guards@1.1.1':
+ resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.1':
+ resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.2':
+ resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-id@1.1.0':
+ resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-label@2.1.1':
+ resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popover@1.1.5':
+ resolution: {integrity: sha512-YXkTAftOIW2Bt3qKH8vYr6n9gCkVrvyvfiTObVjoHVTHnNj26rmvO87IKa3VgtgCjb8FAQ6qOjNViwl+9iIzlg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popper@1.2.1':
+ resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-popper@1.2.2':
+ resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.3':
+ resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.4':
+ resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-presence@1.1.2':
+ resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.0.1':
+ resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.0.2':
+ resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-progress@1.1.2':
+ resolution: {integrity: sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-radio-group@1.2.2':
+ resolution: {integrity: sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-roving-focus@1.1.1':
+ resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-roving-focus@1.1.2':
+ resolution: {integrity: sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-scroll-area@1.2.3':
+ resolution: {integrity: sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-select@2.1.6':
+ resolution: {integrity: sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-separator@1.1.2':
+ resolution: {integrity: sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slider@1.2.3':
+ resolution: {integrity: sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-slot@1.1.1':
+ resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-slot@1.1.2':
+ resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-switch@1.1.3':
+ resolution: {integrity: sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tabs@1.1.3':
+ resolution: {integrity: sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toast@1.2.6':
+ resolution: {integrity: sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-toggle@1.1.2':
+ resolution: {integrity: sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-tooltip@1.1.8':
+ resolution: {integrity: sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-use-callback-ref@1.1.0':
+ resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.1.0':
+ resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.0':
+ resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.0':
+ resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.0':
+ resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.0':
+ resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.0':
+ resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-visually-hidden@1.1.2':
+ resolution: {integrity: sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/rect@1.1.0':
+ resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
+
+ '@react-aria/focus@3.19.1':
+ resolution: {integrity: sha512-bix9Bu1Ue7RPcYmjwcjhB14BMu2qzfJ3tMQLqDc9pweJA66nOw8DThy3IfVr8Z7j2PHktOLf9kcbiZpydKHqzg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/interactions@3.23.0':
+ resolution: {integrity: sha512-0qR1atBIWrb7FzQ+Tmr3s8uH5mQdyRH78n0krYaG8tng9+u1JlSi8DGRSaC9ezKyNB84m7vHT207xnHXGeJ3Fg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/ssr@3.9.7':
+ resolution: {integrity: sha512-GQygZaGlmYjmYM+tiNBA5C6acmiDWF52Nqd40bBp0Znk4M4hP+LTmI0lpI1BuKMw45T8RIhrAsICIfKwZvi2Gg==}
+ engines: {node: '>= 12'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-aria/utils@3.27.0':
+ resolution: {integrity: sha512-p681OtApnKOdbeN8ITfnnYqfdHS0z7GE+4l8EXlfLnr70Rp/9xicBO6d2rU+V/B3JujDw2gPWxYKEnEeh0CGCw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+ react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-stately/utils@3.10.5':
+ resolution: {integrity: sha512-iMQSGcpaecghDIh3mZEpZfoFH3ExBwTtuBEcvZ2XnGzCgQjeYXcMdIUwAfVQLXFTdHUHGF6Gu6/dFrYsCzySBQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@react-types/shared@3.27.0':
+ resolution: {integrity: sha512-gvznmLhi6JPEf0bsq7SwRYTHAKKq/wcmKqFez9sRdbED+SPMUmK5omfZ6w3EwUFQHbYUa4zPBYedQ7Knv70RMw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
+
+ '@rollup/rollup-android-arm-eabi@4.34.3':
+ resolution: {integrity: sha512-8kq/NjMKkMTGKMPldWihncOl62kgnLYk7cW+/4NCUWfS70/wz4+gQ7rMxMMpZ3dIOP/xw7wKNzIuUnN/H2GfUg==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.34.3':
+ resolution: {integrity: sha512-1PqMHiuRochQ6++SDI7SaRDWJKr/NgAlezBi5nOne6Da6IWJo3hK0TdECBDwd92IUDPG4j/bZmWuwOnomNT8wA==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.34.3':
+ resolution: {integrity: sha512-fqbrykX4mGV3DlCDXhF4OaMGcchd2tmLYxVt3On5oOZWVDFfdEoYAV2alzNChl8OzNaeMAGqm1f7gk7eIw/uDg==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.34.3':
+ resolution: {integrity: sha512-8Wxrx/KRvMsTyLTbdrMXcVKfpW51cCNW8x7iQD72xSEbjvhCY3b+w83Bea3nQfysTMR7K28esc+ZFITThXm+1w==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.34.3':
+ resolution: {integrity: sha512-lpBmV2qSiELh+ATQPTjQczt5hvbTLsE0c43Rx4bGxN2VpnAZWy77we7OO62LyOSZNY7CzjMoceRPc+Lt4e9J6A==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.34.3':
+ resolution: {integrity: sha512-sNPvBIXpgaYcI6mAeH13GZMXFrrw5mdZVI1M9YQPRG2LpjwL8DSxSIflZoh/B5NEuOi53kxsR/S2GKozK1vDXA==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.34.3':
+ resolution: {integrity: sha512-MW6N3AoC61OfE1VgnN5O1OW0gt8VTbhx9s/ZEPLBM11wEdHjeilPzOxVmmsrx5YmejpGPvez8QwGGvMU+pGxpw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.34.3':
+ resolution: {integrity: sha512-2SQkhr5xvatYq0/+H6qyW0zvrQz9LM4lxGkpWURLoQX5+yP8MsERh4uWmxFohOvwCP6l/+wgiHZ1qVwLDc7Qmw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.34.3':
+ resolution: {integrity: sha512-R3JLYt8YoRwKI5shJsovLpcR6pwIMui/MGG/MmxZ1DYI3iRSKI4qcYrvYgDf4Ss2oCR3RL3F3dYK7uAGQgMIuQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.34.3':
+ resolution: {integrity: sha512-4XQhG8v/t3S7Rxs7rmFUuM6j09hVrTArzONS3fUZ6oBRSN/ps9IPQjVhp62P0W3KhqJdQADo/MRlYRMdgxr/3w==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.34.3':
+ resolution: {integrity: sha512-QlW1jCUZ1LHUIYCAK2FciVw1ptHsxzApYVi05q7bz2A8oNE8QxQ85NhM4arLxkAlcnS42t4avJbSfzSQwbIaKg==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.34.3':
+ resolution: {integrity: sha512-kMbLToizVeCcN69+nnm20Dh0hrRIAjgaaL+Wh0gWZcNt8e542d2FUGtsyuNsHVNNF3gqTJrpzUGIdwMGLEUM7g==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.34.3':
+ resolution: {integrity: sha512-YgD0DnZ3CHtvXRH8rzjVSxwI0kMTr0RQt3o1N92RwxGdx7YejzbBO0ELlSU48DP96u1gYYVWfUhDRyaGNqJqJg==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.34.3':
+ resolution: {integrity: sha512-dIOoOz8altjp6UjAi3U9EW99s8nta4gzi52FeI45GlPyrUH4QixUoBMH9VsVjt+9A2RiZBWyjYNHlJ/HmJOBCQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.34.3':
+ resolution: {integrity: sha512-lOyG3aF4FTKrhpzXfMmBXgeKUUXdAWmP2zSNf8HTAXPqZay6QYT26l64hVizBjq+hJx3pl0DTEyvPi9sTA6VGA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.34.3':
+ resolution: {integrity: sha512-usztyYLu2i+mYzzOjqHZTaRXbUOqw3P6laNUh1zcqxbPH1P2Tz/QdJJCQSnGxCtsRQeuU2bCyraGMtMumC46rw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-win32-arm64-msvc@4.34.3':
+ resolution: {integrity: sha512-ojFOKaz/ZyalIrizdBq2vyc2f0kFbJahEznfZlxdB6pF9Do6++i1zS5Gy6QLf8D7/S57MHrmBLur6AeRYeQXSA==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.34.3':
+ resolution: {integrity: sha512-K/V97GMbNa+Da9mGcZqmSl+DlJmWfHXTuI9V8oB2evGsQUtszCl67+OxWjBKpeOnYwox9Jpmt/J6VhpeRCYqow==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.34.3':
+ resolution: {integrity: sha512-CUypcYP31Q8O04myV6NKGzk9GVXslO5EJNfmARNSzLF2A+5rmZUlDJ4et6eoJaZgBT9wrC2p4JZH04Vkic8HdQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tailwindcss/node@4.0.3':
+ resolution: {integrity: sha512-QsVJokOl0pJ4AbJV33D2npvLcHGPWi5MOSZtrtE0GT3tSx+3D0JE2lokLA8yHS1x3oCY/3IyRyy7XX6tmzid7A==}
+
+ '@tailwindcss/oxide-android-arm64@4.0.3':
+ resolution: {integrity: sha512-S8XOTQuMnpijZRlPm5HBzPJjZ28quB+40LSRHjRnQF6rRYKsvpr1qkY7dfwsetNdd+kMLOMDsvmuT8WnqqETvg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.0.3':
+ resolution: {integrity: sha512-smrY2DpzhXvgDhZtQlYAl8+vxJ04lv2/64C1eiRxvsRT2nkw/q+zA1/eAYKvUHat6cIuwqDku3QucmrUT6pCeg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.0.3':
+ resolution: {integrity: sha512-NTz8x/LcGUjpZAWUxz0ZuzHao90Wj9spoQgomwB+/hgceh5gcJDfvaBYqxLFpKzVglpnbDSq1Fg0p0zI4oa5Pg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.0.3':
+ resolution: {integrity: sha512-yQc9Q0JCOp3kkAV8gKgDctXO60IkQhHpqGB+KgOccDtD5UmN6Q5+gd+lcsDyQ7N8dRuK1fAud51xQpZJgKfm7g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.3':
+ resolution: {integrity: sha512-e1ivVMLSnxTOU1O3npnxN16FEyWM/g3SuH2pP6udxXwa0/SnSAijRwcAYRpqIlhVKujr158S8UeHxQjC4fGl4w==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.0.3':
+ resolution: {integrity: sha512-PLrToqQqX6sdJ9DmMi8IxZWWrfjc9pdi9AEEPTrtMts3Jm9HBi1WqEeF1VwZZ2aW9TXloE5OwA35zuuq1Bhb/Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.0.3':
+ resolution: {integrity: sha512-YlzRxx7N1ampfgSKzEDw0iwDkJXUInR4cgNEqmR4TzHkU2Vhg59CGPJrTI7dxOBofD8+O35R13Nk9Ytyv0JUFg==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.0.3':
+ resolution: {integrity: sha512-Xfc3z/li6XkuD7Hs+Uk6pjyCXnfnd9zuQTKOyDTZJ544xc2yoMKUkuDw6Et9wb31MzU2/c0CIUpTDa71lL9KHw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.0.3':
+ resolution: {integrity: sha512-ugKVqKzwa/cjmqSQG17aS9DYrEcQ/a5NITcgmOr3JLW4Iz64C37eoDlkC8tIepD3S/Td/ywKAolTQ8fKbjEL4g==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.0.3':
+ resolution: {integrity: sha512-qHPDMl+UUwsk1RMJMgAXvhraWqUUT+LR/tkXix5RA39UGxtTrHwsLIN1AhNxI5i2RFXAXfmFXDqZCdyQ4dWmAQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.0.3':
+ resolution: {integrity: sha512-+ujwN4phBGyOsPyLgGgeCyUm4Mul+gqWVCIGuSXWgrx9xVUnf6LVXrw0BDBc9Aq1S2qMyOTX4OkCGbZeoIo8Qw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.0.3':
+ resolution: {integrity: sha512-FFcp3VNvRjjmFA39ORM27g2mbflMQljhvM7gxBAujHxUy4LXlKa6yMF9wbHdTbPqTONiCyyOYxccvJyVyI/XBg==}
+ engines: {node: '>= 10'}
+
+ '@tailwindcss/postcss@4.0.3':
+ resolution: {integrity: sha512-qUyxuhuI2eTgRJ+qfCQRAr69Cw7BdSz+PoNFUNoRuhPjikNC8+sxK+Mi/chaXAXewjv/zbf6if6z6ItVLh+e9Q==}
+
+ '@tailwindcss/typography@0.5.16':
+ resolution: {integrity: sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+
+ '@tailwindcss/vite@4.0.3':
+ resolution: {integrity: sha512-Qj6rSO+EvXnNDymloKZ11D54JJTnDrkRWJBzNHENDxjt0HtrCZJbSLIrcJ/WdaoU4othrel/oFqHpO/doxIS/Q==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6
+
+ '@tanstack/history@1.99.0':
+ resolution: {integrity: sha512-MQS1Lg8D+1vpasEJKf4zs1sxhxbXcoejmVCZDbo0bq2wq+xVK+kRixj5Pae2kb2APzdXYga4u236GBbgCKTcnQ==}
+ engines: {node: '>=12'}
+
+ '@tanstack/query-core@5.66.0':
+ resolution: {integrity: sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw==}
+
+ '@tanstack/query-devtools@5.65.0':
+ resolution: {integrity: sha512-g5y7zc07U9D3esMdqUfTEVu9kMHoIaVBsD0+M3LPdAdD710RpTcLiNvJY1JkYXqkq9+NV+CQoemVNpQPBXVsJg==}
+
+ '@tanstack/react-query-devtools@5.66.0':
+ resolution: {integrity: sha512-uB57wA2YZaQ2fPcFW0E9O1zAGDGSbRKRx84uMk/86VyU9jWVxvJ3Uzp+zNm+nZJYsuekCIo2opTdgNuvM3cKgA==}
+ peerDependencies:
+ '@tanstack/react-query': ^5.66.0
+ react: ^18 || ^19
+
+ '@tanstack/react-query@5.66.0':
+ resolution: {integrity: sha512-z3sYixFQJe8hndFnXgWu7C79ctL+pI0KAelYyW+khaNJ1m22lWrhJU2QrsTcRKMuVPtoZvfBYrTStIdKo+x0Xw==}
+ peerDependencies:
+ react: ^18 || ^19
+
+ '@tanstack/react-router@1.99.6':
+ resolution: {integrity: sha512-mH4wkW2h4uILKsCB4qxJWZ8LtVlxvDzZXnXmb5FuAigdTN2qsQhgpxM1bxecII1cDQMjqq8bZvqYw/62MZ5elg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+
+ '@tanstack/react-store@0.7.0':
+ resolution: {integrity: sha512-S/Rq17HaGOk+tQHV/yrePMnG1xbsKZIl/VsNWnNXt4XW+tTY8dTlvpJH2ZQ3GRALsusG5K6Q3unAGJ2pd9W/Ng==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tanstack/react-virtual@3.12.0':
+ resolution: {integrity: sha512-6krceiPN07kpxXmU6m8AY7EL0X1gHLu8m3nJdh4phvktzVNxkQfBmSwnRUpoUjGQO1PAn8wSAhYaL8hY1cS1vw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@tanstack/router-core@1.99.6':
+ resolution: {integrity: sha512-tEfMLeONfyoyI1e/ygUeGFtTWeWQu0GR3OT8OR75EOeNXRmUEtI6H4ThrXcV8nwBd6B88wmp9LhSPLl9H2VwSA==}
+ engines: {node: '>=12'}
+
+ '@tanstack/router-devtools@1.99.6':
+ resolution: {integrity: sha512-X+Nb9WDv7qttqo81tAytUesnf8Yh/YG6E3xb1onk6zDyfdp0pGD/ZRIA7kOMnk5OQ+jB/M/w3Ar2/CPUlM3Yew==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@tanstack/react-router': ^1.99.6
+ react: '>=18.0.0 || >=19.0.0'
+ react-dom: '>=18.0.0 || >=19.0.0'
+
+ '@tanstack/router-generator@1.99.6':
+ resolution: {integrity: sha512-XxAxESvr73zZVBoWvkkN2WSD9eVvdRktiB7gGPYaHoYV/35u1nIMyDoVM5PmeNhrNopzt8ZZzpr2TqTxeQAhjw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@tanstack/react-router': ^1.99.6
+ peerDependenciesMeta:
+ '@tanstack/react-router':
+ optional: true
+
+ '@tanstack/router-plugin@1.99.6':
+ resolution: {integrity: sha512-UeO/eTHQzTIK28GF2kRBvOaJMgVSiQA6+G4EFd7pAyFuOt6onoRoLBiljJ1gsMPct+Xf5FQCHifPbliaDN7e8Q==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ '@rsbuild/core': '>=1.0.2'
+ '@tanstack/react-router': ^1.99.6
+ vite: '>=5.0.0 || >=6.0.0'
+ webpack: '>=5.92.0'
+ peerDependenciesMeta:
+ '@rsbuild/core':
+ optional: true
+ '@tanstack/react-router':
+ optional: true
+ vite:
+ optional: true
+ webpack:
+ optional: true
+
+ '@tanstack/router-utils@1.99.5':
+ resolution: {integrity: sha512-weYNg+aqXX1aZkcD7nOkjymtJiLgyp5A1Gtg6Ey0ttIaAlL3NuLlwX9z0CCnCLb3AGxGL4OgdZ2xVbH/DVaURQ==}
+ engines: {node: '>=12'}
+
+ '@tanstack/store@0.7.0':
+ resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==}
+
+ '@tanstack/virtual-core@3.12.0':
+ resolution: {integrity: sha512-7mDINtua3v/pOnn6WUmuT9dPXYSO7WidFej7JzoAfqEOcbbpt/iZ1WPqd+eg+FnrL9nUJK8radqj4iAU51Zchg==}
+
+ '@tanstack/virtual-file-routes@1.99.0':
+ resolution: {integrity: sha512-XvX8bfdo4CYiCW+ItVdBfCorh3PwQFqYqd7ll+XKWiWOJpqUGIG7VlziVavARZpUySiY2VBlHadiUYS7jhgjRg==}
+ engines: {node: '>=12'}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.6.8':
+ resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.20.6':
+ resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==}
+
+ '@types/estree@1.0.6':
+ resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/node@22.13.1':
+ resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==}
+
+ '@types/prop-types@15.7.14':
+ resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
+
+ '@types/react-dom@18.3.5':
+ resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react@18.3.18':
+ resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==}
+
+ '@typescript-eslint/eslint-plugin@8.23.0':
+ resolution: {integrity: sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/parser@8.23.0':
+ resolution: {integrity: sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/scope-manager@8.23.0':
+ resolution: {integrity: sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/type-utils@8.23.0':
+ resolution: {integrity: sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/types@8.23.0':
+ resolution: {integrity: sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.23.0':
+ resolution: {integrity: sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/utils@8.23.0':
+ resolution: {integrity: sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ '@typescript-eslint/visitor-keys@8.23.0':
+ resolution: {integrity: sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@vitejs/plugin-react@4.3.4':
+ resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.14.0:
+ resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@6.1.0:
+ resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
+ engines: {node: '>=12'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@6.2.1:
+ resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+ engines: {node: '>=12'}
+
+ ansis@3.10.0:
+ resolution: {integrity: sha512-hxDKLYT7hy3Y4sF3HxI926A3urzPxi73mZBB629m9bCVF+NyKNxbwCqqm+C/YrGPtxLwnl6d8/ZASCsz6SyvJA==}
+ engines: {node: '>=16'}
+
+ any-promise@1.3.0:
+ resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ arg@5.0.2:
+ resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-hidden@1.2.4:
+ resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
+ engines: {node: '>=10'}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ attr-accept@2.2.5:
+ resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==}
+ engines: {node: '>=4'}
+
+ autoprefixer@10.4.20:
+ resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
+ engines: {node: ^10 || ^12 || >=14}
+ hasBin: true
+ peerDependencies:
+ postcss: ^8.1.0
+
+ axios@1.7.9:
+ resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
+
+ babel-dead-code-elimination@1.0.8:
+ resolution: {integrity: sha512-og6HQERk0Cmm+nTT4Od2wbPtgABXFMPaHACjbKLulZIFMkYyXZLkUGuAxdgpMJBrxyt/XFpSz++lNzjbcMnPkQ==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ binary-extensions@2.3.0:
+ resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+ engines: {node: '>=8'}
+
+ brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+ brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.24.4:
+ resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camelcase-css@2.0.1:
+ resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
+ engines: {node: '>= 6'}
+
+ caniuse-lite@1.0.30001697:
+ resolution: {integrity: sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ chokidar@3.6.0:
+ resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+ engines: {node: '>= 8.10.0'}
+
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ cmdk@1.0.4:
+ resolution: {integrity: sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==}
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^18 || ^19 || ^19.0.0-rc
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ commander@4.1.1:
+ resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
+ engines: {node: '>= 6'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+ date-fns-jalali@4.1.0-0:
+ resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==}
+
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
+ debug@4.4.0:
+ resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
+ didyoumean@1.2.2:
+ resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
+
+ diff@7.0.0:
+ resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
+ engines: {node: '>=0.3.1'}
+
+ dlv@1.1.3:
+ resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
+
+ eastasianwidth@0.2.0:
+ resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+
+ electron-to-chromium@1.5.92:
+ resolution: {integrity: sha512-BeHgmNobs05N1HMmMZ7YIuHfYBGlq/UmvlsTgg+fsbFs9xVMj+xJHFg19GN04+9Q+r8Xnh9LXqaYIyEWElnNgQ==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+
+ enhanced-resolve@5.18.1:
+ resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==}
+ engines: {node: '>=10.13.0'}
+
+ esbuild@0.23.1:
+ resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ esbuild@0.24.2:
+ resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-plugin-react-hooks@5.1.0:
+ resolution: {integrity: sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react-refresh@0.4.18:
+ resolution: {integrity: sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==}
+ peerDependencies:
+ eslint: '>=8.40'
+
+ eslint-scope@8.2.0:
+ resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.0:
+ resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint@9.19.0:
+ resolution: {integrity: sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.3.0:
+ resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esquery@1.6.0:
+ resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fastq@1.19.0:
+ resolution: {integrity: sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==}
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ file-selector@2.1.2:
+ resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==}
+ engines: {node: '>= 12'}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.3.2:
+ resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
+
+ follow-redirects@1.15.9:
+ resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+
+ foreground-child@3.3.0:
+ resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
+ engines: {node: '>=14'}
+
+ form-data@4.0.1:
+ resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
+ engines: {node: '>= 6'}
+
+ fraction.js@4.3.7:
+ resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
+ get-tsconfig@4.10.0:
+ resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ glob@10.4.5:
+ resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ hasBin: true
+
+ globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@15.14.0:
+ resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==}
+ engines: {node: '>=18'}
+
+ goober@2.1.16:
+ resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==}
+ peerDependencies:
+ csstype: ^3.0.10
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ graphemer@1.4.0:
+ resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ html-parse-stringify@3.0.1:
+ resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
+
+ i18next@24.2.2:
+ resolution: {integrity: sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ==}
+ peerDependencies:
+ typescript: ^5
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ jackspeak@3.4.3:
+ resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+
+ jiti@1.21.7:
+ resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
+ hasBin: true
+
+ jiti@2.4.2:
+ resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
+ hasBin: true
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lightningcss-darwin-arm64@1.29.1:
+ resolution: {integrity: sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.29.1:
+ resolution: {integrity: sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.29.1:
+ resolution: {integrity: sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.29.1:
+ resolution: {integrity: sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.29.1:
+ resolution: {integrity: sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.29.1:
+ resolution: {integrity: sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.29.1:
+ resolution: {integrity: sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.29.1:
+ resolution: {integrity: sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.29.1:
+ resolution: {integrity: sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.29.1:
+ resolution: {integrity: sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.29.1:
+ resolution: {integrity: sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==}
+ engines: {node: '>= 12.0.0'}
+
+ lilconfig@2.1.0:
+ resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
+ engines: {node: '>=10'}
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.castarray@4.4.0:
+ resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lucide-react@0.474.0:
+ resolution: {integrity: sha512-CmghgHkh0OJNmxGKWc0qfPJCYHASPMVSyGY8fj3xgk4v84ItqDg64JNKFZn5hC6E0vHi6gxnbCgwhyVB09wQtA==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minipass@7.1.2:
+ resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mz@2.7.0:
+ resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
+
+ nanoid@3.3.8:
+ resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ node-releases@2.0.19:
+ resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ normalize-range@0.1.2:
+ resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
+ engines: {node: '>=0.10.0'}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-hash@3.0.0:
+ resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
+ engines: {node: '>= 6'}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-scurry@1.11.1:
+ resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
+ engines: {node: '>=16 || 14 >=14.18'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ pify@2.3.0:
+ resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
+ engines: {node: '>=0.10.0'}
+
+ pirates@4.0.6:
+ resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==}
+ engines: {node: '>= 6'}
+
+ postcss-import@15.1.0:
+ resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ postcss: ^8.0.0
+
+ postcss-js@4.0.1:
+ resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==}
+ engines: {node: ^12 || ^14 || >= 16}
+ peerDependencies:
+ postcss: ^8.4.21
+
+ postcss-load-config@4.0.2:
+ resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
+ engines: {node: '>= 14'}
+ peerDependencies:
+ postcss: '>=8.0.9'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+ ts-node:
+ optional: true
+
+ postcss-nested@6.2.0:
+ resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.2.14
+
+ postcss-selector-parser@6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+
+ postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.5.1:
+ resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier@3.4.2:
+ resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ react-day-picker@9.5.1:
+ resolution: {integrity: sha512-PxuK8inYLlYgM2zZUVBPsaBM5jI40suPeG+naKyx7kpyF032RRlEAUEjkpW9/poTASh/vyWAOVqjGuGw+47isw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
+ react-dropzone@14.3.5:
+ resolution: {integrity: sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==}
+ engines: {node: '>= 10.13'}
+ peerDependencies:
+ react: '>= 16.8 || 18.0.0'
+
+ react-hook-form@7.54.2:
+ resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
+ react-i18next@15.4.0:
+ resolution: {integrity: sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==}
+ peerDependencies:
+ i18next: '>= 23.2.3'
+ react: '>= 16.8.0'
+ react-dom: '*'
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-refresh@0.14.2:
+ resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
+ engines: {node: '>=0.10.0'}
+
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.6.3:
+ resolution: {integrity: sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ read-cache@1.0.0:
+ resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==}
+
+ readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+
+ regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
+ resolve@1.22.10:
+ resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ reusify@1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rollup@4.34.3:
+ resolution: {integrity: sha512-ORCtU0UBJyiAIn9m0llUXJXAswG/68pZptCrqxHG7//Z2DDzAUeyyY5hqf4XrsGlUxscMr9GkQ2QI7KTLqeyPw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.1:
+ resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string-width@5.1.2:
+ resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
+ engines: {node: '>=12'}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-ansi@7.1.0:
+ resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
+ engines: {node: '>=12'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ sucrase@3.35.0:
+ resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ hasBin: true
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ tabbable@6.2.0:
+ resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
+
+ tailwind-merge@3.0.1:
+ resolution: {integrity: sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==}
+
+ tailwindcss-animate@1.0.7:
+ resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+
+ tailwindcss@3.4.3:
+ resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ tailwindcss@4.0.3:
+ resolution: {integrity: sha512-ImmZF0Lon5RrQpsEAKGxRvHwCvMgSC4XVlFRqmbzTEDb/3wvin9zfEZrMwgsa3yqBbPqahYcVI6lulM2S7IZAA==}
+
+ tapable@2.2.1:
+ resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
+ engines: {node: '>=6'}
+
+ thenify-all@1.6.0:
+ resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
+ engines: {node: '>=0.8'}
+
+ thenify@3.3.1:
+ resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
+
+ tiny-invariant@1.3.3:
+ resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
+ tiny-warning@1.0.3:
+ resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ ts-api-utils@2.0.1:
+ resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ ts-interface-checker@0.1.13:
+ resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ tsx@4.19.2:
+ resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ typescript-eslint@8.23.0:
+ resolution: {integrity: sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <5.8.0'
+
+ typescript@5.6.3:
+ resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.20.0:
+ resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
+
+ unplugin@2.1.2:
+ resolution: {integrity: sha512-Q3LU0e4zxKfRko1wMV2HmP8lB9KWislY7hxXpxd+lGx0PRInE4vhMBVEZwpdVYHvtqzhSrzuIfErsob6bQfCzw==}
+ engines: {node: '>=18.12.0'}
+
+ update-browserslist-db@1.1.2:
+ resolution: {integrity: sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-sync-external-store@1.4.0:
+ resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ uuid@11.0.5:
+ resolution: {integrity: sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==}
+ hasBin: true
+
+ vite@6.1.0:
+ resolution: {integrity: sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ void-elements@3.1.0:
+ resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+ engines: {node: '>=0.10.0'}
+
+ webpack-virtual-modules@0.6.2:
+ resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrap-ansi@8.1.0:
+ resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
+ engines: {node: '>=12'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yaml@2.7.0:
+ resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==}
+ engines: {node: '>= 14'}
+ hasBin: true
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod@3.24.1:
+ resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
+
+ zustand@5.0.3:
+ resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
+ engines: {node: '>=12.20.0'}
+ peerDependencies:
+ '@types/react': '>=18.0.0'
+ immer: '>=9.0.6'
+ react: '>=18.0.0'
+ use-sync-external-store: '>=1.2.0'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+ use-sync-external-store:
+ optional: true
+
+snapshots:
+
+ '@alloc/quick-lru@5.2.0': {}
+
+ '@ampproject/remapping@2.3.0':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@babel/code-frame@7.26.2':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.25.9
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.26.5': {}
+
+ '@babel/core@7.26.7':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@babel/code-frame': 7.26.2
+ '@babel/generator': 7.26.5
+ '@babel/helper-compilation-targets': 7.26.5
+ '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.7)
+ '@babel/helpers': 7.26.7
+ '@babel/parser': 7.26.7
+ '@babel/template': 7.25.9
+ '@babel/traverse': 7.26.7
+ '@babel/types': 7.26.7
+ convert-source-map: 2.0.0
+ debug: 4.4.0
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.26.5':
+ dependencies:
+ '@babel/parser': 7.26.7
+ '@babel/types': 7.26.7
+ '@jridgewell/gen-mapping': 0.3.8
+ '@jridgewell/trace-mapping': 0.3.25
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.26.5':
+ dependencies:
+ '@babel/compat-data': 7.26.5
+ '@babel/helper-validator-option': 7.25.9
+ browserslist: 4.24.4
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-module-imports@7.25.9':
+ dependencies:
+ '@babel/traverse': 7.26.7
+ '@babel/types': 7.26.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.7)':
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/helper-module-imports': 7.25.9
+ '@babel/helper-validator-identifier': 7.25.9
+ '@babel/traverse': 7.26.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.26.5': {}
+
+ '@babel/helper-string-parser@7.25.9': {}
+
+ '@babel/helper-validator-identifier@7.25.9': {}
+
+ '@babel/helper-validator-option@7.25.9': {}
+
+ '@babel/helpers@7.26.7':
+ dependencies:
+ '@babel/template': 7.25.9
+ '@babel/types': 7.26.7
+
+ '@babel/parser@7.26.7':
+ dependencies:
+ '@babel/types': 7.26.7
+
+ '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.7)':
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/helper-plugin-utils': 7.26.5
+
+ '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.7)':
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/helper-plugin-utils': 7.26.5
+
+ '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.7)':
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/helper-plugin-utils': 7.26.5
+
+ '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.7)':
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/helper-plugin-utils': 7.26.5
+
+ '@babel/runtime@7.26.7':
+ dependencies:
+ regenerator-runtime: 0.14.1
+
+ '@babel/template@7.25.9':
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@babel/parser': 7.26.7
+ '@babel/types': 7.26.7
+
+ '@babel/traverse@7.26.7':
+ dependencies:
+ '@babel/code-frame': 7.26.2
+ '@babel/generator': 7.26.5
+ '@babel/parser': 7.26.7
+ '@babel/template': 7.25.9
+ '@babel/types': 7.26.7
+ debug: 4.4.0
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.26.7':
+ dependencies:
+ '@babel/helper-string-parser': 7.25.9
+ '@babel/helper-validator-identifier': 7.25.9
+
+ '@date-fns/tz@1.2.0': {}
+
+ '@esbuild/aix-ppc64@0.23.1':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.24.2':
+ optional: true
+
+ '@esbuild/android-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/android-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/android-arm@0.23.1':
+ optional: true
+
+ '@esbuild/android-arm@0.24.2':
+ optional: true
+
+ '@esbuild/android-x64@0.23.1':
+ optional: true
+
+ '@esbuild/android-x64@0.24.2':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/darwin-x64@0.23.1':
+ optional: true
+
+ '@esbuild/darwin-x64@0.24.2':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-arm@0.23.1':
+ optional: true
+
+ '@esbuild/linux-arm@0.24.2':
+ optional: true
+
+ '@esbuild/linux-ia32@0.23.1':
+ optional: true
+
+ '@esbuild/linux-ia32@0.24.2':
+ optional: true
+
+ '@esbuild/linux-loong64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-loong64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.23.1':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.24.2':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.24.2':
+ optional: true
+
+ '@esbuild/linux-s390x@0.23.1':
+ optional: true
+
+ '@esbuild/linux-s390x@0.24.2':
+ optional: true
+
+ '@esbuild/linux-x64@0.23.1':
+ optional: true
+
+ '@esbuild/linux-x64@0.24.2':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.24.2':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.23.1':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.24.2':
+ optional: true
+
+ '@esbuild/sunos-x64@0.23.1':
+ optional: true
+
+ '@esbuild/sunos-x64@0.24.2':
+ optional: true
+
+ '@esbuild/win32-arm64@0.23.1':
+ optional: true
+
+ '@esbuild/win32-arm64@0.24.2':
+ optional: true
+
+ '@esbuild/win32-ia32@0.23.1':
+ optional: true
+
+ '@esbuild/win32-ia32@0.24.2':
+ optional: true
+
+ '@esbuild/win32-x64@0.23.1':
+ optional: true
+
+ '@esbuild/win32-x64@0.24.2':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.4.1(eslint@9.19.0(jiti@2.4.2))':
+ dependencies:
+ eslint: 9.19.0(jiti@2.4.2)
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.1': {}
+
+ '@eslint/config-array@0.19.2':
+ dependencies:
+ '@eslint/object-schema': 2.1.6
+ debug: 4.4.0
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/core@0.10.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.2.0':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.0
+ espree: 10.3.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.19.0': {}
+
+ '@eslint/object-schema@2.1.6': {}
+
+ '@eslint/plugin-kit@0.2.5':
+ dependencies:
+ '@eslint/core': 0.10.0
+ levn: 0.4.1
+
+ '@floating-ui/core@1.6.9':
+ dependencies:
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/dom@1.6.13':
+ dependencies:
+ '@floating-ui/core': 1.6.9
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/dom': 1.6.13
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@floating-ui/react@0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@floating-ui/utils': 0.2.9
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ tabbable: 6.2.0
+
+ '@floating-ui/utils@0.2.9': {}
+
+ '@headlessui/react@2.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react': 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-aria/focus': 3.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-aria/interactions': 3.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tanstack/react-virtual': 3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@heroicons/react@2.2.0(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.6':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.3.1
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.3.1': {}
+
+ '@humanwhocodes/retry@0.4.1': {}
+
+ '@isaacs/cliui@8.0.2':
+ dependencies:
+ string-width: 5.1.2
+ string-width-cjs: string-width@4.2.3
+ strip-ansi: 7.1.0
+ strip-ansi-cjs: strip-ansi@6.0.1
+ wrap-ansi: 8.1.0
+ wrap-ansi-cjs: wrap-ansi@7.0.0
+
+ '@jridgewell/gen-mapping@0.3.8':
+ dependencies:
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/set-array@1.2.1': {}
+
+ '@jridgewell/sourcemap-codec@1.5.0': {}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.19.0
+
+ '@pkgjs/parseargs@0.11.0':
+ optional: true
+
+ '@radix-ui/number@1.1.0': {}
+
+ '@radix-ui/primitive@1.1.1': {}
+
+ '@radix-ui/react-alert-dialog@1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-dialog': 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-arrow@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-arrow@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-collapsible@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-collection@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-collection@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-context@1.1.1(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-dialog@1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-portal': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ aria-hidden: 1.2.4
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-direction@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-dismissable-layer@1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-focus-scope@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-id@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-label@2.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-popover@1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ aria-hidden: 1.2.4
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-popper@1.2.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-arrow': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/rect': 1.1.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-popper@1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-arrow': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-rect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/rect': 1.1.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-portal@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-portal@1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-primitive@2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-progress@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-radio-group@1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-roving-focus@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-scroll-area@1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/number': 1.1.0
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-select@2.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/number': 1.1.0
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-popper': 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-portal': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-visually-hidden': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ aria-hidden: 1.2.4
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-remove-scroll: 2.6.3(@types/react@18.3.18)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-separator@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-slider@1.2.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/number': 1.1.0
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-slot@1.1.1(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-slot@1.1.2(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-switch@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-tabs@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-direction': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-roving-focus': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-toast@1.2.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-collection': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-portal': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-visually-hidden': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-toggle@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-tooltip@1.1.8(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.1
+ '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-popper': 1.2.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-portal': 1.1.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.2(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-visually-hidden': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-use-previous@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-use-rect@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/rect': 1.1.0
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-use-size@1.1.0(@types/react@18.3.18)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ '@radix-ui/react-visually-hidden@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+ '@types/react-dom': 18.3.5(@types/react@18.3.18)
+
+ '@radix-ui/rect@1.1.0': {}
+
+ '@react-aria/focus@3.19.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@react-aria/interactions': 3.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-aria/utils': 3.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-types/shared': 3.27.0(react@18.3.1)
+ '@swc/helpers': 0.5.15
+ clsx: 2.1.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@react-aria/interactions@3.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@react-aria/ssr': 3.9.7(react@18.3.1)
+ '@react-aria/utils': 3.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@react-types/shared': 3.27.0(react@18.3.1)
+ '@swc/helpers': 0.5.15
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@react-aria/ssr@3.9.7(react@18.3.1)':
+ dependencies:
+ '@swc/helpers': 0.5.15
+ react: 18.3.1
+
+ '@react-aria/utils@3.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@react-aria/ssr': 3.9.7(react@18.3.1)
+ '@react-stately/utils': 3.10.5(react@18.3.1)
+ '@react-types/shared': 3.27.0(react@18.3.1)
+ '@swc/helpers': 0.5.15
+ clsx: 2.1.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@react-stately/utils@3.10.5(react@18.3.1)':
+ dependencies:
+ '@swc/helpers': 0.5.15
+ react: 18.3.1
+
+ '@react-types/shared@3.27.0(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@rollup/rollup-android-arm-eabi@4.34.3':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.34.3':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.34.3':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.34.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.34.3':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-powerpc64le-gnu@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.34.3':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.34.3':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.34.3':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.34.3':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.34.3':
+ optional: true
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/node@4.0.3':
+ dependencies:
+ enhanced-resolve: 5.18.1
+ jiti: 2.4.2
+ tailwindcss: 4.0.3
+
+ '@tailwindcss/oxide-android-arm64@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-arm64@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-darwin-x64@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-freebsd-x64@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-linux-x64-musl@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.0.3':
+ optional: true
+
+ '@tailwindcss/oxide@4.0.3':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.0.3
+ '@tailwindcss/oxide-darwin-arm64': 4.0.3
+ '@tailwindcss/oxide-darwin-x64': 4.0.3
+ '@tailwindcss/oxide-freebsd-x64': 4.0.3
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.3
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.0.3
+ '@tailwindcss/oxide-linux-arm64-musl': 4.0.3
+ '@tailwindcss/oxide-linux-x64-gnu': 4.0.3
+ '@tailwindcss/oxide-linux-x64-musl': 4.0.3
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.0.3
+ '@tailwindcss/oxide-win32-x64-msvc': 4.0.3
+
+ '@tailwindcss/postcss@4.0.3':
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ '@tailwindcss/node': 4.0.3
+ '@tailwindcss/oxide': 4.0.3
+ lightningcss: 1.29.1
+ postcss: 8.5.1
+ tailwindcss: 4.0.3
+
+ '@tailwindcss/typography@0.5.16(tailwindcss@3.4.3)':
+ dependencies:
+ lodash.castarray: 4.4.0
+ lodash.isplainobject: 4.0.6
+ lodash.merge: 4.6.2
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 3.4.3
+
+ '@tailwindcss/vite@4.0.3(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0))':
+ dependencies:
+ '@tailwindcss/node': 4.0.3
+ '@tailwindcss/oxide': 4.0.3
+ lightningcss: 1.29.1
+ tailwindcss: 4.0.3
+ vite: 6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0)
+
+ '@tanstack/history@1.99.0': {}
+
+ '@tanstack/query-core@5.66.0': {}
+
+ '@tanstack/query-devtools@5.65.0': {}
+
+ '@tanstack/react-query-devtools@5.66.0(@tanstack/react-query@5.66.0(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/query-devtools': 5.65.0
+ '@tanstack/react-query': 5.66.0(react@18.3.1)
+ react: 18.3.1
+
+ '@tanstack/react-query@5.66.0(react@18.3.1)':
+ dependencies:
+ '@tanstack/query-core': 5.66.0
+ react: 18.3.1
+
+ '@tanstack/react-router@1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/history': 1.99.0
+ '@tanstack/react-store': 0.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@tanstack/router-core': 1.99.6
+ jsesc: 3.1.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ tiny-invariant: 1.3.3
+ tiny-warning: 1.0.3
+
+ '@tanstack/react-store@0.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/store': 0.7.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ use-sync-external-store: 1.4.0(react@18.3.1)
+
+ '@tanstack/react-virtual@3.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/virtual-core': 3.12.0
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
+ '@tanstack/router-core@1.99.6':
+ dependencies:
+ '@tanstack/history': 1.99.0
+ '@tanstack/store': 0.7.0
+
+ '@tanstack/router-devtools@1.99.6(@tanstack/react-router@1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@tanstack/react-router': 1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ clsx: 2.1.1
+ goober: 2.1.16(csstype@3.1.3)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ transitivePeerDependencies:
+ - csstype
+
+ '@tanstack/router-generator@1.99.6(@tanstack/react-router@1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))':
+ dependencies:
+ '@tanstack/virtual-file-routes': 1.99.0
+ prettier: 3.4.2
+ tsx: 4.19.2
+ zod: 3.24.1
+ optionalDependencies:
+ '@tanstack/react-router': 1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+
+ '@tanstack/router-plugin@1.99.6(@tanstack/react-router@1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0))':
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.7)
+ '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.7)
+ '@babel/template': 7.25.9
+ '@babel/traverse': 7.26.7
+ '@babel/types': 7.26.7
+ '@tanstack/router-generator': 1.99.6(@tanstack/react-router@1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
+ '@tanstack/router-utils': 1.99.5
+ '@tanstack/virtual-file-routes': 1.99.0
+ '@types/babel__core': 7.20.5
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.20.6
+ babel-dead-code-elimination: 1.0.8
+ chokidar: 3.6.0
+ unplugin: 2.1.2
+ zod: 3.24.1
+ optionalDependencies:
+ '@tanstack/react-router': 1.99.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ vite: 6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ '@tanstack/router-utils@1.99.5':
+ dependencies:
+ '@babel/generator': 7.26.5
+ '@babel/parser': 7.26.7
+ ansis: 3.10.0
+ diff: 7.0.0
+
+ '@tanstack/store@0.7.0': {}
+
+ '@tanstack/virtual-core@3.12.0': {}
+
+ '@tanstack/virtual-file-routes@1.99.0': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.26.7
+ '@babel/types': 7.26.7
+ '@types/babel__generator': 7.6.8
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.20.6
+
+ '@types/babel__generator@7.6.8':
+ dependencies:
+ '@babel/types': 7.26.7
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.26.7
+ '@babel/types': 7.26.7
+
+ '@types/babel__traverse@7.20.6':
+ dependencies:
+ '@babel/types': 7.26.7
+
+ '@types/estree@1.0.6': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/node@22.13.1':
+ dependencies:
+ undici-types: 6.20.0
+
+ '@types/prop-types@15.7.14': {}
+
+ '@types/react-dom@18.3.5(@types/react@18.3.18)':
+ dependencies:
+ '@types/react': 18.3.18
+
+ '@types/react@18.3.18':
+ dependencies:
+ '@types/prop-types': 15.7.14
+ csstype: 3.1.3
+
+ '@typescript-eslint/eslint-plugin@8.23.0(@typescript-eslint/parser@8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.1
+ '@typescript-eslint/parser': 8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ '@typescript-eslint/scope-manager': 8.23.0
+ '@typescript-eslint/type-utils': 8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ '@typescript-eslint/utils': 8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ '@typescript-eslint/visitor-keys': 8.23.0
+ eslint: 9.19.0(jiti@2.4.2)
+ graphemer: 1.4.0
+ ignore: 5.3.2
+ natural-compare: 1.4.0
+ ts-api-utils: 2.0.1(typescript@5.6.3)
+ typescript: 5.6.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.23.0
+ '@typescript-eslint/types': 8.23.0
+ '@typescript-eslint/typescript-estree': 8.23.0(typescript@5.6.3)
+ '@typescript-eslint/visitor-keys': 8.23.0
+ debug: 4.4.0
+ eslint: 9.19.0(jiti@2.4.2)
+ typescript: 5.6.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.23.0':
+ dependencies:
+ '@typescript-eslint/types': 8.23.0
+ '@typescript-eslint/visitor-keys': 8.23.0
+
+ '@typescript-eslint/type-utils@8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)':
+ dependencies:
+ '@typescript-eslint/typescript-estree': 8.23.0(typescript@5.6.3)
+ '@typescript-eslint/utils': 8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ debug: 4.4.0
+ eslint: 9.19.0(jiti@2.4.2)
+ ts-api-utils: 2.0.1(typescript@5.6.3)
+ typescript: 5.6.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.23.0': {}
+
+ '@typescript-eslint/typescript-estree@8.23.0(typescript@5.6.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.23.0
+ '@typescript-eslint/visitor-keys': 8.23.0
+ debug: 4.4.0
+ fast-glob: 3.3.3
+ is-glob: 4.0.3
+ minimatch: 9.0.5
+ semver: 7.7.1
+ ts-api-utils: 2.0.1(typescript@5.6.3)
+ typescript: 5.6.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0(jiti@2.4.2))
+ '@typescript-eslint/scope-manager': 8.23.0
+ '@typescript-eslint/types': 8.23.0
+ '@typescript-eslint/typescript-estree': 8.23.0(typescript@5.6.3)
+ eslint: 9.19.0(jiti@2.4.2)
+ typescript: 5.6.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.23.0':
+ dependencies:
+ '@typescript-eslint/types': 8.23.0
+ eslint-visitor-keys: 4.2.0
+
+ '@vitejs/plugin-react@4.3.4(vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0))':
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.7)
+ '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.7)
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.14.2
+ vite: 6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0)
+ transitivePeerDependencies:
+ - supports-color
+
+ acorn-jsx@5.3.2(acorn@8.14.0):
+ dependencies:
+ acorn: 8.14.0
+
+ acorn@8.14.0: {}
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ansi-regex@5.0.1: {}
+
+ ansi-regex@6.1.0: {}
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@6.2.1: {}
+
+ ansis@3.10.0: {}
+
+ any-promise@1.3.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ arg@5.0.2: {}
+
+ argparse@2.0.1: {}
+
+ aria-hidden@1.2.4:
+ dependencies:
+ tslib: 2.8.1
+
+ asynckit@0.4.0: {}
+
+ attr-accept@2.2.5: {}
+
+ autoprefixer@10.4.20(postcss@8.5.1):
+ dependencies:
+ browserslist: 4.24.4
+ caniuse-lite: 1.0.30001697
+ fraction.js: 4.3.7
+ normalize-range: 0.1.2
+ picocolors: 1.1.1
+ postcss: 8.5.1
+ postcss-value-parser: 4.2.0
+
+ axios@1.7.9:
+ dependencies:
+ follow-redirects: 1.15.9
+ form-data: 4.0.1
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+
+ babel-dead-code-elimination@1.0.8:
+ dependencies:
+ '@babel/core': 7.26.7
+ '@babel/parser': 7.26.7
+ '@babel/traverse': 7.26.7
+ '@babel/types': 7.26.7
+ transitivePeerDependencies:
+ - supports-color
+
+ balanced-match@1.0.2: {}
+
+ binary-extensions@2.3.0: {}
+
+ brace-expansion@1.1.11:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.1:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.24.4:
+ dependencies:
+ caniuse-lite: 1.0.30001697
+ electron-to-chromium: 1.5.92
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.2(browserslist@4.24.4)
+
+ callsites@3.1.0: {}
+
+ camelcase-css@2.0.1: {}
+
+ caniuse-lite@1.0.30001697: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chokidar@3.6.0:
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.3
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
+ clsx@2.1.1: {}
+
+ cmdk@1.0.4(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@radix-ui/react-dialog': 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.18)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ use-sync-external-store: 1.4.0(react@18.3.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ commander@4.1.1: {}
+
+ concat-map@0.0.1: {}
+
+ convert-source-map@2.0.0: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ cssesc@3.0.0: {}
+
+ csstype@3.1.3: {}
+
+ date-fns-jalali@4.1.0-0: {}
+
+ date-fns@4.1.0: {}
+
+ debug@4.4.0:
+ dependencies:
+ ms: 2.1.3
+
+ deep-is@0.1.4: {}
+
+ delayed-stream@1.0.0: {}
+
+ detect-libc@1.0.3: {}
+
+ detect-node-es@1.1.0: {}
+
+ didyoumean@1.2.2: {}
+
+ diff@7.0.0: {}
+
+ dlv@1.1.3: {}
+
+ eastasianwidth@0.2.0: {}
+
+ electron-to-chromium@1.5.92: {}
+
+ emoji-regex@8.0.0: {}
+
+ emoji-regex@9.2.2: {}
+
+ enhanced-resolve@5.18.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.1
+
+ esbuild@0.23.1:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.23.1
+ '@esbuild/android-arm': 0.23.1
+ '@esbuild/android-arm64': 0.23.1
+ '@esbuild/android-x64': 0.23.1
+ '@esbuild/darwin-arm64': 0.23.1
+ '@esbuild/darwin-x64': 0.23.1
+ '@esbuild/freebsd-arm64': 0.23.1
+ '@esbuild/freebsd-x64': 0.23.1
+ '@esbuild/linux-arm': 0.23.1
+ '@esbuild/linux-arm64': 0.23.1
+ '@esbuild/linux-ia32': 0.23.1
+ '@esbuild/linux-loong64': 0.23.1
+ '@esbuild/linux-mips64el': 0.23.1
+ '@esbuild/linux-ppc64': 0.23.1
+ '@esbuild/linux-riscv64': 0.23.1
+ '@esbuild/linux-s390x': 0.23.1
+ '@esbuild/linux-x64': 0.23.1
+ '@esbuild/netbsd-x64': 0.23.1
+ '@esbuild/openbsd-arm64': 0.23.1
+ '@esbuild/openbsd-x64': 0.23.1
+ '@esbuild/sunos-x64': 0.23.1
+ '@esbuild/win32-arm64': 0.23.1
+ '@esbuild/win32-ia32': 0.23.1
+ '@esbuild/win32-x64': 0.23.1
+
+ esbuild@0.24.2:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.24.2
+ '@esbuild/android-arm': 0.24.2
+ '@esbuild/android-arm64': 0.24.2
+ '@esbuild/android-x64': 0.24.2
+ '@esbuild/darwin-arm64': 0.24.2
+ '@esbuild/darwin-x64': 0.24.2
+ '@esbuild/freebsd-arm64': 0.24.2
+ '@esbuild/freebsd-x64': 0.24.2
+ '@esbuild/linux-arm': 0.24.2
+ '@esbuild/linux-arm64': 0.24.2
+ '@esbuild/linux-ia32': 0.24.2
+ '@esbuild/linux-loong64': 0.24.2
+ '@esbuild/linux-mips64el': 0.24.2
+ '@esbuild/linux-ppc64': 0.24.2
+ '@esbuild/linux-riscv64': 0.24.2
+ '@esbuild/linux-s390x': 0.24.2
+ '@esbuild/linux-x64': 0.24.2
+ '@esbuild/netbsd-arm64': 0.24.2
+ '@esbuild/netbsd-x64': 0.24.2
+ '@esbuild/openbsd-arm64': 0.24.2
+ '@esbuild/openbsd-x64': 0.24.2
+ '@esbuild/sunos-x64': 0.24.2
+ '@esbuild/win32-arm64': 0.24.2
+ '@esbuild/win32-ia32': 0.24.2
+ '@esbuild/win32-x64': 0.24.2
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-plugin-react-hooks@5.1.0(eslint@9.19.0(jiti@2.4.2)):
+ dependencies:
+ eslint: 9.19.0(jiti@2.4.2)
+
+ eslint-plugin-react-refresh@0.4.18(eslint@9.19.0(jiti@2.4.2)):
+ dependencies:
+ eslint: 9.19.0(jiti@2.4.2)
+
+ eslint-scope@8.2.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.0: {}
+
+ eslint@9.19.0(jiti@2.4.2):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0(jiti@2.4.2))
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/config-array': 0.19.2
+ '@eslint/core': 0.10.0
+ '@eslint/eslintrc': 3.2.0
+ '@eslint/js': 9.19.0
+ '@eslint/plugin-kit': 0.2.5
+ '@humanfs/node': 0.16.6
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.1
+ '@types/estree': 1.0.6
+ '@types/json-schema': 7.0.15
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.0
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.2.0
+ eslint-visitor-keys: 4.2.0
+ espree: 10.3.0
+ esquery: 1.6.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ optionalDependencies:
+ jiti: 2.4.2
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.3.0:
+ dependencies:
+ acorn: 8.14.0
+ acorn-jsx: 5.3.2(acorn@8.14.0)
+ eslint-visitor-keys: 4.2.0
+
+ esquery@1.6.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fastq@1.19.0:
+ dependencies:
+ reusify: 1.0.4
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ file-selector@2.1.2:
+ dependencies:
+ tslib: 2.8.1
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.2
+ keyv: 4.5.4
+
+ flatted@3.3.2: {}
+
+ follow-redirects@1.15.9: {}
+
+ foreground-child@3.3.0:
+ dependencies:
+ cross-spawn: 7.0.6
+ signal-exit: 4.1.0
+
+ form-data@4.0.1:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+
+ fraction.js@4.3.7: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-nonce@1.0.1: {}
+
+ get-tsconfig@4.10.0:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@10.4.5:
+ dependencies:
+ foreground-child: 3.3.0
+ jackspeak: 3.4.3
+ minimatch: 9.0.5
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 1.11.1
+
+ globals@11.12.0: {}
+
+ globals@14.0.0: {}
+
+ globals@15.14.0: {}
+
+ goober@2.1.16(csstype@3.1.3):
+ dependencies:
+ csstype: 3.1.3
+
+ graceful-fs@4.2.11: {}
+
+ graphemer@1.4.0: {}
+
+ has-flag@4.0.0: {}
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ html-parse-stringify@3.0.1:
+ dependencies:
+ void-elements: 3.1.0
+
+ i18next@24.2.2(typescript@5.6.3):
+ dependencies:
+ '@babel/runtime': 7.26.7
+ optionalDependencies:
+ typescript: 5.6.3
+
+ ignore@5.3.2: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ is-binary-path@2.1.0:
+ dependencies:
+ binary-extensions: 2.3.0
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-extglob@2.1.1: {}
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-number@7.0.0: {}
+
+ isexe@2.0.0: {}
+
+ jackspeak@3.4.3:
+ dependencies:
+ '@isaacs/cliui': 8.0.2
+ optionalDependencies:
+ '@pkgjs/parseargs': 0.11.0
+
+ jiti@1.21.7: {}
+
+ jiti@2.4.2: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@2.2.3: {}
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lightningcss-darwin-arm64@1.29.1:
+ optional: true
+
+ lightningcss-darwin-x64@1.29.1:
+ optional: true
+
+ lightningcss-freebsd-x64@1.29.1:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.29.1:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.29.1:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.29.1:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.29.1:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.29.1:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.29.1:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.29.1:
+ optional: true
+
+ lightningcss@1.29.1:
+ dependencies:
+ detect-libc: 1.0.3
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.29.1
+ lightningcss-darwin-x64: 1.29.1
+ lightningcss-freebsd-x64: 1.29.1
+ lightningcss-linux-arm-gnueabihf: 1.29.1
+ lightningcss-linux-arm64-gnu: 1.29.1
+ lightningcss-linux-arm64-musl: 1.29.1
+ lightningcss-linux-x64-gnu: 1.29.1
+ lightningcss-linux-x64-musl: 1.29.1
+ lightningcss-win32-arm64-msvc: 1.29.1
+ lightningcss-win32-x64-msvc: 1.29.1
+
+ lilconfig@2.1.0: {}
+
+ lilconfig@3.1.3: {}
+
+ lines-and-columns@1.2.4: {}
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.castarray@4.4.0: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.merge@4.6.2: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lru-cache@10.4.3: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lucide-react@0.474.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.11
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.1
+
+ minipass@7.1.2: {}
+
+ ms@2.1.3: {}
+
+ mz@2.7.0:
+ dependencies:
+ any-promise: 1.3.0
+ object-assign: 4.1.1
+ thenify-all: 1.6.0
+
+ nanoid@3.3.8: {}
+
+ natural-compare@1.4.0: {}
+
+ node-releases@2.0.19: {}
+
+ normalize-path@3.0.0: {}
+
+ normalize-range@0.1.2: {}
+
+ object-assign@4.1.1: {}
+
+ object-hash@3.0.0: {}
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ package-json-from-dist@1.0.1: {}
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-scurry@1.11.1:
+ dependencies:
+ lru-cache: 10.4.3
+ minipass: 7.1.2
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ pify@2.3.0: {}
+
+ pirates@4.0.6: {}
+
+ postcss-import@15.1.0(postcss@8.5.1):
+ dependencies:
+ postcss: 8.5.1
+ postcss-value-parser: 4.2.0
+ read-cache: 1.0.0
+ resolve: 1.22.10
+
+ postcss-js@4.0.1(postcss@8.5.1):
+ dependencies:
+ camelcase-css: 2.0.1
+ postcss: 8.5.1
+
+ postcss-load-config@4.0.2(postcss@8.5.1):
+ dependencies:
+ lilconfig: 3.1.3
+ yaml: 2.7.0
+ optionalDependencies:
+ postcss: 8.5.1
+
+ postcss-nested@6.2.0(postcss@8.5.1):
+ dependencies:
+ postcss: 8.5.1
+ postcss-selector-parser: 6.1.2
+
+ postcss-selector-parser@6.0.10:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-selector-parser@6.1.2:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.5.1:
+ dependencies:
+ nanoid: 3.3.8
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prelude-ls@1.2.1: {}
+
+ prettier@3.4.2: {}
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ proxy-from-env@1.1.0: {}
+
+ punycode@2.3.1: {}
+
+ queue-microtask@1.2.3: {}
+
+ react-day-picker@9.5.1(react@18.3.1):
+ dependencies:
+ '@date-fns/tz': 1.2.0
+ date-fns: 4.1.0
+ date-fns-jalali: 4.1.0-0
+ react: 18.3.1
+
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
+ react-dropzone@14.3.5(react@18.3.1):
+ dependencies:
+ attr-accept: 2.2.5
+ file-selector: 2.1.2
+ prop-types: 15.8.1
+ react: 18.3.1
+
+ react-hook-form@7.54.2(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ react-i18next@15.4.0(i18next@24.2.2(typescript@5.6.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ '@babel/runtime': 7.26.7
+ html-parse-stringify: 3.0.1
+ i18next: 24.2.2(typescript@5.6.3)
+ react: 18.3.1
+ optionalDependencies:
+ react-dom: 18.3.1(react@18.3.1)
+
+ react-is@16.13.1: {}
+
+ react-refresh@0.14.2: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@18.3.18)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.3.1)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ react-remove-scroll@2.6.3(@types/react@18.3.18)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-remove-scroll-bar: 2.3.8(@types/react@18.3.18)(react@18.3.1)
+ react-style-singleton: 2.2.3(@types/react@18.3.18)(react@18.3.1)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@18.3.18)(react@18.3.1)
+ use-sidecar: 1.1.3(@types/react@18.3.18)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ react-style-singleton@2.2.3(@types/react@18.3.18)(react@18.3.1):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 18.3.1
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ read-cache@1.0.0:
+ dependencies:
+ pify: 2.3.0
+
+ readdirp@3.6.0:
+ dependencies:
+ picomatch: 2.3.1
+
+ regenerator-runtime@0.14.1: {}
+
+ resolve-from@4.0.0: {}
+
+ resolve-pkg-maps@1.0.0: {}
+
+ resolve@1.22.10:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ reusify@1.0.4: {}
+
+ rollup@4.34.3:
+ dependencies:
+ '@types/estree': 1.0.6
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.34.3
+ '@rollup/rollup-android-arm64': 4.34.3
+ '@rollup/rollup-darwin-arm64': 4.34.3
+ '@rollup/rollup-darwin-x64': 4.34.3
+ '@rollup/rollup-freebsd-arm64': 4.34.3
+ '@rollup/rollup-freebsd-x64': 4.34.3
+ '@rollup/rollup-linux-arm-gnueabihf': 4.34.3
+ '@rollup/rollup-linux-arm-musleabihf': 4.34.3
+ '@rollup/rollup-linux-arm64-gnu': 4.34.3
+ '@rollup/rollup-linux-arm64-musl': 4.34.3
+ '@rollup/rollup-linux-loongarch64-gnu': 4.34.3
+ '@rollup/rollup-linux-powerpc64le-gnu': 4.34.3
+ '@rollup/rollup-linux-riscv64-gnu': 4.34.3
+ '@rollup/rollup-linux-s390x-gnu': 4.34.3
+ '@rollup/rollup-linux-x64-gnu': 4.34.3
+ '@rollup/rollup-linux-x64-musl': 4.34.3
+ '@rollup/rollup-win32-arm64-msvc': 4.34.3
+ '@rollup/rollup-win32-ia32-msvc': 4.34.3
+ '@rollup/rollup-win32-x64-msvc': 4.34.3
+ fsevents: 2.3.3
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
+ semver@6.3.1: {}
+
+ semver@7.7.1: {}
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ signal-exit@4.1.0: {}
+
+ source-map-js@1.2.1: {}
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string-width@5.1.2:
+ dependencies:
+ eastasianwidth: 0.2.0
+ emoji-regex: 9.2.2
+ strip-ansi: 7.1.0
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-ansi@7.1.0:
+ dependencies:
+ ansi-regex: 6.1.0
+
+ strip-json-comments@3.1.1: {}
+
+ sucrase@3.35.0:
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.8
+ commander: 4.1.1
+ glob: 10.4.5
+ lines-and-columns: 1.2.4
+ mz: 2.7.0
+ pirates: 4.0.6
+ ts-interface-checker: 0.1.13
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ tabbable@6.2.0: {}
+
+ tailwind-merge@3.0.1: {}
+
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.3):
+ dependencies:
+ tailwindcss: 3.4.3
+
+ tailwindcss@3.4.3:
+ dependencies:
+ '@alloc/quick-lru': 5.2.0
+ arg: 5.0.2
+ chokidar: 3.6.0
+ didyoumean: 1.2.2
+ dlv: 1.1.3
+ fast-glob: 3.3.3
+ glob-parent: 6.0.2
+ is-glob: 4.0.3
+ jiti: 1.21.7
+ lilconfig: 2.1.0
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ object-hash: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.1
+ postcss-import: 15.1.0(postcss@8.5.1)
+ postcss-js: 4.0.1(postcss@8.5.1)
+ postcss-load-config: 4.0.2(postcss@8.5.1)
+ postcss-nested: 6.2.0(postcss@8.5.1)
+ postcss-selector-parser: 6.1.2
+ resolve: 1.22.10
+ sucrase: 3.35.0
+ transitivePeerDependencies:
+ - ts-node
+
+ tailwindcss@4.0.3: {}
+
+ tapable@2.2.1: {}
+
+ thenify-all@1.6.0:
+ dependencies:
+ thenify: 3.3.1
+
+ thenify@3.3.1:
+ dependencies:
+ any-promise: 1.3.0
+
+ tiny-invariant@1.3.3: {}
+
+ tiny-warning@1.0.3: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ ts-api-utils@2.0.1(typescript@5.6.3):
+ dependencies:
+ typescript: 5.6.3
+
+ ts-interface-checker@0.1.13: {}
+
+ tslib@2.8.1: {}
+
+ tsx@4.19.2:
+ dependencies:
+ esbuild: 0.23.1
+ get-tsconfig: 4.10.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ typescript-eslint@8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.23.0(@typescript-eslint/parser@8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3))(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ '@typescript-eslint/parser': 8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ '@typescript-eslint/utils': 8.23.0(eslint@9.19.0(jiti@2.4.2))(typescript@5.6.3)
+ eslint: 9.19.0(jiti@2.4.2)
+ typescript: 5.6.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@5.6.3: {}
+
+ undici-types@6.20.0: {}
+
+ unplugin@2.1.2:
+ dependencies:
+ acorn: 8.14.0
+ webpack-virtual-modules: 0.6.2
+
+ update-browserslist-db@1.1.2(browserslist@4.24.4):
+ dependencies:
+ browserslist: 4.24.4
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ use-callback-ref@1.3.3(@types/react@18.3.18)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ use-sidecar@1.1.3(@types/react@18.3.18)(react@18.3.1):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 18.3.1
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 18.3.18
+
+ use-sync-external-store@1.4.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
+ util-deprecate@1.0.2: {}
+
+ uuid@11.0.5: {}
+
+ vite@6.1.0(@types/node@22.13.1)(jiti@2.4.2)(lightningcss@1.29.1)(tsx@4.19.2)(yaml@2.7.0):
+ dependencies:
+ esbuild: 0.24.2
+ postcss: 8.5.1
+ rollup: 4.34.3
+ optionalDependencies:
+ '@types/node': 22.13.1
+ fsevents: 2.3.3
+ jiti: 2.4.2
+ lightningcss: 1.29.1
+ tsx: 4.19.2
+ yaml: 2.7.0
+
+ void-elements@3.1.0: {}
+
+ webpack-virtual-modules@0.6.2: {}
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@8.1.0:
+ dependencies:
+ ansi-styles: 6.2.1
+ string-width: 5.1.2
+ strip-ansi: 7.1.0
+
+ yallist@3.1.1: {}
+
+ yaml@2.7.0: {}
+
+ yocto-queue@0.1.0: {}
+
+ zod@3.24.1: {}
+
+ zustand@5.0.3(@types/react@18.3.18)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)):
+ optionalDependencies:
+ '@types/react': 18.3.18
+ react: 18.3.1
+ use-sync-external-store: 1.4.0(react@18.3.1)
diff --git a/web-citizen-reporting/postcss.config.cjs b/web-citizen-reporting/postcss.config.cjs
new file mode 100644
index 000000000..12a703d90
--- /dev/null
+++ b/web-citizen-reporting/postcss.config.cjs
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/web-citizen-reporting/public/favicon.png b/web-citizen-reporting/public/favicon.png
new file mode 100644
index 000000000..6d123b3ce
Binary files /dev/null and b/web-citizen-reporting/public/favicon.png differ
diff --git a/web-citizen-reporting/src/assets/react.svg b/web-citizen-reporting/src/assets/react.svg
new file mode 100644
index 000000000..6c87de9bb
--- /dev/null
+++ b/web-citizen-reporting/src/assets/react.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web-citizen-reporting/src/common/README.md b/web-citizen-reporting/src/common/README.md
new file mode 100644
index 000000000..560ed8c19
--- /dev/null
+++ b/web-citizen-reporting/src/common/README.md
@@ -0,0 +1,5 @@
+# src/common
+
+The common directory (also called lib) contains files common to the entire application.
+
+Currently, the directory contains a `types.ts` file with 2 exported types. One for function components and another for heroicons.
diff --git a/web-citizen-reporting/src/common/data-source-store.ts b/web-citizen-reporting/src/common/data-source-store.ts
new file mode 100644
index 000000000..8ed3d1962
--- /dev/null
+++ b/web-citizen-reporting/src/common/data-source-store.ts
@@ -0,0 +1,23 @@
+import { DataSources } from './types';
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+
+type DataSourceStore = {
+ dataSource: DataSources;
+ setDataSource: (dataSource: DataSources) => void;
+};
+
+const useDataSourceStore = create(
+ persist(
+ (set) => ({
+ dataSource: DataSources.Ngo,
+ setDataSource: (dataSource: DataSources) => {
+ set({ dataSource });
+ },
+ }),
+ { name: 'data-source' }
+ )
+);
+
+export const useDataSource = () => useDataSourceStore((state) => state.dataSource);
+export const useSetDataSource = () => useDataSourceStore((state) => state.setDataSource);
diff --git a/web-citizen-reporting/src/common/form-types.tsx b/web-citizen-reporting/src/common/form-types.tsx
new file mode 100644
index 000000000..e0b927802
--- /dev/null
+++ b/web-citizen-reporting/src/common/form-types.tsx
@@ -0,0 +1,411 @@
+import {
+ DateQuestion,
+ MultiSelectQuestion,
+ NumberQuestion,
+ QuestionType,
+ RatingQuestion,
+ RatingScaleType,
+ SingleSelectQuestion,
+ TextQuestion,
+ ZDisplayLogicCondition,
+ ZFormType,
+} from "@/common/types";
+import { isNilOrWhitespace, isNotNilOrWhitespace } from "@/lib/utils";
+import { z } from "zod";
+
+export const ZTranslatedString = z.record(z.string());
+
+const ZBaseEditQuestionType = z.object({
+ questionId: z.string(),
+ defaultLanguage: z.string().trim().min(1),
+ languageCode: z.string().trim().min(1),
+ text: ZTranslatedString,
+ helptext: ZTranslatedString,
+
+ hasDisplayLogic: z.boolean().catch(false),
+ parentQuestionId: z.string().optional(),
+ condition: ZDisplayLogicCondition.optional().catch("Equals"),
+ value: z.string().optional(),
+
+ code: z.string().trim().min(1),
+});
+
+const ZEditRatingQuestionType = ZBaseEditQuestionType.extend({
+ $questionType: z.literal(QuestionType.RatingQuestionType),
+ lowerLabel: ZTranslatedString,
+ upperLabel: ZTranslatedString,
+ scale: z.enum([
+ RatingScaleType.OneTo3,
+ RatingScaleType.OneTo4,
+ RatingScaleType.OneTo5,
+ RatingScaleType.OneTo6,
+ RatingScaleType.OneTo7,
+ RatingScaleType.OneTo8,
+ RatingScaleType.OneTo9,
+ RatingScaleType.OneTo10,
+ ]),
+});
+
+const ZEditDateQuestionType = ZBaseEditQuestionType.extend({
+ $questionType: z.literal(QuestionType.DateQuestionType),
+});
+
+const ZEditNumberQuestionType = ZBaseEditQuestionType.extend({
+ $questionType: z.literal(QuestionType.NumberQuestionType),
+ inputPlaceholder: ZTranslatedString,
+});
+
+const ZEditTextQuestionType = ZBaseEditQuestionType.extend({
+ $questionType: z.literal(QuestionType.TextQuestionType),
+ inputPlaceholder: ZTranslatedString,
+});
+
+const ZEditSelectOptionType = z.object({
+ optionId: z.string(),
+ text: ZTranslatedString,
+ isFlagged: z.boolean(),
+ isFreeText: z.boolean(),
+});
+
+const ZEditMultiSelectQuestionType = ZBaseEditQuestionType.extend({
+ $questionType: z.literal(QuestionType.MultiSelectQuestionType),
+ options: z.array(ZEditSelectOptionType).min(1),
+});
+
+const ZEditSingleSelectQuestionType = ZBaseEditQuestionType.extend({
+ $questionType: z.literal(QuestionType.SingleSelectQuestionType),
+ options: z.array(ZEditSelectOptionType).min(1),
+});
+
+export const ZEditQuestionType = z.discriminatedUnion("$questionType", [
+ ZEditTextQuestionType,
+ ZEditNumberQuestionType,
+ ZEditDateQuestionType,
+ ZEditRatingQuestionType,
+ ZEditSingleSelectQuestionType,
+ ZEditMultiSelectQuestionType,
+]);
+
+export type EditSelectOptionType = z.infer;
+export type EditMultiSelectQuestionType = z.infer<
+ typeof ZEditMultiSelectQuestionType
+>;
+export type EditSingleSelectQuestionType = z.infer<
+ typeof ZEditSingleSelectQuestionType
+>;
+export type EditTextQuestionType = z.infer;
+export type EditNumberQuestionType = z.infer;
+export type EditRatingQuestionType = z.infer;
+export type EditDateQuestionType = z.infer;
+
+export type EditQuestionType = z.infer;
+
+export const mapToQuestionRequest = (
+ q: EditQuestionType
+):
+ | NumberQuestion
+ | TextQuestion
+ | RatingQuestion
+ | DateQuestion
+ | SingleSelectQuestion
+ | MultiSelectQuestion => {
+ if (q.$questionType === QuestionType.NumberQuestionType) {
+ const numberQuestion: NumberQuestion = {
+ $questionType: QuestionType.NumberQuestionType,
+ code: q.code,
+ id: q.questionId,
+ text: q.text,
+ helptext: q.helptext,
+ inputPlaceholder: q.inputPlaceholder,
+ displayLogic: q.hasDisplayLogic
+ ? {
+ condition: q.condition!,
+ parentQuestionId: q.parentQuestionId!,
+ value: q.value!,
+ }
+ : undefined,
+ };
+
+ return numberQuestion;
+ }
+
+ if (q.$questionType === QuestionType.TextQuestionType) {
+ const textQuestion: TextQuestion = {
+ $questionType: QuestionType.TextQuestionType,
+ code: q.code,
+ id: q.questionId,
+ text: q.text,
+ helptext: q.helptext,
+ inputPlaceholder: q.inputPlaceholder,
+ displayLogic: q.hasDisplayLogic
+ ? {
+ condition: q.condition!,
+ parentQuestionId: q.parentQuestionId!,
+ value: q.value!,
+ }
+ : undefined,
+ };
+
+ return textQuestion;
+ }
+
+ if (q.$questionType === QuestionType.RatingQuestionType) {
+ const ratingQuestion: RatingQuestion = {
+ $questionType: QuestionType.RatingQuestionType,
+ code: q.code,
+ id: q.questionId,
+ text: q.text,
+ helptext: q.helptext,
+ scale: q.scale,
+ lowerLabel: q.lowerLabel,
+ upperLabel: q.upperLabel,
+ displayLogic: q.hasDisplayLogic
+ ? {
+ condition: q.condition!,
+ parentQuestionId: q.parentQuestionId!,
+ value: q.value!,
+ }
+ : undefined,
+ };
+
+ return ratingQuestion;
+ }
+
+ if (q.$questionType === QuestionType.DateQuestionType) {
+ const dateQuestion: DateQuestion = {
+ $questionType: QuestionType.DateQuestionType,
+ code: q.code,
+ id: q.questionId,
+ text: q.text,
+ helptext: q.helptext,
+ displayLogic: q.hasDisplayLogic
+ ? {
+ condition: q.condition!,
+ parentQuestionId: q.parentQuestionId!,
+ value: q.value!,
+ }
+ : undefined,
+ };
+
+ return dateQuestion;
+ }
+
+ if (q.$questionType === QuestionType.SingleSelectQuestionType) {
+ const singleSelectQuestion: SingleSelectQuestion = {
+ $questionType: QuestionType.SingleSelectQuestionType,
+ code: q.code,
+ id: q.questionId,
+ text: q.text,
+ helptext: q.helptext,
+ options: q.options.map((o) => ({
+ id: o.optionId,
+ isFlagged: o.isFlagged,
+ isFreeText: o.isFreeText,
+ text: o.text,
+ })),
+ displayLogic: q.hasDisplayLogic
+ ? {
+ condition: q.condition!,
+ parentQuestionId: q.parentQuestionId!,
+ value: q.value!,
+ }
+ : undefined,
+ };
+
+ return singleSelectQuestion;
+ }
+
+ if (q.$questionType === QuestionType.MultiSelectQuestionType) {
+ const multiSelectQuestion: MultiSelectQuestion = {
+ $questionType: QuestionType.MultiSelectQuestionType,
+ code: q.code,
+ id: q.questionId,
+ text: q.text,
+ helptext: q.helptext,
+ options: q.options.map((o) => ({
+ id: o.optionId,
+ isFlagged: o.isFlagged,
+ isFreeText: o.isFreeText,
+ text: o.text,
+ })),
+ displayLogic: q.hasDisplayLogic
+ ? {
+ condition: q.condition!,
+ parentQuestionId: q.parentQuestionId!,
+ value: q.value!,
+ }
+ : undefined,
+ };
+
+ return multiSelectQuestion;
+ }
+
+ throw new Error("unknown question type");
+};
+
+export const ZEditFormType = z
+ .object({
+ formId: z.string().trim().min(1),
+ languageCode: z.string().trim().min(1),
+ defaultLanguage: z.string().trim().min(1),
+ code: z.string().trim().min(1),
+ name: ZTranslatedString,
+ description: ZTranslatedString.optional(),
+ icon: z.string().optional(),
+ languages: z.array(z.string()),
+ formType: ZFormType.catch(ZFormType.Values.Opening),
+ questions: z.array(ZEditQuestionType),
+ })
+ .superRefine((data, ctx) => {
+ if (isNilOrWhitespace(data.name[data.languageCode])) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Form name is required",
+ path: ["name"],
+ });
+ }
+
+ if (
+ isNotNilOrWhitespace(data.description?.[data.defaultLanguage]) &&
+ isNilOrWhitespace(data.description?.[data.languageCode])
+ ) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Form description is required",
+ path: ["description"],
+ });
+ }
+
+ data.questions.forEach((question, index) => {
+ if (isNilOrWhitespace(question.text[question.languageCode])) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Question text is required",
+ path: ["questions", index, "text"],
+ });
+ }
+
+ if (
+ isNotNilOrWhitespace(question.helptext[question.defaultLanguage]) &&
+ isNilOrWhitespace(question.helptext[question.languageCode])
+ ) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Question helptext is required",
+ path: ["questions", index, "helptext"],
+ });
+ }
+
+ if (
+ question.$questionType === QuestionType.NumberQuestionType ||
+ question.$questionType === QuestionType.TextQuestionType
+ ) {
+ if (
+ isNotNilOrWhitespace(
+ question.inputPlaceholder[question.defaultLanguage]
+ ) &&
+ isNilOrWhitespace(question.inputPlaceholder[question.languageCode])
+ ) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Input placeholder is required",
+ path: ["questions", index, "inputPlaceholder"],
+ });
+ }
+ }
+
+ if (question.$questionType === QuestionType.RatingQuestionType) {
+ if (
+ isNotNilOrWhitespace(question.lowerLabel[question.defaultLanguage]) &&
+ isNilOrWhitespace(question.lowerLabel[question.languageCode])
+ ) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Question lower label is required",
+ path: ["questions", index, "lowerLabel"],
+ });
+ }
+
+ if (
+ isNotNilOrWhitespace(question.upperLabel[question.defaultLanguage]) &&
+ isNilOrWhitespace(question.upperLabel[question.languageCode])
+ ) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Question upper label is required",
+ path: ["questions", index, "upperLabel"],
+ });
+ }
+ }
+
+ if (
+ question.$questionType === QuestionType.SingleSelectQuestionType ||
+ question.$questionType === QuestionType.MultiSelectQuestionType
+ ) {
+ question.options.forEach((option, optionIndex) => {
+ if (isNilOrWhitespace(option.text[question.languageCode])) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Option text is required",
+ path: ["questions", index, "options", optionIndex, "text"],
+ });
+ }
+ });
+
+ // check uniqueness of options
+ const optionTexts = question.options.map(
+ (o) => o.text[question.languageCode]
+ );
+ const textCountMap = new Map();
+ const duplicatedIndexesMap = new Map();
+
+ // Step 1: Count occurrences of each option
+ optionTexts.forEach((text, optionIndex) => {
+ const numberOfOccurrences = (textCountMap.get(text) || 0) + 1;
+ if (numberOfOccurrences > 1) {
+ duplicatedIndexesMap.set(text, optionIndex);
+ }
+ textCountMap.set(text, numberOfOccurrences);
+ });
+
+ // Step 2: Mark duplicated options as invalid
+ for (const [_, optionIndex] of duplicatedIndexesMap.entries()) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Option text is not unique",
+ path: ["questions", index, "options", optionIndex, "text"],
+ });
+ }
+ }
+
+ if (question.hasDisplayLogic) {
+ if (question.condition === undefined) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Question condition is required",
+ path: ["questions", index, "condition"],
+ });
+ }
+
+ if (question.parentQuestionId === undefined) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Question parent question is required",
+ path: ["questions", index, "parentQuestionId"],
+ });
+ }
+
+ if (question.value === undefined) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Question value is required",
+ path: ["questions", index, "value"],
+ });
+ }
+ }
+
+ return z.NEVER;
+ });
+ });
+
+export type EditFormType = z.infer;
diff --git a/web-citizen-reporting/src/common/formats.ts b/web-citizen-reporting/src/common/formats.ts
new file mode 100644
index 000000000..597bfe430
--- /dev/null
+++ b/web-citizen-reporting/src/common/formats.ts
@@ -0,0 +1,2 @@
+export const DateTimeFormat = 'u-MM-dd kk:mm';
+export const DateTimeHourBucketFormat = 'u-MM-dd kk:00';
\ No newline at end of file
diff --git a/web-citizen-reporting/src/common/guards.ts b/web-citizen-reporting/src/common/guards.ts
new file mode 100644
index 000000000..2a4f5ff25
--- /dev/null
+++ b/web-citizen-reporting/src/common/guards.ts
@@ -0,0 +1,71 @@
+import {
+ DateAnswerSchema,
+ MultiSelectAnswerSchema,
+ NumberAnswerSchema,
+ QuestionType,
+ RatingAnswerSchema,
+ SingleSelectAnswerSchema,
+ TextAnswerSchema,
+ type BaseAnswer,
+ type BaseQuestion,
+ type DateAnswer,
+ type MultiSelectAnswer,
+ type MultiSelectQuestion,
+ type NumberAnswer,
+ type RatingAnswer,
+ type RatingQuestion,
+ type SingleSelectAnswer,
+ type SingleSelectQuestion,
+ type TextAnswer,
+ type TextQuestion,
+ type NumberQuestion,
+ type DateQuestion,
+} from './types';
+
+export function isDateAnswer(answer: BaseAnswer): answer is DateAnswer {
+ return DateAnswerSchema.safeParse(answer).success;
+}
+
+export function isDateQuestion(question: BaseQuestion): question is DateQuestion {
+ return question.$questionType === QuestionType.DateQuestionType;
+}
+
+export function isMultiSelectAnswer(answer: BaseAnswer): answer is MultiSelectAnswer {
+ return MultiSelectAnswerSchema.safeParse(answer).success;
+}
+
+export function isMultiSelectQuestion(question: BaseQuestion): question is MultiSelectQuestion {
+ return question.$questionType === QuestionType.MultiSelectQuestionType;
+}
+
+export function isNumberAnswer(answer: BaseAnswer): answer is NumberAnswer {
+ return NumberAnswerSchema.safeParse(answer).success;
+}
+
+export function isNumberQuestion(question: BaseQuestion): question is NumberQuestion {
+ return question.$questionType === QuestionType.NumberQuestionType;
+}
+
+export function isRatingAnswer(answer: BaseAnswer): answer is RatingAnswer {
+ return RatingAnswerSchema.safeParse(answer).success;
+}
+
+export function isRatingQuestion(question: BaseQuestion): question is RatingQuestion {
+ return question.$questionType === QuestionType.RatingQuestionType;
+}
+
+export function isSingleSelectAnswer(answer: BaseAnswer): answer is SingleSelectAnswer {
+ return SingleSelectAnswerSchema.safeParse(answer).success;
+}
+
+export function isSingleSelectQuestion(question: BaseQuestion): question is SingleSelectQuestion {
+ return question.$questionType === QuestionType.SingleSelectQuestionType;
+}
+
+export function isTextAnswer(answer: BaseAnswer): answer is TextAnswer {
+ return TextAnswerSchema.safeParse(answer).success;
+}
+
+export function isTextQuestion(question: BaseQuestion): question is TextQuestion {
+ return question.$questionType === QuestionType.TextQuestionType;
+}
diff --git a/web-citizen-reporting/src/common/no-auth-api.ts b/web-citizen-reporting/src/common/no-auth-api.ts
new file mode 100644
index 000000000..163d757ca
--- /dev/null
+++ b/web-citizen-reporting/src/common/no-auth-api.ts
@@ -0,0 +1,11 @@
+/* eslint-disable @typescript-eslint/no-unsafe-member-access */
+/* eslint-disable @typescript-eslint/no-unsafe-assignment */
+
+import axios from "axios";
+
+export const noAuthApi = axios.create({
+ baseURL: import.meta.env["VITE_API_URL"],
+ // withCredentials: true, // TODO Enable this when using a real login and authentication system
+});
+
+noAuthApi.defaults.headers.common["Content-Type"] = "application/json";
diff --git a/web-citizen-reporting/src/common/prev-search-store.ts b/web-citizen-reporting/src/common/prev-search-store.ts
new file mode 100644
index 000000000..04b103ffb
--- /dev/null
+++ b/web-citizen-reporting/src/common/prev-search-store.ts
@@ -0,0 +1,22 @@
+import { create } from 'zustand';
+import { persist } from 'zustand/middleware';
+
+type PrevSearchStore = {
+ search: Record;
+ setSearch: (prevSearch: Record) => void;
+};
+
+const usePrevSearchStore = create(
+ persist(
+ (set) => ({
+ search: {},
+ setSearch: (search) => {
+ set({ search });
+ },
+ }),
+ { name: 'prev-search' }
+ )
+);
+
+export const usePrevSearch = () => usePrevSearchStore((state) => state.search);
+export const useSetPrevSearch = () => usePrevSearchStore((state) => state.setSearch);
diff --git a/web-citizen-reporting/src/common/types.ts b/web-citizen-reporting/src/common/types.ts
new file mode 100644
index 000000000..7e8004dd6
--- /dev/null
+++ b/web-citizen-reporting/src/common/types.ts
@@ -0,0 +1,304 @@
+import { FormFull } from "@/models/form";
+import { z } from "zod";
+
+export type FunctionComponent = React.ReactElement | null;
+
+type HeroIconSVGProps = React.PropsWithoutRef> &
+ React.RefAttributes;
+
+type IconProps = HeroIconSVGProps & {
+ title?: string;
+ titleId?: string;
+};
+export type Heroicon = React.FC;
+
+export type PageParameters = {
+ pageNumber: number; // 1-based (the first page is 1)
+ pageSize: number;
+};
+
+export enum SortOrder {
+ asc = "Asc",
+ desc = "Desc",
+}
+
+export type SortParameters = {
+ sortColumnName: string;
+ sortOrder: SortOrder;
+ searchText?: string;
+};
+
+export type CitizenReportPageResponse = {
+ electionRoundId: string;
+ version: string;
+ forms: FormFull[];
+};
+
+export type PageResponse = {
+ currentPage: number;
+ pageSize: number;
+ totalCount: number;
+ items: T[];
+ isEmpty?: boolean;
+};
+
+export type DataTableParameters = PageParameters &
+ SortParameters & { otherParams?: TQueryParams };
+
+export const ZTranslatedString = z.record(z.string());
+export type TranslatedString = z.infer;
+
+export enum QuestionType {
+ TextQuestionType = "textQuestion",
+ NumberQuestionType = "numberQuestion",
+ DateQuestionType = "dateQuestion",
+ SingleSelectQuestionType = "singleSelectQuestion",
+ MultiSelectQuestionType = "multiSelectQuestion",
+ RatingQuestionType = "ratingQuestion",
+}
+
+export const ZDisplayLogicCondition = z.enum([
+ "Equals",
+ "NotEquals",
+ "LessThan",
+ "LessEqual",
+ "GreaterThan",
+ "GreaterEqual",
+ "Includes",
+]);
+
+export type DisplayLogicCondition = z.infer;
+
+export interface DisplayLogic {
+ parentQuestionId: string;
+ condition: DisplayLogicCondition;
+ value: string;
+}
+
+export interface BaseQuestion {
+ id: string;
+ $questionType: QuestionType;
+ code: string;
+ text: TranslatedString;
+ helptext?: TranslatedString;
+ displayLogic?: DisplayLogic;
+}
+
+export interface DateQuestion extends BaseQuestion {
+ $questionType: QuestionType.DateQuestionType;
+}
+
+export interface TextQuestion extends BaseQuestion {
+ $questionType: QuestionType.TextQuestionType;
+ inputPlaceholder?: TranslatedString;
+}
+export interface NumberQuestion extends BaseQuestion {
+ $questionType: QuestionType.NumberQuestionType;
+ inputPlaceholder?: TranslatedString;
+}
+
+export enum RatingScaleType {
+ OneTo3 = "OneTo3",
+ OneTo4 = "OneTo4",
+ OneTo5 = "OneTo5",
+ OneTo6 = "OneTo6",
+ OneTo7 = "OneTo7",
+ OneTo8 = "OneTo8",
+ OneTo9 = "OneTo9",
+ OneTo10 = "OneTo10",
+}
+
+export interface RatingQuestion extends BaseQuestion {
+ upperLabel?: TranslatedString;
+ lowerLabel?: TranslatedString;
+ $questionType: QuestionType.RatingQuestionType;
+ scale: RatingScaleType;
+}
+
+export interface SelectOption {
+ id: string;
+ text: TranslatedString;
+ isFlagged: boolean;
+ isFreeText: boolean;
+}
+
+export interface SingleSelectQuestion extends BaseQuestion {
+ $questionType: QuestionType.SingleSelectQuestionType;
+ options: SelectOption[];
+}
+export interface MultiSelectQuestion extends BaseQuestion {
+ $questionType: QuestionType.MultiSelectQuestionType;
+ options: SelectOption[];
+}
+
+export enum AnswerType {
+ TextAnswerType = "textAnswer",
+ NumberAnswerType = "numberAnswer",
+ DateAnswerType = "dateAnswer",
+ SingleSelectAnswerType = "singleSelectAnswer",
+ MultiSelectAnswerType = "multiSelectAnswer",
+ RatingAnswerType = "ratingAnswer",
+}
+
+export const BaseAnswerSchema = z.object({
+ $answerType: z.string(),
+ questionId: z.string(),
+});
+export type BaseAnswer = z.infer;
+
+export const TextAnswerSchema = BaseAnswerSchema.extend({
+ $answerType: z.literal(AnswerType.TextAnswerType),
+ text: z.string().optional(),
+});
+export type TextAnswer = z.infer;
+
+export const NumberAnswerSchema = BaseAnswerSchema.extend({
+ $answerType: z.literal(AnswerType.NumberAnswerType),
+ value: z.coerce.number().optional(),
+});
+export type NumberAnswer = z.infer;
+
+export const DateAnswerSchema = BaseAnswerSchema.extend({
+ $answerType: z.literal(AnswerType.DateAnswerType),
+ date: z.string().datetime({ offset: true }).optional(),
+});
+export type DateAnswer = z.infer;
+
+export const RatingAnswerSchema = BaseAnswerSchema.extend({
+ $answerType: z.literal(AnswerType.RatingAnswerType),
+ value: z.coerce.number().optional(),
+});
+export type RatingAnswer = z.infer;
+
+export const SelectedOptionSchema = z.object({
+ optionId: z.string().optional(),
+ text: z.string().optional().nullable(),
+});
+export type SelectedOption = z.infer;
+
+export const SingleSelectAnswerSchema = BaseAnswerSchema.extend({
+ $answerType: z.literal(AnswerType.SingleSelectAnswerType),
+ selection: SelectedOptionSchema.optional(),
+});
+export type SingleSelectAnswer = z.infer;
+
+export const MultiSelectAnswerSchema = BaseAnswerSchema.extend({
+ $answerType: z.literal(AnswerType.MultiSelectAnswerType),
+ selection: z.array(SelectedOptionSchema).optional(),
+});
+export type MultiSelectAnswer = z.infer;
+
+export enum ElectionRoundStatus {
+ NotStarted = "NotStarted",
+ Started = "Started",
+ Archived = "Archived",
+}
+
+export type LevelNode = {
+ id: number;
+ name: string;
+ depth: number;
+ parentId: number;
+ locationId?: string;
+};
+
+export type UserPayload = {
+ "user-role": string;
+};
+
+export enum FormSubmissionFollowUpStatus {
+ NotApplicable = "NotApplicable",
+ NeedsFollowUp = "NeedsFollowUp",
+ Resolved = "Resolved",
+}
+
+export enum QuickReportFollowUpStatus {
+ NotApplicable = "NotApplicable",
+ NeedsFollowUp = "NeedsFollowUp",
+ Resolved = "Resolved",
+}
+
+export enum IncidentReportFollowUpStatus {
+ NotApplicable = "NotApplicable",
+ NeedsFollowUp = "NeedsFollowUp",
+ Resolved = "Resolved",
+}
+
+export enum CitizenReportFollowUpStatus {
+ NotApplicable = "NotApplicable",
+ NeedsFollowUp = "NeedsFollowUp",
+ Resolved = "Resolved",
+}
+
+export enum QuestionsAnswered {
+ None = "None",
+ Some = "Some",
+ All = "All",
+}
+export type HistogramData = {
+ [bucket: string]: number;
+};
+
+export const ZFormType = z.enum([
+ "PSI",
+ "Opening",
+ "Voting",
+ "ClosingAndCounting",
+ "CitizenReporting",
+ "IncidentReporting",
+ "Other",
+]);
+
+export type FormType = z.infer;
+
+export const ZTranslationStatus = z.enum(["Translated", "MissingTranslations"]);
+export type TranslationStatus = z.infer;
+
+const ZLanguagesTranslationStatus = z.record(z.string(), ZTranslationStatus);
+export type LanguagesTranslationStatus = z.infer<
+ typeof ZLanguagesTranslationStatus
+>;
+
+export interface Country {
+ id: string;
+ iso2: string;
+ iso3: string;
+ numericCode: string;
+ name: string;
+ fullName: string;
+}
+
+export interface Language {
+ id: string;
+ code: string;
+ name: string;
+ nativeName: string;
+}
+
+export interface CoalitionMember {
+ id: string;
+ name: string;
+}
+export interface Coalition {
+ id: string;
+ isInCoalition: boolean;
+ name: string;
+ leaderId: string;
+ leaderName: string;
+ numberOfMembers: number;
+ members: CoalitionMember[];
+}
+
+export enum DataSources {
+ Ngo = "ngo",
+ Coalition = "coalition",
+}
+
+export interface EditQuestionProps {
+ questionId: string;
+ text?: string;
+ helptext?: string;
+ inputPlaceholder?: string;
+ code?: string;
+ control: any;
+}
diff --git a/web-citizen-reporting/src/components/FormCard.tsx b/web-citizen-reporting/src/components/FormCard.tsx
new file mode 100644
index 000000000..3a874e404
--- /dev/null
+++ b/web-citizen-reporting/src/components/FormCard.tsx
@@ -0,0 +1,38 @@
+import { FormFull } from "@/models/form";
+import { Link } from "@tanstack/react-router";
+import { FC } from "react";
+import { Button } from "./ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "./ui/card";
+
+interface FornmCardProps {
+ formData: FormFull | undefined;
+ languageCode: string;
+}
+
+export const FormCard: FC = ({ formData, languageCode }) => {
+ return (
+
+
+
+ {formData?.name[languageCode ?? formData.defaultLanguage]}
+
+ {formData?.description && (
+
+ {formData?.description[languageCode ?? formData.defaultLanguage]}
+
+ )}
+
+
+
+
+
+
+
+ );
+};
diff --git a/web-citizen-reporting/src/components/FormQuestions.tsx b/web-citizen-reporting/src/components/FormQuestions.tsx
new file mode 100644
index 000000000..c8eb3f94c
--- /dev/null
+++ b/web-citizen-reporting/src/components/FormQuestions.tsx
@@ -0,0 +1,118 @@
+import { BaseQuestion } from "@/common/types";
+import { FC } from "react";
+
+import {
+ isDateQuestion,
+ isMultiSelectQuestion,
+ isNumberQuestion,
+ isRatingQuestion,
+ isSingleSelectQuestion,
+ isTextQuestion,
+} from "@/common/guards";
+import PreviewDateQuestion from "@/components/questionsEditor/preview/PreviewDateQuestion";
+import PreviewMultiSelectQuestion from "@/components/questionsEditor/preview/PreviewMultiSelectQuestion";
+import PreviewNumberQuestion from "@/components/questionsEditor/preview/PreviewNumberQuestion";
+import PreviewRatingQuestion from "@/components/questionsEditor/preview/PreviewRatingQuestion";
+import PreviewSingleSelectQuestion from "@/components/questionsEditor/preview/PreviewSingleSelectQuestion";
+import PreviewTextQuestion from "@/components/questionsEditor/preview/PreviewTextQuestion";
+
+interface FormQuestionsProps {
+ questions: BaseQuestion[] | undefined;
+ languageCode: string;
+ noContentMessage: string;
+}
+
+export const FormQuestions: FC = ({
+ questions,
+ languageCode,
+ noContentMessage,
+}) => {
+ if (questions?.length === 0)
+ return (
+
+ );
+ return (
+
+
{`${questions?.length} questions`}
+
+ {questions?.map((question) => (
+ <>
+ {isTextQuestion(question) && (
+
+ )}
+
+ {isNumberQuestion(question) && (
+
+ )}
+
+ {isDateQuestion(question) && (
+
+ )}
+
+ {isRatingQuestion(question) && (
+
+ )}
+
+ {isMultiSelectQuestion(question) && (
+
({
+ optionId: o.id,
+ isFreeText: o.isFreeText,
+ text: o.text[languageCode],
+ })) ?? []
+ }
+ code={question.code}
+ />
+ )}
+
+ {isSingleSelectQuestion(question) && (
+ ({
+ optionId: o.id,
+ isFreeText: o.isFreeText,
+ text: o.text[languageCode],
+ })) ?? []
+ }
+ code={question.code}
+ />
+ )}
+ >
+ ))}
+
+ );
+};
diff --git a/web-citizen-reporting/src/components/LocationSelector.tsx b/web-citizen-reporting/src/components/LocationSelector.tsx
new file mode 100644
index 000000000..acff93907
--- /dev/null
+++ b/web-citizen-reporting/src/components/LocationSelector.tsx
@@ -0,0 +1,175 @@
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { HandleReducerSearchParams, useLocationsLevels } from "@/hooks";
+import { LocationState } from "@/location-reducer";
+import { FC, useMemo } from "react";
+
+interface LocationSelectorProps {
+ search: LocationState;
+ handleReducerSearch: (params: HandleReducerSearchParams) => void;
+}
+
+export const LocationSelector: FC = ({
+ search,
+ handleReducerSearch,
+}) => {
+ const currentElectionRoundId = import.meta.env["VITE_ELECTION_ROUND_ID"];
+ const { data } = useLocationsLevels(currentElectionRoundId);
+
+ const selectedLevel1Node = useMemo(
+ () => data?.[1]?.find((node) => node.id === search.level1Filter),
+ [data, search.level1Filter]
+ );
+
+ const selectedLevel2Node = useMemo(
+ () => data?.[2]?.find((node) => node.id === search.level2Filter),
+ [data, search.level2Filter]
+ );
+
+ const selectedLevel3Node = useMemo(
+ () => data?.[3]?.find((node) => node.id === search.level3Filter),
+ [data, search.level3Filter]
+ );
+
+ const selectedLevel4Node = useMemo(
+ () => data?.[4]?.find((node) => node.id === search.level4Filter),
+ [data, search.level4Filter]
+ );
+
+ const filteredLevel2Nodes = useMemo(
+ () => data?.[2]?.filter((node) => node.parentId === selectedLevel1Node?.id),
+ [data, selectedLevel1Node?.id]
+ );
+
+ const filteredLevel3Nodes = useMemo(
+ () => data?.[3]?.filter((node) => node.parentId === selectedLevel2Node?.id),
+ [data, selectedLevel2Node?.id]
+ );
+
+ const filteredLevel4Nodes = useMemo(
+ () => data?.[4]?.filter((node) => node.parentId === selectedLevel3Node?.id),
+ [data, selectedLevel3Node?.id]
+ );
+
+ const filteredLevel5Nodes = useMemo(
+ () => data?.[5]?.filter((node) => node.parentId === selectedLevel4Node?.id),
+ [data, selectedLevel4Node?.id]
+ );
+
+ return (
+
+
Location:
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/web-citizen-reporting/src/components/questionsEditor/FormQuestions.tsx b/web-citizen-reporting/src/components/questionsEditor/FormQuestions.tsx
new file mode 100644
index 000000000..afa43d9b8
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/FormQuestions.tsx
@@ -0,0 +1,116 @@
+import { BaseQuestion } from '@/common/types';
+import { FC } from 'react';
+
+import {
+ isDateQuestion,
+ isMultiSelectQuestion,
+ isNumberQuestion,
+ isRatingQuestion,
+ isSingleSelectQuestion,
+ isTextQuestion,
+} from '@/common/guards';
+import PreviewDateQuestion from '@/components/questionsEditor/preview/PreviewDateQuestion';
+import PreviewMultiSelectQuestion from '@/components/questionsEditor/preview/PreviewMultiSelectQuestion';
+import PreviewNumberQuestion from '@/components/questionsEditor/preview/PreviewNumberQuestion';
+import PreviewRatingQuestion from '@/components/questionsEditor/preview/PreviewRatingQuestion';
+import PreviewSingleSelectQuestion from '@/components/questionsEditor/preview/PreviewSingleSelectQuestion';
+import PreviewTextQuestion from '@/components/questionsEditor/preview/PreviewTextQuestion';
+
+interface FormQuestionsProps {
+ questions: BaseQuestion[] | undefined;
+ languageCode: string;
+ title: string;
+ noContentMessage: string;
+}
+
+export const FormQuestions: FC = ({ questions, languageCode, title, noContentMessage }) => {
+ if (questions?.length === 0)
+ return (
+
+
{title}
+
{noContentMessage}
+
+ );
+ return (
+
+
{`${title}: ${questions?.length}`}
+
+ {questions?.map((question) => (
+ <>
+ {isTextQuestion(question) && (
+
+ )}
+
+ {isNumberQuestion(question) && (
+
+ )}
+
+ {isDateQuestion(question) && (
+
+ )}
+
+ {isRatingQuestion(question) && (
+
+ )}
+
+ {isMultiSelectQuestion(question) && (
+
({
+ optionId: o.id,
+ isFreeText: o.isFreeText,
+ text: o.text[languageCode],
+ })) ?? []
+ }
+ code={question.code}
+ />
+ )}
+
+ {isSingleSelectQuestion(question) && (
+ ({
+ optionId: o.id,
+ isFreeText: o.isFreeText,
+ text: o.text[languageCode],
+ })) ?? []
+ }
+ code={question.code}
+ />
+ )}
+ >
+ ))}
+
+ );
+};
diff --git a/web-citizen-reporting/src/components/questionsEditor/answers-store.tsx b/web-citizen-reporting/src/components/questionsEditor/answers-store.tsx
new file mode 100644
index 000000000..b8da26044
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/answers-store.tsx
@@ -0,0 +1,32 @@
+import { BaseAnswer } from "@/common/types";
+import { create } from "zustand";
+
+export type FormEditorAnswersContextValue = {
+ answers: {
+ [questionId: string]: BaseAnswer | undefined;
+ };
+ setAnswer: (answer: BaseAnswer | undefined) => void;
+ getAnswer: (questionId: string) => BaseAnswer | undefined;
+ resetForm: () => void
+};
+
+export const useFormAnswersStore = create(
+ (set, get) => ({
+ answers: {},
+ setAnswer: (answer: BaseAnswer | undefined) =>
+ set((state) => ({
+ ...state,
+ answers: {
+ ...state.answers,
+ [answer?.questionId!]: answer,
+ },
+ })),
+ getAnswer: (questionId: string) => get().answers[questionId],
+
+ resetForm: () =>
+ set((state) => ({
+ ...state,
+ answers: {},
+ })),
+ })
+);
diff --git a/web-citizen-reporting/src/components/questionsEditor/preview/PreviewDateQuestion.tsx b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewDateQuestion.tsx
new file mode 100644
index 000000000..a9a136eda
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewDateQuestion.tsx
@@ -0,0 +1,56 @@
+import { AnswerType, DateAnswer } from '@/common/types';
+import { Label } from '@/components/ui/label';
+import { cn } from '@/lib/utils';
+import { format, formatISO } from 'date-fns';
+import { CalendarIcon } from 'lucide-react';
+import { Button } from '../../ui/button';
+import { Calendar } from '../../ui/calendar';
+import { Popover, PopoverContent, PopoverTrigger } from '../../ui/popover';
+import { useFormAnswersStore } from '../answers-store';
+
+export interface PreviewDateQuestionProps {
+ questionId: string;
+ text?: string;
+ helptext?: string;
+ code: string;
+}
+
+function PreviewDateQuestion({ code, questionId, text, helptext }: PreviewDateQuestionProps) {
+ const { setAnswer, getAnswer } = useFormAnswersStore();
+ const answer = getAnswer(questionId) as DateAnswer;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {
+ const dateAnswer: DateAnswer = { $answerType: AnswerType.DateAnswerType, date: formatISO(date!, { representation: 'complete' }), questionId };
+ setAnswer(dateAnswer);
+ }}
+ autoFocus
+ />
+
+
+
+
+
+ );
+}
+
+export default PreviewDateQuestion;
diff --git a/web-citizen-reporting/src/components/questionsEditor/preview/PreviewMultiSelectQuestion.tsx b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewMultiSelectQuestion.tsx
new file mode 100644
index 000000000..551470e5a
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewMultiSelectQuestion.tsx
@@ -0,0 +1,125 @@
+import { useEffect, useMemo, useState } from 'react';
+
+import { isMultiSelectAnswer } from '@/common/guards';
+import { AnswerType, MultiSelectAnswer, SelectedOption } from '@/common/types';
+import { Checkbox, CheckboxField, CheckboxGroup } from '@/components/ui/checkbox';
+import { Label } from '@/components/ui/label';
+import { Textarea } from '@/components/ui/textarea';
+import { useFormAnswersStore } from '../answers-store';
+
+export interface PreviewMultiSelectQuestionProps {
+ questionId: string;
+ text?: string;
+ helptext?: string;
+ options: { optionId: string, text?: string, isFreeText: boolean }[];
+ code: string;
+}
+
+function PreviewMultiSelectQuestion({ code, questionId, text, helptext, options }: PreviewMultiSelectQuestionProps) {
+ const { setAnswer, getAnswer } = useFormAnswersStore();
+ const [localAnswer, setLocalAnswer] = useState(undefined)
+
+ const regularOptions = useMemo(() => {
+ if (!options) {
+ return [];
+ }
+ const regularOptions = options.filter((option) => !option.isFreeText);
+ return regularOptions;
+ }, [options]);
+
+ // Currently we only support one free text option
+ const freeTextOption = useMemo(() => options.find((option) => option.isFreeText), [options]);
+
+ useEffect(() => {
+ const multiSelectAnswer = getAnswer(questionId);
+ if (multiSelectAnswer && isMultiSelectAnswer(multiSelectAnswer)) {
+ setLocalAnswer(multiSelectAnswer);
+ } else {
+ const multiSelectAnswer: MultiSelectAnswer = { $answerType: AnswerType.MultiSelectAnswerType, questionId, selection: [] };
+ setAnswer(multiSelectAnswer);
+ setLocalAnswer(multiSelectAnswer);
+ }
+ }, [questionId]);
+
+
+ const handleOptionSelected = (isChecked: boolean, optionId: string) => {
+ let newAnswer: MultiSelectAnswer;
+ if (isChecked) {
+ newAnswer = {
+ questionId: questionId,
+ $answerType: AnswerType.MultiSelectAnswerType,
+ selection: [
+ ...localAnswer?.selection ?? [],
+ { optionId: optionId }
+ ]
+ }
+
+
+ } else {
+ newAnswer = {
+ questionId: questionId,
+ $answerType: AnswerType.MultiSelectAnswerType,
+ selection: [
+ ...localAnswer?.selection?.filter(o => o.optionId !== optionId) ?? []
+ ]
+ }
+ }
+
+ setAnswer(newAnswer);
+ setLocalAnswer(newAnswer);
+ };
+
+ return (
+
+
+
+
+ {regularOptions.map((option) => (
+
+ o.optionId === option.optionId)}
+ onChange={(checked) => handleOptionSelected(checked, option.optionId)} />
+
+
+ ))}
+
+ {!!freeTextOption && (
+
+ o.optionId === freeTextOption.optionId)}
+ onChange={(checked) => {
+ handleOptionSelected(checked, freeTextOption.optionId);
+ }}
+ />
+
+
+ )}
+
+ {localAnswer?.selection?.some(o => o.optionId === freeTextOption?.optionId) && (
+
+ );
+}
+
+export default PreviewMultiSelectQuestion;
diff --git a/web-citizen-reporting/src/components/questionsEditor/preview/PreviewNumberQuestion.tsx b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewNumberQuestion.tsx
new file mode 100644
index 000000000..edc4db3c4
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewNumberQuestion.tsx
@@ -0,0 +1,42 @@
+import { AnswerType, NumberAnswer } from '@/common/types';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { useEffect, useState } from 'react';
+import { useFormAnswersStore } from '../answers-store';
+
+export interface PreviewNumberQuestionProps {
+ questionId: string;
+ text?: string;
+ helptext?: string;
+ inputPlaceholder?: string;
+ code?: string;
+}
+
+function PreviewNumberQuestion({ code, questionId, text, helptext, inputPlaceholder }: PreviewNumberQuestionProps) {
+ const { setAnswer, getAnswer } = useFormAnswersStore();
+ const [localAnswer, setLocalAnswer] = useState(undefined)
+ useEffect(() => { setLocalAnswer(getAnswer(questionId) as NumberAnswer) }, [questionId]);
+
+ return (
+
+
+
+
+ {
+ const numberAnswer: NumberAnswer = { $answerType: AnswerType.NumberAnswerType, questionId, value: Number(e.target.value) };
+ setAnswer(numberAnswer);
+ }}
+ defaultValue="0"
+ min="0"
+ />
+
+
+ );
+}
+
+export default PreviewNumberQuestion;
diff --git a/web-citizen-reporting/src/components/questionsEditor/preview/PreviewQuestion.tsx b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewQuestion.tsx
new file mode 100644
index 000000000..b80fe0d5f
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewQuestion.tsx
@@ -0,0 +1,277 @@
+import {
+ BaseAnswer,
+ DisplayLogicCondition,
+ QuestionType,
+} from "@/common/types";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader } from "@/components/ui/card";
+import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
+import { useFormAnswersStore } from "../answers-store";
+import PreviewDateQuestion from "./PreviewDateQuestion";
+import PreviewMultiSelectQuestion from "./PreviewMultiSelectQuestion";
+import PreviewNumberQuestion from "./PreviewNumberQuestion";
+import PreviewRatingQuestion from "./PreviewRatingQuestion";
+import PreviewSingleSelectQuestion from "./PreviewSingleSelectQuestion";
+import PreviewTextQuestion from "./PreviewTextQuestion";
+
+import { EditFormType } from "@/common/form-types";
+import {
+ isMultiSelectAnswer,
+ isNumberAnswer,
+ isRatingAnswer,
+ isSingleSelectAnswer,
+} from "@/common/guards";
+import { Progress } from "@/components/ui/progress";
+import { isNilOrWhitespace } from "@/lib/utils";
+import { useFormContext, useWatch } from "react-hook-form";
+
+export interface PreviewQuestionProps {
+ activeQuestionId: string | undefined;
+ setActiveQuestionId: (questionId: string | undefined) => void;
+ questionIndex: number;
+}
+
+function PreviewQuestion({
+ questionIndex,
+ activeQuestionId,
+ setActiveQuestionId,
+}: PreviewQuestionProps) {
+ const { getAnswer } = useFormAnswersStore();
+
+ const { control } = useFormContext();
+
+ const questions = useWatch({
+ control,
+ name: `questions`,
+ });
+
+ const languageCode = useWatch({
+ control,
+ name: "languageCode",
+ });
+
+ const question = questions[questionIndex];
+
+ function meetsDisplayLogicCondition(
+ condition: DisplayLogicCondition | undefined,
+ value: string | undefined,
+ answer: BaseAnswer | undefined
+ ): boolean {
+ if (condition === undefined || value === undefined) return true;
+ if (answer === undefined) return false;
+
+ if (isSingleSelectAnswer(answer)) {
+ return condition === "Includes" && answer.selection?.optionId === value;
+ }
+
+ if (isMultiSelectAnswer(answer)) {
+ return (
+ condition === "Includes" &&
+ (answer.selection?.some((o) => o.optionId === value) ?? false)
+ );
+ }
+ if (isNumberAnswer(answer) || isRatingAnswer(answer)) {
+ if (answer.value === undefined) return false;
+ const numericValue = +value;
+ switch (condition) {
+ case "Equals":
+ return answer.value === numericValue;
+ case "NotEquals":
+ return answer.value !== numericValue;
+ case "GreaterEqual":
+ return answer.value >= numericValue;
+ case "GreaterThan":
+ return answer.value > numericValue;
+ case "LessEqual":
+ return answer.value <= numericValue;
+ case "LessThan":
+ return answer.value < numericValue;
+ default:
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ function onPreviousButtonClicked(): void {
+ if (questionIndex === 0) {
+ return;
+ }
+
+ for (let index = questionIndex - 1; index >= 0; index--) {
+ const prev = questions[index]!;
+ if (prev.hasDisplayLogic) {
+ if (
+ meetsDisplayLogicCondition(
+ prev.condition,
+ prev.value,
+ getAnswer(prev.parentQuestionId!)
+ )
+ ) {
+ setActiveQuestionId(prev.questionId);
+ break;
+ }
+ } else {
+ setActiveQuestionId(prev.questionId);
+ break;
+ }
+ }
+ }
+
+ function onNextButtonClicked(): void {
+ if (questionIndex === questions.length - 1) {
+ setActiveQuestionId("end");
+ return;
+ }
+
+ for (let index = questionIndex + 1; index < questions.length; index++) {
+ const next = questions[index]!;
+ if (next.hasDisplayLogic) {
+ if (
+ meetsDisplayLogicCondition(
+ next.condition,
+ next.value,
+ getAnswer(next.parentQuestionId!)
+ )
+ ) {
+ setActiveQuestionId(next.questionId);
+ break;
+ }
+ } else {
+ setActiveQuestionId(next.questionId);
+ break;
+ }
+ }
+ }
+
+ function resetProgress() {
+ setActiveQuestionId(questions[0]?.questionId);
+ }
+
+ return (
+
+
+ Question preview
+
+
+ {question && (
+
+
+
+ {question?.$questionType === QuestionType.TextQuestionType && (
+
+ )}
+
+ {question?.$questionType === QuestionType.NumberQuestionType && (
+
+ )}
+
+ {question?.$questionType === QuestionType.DateQuestionType && (
+
+ )}
+
+ {question?.$questionType === QuestionType.RatingQuestionType && (
+
+ )}
+
+ {question?.$questionType ===
+ QuestionType.MultiSelectQuestionType && (
+
({
+ optionId: o.optionId,
+ isFreeText: o.isFreeText,
+ text: o.text[languageCode],
+ })) ?? []
+ }
+ code={question.code}
+ />
+ )}
+
+ {question?.$questionType ===
+ QuestionType.SingleSelectQuestionType && (
+ ({
+ optionId: o.optionId,
+ isFreeText: o.isFreeText,
+ text: o.text[languageCode],
+ })) ?? []
+ }
+ code={question.code}
+ />
+ )}
+
+
+
+
+
+ {!!questions.length && (
+
+ )}
+
+ )}
+ {activeQuestionId === "end" && (
+ Finished
+ )}
+ {isNilOrWhitespace(activeQuestionId) && (
+ Select a question
+ )}
+
+
+ );
+}
+export default PreviewQuestion;
diff --git a/web-citizen-reporting/src/components/questionsEditor/preview/PreviewRatingQuestion.tsx b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewRatingQuestion.tsx
new file mode 100644
index 000000000..80f8d9de8
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewRatingQuestion.tsx
@@ -0,0 +1,47 @@
+import { AnswerType, type FunctionComponent, type RatingAnswer, type RatingScaleType } from '@/common/types';
+import { Label } from '@/components/ui/label';
+import { RatingGroup } from '@/components/ui/ratings';
+import { ratingScaleToNumber } from '@/lib/utils';
+import { useEffect, useState } from 'react';
+import { useFormAnswersStore } from '../answers-store';
+
+export interface PreviewRatingQuestionProps {
+ questionId: string;
+ text?: string;
+ helptext?: string;
+ scale: RatingScaleType;
+ lowerLabel?: string;
+ upperLabel?: string;
+ code: string;
+}
+
+function PreviewRatingQuestion({ code, questionId, text, helptext, scale, lowerLabel, upperLabel }: PreviewRatingQuestionProps): FunctionComponent {
+ const { setAnswer, getAnswer } = useFormAnswersStore();
+ const [localAnswer, setLocalAnswer] = useState(undefined)
+
+ useEffect(() => {
+ const ratingAnswer = getAnswer(questionId) as RatingAnswer;
+ setLocalAnswer(ratingAnswer);
+ }, [questionId]);
+
+ return (
+
+
+
+
+ {
+ const ratingAnswer: RatingAnswer = { $answerType: AnswerType.RatingAnswerType, questionId, value: Number(value) };
+ setAnswer(ratingAnswer);
+ }} />
+
+
+ );
+}
+
+export default PreviewRatingQuestion;
diff --git a/web-citizen-reporting/src/components/questionsEditor/preview/PreviewSingleSelectQuestion.tsx b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewSingleSelectQuestion.tsx
new file mode 100644
index 000000000..ca54ef70a
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewSingleSelectQuestion.tsx
@@ -0,0 +1,84 @@
+import { isSingleSelectAnswer } from '@/common/guards';
+import { AnswerType, SingleSelectAnswer } from '@/common/types';
+import { Label } from '@/components/ui/label';
+import { Textarea } from '@/components/ui/textarea';
+import { useEffect, useMemo, useState } from 'react';
+import { RadioGroup, RadioGroupItem } from '../../ui/radio-group';
+import { useFormAnswersStore } from '../answers-store';
+
+export interface PreviewSingleSelectQuestionProps {
+ questionId: string;
+ text?: string;
+ helptext?: string;
+ options: { optionId: string, text?: string, isFreeText: boolean }[];
+ code: string;
+}
+
+function PreviewSingleSelectQuestion({ code, questionId, text, helptext, options }: PreviewSingleSelectQuestionProps) {
+ const { setAnswer, getAnswer } = useFormAnswersStore();
+ const [localAnswer, setLocalAnswer] = useState(undefined)
+
+ const regularOptions = useMemo(() => {
+ if (!options) {
+ return [];
+ }
+ const regularOptions = options.filter((option) => !option.isFreeText);
+ return regularOptions;
+ }, [options]);
+
+ // Currently we only support one free text option
+ const freeTextOption = useMemo(() => options.find((option) => option.isFreeText), [options]);
+
+ useEffect(() => {
+ const singleSelectAnswer = getAnswer(questionId) as SingleSelectAnswer;
+ setLocalAnswer(singleSelectAnswer);
+ }, [questionId]);
+
+ useEffect(() => {
+ const singleSelectAnswer = getAnswer(questionId);
+ if (singleSelectAnswer && isSingleSelectAnswer(singleSelectAnswer)) {
+ setLocalAnswer(singleSelectAnswer);
+ } else {
+ const multiSelectAnswer: SingleSelectAnswer = { $answerType: AnswerType.SingleSelectAnswerType, questionId };
+ setAnswer(multiSelectAnswer);
+ setLocalAnswer(multiSelectAnswer);
+ }
+ }, [questionId]);
+
+ return (
+
+
+
+
{
+ const newAnswer: SingleSelectAnswer = { $answerType: AnswerType.SingleSelectAnswerType, questionId, selection: { optionId: value, text: '' } };
+ setAnswer(newAnswer);
+ setLocalAnswer(newAnswer);
+ }}
+ value={localAnswer?.selection?.optionId}>
+ {regularOptions?.map((option) => (
+
+
+
+
+ ))}
+ {!!freeTextOption && (
+
+
+
+
+ )}
+
+ {(!!localAnswer?.selection?.optionId && localAnswer?.selection?.optionId === freeTextOption?.optionId) && (
+
+ );
+}
+
+export default PreviewSingleSelectQuestion;
diff --git a/web-citizen-reporting/src/components/questionsEditor/preview/PreviewTextQuestion.tsx b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewTextQuestion.tsx
new file mode 100644
index 000000000..88430fd27
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/preview/PreviewTextQuestion.tsx
@@ -0,0 +1,39 @@
+import { Label } from '@/components/ui/label';
+import { Textarea } from '../../ui/textarea';
+import { AnswerType, TextAnswer } from '@/common/types';
+import { useFormAnswersStore } from '../answers-store';
+import { useEffect, useState } from 'react';
+
+export interface PreviewTextQuestionProps {
+ questionId: string;
+ text?: string;
+ helptext?: string;
+ inputPlaceholder?: string;
+ code: string;
+}
+
+function PreviewTextQuestion({ code, questionId, text, helptext, inputPlaceholder }: PreviewTextQuestionProps) {
+ const { setAnswer, getAnswer } = useFormAnswersStore();
+ const [localAnswer, setLocalAnswer] = useState(undefined)
+ useEffect(() => { setLocalAnswer(getAnswer(questionId) as TextAnswer) }, [questionId]);
+
+ return (
+
+
+
+
+
+
+ );
+}
+
+export default PreviewTextQuestion;
diff --git a/web-citizen-reporting/src/components/questionsEditor/utils.ts b/web-citizen-reporting/src/components/questionsEditor/utils.ts
new file mode 100644
index 000000000..21ea4e2e4
--- /dev/null
+++ b/web-citizen-reporting/src/components/questionsEditor/utils.ts
@@ -0,0 +1,74 @@
+import { EditQuestionType } from "@/common/form-types";
+import { QuestionType } from "@/common/types";
+import { isNotNilOrWhitespace } from "@/lib/utils";
+import {
+ Bars3BottomLeftIcon,
+ CalculatorIcon,
+ CalendarIcon,
+ CheckCircleIcon,
+ ListBulletIcon,
+ StarIcon,
+} from "@heroicons/react/24/solid";
+
+export function isQuestionTranslated(
+ question: EditQuestionType,
+ languageCode: string,
+ defaultLanguageCode: string
+): boolean {
+ const questionTextValid = isNotNilOrWhitespace(question.text[languageCode]);
+
+ const helptextIsValid = isNotNilOrWhitespace(
+ question.helptext?.[defaultLanguageCode]
+ )
+ ? isNotNilOrWhitespace(question.helptext[languageCode])
+ : true;
+
+ const isQuestionValid = questionTextValid && helptextIsValid;
+
+ if (
+ question.$questionType === QuestionType.TextQuestionType ||
+ question.$questionType === QuestionType.NumberQuestionType
+ ) {
+ return (
+ isQuestionValid &&
+ (isNotNilOrWhitespace(question.inputPlaceholder?.[defaultLanguageCode])
+ ? isNotNilOrWhitespace(question.inputPlaceholder[languageCode])
+ : true)
+ );
+ }
+
+ if (question.$questionType === QuestionType.RatingQuestionType) {
+ return (
+ isQuestionValid &&
+ (isNotNilOrWhitespace(question.lowerLabel?.[defaultLanguageCode])
+ ? isNotNilOrWhitespace(question.lowerLabel[languageCode])
+ : true) &&
+ (isNotNilOrWhitespace(question.upperLabel?.[defaultLanguageCode])
+ ? isNotNilOrWhitespace(question.upperLabel[languageCode])
+ : true)
+ );
+ }
+
+ if (
+ question.$questionType === QuestionType.SingleSelectQuestionType ||
+ question.$questionType === QuestionType.MultiSelectQuestionType
+ ) {
+ return (
+ isQuestionValid &&
+ question.options.every((option) =>
+ isNotNilOrWhitespace(option.text[languageCode])
+ )
+ );
+ }
+
+ return isQuestionValid;
+}
+
+export const questionsIconMapping = {
+ [QuestionType.TextQuestionType]: Bars3BottomLeftIcon,
+ [QuestionType.NumberQuestionType]: CalculatorIcon,
+ [QuestionType.DateQuestionType]: CalendarIcon,
+ [QuestionType.RatingQuestionType]: StarIcon,
+ [QuestionType.SingleSelectQuestionType]: CheckCircleIcon,
+ [QuestionType.MultiSelectQuestionType]: ListBulletIcon,
+};
diff --git a/web-citizen-reporting/src/components/ui/README.md b/web-citizen-reporting/src/components/ui/README.md
new file mode 100644
index 000000000..5122d78d0
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/README.md
@@ -0,0 +1,13 @@
+# src/components/ui
+
+Components relating to ui should be placed in this directory.
+
+Example:
+
+- ui/Button/
+ - Button.tsx
+ - Button.stories.tsx
+ - Button.test.tsx
+ - index.ts
+
+Lowercase files like `table.tsx` or `button.tsx` are automatically generated by https://ui.shadcn.com/
diff --git a/web-citizen-reporting/src/components/ui/alert-dialog-provider.tsx b/web-citizen-reporting/src/components/ui/alert-dialog-provider.tsx
new file mode 100644
index 000000000..6d31aa5fc
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/alert-dialog-provider.tsx
@@ -0,0 +1,144 @@
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle
+} from "@/components/ui/alert-dialog";
+import { Separator } from "@/components/ui/separator";
+
+import * as React from "react";
+import { buttonVariants } from "./button";
+
+export const AlertDialogContext = React.createContext<(params: AlertAction) => Promise>(() => null!);
+
+export type AlertAction =
+ {
+ type: "confirm";
+ title: string | React.ReactNode;
+ body?: string | React.ReactNode;
+ cancelButton?: string;
+ cancelButtonClass?: string;
+ actionButton?: string;
+ actionButtonClass?: string;
+ }
+ | { type: "close" };
+
+interface AlertDialogState {
+ open: boolean;
+ title: string | React.ReactNode;
+ body: string | React.ReactNode;
+ type: "confirm";
+ cancelButton?: string;
+ cancelButtonClass?: string;
+ actionButton: string;
+ actionButtonClass?: string;
+}
+
+export function alertDialogReducer(
+ state: AlertDialogState,
+ action: AlertAction
+): AlertDialogState {
+ switch (action.type) {
+ case "close":
+ return { ...state, open: false };
+ case "confirm":
+ return {
+ ...state,
+ open: true,
+ ...action,
+ cancelButton: action.cancelButton,
+ actionButton: action.actionButton || 'Ok',
+ actionButtonClass: action.actionButtonClass ?? buttonVariants({ variant: "default" })
+ };
+ default:
+ return state;
+ }
+}
+
+export function AlertDialogProvider({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ const [state, dispatch] = React.useReducer(alertDialogReducer, {
+ open: false,
+ title: "",
+ body: "",
+ type: "confirm",
+ cancelButton: "Cancel",
+ actionButton: "Okay",
+ });
+
+ const resolveRef = React.useRef<(tf: any) => void>();
+
+ function close() {
+ dispatch({ type: "close" });
+ resolveRef.current?.(false);
+ }
+
+ function confirm() {
+ dispatch({ type: "close" });
+ resolveRef.current?.(true);
+ }
+
+ const dialog = React.useCallback(async (params: T) => {
+ dispatch(params);
+
+ return new Promise((resolve) => {
+ resolveRef.current = resolve;
+ });
+ }, []);
+
+ return (
+
+ {children}
+ {
+ if (!open) close();
+ return;
+ }}
+ >
+
+
+ {state.title}
+
+ {state.body ? ({state.body}) : null}
+
+
+ {state.cancelButton ? (
+
+ {state.cancelButton}
+ ) : null}
+
+ {state.actionButton}
+
+
+
+
+
+
+ );
+}
+type Params = Omit, "type"> | string;
+
+export function useConfirm() {
+ const dialog = React.useContext(AlertDialogContext);
+
+ return React.useCallback((params: Params<"confirm">) => {
+ return dialog({
+ ...(typeof params === "string" ? { title: params } : params),
+ type: "confirm"
+ });
+ },
+ [dialog]
+ );
+}
\ No newline at end of file
diff --git a/web-citizen-reporting/src/components/ui/alert-dialog.tsx b/web-citizen-reporting/src/components/ui/alert-dialog.tsx
new file mode 100644
index 000000000..8722561cf
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,139 @@
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/web-citizen-reporting/src/components/ui/button.tsx b/web-citizen-reporting/src/components/ui/button.tsx
new file mode 100644
index 000000000..90248d170
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/button.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+
+const buttonVariants = cva(
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
+ {
+ variants: {
+ variant: {
+ default: 'bg-purple-900 text-primary-foreground hover:bg-purple-900/90',
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
+ outline:
+ 'border border-input bg-background hover:bg-purple-50 text-purple-900 border-purple-900 hover:text-accent-foreground',
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ 'ghost-primary': 'text-primary-600 hover:bg-primary-100',
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
+ link: 'text-primary underline-offset-4 hover:underline',
+ select: 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1'
+ },
+ size: {
+ default: 'h-10 px-4 py-2',
+ sm: 'h-9 rounded-md px-3',
+ lg: 'h-11 rounded-md px-8',
+ icon: 'h-8 w-8 rounded-full',
+ none: '',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ }
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : 'button';
+ return ;
+ }
+);
+Button.displayName = 'Button';
+
+export { Button, buttonVariants };
diff --git a/web-citizen-reporting/src/components/ui/calendar.tsx b/web-citizen-reporting/src/components/ui/calendar.tsx
new file mode 100644
index 000000000..be0f466ac
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/calendar.tsx
@@ -0,0 +1,59 @@
+import * as React from 'react';
+import { ChevronLeft, ChevronRight } from 'lucide-react';
+import { DayPicker } from 'react-day-picker';
+
+import { cn } from '@/lib/utils';
+import { buttonVariants } from '@/components/ui/button';
+
+export type CalendarProps = React.ComponentProps;
+
+function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
+ return (
+
+ props.orientation === 'left' ? : ,
+ }}
+ {...props}
+ />
+ );
+}
+Calendar.displayName = 'Calendar';
+
+export { Calendar };
diff --git a/web-citizen-reporting/src/components/ui/card.tsx b/web-citizen-reporting/src/components/ui/card.tsx
new file mode 100644
index 000000000..afa13ecfa
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/web-citizen-reporting/src/components/ui/checkbox.tsx b/web-citizen-reporting/src/components/ui/checkbox.tsx
new file mode 100644
index 000000000..711226084
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/checkbox.tsx
@@ -0,0 +1,175 @@
+import {
+ Checkbox as HeadlessCheckbox,
+ Field as HeadlessField,
+ type CheckboxProps as HeadlessCheckboxProps,
+ type FieldProps as HeadlessFieldProps,
+} from '@headlessui/react'
+import { clsx } from 'clsx'
+import type React from 'react'
+
+export function CheckboxGroup({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
+ return (
+
+ )
+}
+
+export function CheckboxField({ className, ...props }: HeadlessFieldProps) {
+ return (
+ [data-slot=control]]:col-start-1 [&>[data-slot=control]]:row-start-1 [&>[data-slot=control]]:justify-self-center',
+
+ // Label layout
+ '[&>[data-slot=label]]:col-start-2 [&>[data-slot=label]]:row-start-1 [&>[data-slot=label]]:justify-self-start',
+
+ // Description layout
+ '[&>[data-slot=description]]:col-start-2 [&>[data-slot=description]]:row-start-2',
+
+ // With description
+ '[&_[data-slot=label]]:has-[[data-slot=description]]:font-medium'
+ )}
+ />
+ )
+}
+
+const base = [
+ // Basic layout
+ 'relative isolate flex size-[1.125rem] items-center justify-center rounded-[0.3125rem] sm:size-4',
+
+ // Background color + shadow applied to inset pseudo element, so shadow blends with border in light mode
+ 'before:absolute before:inset-0 before:-z-10 before:rounded-[calc(0.3125rem-1px)] before:bg-white before:shadow',
+
+ // Background color when checked
+ 'before:group-data-[checked]:bg-[--checkbox-checked-bg]',
+
+ // Background color is moved to control and shadow is removed in dark mode so hide `before` pseudo
+ 'dark:before:hidden',
+
+ // Background color applied to control in dark mode
+ 'dark:bg-white/5 dark:group-data-[checked]:bg-[--checkbox-checked-bg]',
+
+ // Border
+ 'border border-zinc-950/15 group-data-[checked]:border-transparent group-data-[checked]:group-data-[hover]:border-transparent group-data-[hover]:border-zinc-950/30 group-data-[checked]:bg-[--checkbox-checked-border]',
+ 'dark:border-white/15 dark:group-data-[checked]:border-white/5 dark:group-data-[checked]:group-data-[hover]:border-white/5 dark:group-data-[hover]:border-white/30',
+
+ // Inner highlight shadow
+ 'after:absolute after:inset-0 after:rounded-[calc(0.3125rem-1px)] after:shadow-[inset_0_1px_theme(colors.white/15%)]',
+ 'dark:after:-inset-px dark:after:hidden dark:after:rounded-[0.3125rem] dark:group-data-[checked]:after:block',
+
+ // Focus ring
+ 'group-data-[focus]:outline group-data-[focus]:outline-2 group-data-[focus]:outline-offset-2 group-data-[focus]:outline-blue-500',
+
+ // Disabled state
+ 'group-data-[disabled]:opacity-50',
+ 'group-data-[disabled]:border-zinc-950/25 group-data-[disabled]:bg-zinc-950/5 group-data-[disabled]:[--checkbox-check:theme(colors.zinc.950/50%)] group-data-[disabled]:before:bg-transparent',
+ 'dark:group-data-[disabled]:border-white/20 dark:group-data-[disabled]:bg-white/[2.5%] dark:group-data-[disabled]:[--checkbox-check:theme(colors.white/50%)] dark:group-data-[disabled]:group-data-[checked]:after:hidden',
+
+ // Forced colors mode
+ 'forced-colors:[--checkbox-check:HighlightText] forced-colors:[--checkbox-checked-bg:Highlight] forced-colors:group-data-[disabled]:[--checkbox-check:Highlight]',
+ 'dark:forced-colors:[--checkbox-check:HighlightText] dark:forced-colors:[--checkbox-checked-bg:Highlight] dark:forced-colors:group-data-[disabled]:[--checkbox-check:Highlight]',
+]
+
+const colors = {
+ 'dark/zinc': [
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.900)] [--checkbox-checked-border:theme(colors.zinc.950/90%)]',
+ 'dark:[--checkbox-checked-bg:theme(colors.zinc.600)]',
+ ],
+ 'dark/white': [
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.900)] [--checkbox-checked-border:theme(colors.zinc.950/90%)]',
+ 'dark:[--checkbox-check:theme(colors.zinc.900)] dark:[--checkbox-checked-bg:theme(colors.white)] dark:[--checkbox-checked-border:theme(colors.zinc.950/15%)]',
+ ],
+ white:
+ '[--checkbox-check:theme(colors.zinc.900)] [--checkbox-checked-bg:theme(colors.white)] [--checkbox-checked-border:theme(colors.zinc.950/15%)]',
+ dark: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.900)] [--checkbox-checked-border:theme(colors.zinc.950/90%)]',
+ zinc: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.zinc.600)] [--checkbox-checked-border:theme(colors.zinc.700/90%)]',
+ red: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.red.600)] [--checkbox-checked-border:theme(colors.red.700/90%)]',
+ orange:
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.orange.500)] [--checkbox-checked-border:theme(colors.orange.600/90%)]',
+ amber:
+ '[--checkbox-check:theme(colors.amber.950)] [--checkbox-checked-bg:theme(colors.amber.400)] [--checkbox-checked-border:theme(colors.amber.500/80%)]',
+ yellow:
+ '[--checkbox-check:theme(colors.yellow.950)] [--checkbox-checked-bg:theme(colors.yellow.300)] [--checkbox-checked-border:theme(colors.yellow.400/80%)]',
+ lime: '[--checkbox-check:theme(colors.lime.950)] [--checkbox-checked-bg:theme(colors.lime.300)] [--checkbox-checked-border:theme(colors.lime.400/80%)]',
+ green:
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.green.600)] [--checkbox-checked-border:theme(colors.green.700/90%)]',
+ emerald:
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.emerald.600)] [--checkbox-checked-border:theme(colors.emerald.700/90%)]',
+ teal: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.teal.600)] [--checkbox-checked-border:theme(colors.teal.700/90%)]',
+ cyan: '[--checkbox-check:theme(colors.cyan.950)] [--checkbox-checked-bg:theme(colors.cyan.300)] [--checkbox-checked-border:theme(colors.cyan.400/80%)]',
+ sky: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.sky.500)] [--checkbox-checked-border:theme(colors.sky.600/80%)]',
+ blue: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.blue.600)] [--checkbox-checked-border:theme(colors.blue.700/90%)]',
+ indigo:
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.indigo.500)] [--checkbox-checked-border:theme(colors.indigo.600/90%)]',
+ violet:
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.violet.500)] [--checkbox-checked-border:theme(colors.violet.600/90%)]',
+ purple:
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.purple.500)] [--checkbox-checked-border:theme(colors.purple.600/90%)]',
+ fuchsia:
+ '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.fuchsia.500)] [--checkbox-checked-border:theme(colors.fuchsia.600/90%)]',
+ pink: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.pink.500)] [--checkbox-checked-border:theme(colors.pink.600/90%)]',
+ rose: '[--checkbox-check:theme(colors.white)] [--checkbox-checked-bg:theme(colors.rose.500)] [--checkbox-checked-border:theme(colors.rose.600/90%)]',
+}
+
+type Color = keyof typeof colors
+
+export function Checkbox({
+ color = 'dark/zinc',
+ className,
+ ...props
+}: {
+ color?: Color
+ className?: string
+} & HeadlessCheckboxProps) {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/web-citizen-reporting/src/components/ui/collapsible.tsx b/web-citizen-reporting/src/components/ui/collapsible.tsx
new file mode 100644
index 000000000..a23e7a281
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/collapsible.tsx
@@ -0,0 +1,9 @@
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/web-citizen-reporting/src/components/ui/command.tsx b/web-citizen-reporting/src/components/ui/command.tsx
new file mode 100644
index 000000000..e0aa8cc69
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/command.tsx
@@ -0,0 +1,153 @@
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Command.displayName = CommandPrimitive.displayName
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+ return (
+
+ )
+}
+
+const CommandInput = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>((props, ref) => (
+
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/web-citizen-reporting/src/components/ui/date-time-picker.tsx b/web-citizen-reporting/src/components/ui/date-time-picker.tsx
new file mode 100644
index 000000000..4f3820201
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/date-time-picker.tsx
@@ -0,0 +1,768 @@
+import { Button, buttonVariants } from '@/components/ui/button';
+import type { CalendarProps } from '@/components/ui/calendar';
+import { Input } from '@/components/ui/input';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+import { cn } from '@/lib/utils';
+import { add, format } from 'date-fns';
+import { type Locale, enUS } from 'date-fns/locale';
+import { Calendar as CalendarIcon, ChevronLeft, ChevronRight } from 'lucide-react';
+import { Clock } from 'lucide-react';
+import * as React from 'react';
+import { useImperativeHandle, useRef } from 'react';
+
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { DayPicker } from 'react-day-picker';
+
+// ---------- utils start ----------
+/**
+ * regular expression to check for valid hour format (01-23)
+ */
+function isValidHour(value: string) {
+ return /^(0[0-9]|1[0-9]|2[0-3])$/.test(value);
+}
+
+/**
+ * regular expression to check for valid 12 hour format (01-12)
+ */
+function isValid12Hour(value: string) {
+ return /^(0[1-9]|1[0-2])$/.test(value);
+}
+
+/**
+ * regular expression to check for valid minute format (00-59)
+ */
+function isValidMinuteOrSecond(value: string) {
+ return /^[0-5][0-9]$/.test(value);
+}
+
+type GetValidNumberConfig = { max: number; min?: number; loop?: boolean };
+
+function getValidNumber(value: string, { max, min = 0, loop = false }: GetValidNumberConfig) {
+ let numericValue = parseInt(value, 10);
+
+ if (!Number.isNaN(numericValue)) {
+ if (!loop) {
+ if (numericValue > max) numericValue = max;
+ if (numericValue < min) numericValue = min;
+ } else {
+ if (numericValue > max) numericValue = min;
+ if (numericValue < min) numericValue = max;
+ }
+ return numericValue.toString().padStart(2, '0');
+ }
+
+ return '00';
+}
+
+function getValidHour(value: string) {
+ if (isValidHour(value)) return value;
+ return getValidNumber(value, { max: 23 });
+}
+
+function getValid12Hour(value: string) {
+ if (isValid12Hour(value)) return value;
+ return getValidNumber(value, { min: 1, max: 12 });
+}
+
+function getValidMinuteOrSecond(value: string) {
+ if (isValidMinuteOrSecond(value)) return value;
+ return getValidNumber(value, { max: 59 });
+}
+
+type GetValidArrowNumberConfig = {
+ min: number;
+ max: number;
+ step: number;
+};
+
+function getValidArrowNumber(value: string, { min, max, step }: GetValidArrowNumberConfig) {
+ let numericValue = parseInt(value, 10);
+ if (!Number.isNaN(numericValue)) {
+ numericValue += step;
+ return getValidNumber(String(numericValue), { min, max, loop: true });
+ }
+ return '00';
+}
+
+function getValidArrowHour(value: string, step: number) {
+ return getValidArrowNumber(value, { min: 0, max: 23, step });
+}
+
+function getValidArrow12Hour(value: string, step: number) {
+ return getValidArrowNumber(value, { min: 1, max: 12, step });
+}
+
+function getValidArrowMinuteOrSecond(value: string, step: number) {
+ return getValidArrowNumber(value, { min: 0, max: 59, step });
+}
+
+function setMinutes(date: Date, value: string) {
+ const minutes = getValidMinuteOrSecond(value);
+ date.setMinutes(parseInt(minutes, 10));
+ return date;
+}
+
+function setSeconds(date: Date, value: string) {
+ const seconds = getValidMinuteOrSecond(value);
+ date.setSeconds(parseInt(seconds, 10));
+ return date;
+}
+
+function setHours(date: Date, value: string) {
+ const hours = getValidHour(value);
+ date.setHours(parseInt(hours, 10));
+ return date;
+}
+
+function set12Hours(date: Date, value: string, period: Period) {
+ const hours = parseInt(getValid12Hour(value), 10);
+ const convertedHours = convert12HourTo24Hour(hours, period);
+ date.setHours(convertedHours);
+ return date;
+}
+
+type TimePickerType = 'minutes' | 'seconds' | 'hours' | '12hours';
+type Period = 'AM' | 'PM';
+
+function setDateByType(date: Date, value: string, type: TimePickerType, period?: Period) {
+ switch (type) {
+ case 'minutes':
+ return setMinutes(date, value);
+ case 'seconds':
+ return setSeconds(date, value);
+ case 'hours':
+ return setHours(date, value);
+ case '12hours': {
+ if (!period) return date;
+ return set12Hours(date, value, period);
+ }
+ default:
+ return date;
+ }
+}
+
+function getDateByType(date: Date | null, type: TimePickerType) {
+ if (!date) return '00';
+ switch (type) {
+ case 'minutes':
+ return getValidMinuteOrSecond(String(date.getMinutes()));
+ case 'seconds':
+ return getValidMinuteOrSecond(String(date.getSeconds()));
+ case 'hours':
+ return getValidHour(String(date.getHours()));
+ case '12hours':
+ return getValid12Hour(String(display12HourValue(date.getHours())));
+ default:
+ return '00';
+ }
+}
+
+function getArrowByType(value: string, step: number, type: TimePickerType) {
+ switch (type) {
+ case 'minutes':
+ return getValidArrowMinuteOrSecond(value, step);
+ case 'seconds':
+ return getValidArrowMinuteOrSecond(value, step);
+ case 'hours':
+ return getValidArrowHour(value, step);
+ case '12hours':
+ return getValidArrow12Hour(value, step);
+ default:
+ return '00';
+ }
+}
+
+/**
+ * handles value change of 12-hour input
+ * 12:00 PM is 12:00
+ * 12:00 AM is 00:00
+ */
+function convert12HourTo24Hour(hour: number, period: Period) {
+ if (period === 'PM') {
+ if (hour <= 11) {
+ return hour + 12;
+ }
+ return hour;
+ }
+
+ if (period === 'AM') {
+ if (hour === 12) return 0;
+ return hour;
+ }
+ return hour;
+}
+
+/**
+ * time is stored in the 24-hour form,
+ * but needs to be displayed to the user
+ * in its 12-hour representation
+ */
+function display12HourValue(hours: number) {
+ if (hours === 0 || hours === 12) return '12';
+ if (hours >= 22) return `${hours - 12}`;
+ if (hours % 12 > 9) return `${hours}`;
+ return `0${hours % 12}`;
+}
+
+function genMonths(locale: Pick) {
+ return Array.from({ length: 12 }, (_, i) => ({
+ value: i,
+ label: format(new Date(2021, i), 'MMMM', { locale }),
+ }));
+}
+
+function genYears(yearRange = 50) {
+ const today = new Date();
+ return Array.from({ length: yearRange * 2 + 1 }, (_, i) => ({
+ value: today.getFullYear() - yearRange + i,
+ label: (today.getFullYear() - yearRange + i).toString(),
+ }));
+}
+
+// ---------- utils end ----------
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ yearRange = 50,
+ ...props
+}: CalendarProps & { yearRange?: number }) {
+ const MONTHS = React.useMemo(() => {
+ let locale: Pick = enUS;
+ const { options, localize, formatLong } = props.locale || {};
+ if (options && localize && formatLong) {
+ locale = {
+ options,
+ localize,
+ formatLong,
+ };
+ }
+ return genMonths(locale);
+ }, []);
+
+ const YEARS = React.useMemo(() => genYears(yearRange), []);
+
+ return (
+
+ props.orientation === 'left' ? (
+
+ ) : (
+
+ ),
+ MonthCaption: ({ calendarMonth }) => {
+ return (
+
+
+
+
+ );
+ },
+ }}
+ {...props}
+ />
+ );
+}
+Calendar.displayName = 'Calendar';
+
+interface PeriodSelectorProps {
+ period: Period;
+ setPeriod?: (m: Period) => void;
+ date?: Date | null;
+ onDateChange?: (date: Date | undefined) => void;
+ onRightFocus?: () => void;
+ onLeftFocus?: () => void;
+}
+
+const TimePeriodSelect = React.forwardRef(
+ ({ period, setPeriod, date, onDateChange, onLeftFocus, onRightFocus }, ref) => {
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'ArrowRight') onRightFocus?.();
+ if (e.key === 'ArrowLeft') onLeftFocus?.();
+ };
+
+ const handleValueChange = (value: Period) => {
+ setPeriod?.(value);
+
+ /**
+ * trigger an update whenever the user switches between AM and PM;
+ * otherwise user must manually change the hour each time
+ */
+ if (date) {
+ const tempDate = new Date(date);
+ const hours = display12HourValue(date.getHours());
+ onDateChange?.(
+ setDateByType(tempDate, hours.toString(), '12hours', period === 'AM' ? 'PM' : 'AM'),
+ );
+ }
+ };
+
+ return (
+
+
+
+ );
+ },
+);
+
+TimePeriodSelect.displayName = 'TimePeriodSelect';
+
+interface TimePickerInputProps extends React.InputHTMLAttributes {
+ picker: TimePickerType;
+ date?: Date | null;
+ onDateChange?: (date: Date | undefined) => void;
+ period?: Period;
+ onRightFocus?: () => void;
+ onLeftFocus?: () => void;
+}
+
+const TimePickerInput = React.forwardRef(
+ (
+ {
+ className,
+ type = 'tel',
+ value,
+ id,
+ name,
+ date = new Date(new Date().setHours(0, 0, 0, 0)),
+ onDateChange,
+ onChange,
+ onKeyDown,
+ picker,
+ period,
+ onLeftFocus,
+ onRightFocus,
+ ...props
+ },
+ ref,
+ ) => {
+ const [flag, setFlag] = React.useState(false);
+ const [prevIntKey, setPrevIntKey] = React.useState('0');
+
+ /**
+ * allow the user to enter the second digit within 2 seconds
+ * otherwise start again with entering first digit
+ */
+ // @ts-ignore
+ React.useEffect(() => {
+ if (flag) {
+ const timer = setTimeout(() => {
+ setFlag(false);
+ }, 2000);
+
+ return () => clearTimeout(timer);
+ }
+ }, [flag]);
+
+ const calculatedValue = React.useMemo(() => {
+ return getDateByType(date, picker);
+ }, [date, picker]);
+
+ const calculateNewValue = (key: string) => {
+ /*
+ * If picker is '12hours' and the first digit is 0, then the second digit is automatically set to 1.
+ * The second entered digit will break the condition and the value will be set to 10-12.
+ */
+ if (picker === '12hours') {
+ if (flag && calculatedValue.slice(1, 2) === '1' && prevIntKey === '0') return `0${key}`;
+ }
+
+ return !flag ? `0${key}` : calculatedValue.slice(1, 2) + key;
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Tab') return;
+ e.preventDefault();
+ if (e.key === 'ArrowRight') onRightFocus?.();
+ if (e.key === 'ArrowLeft') onLeftFocus?.();
+ if (['ArrowUp', 'ArrowDown'].includes(e.key)) {
+ const step = e.key === 'ArrowUp' ? 1 : -1;
+ const newValue = getArrowByType(calculatedValue, step, picker);
+ if (flag) setFlag(false);
+ const tempDate = date ? new Date(date) : new Date();
+ onDateChange?.(setDateByType(tempDate, newValue, picker, period));
+ }
+ if (e.key >= '0' && e.key <= '9') {
+ if (picker === '12hours') setPrevIntKey(e.key);
+
+ const newValue = calculateNewValue(e.key);
+ if (flag) onRightFocus?.();
+ setFlag((prev) => !prev);
+ const tempDate = date ? new Date(date) : new Date();
+ onDateChange?.(setDateByType(tempDate, newValue, picker, period));
+ }
+ };
+
+ return (
+ {
+ e.preventDefault();
+ onChange?.(e);
+ }}
+ type={type}
+ inputMode="decimal"
+ onKeyDown={(e) => {
+ onKeyDown?.(e);
+ handleKeyDown(e);
+ }}
+ {...props}
+ />
+ );
+ },
+);
+
+TimePickerInput.displayName = 'TimePickerInput';
+
+interface TimePickerProps {
+ date?: Date | null;
+ onChange?: (date: Date | undefined) => void;
+ hourCycle?: 12 | 24;
+ /**
+ * Determines the smallest unit that is displayed in the datetime picker.
+ * Default is 'second'.
+ * */
+ granularity?: Granularity;
+}
+
+interface TimePickerRef {
+ minuteRef: HTMLInputElement | null;
+ hourRef: HTMLInputElement | null;
+ secondRef: HTMLInputElement | null;
+}
+
+const TimePicker = React.forwardRef(
+ ({ date, onChange, hourCycle = 24, granularity = 'second' }, ref) => {
+ const minuteRef = React.useRef(null);
+ const hourRef = React.useRef(null);
+ const secondRef = React.useRef(null);
+ const periodRef = React.useRef(null);
+ const [period, setPeriod] = React.useState(date && date.getHours() >= 12 ? 'PM' : 'AM');
+
+ useImperativeHandle(
+ ref,
+ () => ({
+ minuteRef: minuteRef.current,
+ hourRef: hourRef.current,
+ secondRef: secondRef.current,
+ periodRef: periodRef.current,
+ }),
+ [minuteRef, hourRef, secondRef],
+ );
+
+ return (
+
+
+
minuteRef?.current?.focus()}
+ />
+ {(granularity === 'minute' || granularity === 'second') && (
+ <>
+ :
+ hourRef?.current?.focus()}
+ onRightFocus={() => secondRef?.current?.focus()}
+ />
+ >
+ )}
+ {granularity === 'second' && (
+ <>
+ :
+ minuteRef?.current?.focus()}
+ onRightFocus={() => periodRef?.current?.focus()}
+ />
+ >
+ )}
+ {hourCycle === 12 && (
+
+ {
+ onChange?.(date);
+ if (date && date?.getHours() >= 12) {
+ setPeriod('PM');
+ } else {
+ setPeriod('AM');
+ }
+ }}
+ ref={periodRef}
+ onLeftFocus={() => secondRef?.current?.focus()}
+ />
+
+ )}
+
+ );
+ },
+);
+TimePicker.displayName = 'TimePicker';
+
+type Granularity = 'day' | 'hour' | 'minute' | 'second';
+
+type DateTimePickerProps = {
+ value?: Date;
+ onChange?: (date: Date | undefined) => void;
+ disabled?: boolean;
+ /** showing `AM/PM` or not. */
+ hourCycle?: 12 | 24;
+ placeholder?: string;
+ /**
+ * The year range will be: `This year + yearRange` and `this year - yearRange`.
+ * Default is 50.
+ * For example:
+ * This year is 2024, The year dropdown will be 1974 to 2024 which is generated by `2024 - 50 = 1974` and `2024 + 50 = 2074`.
+ * */
+ yearRange?: number;
+ /**
+ * The format is derived from the `date-fns` documentation.
+ * @reference https://date-fns.org/v3.6.0/docs/format
+ **/
+ displayFormat?: { hour24?: string; hour12?: string };
+ /**
+ * The granularity prop allows you to control the smallest unit that is displayed by DateTimePicker.
+ * By default, the value is `second` which shows all time inputs.
+ **/
+ granularity?: Granularity;
+ className?: string;
+} & Pick;
+
+type DateTimePickerRef = {
+ value?: Date;
+} & Omit;
+
+const DateTimePicker = React.forwardRef, DateTimePickerProps>(
+ (
+ {
+ locale = enUS,
+ value,
+ onChange,
+ hourCycle = 24,
+ yearRange = 50,
+ disabled = false,
+ displayFormat,
+ granularity = 'second',
+ placeholder = 'Pick a date',
+ className,
+ ...props
+ },
+ ref,
+ ) => {
+ const [month, setMonth] = React.useState(value ?? new Date());
+ const buttonRef = useRef(null);
+ /**
+ * carry over the current time when a user clicks a new day
+ * instead of resetting to 00:00
+ */
+ const handleSelect = (newDay: Date | undefined) => {
+ if (!newDay) return;
+ if (!value) {
+ onChange?.(newDay);
+ setMonth(newDay);
+ return;
+ }
+ const diff = newDay.getTime() - value.getTime();
+ const diffInDays = diff / (1000 * 60 * 60 * 24);
+ const newDateFull = add(value, { days: Math.ceil(diffInDays) });
+ onChange?.(newDateFull);
+ setMonth(newDateFull);
+ };
+
+ useImperativeHandle(
+ ref,
+ () => ({
+ ...buttonRef.current,
+ value,
+ }),
+ [value],
+ );
+
+ const initHourFormat = {
+ hour24:
+ displayFormat?.hour24 ??
+ `PPP HH:mm${!granularity || granularity === 'second' ? ':ss' : ''}`,
+ hour12:
+ displayFormat?.hour12 ??
+ `PP hh:mm${!granularity || granularity === 'second' ? ':ss' : ''} b`,
+ };
+
+ let loc = enUS;
+ const { options, localize, formatLong } = locale;
+ if (options && localize && formatLong) {
+ loc = {
+ ...enUS,
+ options,
+ localize,
+ formatLong,
+ };
+ }
+
+ return (
+
+
+
+
+
+ handleSelect(d)}
+ onMonthChange={handleSelect}
+ yearRange={yearRange}
+ locale={locale}
+ {...props}
+ />
+ {granularity !== 'day' && (
+
+
+
+ )}
+
+
+ );
+ },
+);
+
+DateTimePicker.displayName = 'DateTimePicker';
+
+export { DateTimePicker, TimePickerInput, TimePicker };
+export type { TimePickerType, DateTimePickerProps, DateTimePickerRef };
diff --git a/web-citizen-reporting/src/components/ui/dialog.tsx b/web-citizen-reporting/src/components/ui/dialog.tsx
new file mode 100644
index 000000000..c23630eb8
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/dialog.tsx
@@ -0,0 +1,120 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/web-citizen-reporting/src/components/ui/file-uploader.tsx b/web-citizen-reporting/src/components/ui/file-uploader.tsx
new file mode 100644
index 000000000..77d1fa018
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/file-uploader.tsx
@@ -0,0 +1,227 @@
+import { ArrowUpTrayIcon, DocumentTextIcon, XMarkIcon } from '@heroicons/react/24/solid';
+import * as React from 'react';
+import Dropzone, { type DropzoneProps, type FileRejection } from 'react-dropzone';
+
+import { Button } from '@/components/ui/button';
+import { useControllableState } from '@/components/ui/use-controllable-state';
+import { cn, formatBytes } from '@/lib/utils';
+import { toast } from './use-toast';
+
+interface FileUploaderProps extends React.HTMLAttributes {
+ /**
+ * Value of the uploader.
+ * @type File[]
+ * @default undefined
+ * @example value={files}
+ */
+ value?: File[];
+
+ /**
+ * Function to be called when the value changes.
+ * @type (files: File[]) => void
+ * @default undefined
+ * @example onValueChange={(files) => setFiles(files)}
+ */
+ onValueChange?: (files: File[]) => void;
+
+ /**
+ * Accepted file types for the uploader.
+ * @type { [key: string]: string[]}
+ * @default
+ * ```ts
+ * { "image/*": [] }
+ * ```
+ * @example accept={["image/png", "image/jpeg"]}
+ */
+ accept?: DropzoneProps['accept'];
+
+ /**
+ * Maximum file size for the uploader.
+ * @type number | undefined
+ * @default 1024 * 1024 * 2 // 2MB
+ * @example maxSize={1024 * 1024 * 2} // 2MB
+ */
+ maxSize?: DropzoneProps['maxSize'];
+
+ /**
+ * Maximum number of files for the uploader.
+ * @type number | undefined
+ * @default 1
+ * @example maxFileCount={4}
+ */
+ maxFileCount?: DropzoneProps['maxFiles'];
+
+ /**
+ * Whether the uploader should accept multiple files.
+ * @type boolean
+ * @default false
+ * @example multiple
+ */
+ multiple?: boolean;
+
+ /**
+ * Whether the uploader is disabled.
+ * @type boolean
+ * @default false
+ * @example disabled
+ */
+ disabled?: boolean;
+}
+
+export function FileUploader(props: FileUploaderProps) {
+ const {
+ value: valueProp,
+ onValueChange,
+ accept = {
+ 'image/*': [],
+ },
+ maxSize = 1024 * 1024 * 2,
+ maxFileCount = 1,
+ multiple = false,
+ disabled = false,
+ className,
+ ...dropzoneProps
+ } = props;
+
+ const [files, setFiles] = useControllableState({
+ prop: valueProp,
+ onChange: onValueChange,
+ });
+
+ const onDrop = React.useCallback(
+ (acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
+ if (!multiple && maxFileCount === 1 && acceptedFiles.length > 1) {
+ toast({
+ title: 'Cannot upload more than 1 file at a time',
+ variant: 'destructive',
+ });
+ return;
+ }
+
+ if ((files?.length ?? 0) + acceptedFiles.length > maxFileCount) {
+ toast({ title: `Cannot upload more than ${maxFileCount} files`, variant: 'destructive' });
+ return;
+ }
+
+ const newFiles = acceptedFiles.map((file) =>
+ Object.assign(file, {
+ preview: URL.createObjectURL(file),
+ })
+ );
+
+ const updatedFiles = files ? [...files, ...newFiles] : newFiles;
+
+ setFiles(updatedFiles);
+
+ if (rejectedFiles.length > 0) {
+ rejectedFiles.forEach(({ file }) => {
+ toast({ title: `File ${file.name} was rejected`, variant: 'destructive' });
+ });
+ }
+ },
+
+ [files, maxFileCount, multiple, setFiles]
+ );
+
+ function onRemove(index: number) {
+ if (!files) return;
+ const newFiles = files.filter((_, i) => i !== index);
+ setFiles(newFiles);
+ onValueChange?.(newFiles);
+ }
+
+ const isDisabled = disabled || (files?.length ?? 0) >= maxFileCount;
+
+ return (
+
+ {!files?.length || (files?.length ?? 0) < maxFileCount ? (
+
1 || multiple}
+ disabled={isDisabled}>
+ {({ getRootProps, getInputProps, isDragActive }) => (
+
+
+ {isDragActive ? (
+
+
+
Drop the files here
+
+ ) : (
+
+
+
+
Drag {`'n'`} drop files here to start uploading
+
or
+
Click here to browse files
+
+
+ You can upload
+ {maxFileCount > 1
+ ? ` ${maxFileCount === Infinity ? 'multiple' : maxFileCount}
+ files (up to ${formatBytes(maxSize)} each)`
+ : ` a file with ${formatBytes(maxSize)}`}
+
+
+
+ )}
+
+ )}
+
+ ) : null}
+ {files?.length ? (
+
+ {files?.map((file, index) => onRemove(index)} />)}
+
+ ) : null}
+
+ );
+}
+
+interface FileCardProps {
+ file: File;
+ onRemove: () => void;
+}
+
+function FileCard({ file, onRemove }: FileCardProps) {
+ return (
+
+
+
+
+
{file.name}
+
{formatBytes(file.size)}
+
+
+
+
+
+
+
+ );
+}
+
+interface FilePreviewProps {}
+
+function FilePreview(props: FilePreviewProps) {
+ return ;
+}
diff --git a/web-citizen-reporting/src/components/ui/form.tsx b/web-citizen-reporting/src/components/ui/form.tsx
new file mode 100644
index 000000000..e450974ce
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/form.tsx
@@ -0,0 +1,207 @@
+import * as React from "react"
+import type * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+import type {
+ ControllerProps,
+ FieldError,
+ FieldErrorsImpl,
+ FieldPath,
+ FieldValues,
+ Merge
+} from "react-hook-form";
+import {
+ Controller,
+ FormProvider,
+ useFormContext,
+} from "react-hook-form"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> = {
+ name: TName
+}
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+)
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ )
+}
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext)
+ const itemContext = React.useContext(FormItemContext)
+ const { getFieldState, formState } = useFormContext();
+
+ const fieldState = getFieldState(fieldContext.name, formState)
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ")
+ }
+
+ const { id } = itemContext
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ }
+}
+
+type FormItemContextValue = {
+ id: string
+}
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId()
+
+ return (
+
+
+
+ )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField()
+
+ return (
+
+ )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+ return (
+
+ )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField()
+
+ return (
+
+ )
+})
+FormDescription.displayName = "FormDescription"
+
+type ArrayFieldError = Merge<
+ FieldError,
+ FieldErrorsImpl<{
+ value: string;
+ id: string;
+ }>>
+
+const isFormArrayField = (
+ arg: FieldError | ArrayFieldError
+): arg is ArrayFieldError => {
+ return (arg as ArrayFieldError).value !== undefined;
+};
+
+type FormFieldError = FieldError | ArrayFieldError | undefined;
+
+const FormMessage = React.forwardRef
+>
+ (({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField();
+
+ const extendedError = error as FormFieldError;
+
+ let body;
+ if (extendedError === undefined) {
+ body = children;
+ } else if (isFormArrayField(extendedError)) {
+ body = String(extendedError.value?.message);
+ } else {
+ body = String(extendedError.message);
+ }
+
+ if (!body) {
+ return null;
+ }
+
+ return (
+
+ {body}
+
+ );
+ });
+FormMessage.displayName = "FormMessage";
+
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+}
diff --git a/web-citizen-reporting/src/components/ui/input.tsx b/web-citizen-reporting/src/components/ui/input.tsx
new file mode 100644
index 000000000..2568dbd20
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/input.tsx
@@ -0,0 +1,82 @@
+import { Input as HeadlessInput, type InputProps as HeadlessInputProps } from '@headlessui/react'
+import { clsx } from 'clsx'
+import { forwardRef } from 'react'
+
+const dateTypes = ['date', 'datetime-local', 'month', 'time', 'week']
+type DateType = (typeof dateTypes)[number]
+
+export const Input = forwardRef<
+ HTMLInputElement,
+ { type?: 'email' | 'number' | 'password' | 'search' | 'tel' | 'text' | 'url' | DateType } & HeadlessInputProps
+>(function Input({ className, ...props }, ref) {
+ return (
+
+
+
+ )
+})
diff --git a/web-citizen-reporting/src/components/ui/label.tsx b/web-citizen-reporting/src/components/ui/label.tsx
new file mode 100644
index 000000000..7fc657512
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/label.tsx
@@ -0,0 +1,19 @@
+import * as React from 'react';
+import * as LabelPrimitive from '@radix-ui/react-label';
+import { cva, type VariantProps } from 'class-variance-authority';
+
+import { cn } from '@/lib/utils';
+
+const labelVariants = cva(
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 text-gray-700'
+);
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & VariantProps
+>(({ className, ...props }, ref) => (
+
+));
+Label.displayName = LabelPrimitive.Root.displayName;
+
+export { Label };
diff --git a/web-citizen-reporting/src/components/ui/multiple-select-dropdown.tsx b/web-citizen-reporting/src/components/ui/multiple-select-dropdown.tsx
new file mode 100644
index 000000000..0b268c6b8
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/multiple-select-dropdown.tsx
@@ -0,0 +1,232 @@
+import { CheckIcon, ChevronDown, XIcon } from 'lucide-react';
+import * as React from 'react';
+
+import { Button } from '@/components/ui/button';
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from '@/components/ui/command';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+import { Separator } from '@/components/ui/separator';
+import { cn } from '@/lib/utils';
+
+/**
+ * Props for MultiSelect component
+ */
+interface MultiSelectDropdownProps extends React.ButtonHTMLAttributes {
+ /**
+ * An array of option objects to be displayed in the multi-select component.
+ * Each option object has a label, value, and an optional icon.
+ */
+ options: {
+ /** The text to display for the option. */
+ label: string;
+ /** The unique value associated with the option. */
+ value: string;
+ }[];
+
+ /**
+ * Callback function triggered when the selected values change.
+ * Receives an array of the new selected values.
+ */
+ onValueChange: (value: string[]) => void;
+
+ /** The default selected values when the component mounts. */
+ defaultValue?: string[];
+
+ /**
+ * Placeholder text to be displayed when no values are selected.
+ * Optional, defaults to "Select options".
+ */
+ placeholder?: string;
+
+ /**
+ * The modality of the popover. When set to true, interaction with outside elements
+ * will be disabled and only popover content will be visible to screen readers.
+ * Optional, defaults to false.
+ */
+ modalPopover?: boolean;
+
+ /**
+ * If true, renders the multi-select component as a child of another component.
+ * Optional, defaults to false.
+ */
+ asChild?: boolean;
+
+ /**
+ * Additional class names to apply custom styles to the multi-select component.
+ * Optional, can be used to add custom styles.
+ */
+ className?: string;
+
+ /**
+ * Additional class names to apply custom styles to the multi-select component.
+ * Optional, can be used to add custom styles.
+ */
+ selectionDisplay: React.ReactNode;
+}
+
+export const MultiSelectDropdown = React.forwardRef(
+ (
+ {
+ options,
+ onValueChange,
+ defaultValue = [],
+ placeholder = 'Select options',
+ modalPopover = false,
+ asChild = false,
+ className,
+ selectionDisplay,
+ ...props
+ },
+ ref
+ ) => {
+ const [selectedValues, setSelectedValues] = React.useState(defaultValue);
+ const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
+
+ const handleInputKeyDown = (event: React.KeyboardEvent) => {
+ if (event.key === 'Enter') {
+ setIsPopoverOpen(true);
+ } else if (event.key === 'Backspace' && !event.currentTarget.value) {
+ const newSelectedValues = [...selectedValues];
+ newSelectedValues.pop();
+ setSelectedValues(newSelectedValues);
+ onValueChange(newSelectedValues);
+ }
+ };
+
+ const toggleOption = (option: string) => {
+ const newSelectedValues = selectedValues.includes(option)
+ ? selectedValues.filter((value) => value !== option)
+ : [...selectedValues, option];
+ setSelectedValues(newSelectedValues);
+ onValueChange(newSelectedValues);
+ };
+
+ const handleClear = () => {
+ setSelectedValues([]);
+ onValueChange([]);
+ };
+
+ const handleTogglePopover = () => {
+ setIsPopoverOpen((prev) => !prev);
+ };
+
+ const toggleAll = () => {
+ if (selectedValues.length === options.length) {
+ handleClear();
+ } else {
+ const allValues = options.map((option) => option.value);
+ setSelectedValues(allValues);
+ onValueChange(allValues);
+ }
+ };
+
+ React.useEffect(() => {
+ setSelectedValues(defaultValue);
+ }, [defaultValue]);
+
+ return (
+
+
+
+
+ setIsPopoverOpen(false)}>
+
+
+
+ No results found.
+
+
+
+
+
+ (Select All)
+
+ {options.map((option) => {
+ const isSelected = selectedValues.includes(option.value);
+ return (
+ toggleOption(option.value)}
+ className='cursor-pointer'>
+
+
+
+ {option.label}
+
+ );
+ })}
+
+
+
+
+ {selectedValues.length > 0 && (
+ <>
+
+ Clear
+
+
+ >
+ )}
+ setIsPopoverOpen(false)}
+ className='justify-center flex-1 max-w-full cursor-pointer'>
+ Close
+
+
+
+
+
+
+
+ );
+ }
+);
+
+MultiSelectDropdown.displayName = 'MultiSelectDropdown';
diff --git a/web-citizen-reporting/src/components/ui/popover.tsx b/web-citizen-reporting/src/components/ui/popover.tsx
new file mode 100644
index 000000000..fbc7adc60
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/popover.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import * as PopoverPrimitive from '@radix-ui/react-popover';
+
+import { cn } from '@/lib/utils';
+
+const Popover = PopoverPrimitive.Root;
+
+const PopoverTrigger = PopoverPrimitive.Trigger;
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
+
+
+
+));
+PopoverContent.displayName = PopoverPrimitive.Content.displayName;
+
+export { Popover, PopoverTrigger, PopoverContent };
diff --git a/web-citizen-reporting/src/components/ui/progress.tsx b/web-citizen-reporting/src/components/ui/progress.tsx
new file mode 100644
index 000000000..9dfb55664
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/progress.tsx
@@ -0,0 +1,22 @@
+import * as React from 'react';
+import * as ProgressPrimitive from '@radix-ui/react-progress';
+
+import { cn } from '@/lib/utils';
+
+const Progress = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, value, ...props }, ref) => (
+
+
+
+));
+Progress.displayName = ProgressPrimitive.Root.displayName;
+
+export { Progress };
diff --git a/web-citizen-reporting/src/components/ui/radio-group.tsx b/web-citizen-reporting/src/components/ui/radio-group.tsx
new file mode 100644
index 000000000..43b43b48b
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/radio-group.tsx
@@ -0,0 +1,42 @@
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const RadioGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+ )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ return (
+
+
+
+
+
+ )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/web-citizen-reporting/src/components/ui/ratings.tsx b/web-citizen-reporting/src/components/ui/ratings.tsx
new file mode 100644
index 000000000..ea9bdfc3c
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/ratings.tsx
@@ -0,0 +1,78 @@
+import * as React from 'react';
+import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
+
+import { cn } from '@/lib/utils';
+
+interface RatingItemProps extends React.ComponentPropsWithoutRef {
+ selectedValue: number | null;
+ scale: number;
+}
+
+const RatingItem = React.forwardRef, RatingItemProps>(
+ ({ className, value, selectedValue, scale, ...props }, ref) => {
+ return (
+
+ {value}
+
+ );
+ }
+);
+
+RatingItem.displayName = RadioGroupPrimitive.Item.displayName;
+
+interface RatingGroupProps extends React.ComponentPropsWithoutRef {
+ scale?: number;
+ lowerLabel?: string;
+ upperLabel?: string;
+}
+
+const RatingGroup = React.forwardRef, RatingGroupProps>(
+ ({ className, scale = 5, lowerLabel, upperLabel, ...props }, ref) => {
+
+ const [selectedValue, setSelectedValue] = React.useState(null);
+
+ React.useEffect(() => {
+ setSelectedValue(props.defaultValue ? Number(props.defaultValue) : null)
+ }, [props.defaultValue])
+
+ return (
+ {
+ setSelectedValue(Number(value));
+ props.onValueChange && props.onValueChange(value);
+ }}
+ tabIndex={0}>
+
+ {Array.from({ length: scale }, (_, i) => i + 1).map((value) => (
+
+ ))}
+
+
+
+ {lowerLabel}
+
+
+ {upperLabel}
+
+
+
+ );
+ }
+);
+RatingGroup.displayName = RadioGroupPrimitive.Root.displayName;
+
+export { RatingGroup, RatingItem };
diff --git a/web-citizen-reporting/src/components/ui/scroll-area.tsx b/web-citizen-reporting/src/components/ui/scroll-area.tsx
new file mode 100644
index 000000000..273c83c5a
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/scroll-area.tsx
@@ -0,0 +1,46 @@
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/web-citizen-reporting/src/components/ui/select.tsx b/web-citizen-reporting/src/components/ui/select.tsx
new file mode 100644
index 000000000..fe56d4d3a
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/select.tsx
@@ -0,0 +1,158 @@
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+ span]:line-clamp-1",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollDownButton.displayName =
+ SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, position = "popper", ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+
+
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+
+ {children}
+
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton,
+}
diff --git a/web-citizen-reporting/src/components/ui/separator.tsx b/web-citizen-reporting/src/components/ui/separator.tsx
new file mode 100644
index 000000000..6d7f12265
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/separator.tsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(
+ (
+ { className, orientation = "horizontal", decorative = true, ...props },
+ ref
+ ) => (
+
+ )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/web-citizen-reporting/src/components/ui/skeleton.tsx b/web-citizen-reporting/src/components/ui/skeleton.tsx
new file mode 100644
index 000000000..01b8b6d4f
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/skeleton.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({
+ className,
+ ...props
+}: React.HTMLAttributes) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/web-citizen-reporting/src/components/ui/stepper.tsx b/web-citizen-reporting/src/components/ui/stepper.tsx
new file mode 100644
index 000000000..40005bd42
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/stepper.tsx
@@ -0,0 +1,1019 @@
+import * as React from "react"
+import { cva } from "class-variance-authority"
+import { CheckIcon, Loader2, LucideIcon, X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "./button"
+import {
+ Collapsible,
+ CollapsibleContent,
+} from "./collapsible"
+
+// <---------- CONTEXT ---------->
+
+interface StepperContextValue extends StepperProps {
+ clickable?: boolean
+ isError?: boolean
+ isLoading?: boolean
+ isVertical?: boolean
+ stepCount?: number
+ expandVerticalSteps?: boolean
+ activeStep: number
+ initialStep: number
+}
+
+const StepperContext = React.createContext<
+ StepperContextValue & {
+ nextStep: () => void
+ prevStep: () => void
+ resetSteps: () => void
+ setStep: (step: number) => void
+ }
+>({
+ steps: [],
+ activeStep: 0,
+ initialStep: 0,
+ nextStep: () => {},
+ prevStep: () => {},
+ resetSteps: () => {},
+ setStep: () => {},
+})
+
+type StepperContextProviderProps = {
+ value: Omit
+ children: React.ReactNode
+}
+
+const StepperProvider = ({ value, children }: StepperContextProviderProps) => {
+ const isError = value.state === "error"
+ const isLoading = value.state === "loading"
+
+ const [activeStep, setActiveStep] = React.useState(value.initialStep)
+
+ const nextStep = () => {
+ setActiveStep((prev) => prev + 1)
+ }
+
+ const prevStep = () => {
+ setActiveStep((prev) => prev - 1)
+ }
+
+ const resetSteps = () => {
+ setActiveStep(value.initialStep)
+ }
+
+ const setStep = (step: number) => {
+ setActiveStep(step)
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+// <---------- HOOKS ---------->
+
+function usePrevious(value: T): T | undefined {
+ const ref = React.useRef()
+
+ React.useEffect(() => {
+ ref.current = value
+ }, [value])
+
+ return ref.current
+}
+
+function useStepper() {
+ const context = React.useContext(StepperContext)
+
+ if (context === undefined) {
+ throw new Error("useStepper must be used within a StepperProvider")
+ }
+
+ const { children, className, ...rest } = context
+
+ const isLastStep = context.activeStep === context.steps.length - 1
+ const hasCompletedAllSteps = context.activeStep === context.steps.length
+
+ const previousActiveStep = usePrevious(context.activeStep)
+
+ const currentStep = context.steps[context.activeStep]
+ const isOptionalStep = !!currentStep?.optional
+
+ const isDisabledStep = context.activeStep === 0
+
+ return {
+ ...rest,
+ isLastStep,
+ hasCompletedAllSteps,
+ isOptionalStep,
+ isDisabledStep,
+ currentStep,
+ previousActiveStep,
+ }
+}
+
+function useMediaQuery(query: string) {
+ const [value, setValue] = React.useState(false)
+
+ React.useEffect(() => {
+ function onChange(event: MediaQueryListEvent) {
+ setValue(event.matches)
+ }
+
+ const result = matchMedia(query)
+ result.addEventListener("change", onChange)
+ setValue(result.matches)
+
+ return () => result.removeEventListener("change", onChange)
+ }, [query])
+
+ return value
+}
+
+// <---------- STEPS ---------->
+
+type StepItem = {
+ id?: string
+ label?: string
+ description?: string
+ icon?: IconType
+ optional?: boolean
+}
+
+interface StepOptions {
+ orientation?: "vertical" | "horizontal"
+ state?: "loading" | "error"
+ responsive?: boolean
+ checkIcon?: IconType
+ errorIcon?: IconType
+ onClickStep?: (step: number, setStep: (step: number) => void) => void
+ mobileBreakpoint?: string
+ variant?: "circle" | "circle-alt" | "line"
+ expandVerticalSteps?: boolean
+ size?: "sm" | "md" | "lg"
+ styles?: {
+ /** Styles for the main container */
+ "main-container"?: string
+ /** Styles for the horizontal step */
+ "horizontal-step"?: string
+ /** Styles for the horizontal step container (button and labels) */
+ "horizontal-step-container"?: string
+ /** Styles for the vertical step */
+ "vertical-step"?: string
+ /** Styles for the vertical step container (button and labels) */
+ "vertical-step-container"?: string
+ /** Styles for the vertical step content */
+ "vertical-step-content"?: string
+ /** Styles for the step button container */
+ "step-button-container"?: string
+ /** Styles for the label and description container */
+ "step-label-container"?: string
+ /** Styles for the step label */
+ "step-label"?: string
+ /** Styles for the step description */
+ "step-description"?: string
+ }
+ variables?: {
+ "--step-icon-size"?: string
+ "--step-gap"?: string
+ }
+ scrollTracking?: boolean
+}
+interface StepperProps extends StepOptions {
+ children?: React.ReactNode
+ className?: string
+ initialStep: number
+ steps: StepItem[]
+}
+
+const VARIABLE_SIZES = {
+ sm: "36px",
+ md: "40px",
+ lg: "44px",
+}
+
+const Stepper = React.forwardRef(
+ (props, ref: React.Ref) => {
+ const {
+ className,
+ children,
+ orientation: orientationProp,
+ state,
+ responsive,
+ checkIcon,
+ errorIcon,
+ onClickStep,
+ mobileBreakpoint,
+ expandVerticalSteps = false,
+ initialStep = 0,
+ size,
+ steps,
+ variant,
+ styles,
+ variables,
+ scrollTracking = false,
+ ...rest
+ } = props
+
+ const childArr = React.Children.toArray(children)
+
+ const items = [] as React.ReactElement[]
+
+ const footer = childArr.map((child, _index) => {
+ if (!React.isValidElement(child)) {
+ throw new Error("Stepper children must be valid React elements.")
+ }
+ if (child.type === Step) {
+ items.push(child)
+ return null
+ }
+
+ return child
+ })
+
+ const stepCount = items.length
+
+ const isMobile = useMediaQuery(
+ `(max-width: ${mobileBreakpoint || "768px"})`
+ )
+
+ const clickable = !!onClickStep
+
+ const orientation = isMobile && responsive ? "vertical" : orientationProp
+
+ const isVertical = orientation === "vertical"
+
+ return (
+
+
+ {items}
+
+ {orientation === "horizontal" && (
+ {items}
+ )}
+ {footer}
+
+ )
+ }
+)
+
+Stepper.defaultProps = {
+ size: "md",
+ orientation: "horizontal",
+ responsive: true,
+}
+
+const VerticalContent = ({ children }: { children: React.ReactNode }) => {
+ const { activeStep } = useStepper()
+
+ const childArr = React.Children.toArray(children)
+ const stepCount = childArr.length
+
+ return (
+ <>
+ {React.Children.map(children, (child, i) => {
+ const isCompletedStep =
+ (React.isValidElement(child) &&
+ (child.props as any).isCompletedStep) ??
+ i < activeStep
+ const isLastStep = i === stepCount - 1
+ const isCurrentStep = i === activeStep
+
+ const stepProps = {
+ index: i,
+ isCompletedStep,
+ isCurrentStep,
+ isLastStep,
+ }
+
+ if (React.isValidElement(child)) {
+ return React.cloneElement(child, stepProps)
+ }
+ return null
+ })}
+ >
+ )
+}
+
+const HorizontalContent = ({ children }: { children: React.ReactNode }) => {
+ const { activeStep } = useStepper()
+ const childArr = React.Children.toArray(children)
+
+ if (activeStep > childArr.length) {
+ return null
+ }
+
+ return (
+ <>
+ {React.Children.map(childArr[activeStep], (node) => {
+ if (!React.isValidElement(node)) {
+ return null
+ }
+ return React.Children.map(node.props.children, (childNode) => childNode)
+ })}
+ >
+ )
+}
+
+// <---------- STEP ---------->
+
+interface StepProps extends React.HTMLAttributes {
+ label?: string | React.ReactNode
+ description?: string
+ icon?: IconType
+ state?: "loading" | "error"
+ checkIcon?: IconType
+ errorIcon?: IconType
+ isCompletedStep?: boolean
+ isKeepError?: boolean
+ onClickStep?: (step: number, setStep: (step: number) => void) => void
+}
+
+interface StepSharedProps extends StepProps {
+ isLastStep?: boolean
+ isCurrentStep?: boolean
+ index?: number
+ hasVisited: boolean | undefined
+ isError?: boolean
+ isLoading?: boolean
+}
+
+// Props which shouldn't be passed to to the Step component from the user
+interface StepInternalConfig {
+ index: number
+ isCompletedStep?: boolean
+ isCurrentStep?: boolean
+ isLastStep?: boolean
+}
+
+interface FullStepProps extends StepProps, StepInternalConfig {}
+
+const Step = React.forwardRef(
+ (props, ref: React.Ref) => {
+ const {
+ children,
+ description,
+ icon,
+ state,
+ checkIcon,
+ errorIcon,
+ index,
+ isCompletedStep,
+ isCurrentStep,
+ isLastStep,
+ isKeepError,
+ label,
+ onClickStep,
+ } = props as FullStepProps
+
+ const { isVertical, isError, isLoading, clickable } = useStepper()
+
+ const hasVisited = isCurrentStep || isCompletedStep
+
+ const sharedProps = {
+ isLastStep,
+ isCompletedStep,
+ isCurrentStep,
+ index,
+ isError,
+ isLoading,
+ clickable,
+ label,
+ description,
+ hasVisited,
+ icon,
+ isKeepError,
+ checkIcon,
+ state,
+ errorIcon,
+ onClickStep,
+ }
+
+ const renderStep = () => {
+ switch (isVertical) {
+ case true:
+ return (
+
+ {children}
+
+ )
+ default:
+ return
+ }
+ }
+
+ return renderStep()
+ }
+)
+
+// <---------- VERTICAL STEP ---------->
+
+type VerticalStepProps = StepSharedProps & {
+ children?: React.ReactNode
+}
+
+const verticalStepVariants = cva(
+ [
+ "flex flex-col relative transition-all duration-200",
+ "data-[completed=true]:[&:not(:last-child)]:after:bg-primary",
+ "data-[invalid=true]:[&:not(:last-child)]:after:bg-destructive",
+ ],
+ {
+ variants: {
+ variant: {
+ circle: cn(
+ "[&:not(:last-child)]:pb-[var(--step-gap)] [&:not(:last-child)]:gap-[var(--step-gap)]",
+ "[&:not(:last-child)]:after:content-[''] [&:not(:last-child)]:after:w-[2px] [&:not(:last-child)]:after:bg-border",
+ "[&:not(:last-child)]:after:inset-x-[calc(var(--step-icon-size)/2)]",
+ "[&:not(:last-child)]:after:absolute",
+ "[&:not(:last-child)]:after:top-[calc(var(--step-icon-size)+var(--step-gap))]",
+ "[&:not(:last-child)]:after:bottom-[var(--step-gap)]",
+ "[&:not(:last-child)]:after:transition-all [&:not(:last-child)]:after:duration-200"
+ ),
+ line: "flex-1 border-t-0 mb-4",
+ },
+ },
+ }
+)
+
+const VerticalStep = React.forwardRef(
+ (props, ref) => {
+ const {
+ children,
+ index,
+ isCompletedStep,
+ isCurrentStep,
+ label,
+ description,
+ icon,
+ hasVisited,
+ state,
+ checkIcon: checkIconProp,
+ errorIcon: errorIconProp,
+ onClickStep,
+ } = props
+
+ const {
+ checkIcon: checkIconContext,
+ errorIcon: errorIconContext,
+ isError,
+ isLoading,
+ variant,
+ onClickStep: onClickStepGeneral,
+ clickable,
+ expandVerticalSteps,
+ styles,
+ scrollTracking,
+ orientation,
+ steps,
+ setStep,
+ isLastStep: isLastStepCurrentStep,
+ previousActiveStep,
+ } = useStepper()
+
+ const opacity = hasVisited ? 1 : 0.8
+ const localIsLoading = isLoading || state === "loading"
+ const localIsError = isError || state === "error"
+
+ const isLastStep = index === steps.length - 1
+
+ const active =
+ variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep
+ const checkIcon = checkIconProp || checkIconContext
+ const errorIcon = errorIconProp || errorIconContext
+
+ const renderChildren = () => {
+ if (!expandVerticalSteps) {
+ return (
+
+ {
+ if (
+ // If the step is the first step and the previous step
+ // was the last step or if the step is not the first step
+ // This prevents initial scrolling when the stepper
+ // is located anywhere other than the top of the view.
+ scrollTracking &&
+ ((index === 0 &&
+ previousActiveStep &&
+ previousActiveStep === steps.length) ||
+ (index && index > 0))
+ ) {
+ node?.scrollIntoView({
+ behavior: "smooth",
+ block: "center",
+ })
+ }
+ }}
+ className="overflow-hidden data-[state=open]:animate-collapsible-down data-[state=closed]:animate-collapsible-up"
+ >
+ {children}
+
+
+ )
+ }
+ return children
+ }
+
+ return (
+
+ onClickStep?.(index || 0, setStep) ||
+ onClickStepGeneral?.(index || 0, setStep)
+ }
+ >
+
+
+
+
+
+
+
+ {renderChildren()}
+
+
+ )
+ }
+)
+
+// <---------- HORIZONTAL STEP ---------->
+
+const HorizontalStep = React.forwardRef(
+ (props, ref) => {
+ const {
+ isError,
+ isLoading,
+ onClickStep,
+ variant,
+ clickable,
+ checkIcon: checkIconContext,
+ errorIcon: errorIconContext,
+ styles,
+ steps,
+ setStep,
+ } = useStepper()
+
+ const {
+ index,
+ isCompletedStep,
+ isCurrentStep,
+ hasVisited,
+ icon,
+ label,
+ description,
+ isKeepError,
+ state,
+ checkIcon: checkIconProp,
+ errorIcon: errorIconProp,
+ } = props
+
+ const localIsLoading = isLoading || state === "loading"
+ const localIsError = isError || state === "error"
+
+ const opacity = hasVisited ? 1 : 0.8
+
+ const active =
+ variant === "line" ? isCompletedStep || isCurrentStep : isCompletedStep
+
+ const checkIcon = checkIconProp || checkIconContext
+ const errorIcon = errorIconProp || errorIconContext
+
+ return (
+ onClickStep?.(index || 0, setStep)}
+ ref={ref}
+ >
+
+
+
+
+
+
+
+ )
+ }
+)
+
+// <---------- STEP BUTTON CONTAINER ---------->
+
+type StepButtonContainerProps = StepSharedProps & {
+ children?: React.ReactNode
+}
+
+const StepButtonContainer = ({
+ isCurrentStep,
+ isCompletedStep,
+ children,
+ isError,
+ isLoading: isLoadingProp,
+ onClickStep,
+}: StepButtonContainerProps) => {
+ const {
+ clickable,
+ isLoading: isLoadingContext,
+ variant,
+ styles,
+ } = useStepper()
+
+ const currentStepClickable = clickable || !!onClickStep
+
+ const isLoading = isLoadingProp || isLoadingContext
+
+ if (variant === "line") {
+ return null
+ }
+
+ return (
+
+ )
+}
+
+// <---------- STEP ICON ---------->
+
+type IconType = LucideIcon | React.ComponentType | undefined
+
+const iconVariants = cva("", {
+ variants: {
+ size: {
+ sm: "size-4",
+ md: "size-4",
+ lg: "size-5",
+ },
+ },
+ defaultVariants: {
+ size: "md",
+ },
+})
+
+interface StepIconProps {
+ isCompletedStep?: boolean
+ isCurrentStep?: boolean
+ isError?: boolean
+ isLoading?: boolean
+ isKeepError?: boolean
+ icon?: IconType
+ index?: number
+ checkIcon?: IconType
+ errorIcon?: IconType
+}
+
+const StepIcon = React.forwardRef(
+ (props, ref) => {
+ const { size } = useStepper()
+
+ const {
+ isCompletedStep,
+ isCurrentStep,
+ isError,
+ isLoading,
+ isKeepError,
+ icon: CustomIcon,
+ index,
+ checkIcon: CustomCheckIcon,
+ errorIcon: CustomErrorIcon,
+ } = props
+
+ const Icon = React.useMemo(
+ () => (CustomIcon ? CustomIcon : null),
+ [CustomIcon]
+ )
+
+ const ErrorIcon = React.useMemo(
+ () => (CustomErrorIcon ? CustomErrorIcon : null),
+ [CustomErrorIcon]
+ )
+
+ const Check = React.useMemo(
+ () => (CustomCheckIcon ? CustomCheckIcon : CheckIcon),
+ [CustomCheckIcon]
+ )
+
+ return React.useMemo(() => {
+ if (isCompletedStep) {
+ if (isError && isKeepError) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+
+
+ )
+ }
+ if (isCurrentStep) {
+ if (isError && ErrorIcon) {
+ return (
+
+
+
+ )
+ }
+ if (isError) {
+ return (
+
+
+
+ )
+ }
+ if (isLoading) {
+ return (
+
+ )
+ }
+ }
+ if (Icon) {
+ return (
+
+
+
+ )
+ }
+ return (
+
+ {(index || 0) + 1}
+
+ )
+ }, [
+ isCompletedStep,
+ isCurrentStep,
+ isError,
+ isLoading,
+ Icon,
+ index,
+ Check,
+ ErrorIcon,
+ isKeepError,
+ ref,
+ size,
+ ])
+ }
+)
+
+// <---------- STEP LABEL ---------->
+
+interface StepLabelProps {
+ isCurrentStep?: boolean
+ opacity: number
+ label?: string | React.ReactNode
+ description?: string | null
+}
+
+const labelVariants = cva("", {
+ variants: {
+ size: {
+ sm: "text-sm",
+ md: "text-sm",
+ lg: "text-base",
+ },
+ },
+ defaultVariants: {
+ size: "md",
+ },
+})
+
+const descriptionVariants = cva("", {
+ variants: {
+ size: {
+ sm: "text-xs",
+ md: "text-xs",
+ lg: "text-sm",
+ },
+ },
+ defaultVariants: {
+ size: "md",
+ },
+})
+
+const StepLabel = ({
+ isCurrentStep,
+ opacity,
+ label,
+ description,
+}: StepLabelProps) => {
+ const { variant, styles, size, orientation } = useStepper()
+ const shouldRender = !!label || !!description
+
+ return shouldRender ? (
+
+ {!!label && (
+
+ {label}
+
+ )}
+ {!!description && (
+
+ {description}
+
+ )}
+
+ ) : null
+}
+
+export { Stepper, Step, useStepper }
+export type { StepProps, StepperProps, StepItem }
diff --git a/web-citizen-reporting/src/components/ui/switch.tsx b/web-citizen-reporting/src/components/ui/switch.tsx
new file mode 100644
index 000000000..dfc1e7f58
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/switch.tsx
@@ -0,0 +1,22 @@
+import * as SwitchPrimitive from '@radix-ui/react-switch';
+import { forwardRef } from 'react';
+
+const Switch = forwardRef((props, ref) => {
+ return (
+
+
+
+
+ {props.children && (
+
+ )}
+
+ );
+});
+
+export { Switch };
diff --git a/web-citizen-reporting/src/components/ui/table.tsx b/web-citizen-reporting/src/components/ui/table.tsx
new file mode 100644
index 000000000..4490fc994
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/table.tsx
@@ -0,0 +1,81 @@
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+const Table = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+Table.displayName = 'Table';
+
+const TableHeader = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+TableHeader.displayName = 'TableHeader';
+
+const TableBody = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+TableBody.displayName = 'TableBody';
+
+const TableFooter = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+ tr]:last:border-b-0', className)} {...props} />
+ )
+);
+TableFooter.displayName = 'TableFooter';
+
+const TableRow = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+TableRow.displayName = 'TableRow';
+
+const TableHead = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+ |
+ )
+);
+TableHead.displayName = 'TableHead';
+
+const TableCell = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ {props.children}
+ |
+ )
+);
+TableCell.displayName = 'TableCell';
+
+const TableCaption = React.forwardRef>(
+ ({ className, ...props }, ref) => (
+
+ )
+);
+TableCaption.displayName = 'TableCaption';
+
+export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow };
diff --git a/web-citizen-reporting/src/components/ui/tabs.tsx b/web-citizen-reporting/src/components/ui/tabs.tsx
new file mode 100644
index 000000000..bd3c5049d
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/tabs.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import * as TabsPrimitive from '@radix-ui/react-tabs';
+
+import { cn } from '@/lib/utils';
+
+const Tabs = TabsPrimitive.Root;
+
+const TabsList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsList.displayName = TabsPrimitive.List.displayName;
+
+const TabsTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
+
+const TabsContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+TabsContent.displayName = TabsPrimitive.Content.displayName;
+
+export { Tabs, TabsList, TabsTrigger, TabsContent };
diff --git a/web-citizen-reporting/src/components/ui/text.tsx b/web-citizen-reporting/src/components/ui/text.tsx
new file mode 100644
index 000000000..5d12a8023
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/text.tsx
@@ -0,0 +1,40 @@
+import { clsx } from 'clsx'
+// import { Link } from './link'
+
+export function Text({ className, ...props }: React.ComponentPropsWithoutRef<'p'>) {
+ return (
+
+ )
+}
+
+// export function TextLink({ className, ...props }: React.ComponentPropsWithoutRef) {
+// return (
+//
+// )
+// }
+
+export function Strong({ className, ...props }: React.ComponentPropsWithoutRef<'strong'>) {
+ return
+}
+
+export function Code({ className, ...props }: React.ComponentPropsWithoutRef<'code'>) {
+ return (
+
+ )
+}
diff --git a/web-citizen-reporting/src/components/ui/textarea.tsx b/web-citizen-reporting/src/components/ui/textarea.tsx
new file mode 100644
index 000000000..8df1575ce
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/textarea.tsx
@@ -0,0 +1,65 @@
+import {
+ Textarea as HeadlessTextarea,
+ type TextareaProps as HeadlessTextareaProps,
+} from "@headlessui/react";
+import { clsx } from "clsx";
+import { forwardRef } from "react";
+
+export const Textarea = forwardRef<
+ HTMLTextAreaElement,
+ { resizable?: boolean } & HeadlessTextareaProps
+>(function Textarea({ className, resizable = true, ...props }, ref) {
+ return (
+
+
+
+ );
+});
diff --git a/web-citizen-reporting/src/components/ui/timeline-slider.tsx b/web-citizen-reporting/src/components/ui/timeline-slider.tsx
new file mode 100644
index 000000000..b3e6cc77d
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/timeline-slider.tsx
@@ -0,0 +1,106 @@
+import * as React from 'react';
+import * as SliderPrimitive from '@radix-ui/react-slider';
+
+import { cn } from '@/lib/utils';
+import { differenceInHours, subHours, addHours, roundToNearestHours } from 'date-fns';
+
+export interface TimelineIntervalValue {
+ start: Date;
+ end: Date;
+}
+
+interface TimelineSliderProps {
+ name?: string;
+ disabled?: boolean;
+ orientation?: React.AriaAttributes['aria-orientation'];
+ start?: Date;
+ end?: Date;
+ value?: TimelineIntervalValue;
+ defaultValue?: TimelineIntervalValue;
+ onValueChange?(value: TimelineIntervalValue): void;
+ onValueCommit?(value: TimelineIntervalValue): void;
+ form?: string;
+ labelFormatter: (value: Date) => string;
+ className?: string | undefined;
+}
+
+const TimelineSlider = React.forwardRef, TimelineSliderProps>(
+ ({ className, labelFormatter, start, end, value, defaultValue, onValueChange, onValueCommit, ...props }, ref) => {
+ const rightEndpoint = end ?? roundToNearestHours(new Date(), { roundingMethod: 'ceil' });
+ const leftEndpoint = start ?? subHours(rightEndpoint, 12);
+
+ const initialValue = [
+ differenceInHours(value?.start ?? leftEndpoint, leftEndpoint),
+ differenceInHours(value?.end ?? rightEndpoint, leftEndpoint),
+ ];
+
+ const sliderValue = React.useMemo(() => {
+ if (value) {
+ return [differenceInHours(value.start, leftEndpoint), differenceInHours(value.end, leftEndpoint)];
+ }
+
+ return undefined;
+ }, [value]);
+
+ const sliderDefaultValue = React.useMemo(() => {
+ if (defaultValue)
+ return [differenceInHours(defaultValue.start, leftEndpoint), differenceInHours(defaultValue.end, leftEndpoint)];
+
+ return undefined;
+ }, [defaultValue]);
+
+ function internalOnValueChange(value: number[]) {
+ onValueChange?.({
+ start: addHours(leftEndpoint, value[0] ?? 0),
+ end: addHours(leftEndpoint, value[1] ?? 0),
+ });
+ }
+
+ function internalOnValueCommit(value: number[]) {
+ onValueCommit?.({
+ start: addHours(leftEndpoint, value[0] ?? 0),
+ end: addHours(leftEndpoint, value[1] ?? 0),
+ });
+ }
+
+ return (
+
+
+
+
+
+ {initialValue.map((value, index) => (
+
+
+ {labelFormatter && (
+
+ {labelFormatter(addHours(leftEndpoint, value))}
+
+ )}
+
+
+ ))}
+
+
+ );
+ }
+);
+TimelineSlider.displayName = 'TimelineSlider';
+
+export { TimelineSlider };
diff --git a/web-citizen-reporting/src/components/ui/toast.tsx b/web-citizen-reporting/src/components/ui/toast.tsx
new file mode 100644
index 000000000..a82247753
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/toast.tsx
@@ -0,0 +1,127 @@
+import * as React from "react"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef
+
+type ToastActionElement = React.ReactElement
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+}
diff --git a/web-citizen-reporting/src/components/ui/toaster.tsx b/web-citizen-reporting/src/components/ui/toaster.tsx
new file mode 100644
index 000000000..a2209ba58
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/toaster.tsx
@@ -0,0 +1,33 @@
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast"
+import { useToast } from "@/components/ui/use-toast"
+
+export function Toaster() {
+ const { toasts } = useToast()
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title}}
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/web-citizen-reporting/src/components/ui/toggle.tsx b/web-citizen-reporting/src/components/ui/toggle.tsx
new file mode 100644
index 000000000..9ecac28ee
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/toggle.tsx
@@ -0,0 +1,43 @@
+import * as React from "react"
+import * as TogglePrimitive from "@radix-ui/react-toggle"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline:
+ "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
+ },
+ size: {
+ default: "h-10 px-3",
+ sm: "h-9 px-2.5",
+ lg: "h-11 px-5",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const Toggle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, size, ...props }, ref) => (
+
+))
+
+Toggle.displayName = TogglePrimitive.Root.displayName
+
+export { Toggle, toggleVariants }
diff --git a/web-citizen-reporting/src/components/ui/tooltip.tsx b/web-citizen-reporting/src/components/ui/tooltip.tsx
new file mode 100644
index 000000000..e121f0aea
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/tooltip.tsx
@@ -0,0 +1,28 @@
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+const TooltipProvider = TooltipPrimitive.Provider
+
+const Tooltip = TooltipPrimitive.Root
+
+const TooltipTrigger = TooltipPrimitive.Trigger
+
+const TooltipContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+))
+TooltipContent.displayName = TooltipPrimitive.Content.displayName
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/web-citizen-reporting/src/components/ui/use-callback-ref.ts b/web-citizen-reporting/src/components/ui/use-callback-ref.ts
new file mode 100644
index 000000000..f379a9c39
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/use-callback-ref.ts
@@ -0,0 +1,27 @@
+import * as React from "react"
+
+/**
+ * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-callback-ref/src/useCallbackRef.tsx
+ */
+
+/**
+ * A custom hook that converts a callback to a ref to avoid triggering re-renders when passed as a
+ * prop or avoid re-executing effects when passed as a dependency
+ */
+function useCallbackRef unknown>(
+ callback: T | undefined
+): T {
+ const callbackRef = React.useRef(callback)
+
+ React.useEffect(() => {
+ callbackRef.current = callback
+ })
+
+ // https://github.com/facebook/react/issues/19240
+ return React.useMemo(
+ () => ((...args) => callbackRef.current?.(...args)) as T,
+ []
+ )
+}
+
+export { useCallbackRef }
diff --git a/web-citizen-reporting/src/components/ui/use-controllable-state.ts b/web-citizen-reporting/src/components/ui/use-controllable-state.ts
new file mode 100644
index 000000000..4957af225
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/use-controllable-state.ts
@@ -0,0 +1,67 @@
+import * as React from "react"
+import { useCallbackRef } from "./use-callback-ref"
+
+
+/**
+ * @see https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/useControllableState.tsx
+ */
+
+type UseControllableStateParams = {
+ prop?: T | undefined
+ defaultProp?: T | undefined
+ onChange?: (state: T) => void
+}
+
+type SetStateFn = (prevState?: T) => T
+
+function useControllableState({
+ prop,
+ defaultProp,
+ onChange = () => {},
+}: UseControllableStateParams) {
+ const [uncontrolledProp, setUncontrolledProp] = useUncontrolledState({
+ defaultProp,
+ onChange,
+ })
+ const isControlled = prop !== undefined
+ const value = isControlled ? prop : uncontrolledProp
+ const handleChange = useCallbackRef(onChange)
+
+ const setValue: React.Dispatch> =
+ React.useCallback(
+ (nextValue) => {
+ if (isControlled) {
+ const setter = nextValue as SetStateFn
+ const value =
+ typeof nextValue === "function" ? setter(prop) : nextValue
+ if (value !== prop) handleChange(value as T)
+ } else {
+ setUncontrolledProp(nextValue)
+ }
+ },
+ [isControlled, prop, setUncontrolledProp, handleChange]
+ )
+
+ return [value, setValue] as const
+}
+
+function useUncontrolledState({
+ defaultProp,
+ onChange,
+}: Omit, "prop">) {
+ const uncontrolledState = React.useState(defaultProp)
+ const [value] = uncontrolledState
+ const prevValueRef = React.useRef(value)
+ const handleChange = useCallbackRef(onChange)
+
+ React.useEffect(() => {
+ if (prevValueRef.current !== value) {
+ handleChange(value as T)
+ prevValueRef.current = value
+ }
+ }, [value, prevValueRef, handleChange])
+
+ return uncontrolledState
+}
+
+export { useControllableState }
diff --git a/web-citizen-reporting/src/components/ui/use-dialog.tsx b/web-citizen-reporting/src/components/ui/use-dialog.tsx
new file mode 100644
index 000000000..f1df5edea
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/use-dialog.tsx
@@ -0,0 +1,33 @@
+import { useRef, useState } from "react";
+
+export function useDialog() {
+ const [isOpen, setIsOpen] = useState(false);
+ const triggerRef = useRef();
+
+ function trigger() {
+ setIsOpen(true);
+ }
+
+ function dismiss() {
+ setIsOpen(false);
+ // @ts-ignore
+ triggerRef.current?.focus();
+ }
+
+ return {
+ triggerProps: {
+ ref: triggerRef,
+ onClick: trigger,
+ },
+ dialogProps: {
+ open: isOpen,
+ // @ts-ignore
+ onOpenChange: open => {
+ if (open) trigger();
+ else dismiss();
+ },
+ },
+ trigger,
+ dismiss,
+ };
+}
diff --git a/web-citizen-reporting/src/components/ui/use-toast.ts b/web-citizen-reporting/src/components/ui/use-toast.ts
new file mode 100644
index 000000000..16713070d
--- /dev/null
+++ b/web-citizen-reporting/src/components/ui/use-toast.ts
@@ -0,0 +1,192 @@
+// Inspired by react-hot-toast library
+import * as React from "react"
+
+import type {
+ ToastActionElement,
+ ToastProps,
+} from "@/components/ui/toast"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 1000000
+
+type ToasterToast = ToastProps & {
+ id: string
+ title?: React.ReactNode
+ description?: React.ReactNode
+ action?: ToastActionElement
+}
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const
+
+let count = 0
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER
+ return count.toString()
+}
+
+type ActionType = typeof actionTypes
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"]
+ toast: ToasterToast
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"]
+ toast: Partial
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map>()
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId)
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ })
+ }, TOAST_REMOVE_DELAY)
+
+ toastTimeouts.set(toastId, timeout)
+}
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ }
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ }
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId)
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id)
+ })
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ }
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ }
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ }
+ }
+}
+
+const listeners: Array<(state: State) => void> = []
+
+let memoryState: State = { toasts: [] }
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action)
+ listeners.forEach((listener) => {
+ listener(memoryState)
+ })
+}
+
+type Toast = Omit
+
+function toast({ ...props }: Toast) {
+ const id = genId()
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ })
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss()
+ },
+ },
+ })
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ }
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState)
+
+ React.useEffect(() => {
+ listeners.push(setState)
+ return () => {
+ const index = listeners.indexOf(setState)
+ if (index > -1) {
+ listeners.splice(index, 1)
+ }
+ }
+ }, [state])
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ }
+}
+
+export { useToast, toast }
diff --git a/web-citizen-reporting/src/hooks.ts b/web-citizen-reporting/src/hooks.ts
new file mode 100644
index 000000000..69120fb59
--- /dev/null
+++ b/web-citizen-reporting/src/hooks.ts
@@ -0,0 +1,121 @@
+import { useMutation, useQuery, UseQueryResult } from "@tanstack/react-query";
+import { useNavigate } from "@tanstack/react-router";
+import { useReducer } from "react";
+import { noAuthApi } from "./common/no-auth-api";
+import { CitizenReportPageResponse, LevelNode } from "./common/types";
+import { locationReducer, LocationState } from "./location-reducer";
+
+type LocationsLevelsResponse = { nodes: LevelNode[] };
+
+type UseLocationsLevelsResult = UseQueryResult<
+ Record,
+ Error
+>;
+
+export const useCitizenForms = (electionRoundId: string) => {
+ return useQuery({
+ queryKey: ["citizen-reporting-forms"],
+ queryFn: async () => {
+ const response = await noAuthApi.get(
+ `/election-rounds/${electionRoundId}/citizen-reporting-forms`,
+ {}
+ );
+
+ if (response.status !== 200) {
+ throw new Error("Failed to fetch forms");
+ }
+
+ return response.data;
+ },
+ enabled: !!electionRoundId,
+ });
+};
+
+export function useLocationsNodes(electionRoundId: string) {
+ return useQuery({
+ queryKey: ["locations", "nodes"],
+ queryFn: async () => {
+ const response = await noAuthApi.get(
+ `/election-rounds/${electionRoundId}/locations:fetchAll`
+ );
+
+ return response.data.nodes;
+ },
+ enabled: !!electionRoundId,
+ });
+}
+
+export function useLocationsLevels(
+ electionRoundId: string
+): UseLocationsLevelsResult {
+ return useQuery({
+ queryKey: ["locations", "levels"],
+ queryFn: async () => {
+ const response = await noAuthApi.get(
+ `/election-rounds/${electionRoundId}/locations:fetchAll`
+ );
+
+ return response.data.nodes.reduce>(
+ (group, node) => ({
+ ...group,
+ [node.depth]: [...(group[node.depth] ?? []), node],
+ }),
+ {}
+ );
+ },
+ enabled: !!electionRoundId,
+ });
+}
+
+export type HandleReducerSearchParams = {
+ level: keyof LocationState;
+ value: string;
+};
+
+export const useLocationFilters = (electionRoundId: string) => {
+ const [search, dispatch] = useReducer(locationReducer, {});
+ const { data: nodes } = useLocationsNodes(electionRoundId);
+ const nodesWithLocationId = new Map(
+ nodes
+ ?.filter((node) => "locationId" in node)
+ .map((node) => [node.id, node.locationId])
+ );
+
+ const lastNodeIdSelected = Object.values(search)
+ .filter((nodeId) => nodeId)
+ .at(-1);
+ const locationId =
+ lastNodeIdSelected && nodesWithLocationId.get(lastNodeIdSelected);
+
+ const handleLocationChange = ({
+ level,
+ value,
+ }: HandleReducerSearchParams) => {
+ return dispatch({ type: "SET_FILTER", level, value: Number(value) });
+ };
+
+ return { search, locationId, handleLocationChange };
+};
+
+export const usePostFormMutation = (electionRoundId: string) => {
+ const navigate = useNavigate();
+
+ const postFormMutation = useMutation({
+ mutationFn: (obj: any) => {
+ return noAuthApi.post(
+ `/election-rounds/${electionRoundId}/citizen-reports`,
+ obj
+ );
+ },
+
+ onSuccess: () => {
+ navigate({ to: "/thank-you" });
+ },
+
+ onError: (err) => {
+ alert("An error occurred");
+ console.error(err);
+ },
+ });
+ return { postFormMutation };
+};
diff --git a/web-citizen-reporting/src/i18n.ts b/web-citizen-reporting/src/i18n.ts
new file mode 100644
index 000000000..6d167a3a0
--- /dev/null
+++ b/web-citizen-reporting/src/i18n.ts
@@ -0,0 +1,36 @@
+import i18n from "i18next";
+import { initReactI18next } from "react-i18next";
+
+import en from "./locales/en.json";
+import ro from "./locales/ro.json";
+
+const resources = {
+ en: {
+ translation: en,
+ },
+ ro: {
+ translation: ro,
+ },
+} as const;
+
+export type Dict = typeof resources.en;
+
+const DETECTION_OPTIONS = {
+ order: ["localStorage", "navigator"],
+ caches: ["localStorage"],
+};
+
+i18n.use(initReactI18next).init({
+ debug: true,
+ defaultNS: "translation",
+ contextSeparator: "|",
+ detection: DETECTION_OPTIONS,
+ interpolation: {
+ escapeValue: false,
+ },
+ resources,
+ load: "languageOnly",
+ fallbackLng: "en",
+});
+
+export default i18n;
diff --git a/web-citizen-reporting/src/lib/csv-helpers.ts b/web-citizen-reporting/src/lib/csv-helpers.ts
new file mode 100644
index 000000000..82d2e9f73
--- /dev/null
+++ b/web-citizen-reporting/src/lib/csv-helpers.ts
@@ -0,0 +1,29 @@
+// Utility function to convert an array of objects to CSV format
+export const convertToCSV = (data: any[]) => {
+ const csvRows: string[] = [];
+
+ // Extract headers
+ const headers = Object.keys(data[0]);
+ csvRows.push(headers.join(',')); // Push headers as the first row
+
+ // Map through the data and add rows
+ data.forEach((row) => {
+ const values = headers.map((header) => `"${row[header]}"`);
+ csvRows.push(values.join(','));
+ });
+
+ return csvRows.join('\n');
+};
+
+// Function to trigger download of CSV file
+export const downloadCSV = (csvData: string, filename: string) => {
+ const blob = new Blob([csvData], { type: 'text/csv' });
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url); // Clean up the URL
+};
diff --git a/web-citizen-reporting/src/lib/utils.ts b/web-citizen-reporting/src/lib/utils.ts
new file mode 100644
index 000000000..91b2cd472
--- /dev/null
+++ b/web-citizen-reporting/src/lib/utils.ts
@@ -0,0 +1,550 @@
+import {
+ FormType,
+ RatingScaleType,
+ TranslatedString,
+ UserPayload,
+ ZFormType,
+} from "@/common/types";
+import i18n from "@/i18n";
+import { redirect } from "@tanstack/react-router";
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+export function valueOrDefault(
+ value: number | null | undefined,
+ fallbackValue: number
+): number {
+ if (value === null || value === undefined || isNaN(value)) {
+ return fallbackValue;
+ }
+
+ return value;
+}
+
+// https://colorhunt.co/palettes/pastel
+const colors = [
+ "#618264",
+ "#79ac78",
+ "#b0d9b1",
+ "#d0e7d2",
+ "#ecee81",
+ "#8ddfcb",
+ "#82a0d8",
+ "#edb7ed",
+ "#ef9595",
+ "#efb495",
+ "#efd595",
+ "#ebef95",
+ "#94a684",
+ "#aec3ae",
+ "#e4e4d0",
+ "#ffeef4",
+ "#fff3da",
+ "#dfccfb",
+ "#d0bfff",
+ "#beadfa",
+ "#96b6c5",
+ "#adc4ce",
+ "#eee0c9",
+ "#f1f0e8",
+ "#c8e4b2",
+ "#9ed2be",
+ "#7eaa92",
+ "#ffd9b7",
+ "#ffc6ac",
+ "#fff6dc",
+ "#c4c1a4",
+ "#9e9fa5",
+ "#faf3f0",
+ "#d4e2d4",
+ "#ffcacc",
+ "#dbc4f0",
+ "#a1ccd1",
+ "#f4f2de",
+ "#e9b384",
+ "#7c9d96",
+ "#aac8a7",
+ "#c3edc0",
+ "#e9ffc2",
+ "#fdffae",
+ "#ff9b9b",
+ "#ffd6a5",
+ "#fffec4",
+ "#cbffa9",
+ "#f1c27b",
+ "#ffd89c",
+ "#a2cdb0",
+ "#85a389",
+ "#a0c49d",
+ "#c4d7b2",
+ "#e1ecc8",
+ "#f7ffe5",
+ "#c2dedc",
+ "#ece5c7",
+ "#cdc2ae",
+ "#116a7b",
+ "#9babb8",
+ "#eee3cb",
+ "#d7c0ae",
+ "#967e76",
+ "#f2d8d8",
+ "#5c8984",
+ "#545b77",
+ "#374259",
+ "#f9f5f6",
+ "#f8e8ee",
+ "#fdcedf",
+ "#f2bed1",
+ "#c4dfdf",
+ "#d2e9e9",
+ "#e3f4f4",
+ "#f8f6f4",
+ "#f5f0bb",
+ "#dbdfaa",
+ "#b3c890",
+ "#73a9ad",
+ "#537188",
+ "#cbb279",
+ "#e1d4bb",
+ "#eeeeee",
+ "#8294c4",
+ "#acb1d6",
+ "#dbdfea",
+ "#ffead2",
+ "#bfccb5",
+ "#7c96ab",
+ "#b7b7b7",
+ "#edc6b1",
+ "#fdf4f5",
+ "#e8a0bf",
+ "#ba90c6",
+ "#c0dbea",
+ "#ddffbb",
+ "#c7e9b0",
+ "#b3c99c",
+ "#a4bc92",
+ "#b2a4ff",
+ "#ffb4b4",
+ "#ffdeb4",
+ "#fdf7c3",
+ "#fff2cc",
+ "#ffd966",
+ "#f4b183",
+ "#dfa67b",
+ "#d5b4b4",
+ "#e4d0d0",
+ "#f5ebeb",
+ "#bbd6b8",
+ "#aec2b6",
+ "#94af9f",
+ "#dbe4c6",
+ "#ccd5ae",
+ "#e9edc9",
+ "#fefae0",
+ "#faedcd",
+ "#a86464",
+ "#b3e5be",
+ "#f5ffc9",
+ "#f7c8e0",
+ "#dfffd8",
+ "#b4e4ff",
+ "#95bdff",
+ "#b9f3e4",
+ "#ea8fea",
+ "#ffaacf",
+ "#f6e6c2",
+ "#b5f1cc",
+ "#e5fdd1",
+ "#c9f4aa",
+ "#fcc2fc",
+ "#6096b4",
+ "#93bfcf",
+ "#bdcdd6",
+ "#eee9da",
+ "#a7727d",
+ "#eddbc7",
+ "#f8ead8",
+ "#f9f5e7",
+ "#aae3e2",
+ "#d9acf5",
+ "#ffcefe",
+ "#fdebed",
+ "#b9f3fc",
+ "#aee2ff",
+ "#93c6e7",
+ "#fedeff",
+ "#7286d3",
+ "#8ea7e9",
+ "#e5e0ff",
+ "#fff2f2",
+ "#eac7c7",
+ "#a0c3d2",
+ "#f7f5eb",
+ "#eae0da",
+];
+
+export function getTagColor(tag: string) {
+ tag = tag.toLocaleLowerCase();
+ let hash = 0;
+ for (let i = 0; i < tag.length; i++) {
+ hash = tag.charCodeAt(i) + ((hash << 5) - hash);
+ }
+
+ return colors[Math.abs(hash) % colors.length];
+}
+
+export function redirectIfNotAuth(): void {
+ const token = localStorage.getItem("token");
+ if (!token) {
+ throw redirect({
+ to: "/login",
+ });
+ }
+}
+
+export function parseJwt(token: string | undefined): UserPayload {
+ let base64Url: string | undefined = token!.split(".")[1];
+ let base64 = base64Url!.replace(/-/g, "+").replace(/_/g, "/");
+ let jsonPayload = decodeURIComponent(
+ window
+ .atob(base64)
+ .split("")
+ .map(function (c) {
+ return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
+ })
+ .join("")
+ );
+
+ return JSON.parse(jsonPayload) as UserPayload;
+}
+
+export function ratingScaleToNumber(scale: RatingScaleType): number {
+ switch (scale) {
+ case RatingScaleType.OneTo3: {
+ return 3;
+ }
+ case RatingScaleType.OneTo4: {
+ return 4;
+ }
+ case RatingScaleType.OneTo5: {
+ return 5;
+ }
+ case RatingScaleType.OneTo6: {
+ return 6;
+ }
+ case RatingScaleType.OneTo7: {
+ return 7;
+ }
+ case RatingScaleType.OneTo8: {
+ return 8;
+ }
+ case RatingScaleType.OneTo9: {
+ return 9;
+ }
+ case RatingScaleType.OneTo10: {
+ return 10;
+ }
+ default: {
+ return 5;
+ }
+ }
+}
+
+export function buildURLSearchParams(data: any) {
+ const params = new URLSearchParams();
+
+ Object.entries(data)
+ .filter(([_, value]) => !!value)
+ .forEach(([key, value]) => {
+ if (Array.isArray(value)) {
+ // @ts-ignore
+ value.forEach((value) => params.append(key, value.toString()));
+ } else {
+ // @ts-ignore
+ params.append(key, value.toString());
+ }
+ });
+
+ return params;
+}
+
+export function round(value: number, decimals: number): number {
+ //@ts-ignore
+ return Number(Math.round(value + "e" + decimals) + "e-" + decimals);
+}
+
+export function isQueryFiltered(queryParams: Record): boolean {
+ return Object.entries(queryParams).some(
+ ([key, value]) =>
+ !["PageNumber", "PageSize", "SortColumnName", "SortOrder"].includes(
+ key
+ ) && Boolean(value)
+ );
+}
+
+export const isNotNilOrWhitespace = (input?: string | null) =>
+ (input?.trim()?.length || 0) > 0;
+
+export const isNilOrWhitespace = (input?: string | null) =>
+ (input?.trim()?.length || 0) === 0;
+
+export function takewhile(arr: T[], predicate: (value: T) => boolean): T[] {
+ const result: T[] = [];
+ for (let i = 0; i < arr.length; i++) {
+ if (!predicate(arr[i]!)) {
+ break;
+ }
+ result.push(arr[i]!);
+ }
+ return result;
+}
+
+export function mapFormType(formType: FormType): string {
+ switch (formType) {
+ case ZFormType.Values.Opening:
+ return i18n.t("form.type.opening");
+ case ZFormType.Values.Voting:
+ return i18n.t("form.type.voting");
+ case ZFormType.Values.ClosingAndCounting:
+ return i18n.t("form.type.closingAndCounting");
+ case ZFormType.Values.CitizenReporting:
+ return i18n.t("form.type.citizenReporting");
+ case ZFormType.Values.IncidentReporting:
+ return i18n.t("form.type.incidentReporting");
+ case ZFormType.Values.PSI:
+ return i18n.t("form.type.psi");
+ case ZFormType.Values.Other:
+ return i18n.t("form.type.other");
+
+ default:
+ return "Unknown";
+ }
+}
+
+/**
+ * Creates a new Translated String containing all available languages
+ * @param availableLanguages available translations list
+ * @param languageCode language code for which to add value
+ * @param value value to set for required languageCode
+ * @returns new instance of @see {@link TranslatedString}
+ */
+export const newTranslatedString = (
+ availableLanguages: string[],
+ languageCode: string,
+ value: string = ""
+): TranslatedString => {
+ const translatedString: TranslatedString = {};
+ availableLanguages.forEach((language) => {
+ translatedString[language] = "";
+ });
+
+ translatedString[languageCode] = value;
+
+ return translatedString;
+};
+
+/**
+ * Creates a new Translated String containing all available languages
+ * @param availableLanguages available translations list
+ * @param value value to set for required languageCode
+ * @returns new instance of @see {@link TranslatedString}
+ */
+export const emptyTranslatedString = (
+ availableLanguages: string[],
+ value: string = ""
+): TranslatedString => {
+ const translatedString: TranslatedString = {};
+ availableLanguages.forEach((language) => {
+ translatedString[language] = value;
+ });
+
+ return translatedString;
+};
+
+export const updateTranslationString = (
+ translatedString: TranslatedString | undefined,
+ availableLanguages: string[],
+ languageCode: string,
+ value: string
+): TranslatedString => {
+ if (translatedString === undefined) {
+ translatedString = newTranslatedString(availableLanguages, languageCode);
+ }
+
+ translatedString[languageCode] = value;
+
+ return translatedString;
+};
+
+/**
+ * Clones translation from a language code to a language code in @see {@link TranslatedString} instance
+ * @param translatedString a instance of @see {@link TranslatedString}
+ * @param fromLanguageCode language code from which to borrow translation
+ * @param toLanguageCode destination
+ * @param defaultValue default value
+ * @returns new instance of @see {@link TranslatedString}
+ */
+export const cloneTranslation = (
+ translatedString: TranslatedString | undefined,
+ fromLanguageCode: string,
+ toLanguageCode: string,
+ defaultValue: string = ""
+): TranslatedString | undefined => {
+ if (translatedString) {
+ translatedString[toLanguageCode] =
+ translatedString[fromLanguageCode] ?? defaultValue;
+ }
+
+ return translatedString;
+};
+
+/**
+ * Changes language code to another in @see {@link TranslatedString} instance
+ * @param translatedString a instance of @see {@link TranslatedString}
+ * @param fromLanguageCode language code from which to borrow translation
+ * @param toLanguageCode destination
+ * @param defaultValue default value
+ * @returns new instance of @see {@link TranslatedString}
+ */
+export const changeLanguageCode = (
+ translatedString: TranslatedString | undefined,
+ fromLanguageCode: string,
+ toLanguageCode: string,
+ defaultValue: string = ""
+): TranslatedString => {
+ if (translatedString === undefined) {
+ return {};
+ }
+
+ const text = translatedString[fromLanguageCode];
+ delete translatedString[fromLanguageCode];
+
+ return {
+ ...translatedString,
+ [toLanguageCode]: text ?? defaultValue,
+ };
+};
+
+/**
+ * Gets translation from a translated string.
+ * If translation string is undefined or it does not contain translation for the requested language code then it will return a default value
+ * @param translatedString a instance of @see {@link TranslatedString}
+ * @param languageCode language code for which to get translation
+ * @param value value to set for required languageCode
+ * @returns translation or a default value
+ */
+export const getTranslationOrDefault = (
+ translatedString: TranslatedString | undefined,
+ languageCode: string,
+ value: string = ""
+): string => {
+ if (translatedString === undefined) {
+ return value;
+ }
+
+ const translation = translatedString[languageCode];
+ if (translation === undefined) {
+ return value;
+ }
+
+ return translation;
+};
+
+export function formatBytes(
+ bytes: number,
+ opts: {
+ decimals?: number;
+ sizeType?: "accurate" | "normal";
+ } = {}
+) {
+ const { decimals = 0, sizeType = "normal" } = opts;
+
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
+ const accurateSizes = ["Bytes", "KiB", "MiB", "GiB", "TiB"];
+ if (bytes === 0) return "0 Byte";
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
+ return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${
+ sizeType === "accurate" ? accurateSizes[i] ?? "Bytest" : sizes[i] ?? "Bytes"
+ }`;
+}
+
+/**
+ * Ensures translated string contains a value for every available languages
+ * @param availableLanguages available translations list
+ * @param value value to set for required languageCode
+ * @returns new instance of @see {@link TranslatedString}
+ */
+export const ensureTranslatedStringCorrectness = (
+ translatedString: TranslatedString | null | undefined,
+ availableLanguages: string[],
+ value: string = ""
+): TranslatedString => {
+ if (translatedString === undefined || translatedString === null)
+ return emptyTranslatedString(availableLanguages);
+
+ availableLanguages.forEach((language) => {
+ if (translatedString[language] === undefined) {
+ translatedString[language] = value;
+ }
+ });
+
+ return translatedString;
+};
+
+export async function sleep(ms: number) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+export const toKebabCase = (str: string): string => {
+ return str
+ .replace(/([a-z])([A-Z])/g, "$1-$2") // Handle camelCase (e.g., myString -> my-string)
+ .replace(/\s+/g, "-") // Replace spaces with hyphens
+ .replace(/_+/g, "-") // Replace underscores with hyphens
+ .replace(/[^\w\-]+/g, "") // Remove all non-word characters except hyphens
+ .replace(/--+/g, "-") // Remove multiple hyphens
+ .toLowerCase(); // Convert the string to lowercase
+};
+
+export const getFileCategory = (
+ mimeType: string
+): "image" | "video" | "audio" | "unknown" => {
+ if (mimeType.startsWith("image/")) {
+ return "image";
+ } else if (mimeType.startsWith("video/")) {
+ return "video";
+ } else if (mimeType.startsWith("audio/")) {
+ return "audio";
+ } else {
+ return "unknown";
+ }
+};
+
+export const toBoolean = (value: string | undefined): boolean | undefined => {
+ if (value === undefined) return undefined;
+
+ if (value.toLowerCase().trim() === "true") {
+ return true;
+ }
+
+ if (value.toLowerCase().trim() === "false") {
+ return false;
+ }
+
+ return undefined;
+};
+
+export function getValueOrDefault(
+ value: T | undefined | null,
+ defaultValue: T
+): T {
+ return value !== undefined && value !== null ? value : defaultValue;
+}
+
+export function omit(obj: T, key: K): Omit {
+ const { [key]: _, ...rest } = obj;
+ return rest;
+}
diff --git a/web-citizen-reporting/src/locales/en.json b/web-citizen-reporting/src/locales/en.json
new file mode 100644
index 000000000..517c70ab5
--- /dev/null
+++ b/web-citizen-reporting/src/locales/en.json
@@ -0,0 +1,398 @@
+{
+ "app": {
+ "action": {
+ "activate": "Activate",
+ "add": "Add",
+ "addChoice": "Add choice",
+ "apply": "Apply",
+ "attach": "Attach",
+ "back": "Back",
+ "cancel": "Cancel",
+ "clear": "Clear",
+ "close": "Close",
+ "confirm": "Confirm",
+ "create": "Create",
+ "deactivate": "Deactivate",
+ "delete": "Delete",
+ "download": "Download",
+ "edit": "Edit",
+ "exit": "Exit",
+ "export": "Export",
+ "filter": "Filter",
+ "leave": "Leave",
+ "remove": "Remove",
+ "reset": "Reset",
+ "restore": "Restore",
+ "save": "Save",
+ "select": "Select",
+ "update": "Update",
+ "upload": "Upload",
+ "view": "View",
+ "publish": "Publish"
+ }
+ },
+ "app.input.pleaseSpecify": "Please specify",
+ "election-round.action.create": "Create new election event",
+ "election-round.field.country": "Country",
+ "election-round.field.englishTitle": "English title",
+ "election-round.field.startDate": "Start date",
+ "election-round.field.title": "Title",
+ "election-round.placeholder.englishTitle": "Title in English",
+ "election-round.placeholder.title": "Title",
+ "form-template.field.code": "Form Code",
+ "form-template.field.name": "Form Name",
+ "form-template.field.description": "Description",
+ "form-template.field.defaultLanguage": "Base language",
+ "form-template.field.formTemplateType": "Form template type",
+ "form-template.field.languages": "Available languages",
+ "form-template.field.status": "Status",
+ "form-template.placeholder.code": "Form code",
+ "form-template.placeholder.name": "Form name",
+ "form-template.placeholder.description": "Form description",
+ "form-template.placeholder.defaultLanguage": "Select a default language",
+ "form-template.placeholder.languages": "Select available languages",
+ "navigation.button.back": "Back",
+ "navigation.button.next": "Next",
+ "navigation.button.submit": "Submit",
+ "questionEditor.questionType.textQuestion": "Text question",
+ "questionEditor.questionType.numberQuestion": "Number question",
+ "questionEditor.questionType.dateQuestion": "Date question",
+ "questionEditor.questionType.singleSelectQuestion": "Single select question",
+ "questionEditor.questionType.multiSelectQuestion": "Multi select question",
+ "questionEditor.questionType.ratingQuestion": "Rating question",
+ "questionEditor.question.code": "ID code",
+ "questionEditor.question.codeDescription": "Maximum limit of 16 characters. You can use combinations of letters and numbers.",
+ "questionEditor.question.text": "Question text",
+ "questionEditor.question.textRequired": "Question text is required",
+ "questionEditor.question.helptext": "Details (helptext)",
+ "questionEditor.question.iconPlaceholder": "Icon SVG code",
+ "questionEditor.question.inputPlaceholder": "Placeholder",
+ "questionEditor.question.displayLogic": "Apply display logic to this question",
+ "questionEditor.question.displayLogic.chooseQuestion": "Display this question only when answer for previous question",
+ "questionEditor.question.displayLogic.invalid": "You need to have a prior question of type number question/rating question/single select / multiple select to enable display logic",
+ "questionEditor.question.ratingScale.oneTo3": "One to 3",
+ "questionEditor.question.ratingScale.oneTo4": "One to 4",
+ "questionEditor.question.ratingScale.oneTo5": "One to 5",
+ "questionEditor.question.ratingScale.oneTo6": "One to 6",
+ "questionEditor.question.ratingScale.oneTo7": "One to 7",
+ "questionEditor.question.ratingScale.oneTo8": "One to 8",
+ "questionEditor.question.ratingScale.oneTo9": "One to 9",
+ "questionEditor.question.ratingScale.oneTo10": "One to 10",
+ "questionEditor.question.scale.lowerLabel": "Lower label",
+ "questionEditor.question.scale.upperLabel": "Upper label",
+ "questionEditor.ratingQuestion.scale": "Scale",
+ "questionEditor.selectQuestion.toMultiSelectQuestion": "Convert to multi select question",
+ "questionEditor.selectQuestion.toSingleSelectQuestion": "Convert to single select question",
+ "questionEditor.selectQuestion.addFreeTextOption": "Add free text option",
+ "containers": {
+ "languageSelect": {
+ "placeholder": "Select language...",
+ "searchPlaceholder": "Search language...",
+ "noResults": "No language found."
+ },
+ "countrySelect": {
+ "placeholder": "Select country...",
+ "searchPlaceholder": "Search country...",
+ "noResults": "No country found."
+ }
+ },
+ "formTemplateType": {
+ "opening": "Opening",
+ "voting": "Voting",
+ "closingAndCounting": "Closing And Counting",
+ "other": "Other"
+ },
+ "form": {
+ "field.code": "Form Code",
+ "field.name": "Form Name",
+ "field.description": "Description",
+ "field.defaultLanguage": "Base language",
+ "field.languageCode": "Language",
+ "field.formType": "Form type",
+ "field.icon": "Icon",
+ "field.languages": "Available languages",
+ "field.status": "Status",
+ "placeholder.code": "Form code",
+ "placeholder.icon": "",
+ "placeholder.name": "Form name",
+ "placeholder.description": "Form description",
+ "placeholder.defaultLanguage": "Select a default language",
+ "placeholder.languages": "Select available languages",
+ "type": {
+ "opening": "Opening",
+ "voting": "Voting",
+ "closingAndCounting": "Closing And Counting",
+ "psi": "PSI",
+ "other": "Other",
+ "citizenReporting": "Citizen reporting",
+ "incidentReporting": "Incident reporting"
+ },
+ "status": {
+ "drafted": "Drafted",
+ "published": "Published",
+ "obsolete": "Obsolete"
+ }
+ },
+ "observers": {
+ "addObserver": {
+ "title": "Add a new observer",
+ "firstName": "First name",
+ "lastName": "Last name",
+ "email": "Email",
+ "phone": "Phone number",
+ "addBtnText": "Add individual observer",
+ "onSuccess": "Observer created",
+ "onError": "Error adding observer"
+ }
+ },
+ "ngoAdminDashboard": {
+ "title": "Dashboard",
+ "subtitle": "Key indicators",
+ "observersAccounts": {
+ "cardTitle": "Observers Accounts",
+ "indicatorTitle": "Total accounts"
+ },
+ "observersOnFieldCardTitle": "Observers on field",
+ "observersInPollingStations": {
+ "indicatorTitle": "Observers in polling stations",
+ "metricLabel": "With at least one question answered"
+ },
+ "pollingStationCardTitle": "Polling stations",
+ "stationsVisitedByAtLeastOneObserver": {
+ "indicatorTitle": "Stations visited by at least one observer",
+ "metricLabel": "coverage"
+ },
+ "timeSpentObserving": {
+ "cardTitle": "Time spent observing",
+ "indicatorTitle": "Based on start-end times reported"
+ },
+ "startedForms": {
+ "cardTitle": "Started forms",
+ "indicatorTitle": "forms started between {{interval}}"
+ },
+ "questionsAnswered": {
+ "cardTitle": "Questions answered",
+ "indicatorTitle": "questions answered between {{interval}}"
+ },
+ "flaggedAnswers": {
+ "cardTitle": "Flagged answers",
+ "indicatorTitle": "answers were flagged through forms between {{interval}}"
+ },
+ "quickReports": {
+ "cardTitle": "Quick reports",
+ "indicatorTitle": "quick reports were signalled between {{interval}}"
+ },
+ "citizenReports": {
+ "cardTitle": "Citizen reports",
+ "indicatorTitle": "citizen reports were signalled between {{interval}}"
+ },
+ "pollingStationsLevelsCards": {
+ "onFieldObservers": "On field observers",
+ "questionsAnswered": "Questions answered",
+ "flaggedAnswers": "Flagged answers",
+ "visitedPollingStations": "Visited polling stations",
+ "pollingStationsCoverage": "Polling station coverage (%)",
+ "timeSpentObserving": "Time spent observing (hours)",
+ "quickReports": "Quick reports"
+ }
+ },
+ "electionEvent": {
+ "eventDetails": {
+ "tabTitle": "Event Details",
+ "cardTitle": "Event details",
+ "title": "Election event title",
+ "englishTitle": "Election event English title",
+ "country": "Country",
+ "startDate": "Start date",
+ "status": "Status",
+ "coalition": {
+ "cardTitle": "Details on assigned organization(s)",
+ "coalitionName": "Coalition",
+ "leaderName": "Coalition coordinator",
+ "members": "Coalition members (NGOs)"
+ }
+ },
+ "pollingStations": {
+ "tabTitle": "Polling Stations",
+ "cardTitle": "Polling stations",
+ "headers": {
+ "number": "Number",
+ "address": "Address",
+ "tags": "Polling station tags",
+ "level1": "Level 1",
+ "level2": "Level 2",
+ "level3": "Level 3",
+ "level4": "Level 4",
+ "level5": "Level 5"
+ },
+ "resetFilters": "Reset filters"
+ },
+ "guides": {
+ "citizenGuidesTabTitle": "Citizen Guides",
+ "citizenGuidesCardTitle": "Citizen guides",
+ "citizenNotificationsTabTitle": "Citizen Notifications",
+ "citizenNotificationsCardTitle": "Citizen notifications",
+ "observerGuidesTabTitle": "Observer Guides",
+ "observerGuidesCardTitle": "Observer guides",
+ "headers": {
+ "title": "Title",
+ "uploadedOn": "Uploaded on",
+ "createdBy": "Created by"
+ },
+ "textGuide": "Text guide",
+ "urlGuide": "Url guide",
+ "documentGuide": "Attachment guide",
+ "buttonUploadCitizenGuide": "Upload citizen guide",
+ "buttonUploadObserverGuide": "Upload observer guide"
+ },
+ "observerForms": {
+ "tabTitle": "Observer Forms",
+ "cardTitle": "Observation forms",
+ "headers": {
+ "formCode": "Form Code",
+ "icon": "Icon",
+ "name": "Name",
+ "formType": "Form type",
+ "language": "Language",
+ "questions": "Questions",
+ "status": "Status",
+ "updatedOn": "Updated on",
+ "sharedWith": "Shared with"
+ },
+ "createDialogTitle": "Create form",
+ "resetFilters": "Reset filters"
+ },
+ "locations": {
+ "tabTitle": "Locations",
+ "cardTitle": "Locations",
+ "headers": {
+ "tags": "Location tags",
+ "level1": "Level 1",
+ "level2": "Level 2",
+ "level3": "Level 3",
+ "level4": "Level 4",
+ "level5": "Level 5"
+ },
+ "resetFilters": "Reset filters"
+ },
+ "form": {
+ "title": "Create new form",
+ "subtitle": "Choose how you'd like to start your form.",
+ "scratch": {
+ "title": "Start from scratch",
+ "description": "Create a completely new form",
+ "buttonText": "Begin new form"
+ },
+ "template": {
+ "title": "Start from a VM template",
+ "description": "Choose from available templates",
+ "buttonText": "Browse templates",
+ "useTemplate": "Use template",
+ "formBaseLanguage": "Base language",
+ "formBaseLanguageErr": "No base language specified",
+ "formDescription": "Description",
+ "formDescriptionErr": "This template has no description",
+ "formOtherLanguages": "Other languages",
+ "formQuestions": "Questions",
+ "formQuestionsErr": "No questions included in this template"
+ },
+ "reuse": {
+ "title": "Reuse a previous form",
+ "description": "Reuse or modify a form from a past election event you have monitored",
+ "buttonText": "Reuse previous form",
+ "reuseForm": "REuse form",
+ "formBaseLanguage": "Base language",
+ "formBaseLanguageErr": "No base language specified",
+ "formDescription": "Description",
+ "formDescriptionErr": "This template has no description",
+ "formOtherLanguages": "Other languages",
+ "formQuestions": "Questions",
+ "formQuestionsErr": "No questions included in this template"
+ }
+ }
+ },
+ "pagination": {
+ "dataTable": {
+ "resultsSummary": "Showing {{start}} to {{end}} of {{total}} results",
+ "perPage": "per page",
+ "goToFirstPage": "Go to first page",
+ "goToPreviousPage": "Go to previous page",
+ "goToNextPage": "Go to next page",
+ "goToLastPage": "Go to last page"
+ }
+ },
+ "formSubmissions": {
+ "followUpStatus": {
+ "NotApplicable": "Not Applicable",
+ "NeedsFollowUp": "Needs Follow-up",
+ "Resolved": "Resolved"
+ }
+ },
+ "incidentReports": {
+ "locationType": {
+ "PollingStation": "Polling station",
+ "OtherLocation": "Other locations"
+ },
+ "followUpStatus": {
+ "NotApplicable": "Not Applicable",
+ "NeedsFollowUp": "Needs Follow-up",
+ "Resolved": "Resolved"
+ }
+ },
+ "citizenReports": {
+ "followUpStatus": {
+ "NotApplicable": "Not Applicable",
+ "NeedsFollowUp": "Needs Follow-up",
+ "Resolved": "Resolved"
+ }
+ },
+ "quickReports": {
+ "followUpStatus": {
+ "NotApplicable": "Not Applicable",
+ "NeedsFollowUp": "Needs Follow-up",
+ "Resolved": "Resolved"
+ },
+ "locationType": {
+ "NotRelatedToAPollingStation": "Not Related To A Polling Station",
+ "OtherPollingStation": "Other Polling Station",
+ "VisitedPollingStation": "Visited Polling Station"
+ },
+ "incidentCategory": {
+ "PhysicalViolenceIntimidationPressure": "Physical violence/intimidation/pressure",
+ "CampaigningAtPollingStation": "Campaigning at the polling station",
+ "RestrictionOfObserversRights": "Restriction of observer's (representative's/media) rights",
+ "UnauthorizedPersonsAtPollingStation": "Unauthorized person(s) at the polling station",
+ "ViolationDuringVoterVerificationProcess": "Violation during voter verification process",
+ "VotingWithImproperDocumentation": "Voting with improper documentation",
+ "IllegalRestrictionOfVotersRightToVote": "Illegal restriction of voter's right to vote",
+ "DamagingOrSeizingElectionMaterials": "Damaging of/seizing election materials",
+ "ImproperFilingOrHandlingOfElectionDocumentation": "Improper filing/handling of election documentation",
+ "BallotStuffing": "Ballot stuffing",
+ "ViolationsRelatedToControlPaper": "Violations related to the control paper",
+ "NotCheckingVoterIdentificationSafeguardMeasures": "Not checking the voter identification safeguard measures",
+ "VotingWithoutVoterIdentificationSafeguardMeasures": "Voting without voter identification safeguard measures",
+ "BreachOfSecrecyOfVote": "Breach of secrecy of vote",
+ "ViolationsRelatedToMobileBallotBox": "Violations related to the mobile ballot box",
+ "NumberOfBallotsExceedsNumberOfVoters": "Number of ballots exceed the number of voters",
+ "ImproperInvalidationOrValidationOfBallots": "Improper invalidation / validation of ballots",
+ "FalsificationOrImproperCorrectionOfFinalProtocol": "Falsification / improper correction of the final protocol",
+ "RefusalToIssueCopyOfFinalProtocolOrIssuingImproperCopy": "Refusal to issue a copy of the final protocol/issuing an improper copy",
+ "ImproperFillingInOfFinalProtocol": "Improper filling in of the final protocol",
+ "ViolationOfSealingProceduresOfElectionMaterials": "Violation of the sealing procedures of the election materials",
+ "ViolationsRelatedToVoterLists": "Violations related to the voter lists",
+ "Other": "Other"
+ }
+ },
+ "enums": {
+ "questionsAnswered": {
+ "None": "None",
+ "Some": "Some",
+ "All": "All"
+ }
+ },
+ "responses": {
+ "title": "Responses",
+ "subtitle":"View all form answers and other reports submitted by your observers.",
+ "subtitleCoalitionLeader": "View all form answers and other reports submitted by coalition observers."
+ }
+}
\ No newline at end of file
diff --git a/web-citizen-reporting/src/locales/ro.json b/web-citizen-reporting/src/locales/ro.json
new file mode 100644
index 000000000..e8ecf5da7
--- /dev/null
+++ b/web-citizen-reporting/src/locales/ro.json
@@ -0,0 +1,288 @@
+{
+ "app": {
+ "action": {
+ "activate": "Activate",
+ "add": "Add",
+ "addChoice": "Add choice",
+ "apply": "Apply",
+ "attach": "Attach",
+ "back": "Back",
+ "cancel": "Cancel",
+ "clear": "Clear",
+ "close": "Close",
+ "confirm": "Confirm",
+ "create": "Create",
+ "deactivate": "Deactivate",
+ "delete": "Delete",
+ "download": "Download",
+ "edit": "Edit",
+ "exit": "Exit",
+ "export": "Export",
+ "filter": "Filter",
+ "leave": "Leave",
+ "remove": "Remove",
+ "reset": "Reset",
+ "restore": "Restore",
+ "save": "Save",
+ "select": "Select",
+ "update": "Update",
+ "upload": "Upload",
+ "view": "View",
+ "publish": "Publish"
+ }
+ },
+ "app.input.pleaseSpecify": "Please specify",
+ "election-round.action.create": "Create new election event",
+ "election-round.field.country": "Country",
+ "election-round.field.englishTitle": "English title",
+ "election-round.field.startDate": "Start date",
+ "election-round.field.title": "Title",
+ "election-round.placeholder.englishTitle": "Title in English",
+ "election-round.placeholder.title": "Title",
+ "form-template.field.code": "Form Code",
+ "form-template.field.name": "Form Name",
+ "form-template.field.description": "Description",
+ "form-template.field.defaultLanguage": "Base language",
+ "form-template.field.formTemplateType": "Form template type",
+ "form-template.field.languages": "Available languages",
+ "form-template.field.status": "Status",
+ "form-template.placeholder.code": "Form code",
+ "form-template.placeholder.name": "Form name",
+ "form-template.placeholder.description": "Form description",
+ "form-template.placeholder.defaultLanguage": "Select a default language",
+ "form-template.placeholder.languages": "Select available languages",
+ "navigation.button.back": "Back",
+ "navigation.button.next": "Next",
+ "navigation.button.submit": "Submit",
+ "questionEditor.questionType.textQuestion": "Text question",
+ "questionEditor.questionType.numberQuestion": "Number question",
+ "questionEditor.questionType.dateQuestion": "Date question",
+ "questionEditor.questionType.singleSelectQuestion": "Single select question",
+ "questionEditor.questionType.multiSelectQuestion": "Multi select question",
+ "questionEditor.questionType.ratingQuestion": "Rating question",
+ "questionEditor.question.code": "ID code",
+ "questionEditor.question.codeDescription": "Maximum limit of 16 characters. You can use combinations of letters and numbers.",
+ "questionEditor.question.text": "Question text",
+ "questionEditor.question.textRequired": "Question text is required",
+ "questionEditor.question.helptext": "Details (helptext)",
+ "questionEditor.question.inputPlaceholder": "Placeholder",
+ "questionEditor.question.displayLogic": "Apply display logic to this question",
+ "questionEditor.question.displayLogic.chooseQuestion": "Display this question only when answer for previous question",
+ "questionEditor.question.displayLogic.invalid": "You need to have a prior question of type number question/rating question/single select / multiple select to enable display logic",
+ "questionEditor.question.ratingScale.oneTo3": "One to 3",
+ "questionEditor.question.ratingScale.oneTo4": "One to 4",
+ "questionEditor.question.ratingScale.oneTo5": "One to 5",
+ "questionEditor.question.ratingScale.oneTo6": "One to 6",
+ "questionEditor.question.ratingScale.oneTo7": "One to 7",
+ "questionEditor.question.ratingScale.oneTo8": "One to 8",
+ "questionEditor.question.ratingScale.oneTo9": "One to 9",
+ "questionEditor.question.ratingScale.oneTo10": "One to 10",
+ "questionEditor.question.scale.lowerLabel": "Lower label",
+ "questionEditor.question.scale.upperLabel": "Upper label",
+ "questionEditor.ratingQuestion.scale": "Scale",
+ "questionEditor.selectQuestion.toMultiSelectQuestion": "Convert to multi select question",
+ "questionEditor.selectQuestion.toSingleSelectQuestion": "Convert to single select question",
+ "questionEditor.selectQuestion.addFreeTextOption": "Add free text option",
+ "containers": {
+ "languageSelect": {
+ "placeholder": "Select language...",
+ "searchPlaceholder": "Search language...",
+ "noResults": "No language found."
+ },
+ "countrySelect": {
+ "placeholder": "Select country...",
+ "searchPlaceholder": "Search country...",
+ "noResults": "No country found."
+ }
+ },
+ "formTemplateType": {
+ "opening": "Opening",
+ "voting": "Voting",
+ "closingAndCounting": "Closing And Counting",
+ "other": "Other"
+ },
+ "formType": {
+ "opening": "Opening",
+ "voting": "Voting",
+ "closingAndCounting": "Closing And Counting",
+ "psi": "PSI",
+ "other": "Other",
+ "citizenReporting": "Citizen reporting",
+ "incidentReporting": "Incident reporting"
+ },
+ "form": {
+ "field.code": "Form Code",
+ "field.name": "Form Name",
+ "field.description": "Description",
+ "field.defaultLanguage": "Base language",
+ "field.languageCode": "Language",
+ "field.formType": "Form type",
+ "field.Icon": "Icon",
+ "field.languages": "Available languages",
+ "field.status": "Status",
+ "placeholder.code": "Form code",
+ "placeholder.name": "Form name",
+ "placeholder.description": "Form description",
+ "placeholder.defaultLanguage": "Select a default language",
+ "placeholder.languages": "Select available languages"
+ },
+ "ngoAdminDashboard": {
+ "title": "Dashboard",
+ "subtitle": "Key indicators",
+
+ "observersAccounts": {
+ "cardTitle": "Observers Accounts",
+ "indicatorTitle": "Total accounts"
+ },
+
+ "observersOnFieldCardTitle": "Observers on field",
+
+ "observersInPollingStations": {
+ "indicatorTitle": "Observers in polling stations",
+ "metricLabel": "With at least one question answered"
+ },
+
+ "pollingStationCardTitle": "Polling stations",
+
+ "stationsVisitedByAtLeastOneObserver": {
+ "indicatorTitle": "Stations visited by at least one observer",
+ "metricLabel": "coverage"
+ },
+
+ "timeSpentObserving": {
+ "cardTitle": "Time spent observing",
+ "indicatorTitle": "Based on start-end times reported"
+ },
+
+ "startedForms": {
+ "cardTitle": "Started forms",
+ "indicatorTitle": "forms started between {{interval}}"
+ },
+
+ "questionsAnswered": {
+ "cardTitle": "Questions answered",
+ "indicatorTitle": "questions answered between {{interval}}"
+ },
+
+ "flaggedAnswers": {
+ "cardTitle": "Flagged answers",
+ "indicatorTitle": "answers were flagged through forms between {{interval}}"
+ },
+
+ "quickReports": {
+ "cardTitle": "Quick reports",
+ "indicatorTitle": "quick reports were signalled between {{interval}}"
+ },
+
+ "citizenReports": {
+ "cardTitle": "Citizen reports",
+ "indicatorTitle": "citizen reports were signalled between {{interval}}"
+ },
+
+ "pollingStationsLevelsCards": {
+ "onFieldObservers": "On field observers",
+ "questionsAnswered": "Questions answered",
+ "flaggedAnswers": "Flagged answers",
+ "visitedPollingStations": "Visited polling stations",
+ "pollingStationsCoverage": "Polling station coverage (%)",
+ "timeSpentObserving": "Time spent observing (hours)",
+ "quickReports": "Quick reports"
+ }
+ },
+ "electionEvent": {
+ "eventDetails": {
+ "tabTitle": "Event Details",
+ "cardTitle": "Event details",
+ "title": "Election event title",
+ "englishTitle": "Election event English title",
+ "country": "Country",
+ "startDate": "Start date",
+ "status": "Status",
+ "coalition": {
+ "cardTitle": "Details on assigned organization(s)"
+ }
+ },
+ "pollingStations": {
+ "tabTitle": "Polling Stations",
+ "cardTitle": "Polling stations",
+ "headers": {
+ "number": "Number",
+ "address": "Address",
+ "tags": "Polling station tags",
+ "level1": "Level 1",
+ "level2": "Level 2",
+ "level3": "Level 3",
+ "level4": "Level 4",
+ "level5": "Level 5"
+ },
+ "resetFilters": "Reset filters"
+ },
+ "guides": {
+ "citizenGuidesTabTitle": "Citizen Guides",
+ "citizenGuidesCardTitle": "Citizen guides",
+
+ "citizenNotificationsTabTitle": "Citizen Notifications",
+ "citizenNotificationsCardTitle": "Citizen notifications",
+
+ "observerGuidesTabTitle": "Observer Guides",
+ "observerGuidesCardTitle": "Observer guides",
+ "headers": {
+ "title": "Title",
+ "uploadedOn": "Uploaded on",
+ "createdBy": "Created by"
+ },
+ "textGuide": "Text guide",
+ "urlGuide": "Url guide",
+ "documentGuide": "Attachment guide",
+ "buttonUploadCitizenGuide": "Upload citizen guide",
+ "buttonUploadObserverGuide": "Upload observer guide"
+ },
+ "observerForms": {
+ "tabTitle": "Observer Forms",
+ "cardTitle": "Observation forms",
+ "headers": {
+ "formCode": "Form Code",
+ "name": "Name",
+ "formType": "Form type",
+ "language": "Language",
+ "questions": "Questions",
+ "status": "Status",
+ "updatedOn": "Updated on",
+ "sharedWith": "Shared with"
+ },
+ "createDialogTitle": "Create form",
+ "resetFilters": "Reset filters"
+ },
+ "locations": {
+ "tabTitle": "Locations",
+ "cardTitle": "Locations",
+ "headers": {
+ "tags": "Location tags",
+ "level1": "Level 1",
+ "level2": "Level 2",
+ "level3": "Level 3",
+ "level4": "Level 4",
+ "level5": "Level 5"
+ },
+ "resetFilters": "Reset filters"
+ },
+ "form": {
+ "title": "Creează un nou formular",
+ "subtitle": "Alege modul în care vrei să începi."
+ }
+ },
+ "pagination": {
+ "dataTable": {
+ "resultsSummary": "Showing {{start}} to {{end}} of {{total}} results",
+ "perPage": "per pagină",
+ "goToFirstPage": "Go to first page",
+ "goToPreviousPage": "Go to previous page",
+ "goToNextPage": "Go to next page",
+ "goToLastPage": "Go to last page"
+ }
+ },
+ "responses": {
+ "title": "",
+ "subtitle":""
+ }
+}
diff --git a/web-citizen-reporting/src/location-reducer.ts b/web-citizen-reporting/src/location-reducer.ts
new file mode 100644
index 000000000..e57ec8d07
--- /dev/null
+++ b/web-citizen-reporting/src/location-reducer.ts
@@ -0,0 +1,66 @@
+export type LocationState = {
+ level1Filter?: number;
+ level2Filter?: number;
+ level3Filter?: number;
+ level4Filter?: number;
+ level5Filter?: number;
+};
+
+export type LocationAction =
+ | { type: "SET_FILTER"; level: keyof LocationState; value: number }
+ | { type: "CLEAR_FILTER"; level: keyof LocationState }
+ | { type: "RESET_FILTERS" };
+
+export const locationReducer = (
+ state: LocationState,
+ action: LocationAction
+): LocationState => {
+ switch (action.type) {
+ case "SET_FILTER":
+ switch (action.level) {
+ case "level1Filter":
+ return {
+ ...state,
+ level1Filter: action.value,
+ level2Filter: undefined,
+ level3Filter: undefined,
+ level4Filter: undefined,
+ level5Filter: undefined,
+ };
+ case "level2Filter":
+ return {
+ ...state,
+ level2Filter: action.value,
+ level3Filter: undefined,
+ level4Filter: undefined,
+ level5Filter: undefined,
+ };
+ case "level3Filter":
+ return {
+ ...state,
+ level3Filter: action.value,
+ level4Filter: undefined,
+ level5Filter: undefined,
+ };
+ case "level4Filter":
+ return {
+ ...state,
+ level4Filter: action.value,
+ level5Filter: undefined,
+ };
+ case "level5Filter":
+ return {
+ ...state,
+ level5Filter: action.value,
+ };
+ default:
+ return state;
+ }
+ case "CLEAR_FILTER":
+ return { ...state, [action.level]: undefined };
+ case "RESET_FILTERS":
+ return {};
+ default:
+ return state;
+ }
+};
diff --git a/web-citizen-reporting/src/main.tsx b/web-citizen-reporting/src/main.tsx
new file mode 100644
index 000000000..fba0b96c0
--- /dev/null
+++ b/web-citizen-reporting/src/main.tsx
@@ -0,0 +1,56 @@
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import {
+ createRouter,
+ ErrorComponent,
+ RouterProvider,
+} from "@tanstack/react-router";
+import React from "react";
+import { createRoot } from "react-dom/client";
+import { I18nextProvider } from "react-i18next";
+import { AlertDialogProvider } from "./components/ui/alert-dialog-provider";
+import i18n from "./i18n";
+import { routeTree } from "./routeTree.gen";
+import "./styles/tailwind.css";
+
+export const queryClient = new QueryClient();
+
+const DEFAULT_LANGUAGE = import.meta.env["VITE_DEFAULT_LANGUAGE"];
+const ELECTION_ROUND_ID = import.meta.env["VITE_ELECTION_ROUND_ID"];
+
+const router = createRouter({
+ routeTree,
+ defaultErrorComponent: ({ error }) => ,
+ context: {
+ queryClient,
+ DEFAULT_LANGUAGE,
+ ELECTION_ROUND_ID,
+ },
+ defaultPreload: "intent",
+ // Since we're using React Query, we don't want loader calls to ever be stale
+ // This will ensure that the loader is always called when the route is preloaded or visited
+ defaultPreloadStaleTime: 0,
+});
+
+function App() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
+
+const rootElement = document.querySelector("#root") as Element;
+if (!rootElement.innerHTML) {
+ const root = createRoot(rootElement);
+ root.render(
+
+
+
+