diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..46a2456
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,8 @@
+# حالتِ توسعه (npm run dev). درخواستها به /api میروند و Vite آنها را به
+# VITE_DEV_PROXY_TARGET پاس میدهد، پس CORS در توسعه اصلاً مطرح نیست.
+#
+# پیشفرض بکاندِ محلی است (python manage.py runserver). اگر بکاندِ محلی ندارید،
+# در فایلِ .env.development.local این را به آدرسِ سرور عوض کنید — ولی یادتان
+# باشد هرچه آنجا بزنید روی دیتابیسِ واقعی نوشته میشود.
+VITE_API_BASE_URL=/api
+VITE_DEV_PROXY_TARGET=http://127.0.0.1:8000
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..232afe0
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,8 @@
+# آدرسِ API در buildِ پروداکشن. تنها جایی از ریپو که دامنهی بکاند نوشته شده.
+#
+# برای عوض کردنِ دامنه دو راه هست:
+# ۱) همین خط را عوض کنید و دوباره build بگیرید.
+# ۲) بدونِ build: روی سرور، در dist/config.js مقدارِ apiBaseUrl را بنویسید.
+# آن فایل در زمانِ اجرا خوانده میشود و بر این مقدار مقدم است.
+# اگر nginxِ دامنهی فرانت /api را به بکاند پراکسی کند، مقدارِ /api کافی است.
+VITE_API_BASE_URL=https://api.math.tadrisino2.ir/api
diff --git a/.gitignore b/.gitignore
index b6eefa0..805d9ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
node_modules
+dist
-# متغیرهای محلی — هیچوقت کامیت نشوند
+# تنظیماتِ شخصیِ هر توسعهدهنده — کامیت نشوند
.env
.env.local
-dist
+.env.*.local
diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md
deleted file mode 100644
index 5df5c40..0000000
--- a/ATTRIBUTIONS.md
+++ /dev/null
@@ -1,3 +0,0 @@
-This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md).
-
-This Figma Make file includes photos from [Unsplash](https://unsplash.com) used under [license](https://unsplash.com/license).
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2d890b1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,84 @@
+# تدریسینو — فرانتِ اپِ ریاضی
+
+اپِ وبِ موبایلمحورِ تدریسینو (React + TypeScript + Vite + Tailwind). بکاندش ریپوی
+جداست (`Tadrisino/MathApp`)؛ قراردادِ API در `/api/docs/` همان بکاند است.
+
+## راهاندازی
+
+```bash
+npm install
+npm run dev # http://localhost:5173
+```
+
+در حالتِ توسعه، درخواستهای `/api` از خودِ Vite به بکاند پاس داده میشوند، پس CORS
+مطرح نیست. پیشفرض بکاندِ محلی است (`python manage.py runserver` روی پورتِ ۸۰۰۰).
+بدونِ بکاندِ محلی، یک فایلِ `.env.development.local` بسازید:
+
+```bash
+VITE_DEV_PROXY_TARGET=https://api.math.tadrisino2.ir
+```
+
+⚠️ آنوقت هر کاری در اپ بکنید روی دیتابیسِ واقعی نوشته میشود.
+
+| دستور | کار |
+|---|---|
+| `npm run dev` | سرورِ توسعه |
+| `npm run typecheck` | بررسیِ نوعها (strict) |
+| `npm run build` | type-check و بعد build در `dist/` |
+| `npm run preview` | اجرای همان build |
+
+## آدرسِ بکاند و عوض کردنِ دامنه
+
+دامنه هیچجای کد نیامده. آدرسِ API به این ترتیب خوانده میشود
+(`src/config.ts`):
+
+1. **`dist/config.js`** (از `public/config.js`) — در زمانِ اجرا. بعد از build هم روی
+ سرور قابلِ ویرایش است و build دوباره نمیخواهد.
+2. **`VITE_API_BASE_URL`** — در زمانِ build، از `.env.production`.
+3. **`/api`** — همدامنه، وقتی nginxِ دامنهی فرانت مسیرِ `/api` را به بکاند پراکسی کند.
+
+برای جابهجایی دامنه:
+
+- **سریع، بدونِ build:** روی سرور در `dist/config.js` بنویسید
+ `apiBaseUrl: "https://api.new-domain.ir/api"` و کشِ CDN را برای همین فایل پاک کنید.
+- **ماندگار:** خطِ `VITE_API_BASE_URL` در `.env.production` را عوض کنید و build بگیرید.
+- **سمتِ بکاند** هم دامنهی تازهی فرانت باید مجاز باشد (`FRONTEND_URL` در `.env`
+ بکاند). جزئیات در `docs/operations.md` همان ریپو.
+
+## دیپلوی
+
+```bash
+npm ci && npm run build
+```
+
+محتوای `dist/` را سرو کنید. اپ مسیرِ جدا (route) ندارد و همهچیز از `/` بار میشود؛
+فقط `index.html` و `config.js` نباید طولانی کش شوند (باقیِ فایلها نامِ hashدار دارند).
+درگاهِ پرداخت کاربر را به `/?orderId=…&status=…` برمیگرداند و لینکِ دعوت
+`/?ref=CODE` است؛ هر دو را خودِ اپ میخواند.
+
+## ساختار
+
+```
+src/
+ config.ts آدرسِ API (بالا را ببینید)
+ api/ تنها درِ ارتباط با سرور — هیچ جای دیگر fetch نمیزند
+ http.ts timeout، تکرارِ GET، refreshِ تکنسخهای، SSE
+ index.ts یک تابع برای هر اندپوینت
+ types.ts شکلِ پاسخها، از روی پاسخِ واقعیِ سرور
+ app/ App (جریانِ ورود) و MainApp (تبها و صفحهها)
+ nav/ ناوبریِ پشتهای، هماهنگ با دکمهی back گوشی
+ state/ دادهی مشترک (پروفایل، نقشه، …) و گفتگوی دستیار
+ screens/ یک فایل برای هر صفحه
+ components/ اجزای مشترک (سربرگ، برگهی پایین، …)
+ mascots/ سهیار: سیگما، دلتا، پی
+ lib/ اعدادِ فارسی، تاریخِ شمسی، صدا، localStorage
+```
+
+قاعدهها:
+
+- **اعداد:** برای نمایش `toFa`، برای ورودی `digitsOnly` (کیبوردِ فارسی ارقامِ
+ `۰-۹` میفرستد و `\d` در JS آنها را نمیشناسد).
+- **رنگها** در `src/styles/index.css` (`@theme`) تعریف شدهاند: `bg-coral`، `text-ink`، …
+- **خطا:** پیامِ کاربر همیشه از `friendlyError` میآید، نه متنِ خامِ سرور.
+- **کلاینت را از حافظه ننویسید.** قرارداد را از `/api/docs/` بکاند بخوانید و تغییرِ
+ `types.ts` را با پاسخِ واقعیِ سرور بسنجید.
diff --git a/default_shadcn_theme.css b/default_shadcn_theme.css
deleted file mode 100644
index 39e1b44..0000000
--- a/default_shadcn_theme.css
+++ /dev/null
@@ -1,120 +0,0 @@
-/* KEEP_IN_SYNC(fullscreen/resources/figmake/shadcn/globals.css) */
-
-:root {
- --font-size: 16px;
- --background: #ffffff;
- --foreground: oklch(0.145 0 0);
- --card: #ffffff;
- --card-foreground: oklch(0.145 0 0);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.145 0 0);
- --primary: #030213;
- --primary-foreground: oklch(1 0 0);
- --secondary: oklch(0.95 0.0058 264.53);
- --secondary-foreground: #030213;
- --muted: #ececf0;
- --muted-foreground: #717182;
- --accent: #e9ebef;
- --accent-foreground: #030213;
- --destructive: #d4183d;
- --destructive-foreground: #ffffff;
- --border: rgba(0, 0, 0, 0.1);
- --input: transparent;
- --input-background: #f3f3f5;
- --switch-background: #cbced4;
- --font-weight-medium: 500;
- --font-weight-normal: 400;
- --ring: oklch(0.708 0 0);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --radius: 0.625rem;
- --sidebar: oklch(0.985 0 0);
- --sidebar-foreground: oklch(0.145 0 0);
- --sidebar-primary: #030213;
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.97 0 0);
- --sidebar-accent-foreground: oklch(0.205 0 0);
- --sidebar-border: oklch(0.922 0 0);
- --sidebar-ring: oklch(0.708 0 0);
-}
-
-.dark {
- --background: oklch(0.145 0 0);
- --foreground: oklch(0.985 0 0);
- --card: oklch(0.145 0 0);
- --card-foreground: oklch(0.985 0 0);
- --popover: oklch(0.145 0 0);
- --popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.985 0 0);
- --primary-foreground: oklch(0.205 0 0);
- --secondary: oklch(0.269 0 0);
- --secondary-foreground: oklch(0.985 0 0);
- --muted: oklch(0.269 0 0);
- --muted-foreground: oklch(0.708 0 0);
- --accent: oklch(0.269 0 0);
- --accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.396 0.141 25.723);
- --destructive-foreground: oklch(0.637 0.237 25.331);
- --border: oklch(0.269 0 0);
- --input: oklch(0.269 0 0);
- --ring: oklch(0.439 0 0);
- --font-weight-medium: 500;
- --font-weight-normal: 400;
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.205 0 0);
- --sidebar-foreground: oklch(0.985 0 0);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0 0);
- --sidebar-accent: oklch(0.269 0 0);
- --sidebar-accent-foreground: oklch(0.985 0 0);
- --sidebar-border: oklch(0.269 0 0);
- --sidebar-ring: oklch(0.439 0 0);
-}
-
-@theme inline {
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-destructive-foreground: var(--destructive-foreground);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-input-background: var(--input-background);
- --color-switch-background: var(--switch-background);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
-}
diff --git a/guidelines/Guidelines.md b/guidelines/Guidelines.md
deleted file mode 100644
index 110f117..0000000
--- a/guidelines/Guidelines.md
+++ /dev/null
@@ -1,61 +0,0 @@
-**Add your own guidelines here**
-
diff --git a/index.html b/index.html
index be7eca1..738778e 100644
--- a/index.html
+++ b/index.html
@@ -1,22 +1,18 @@
-
-
-
-
-
-
-
- Update Educational App Mascot
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+ تدریسینو
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index 0a30838..e1c4aa5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,92 +1,36 @@
{
- "name": "@figma/my-make-file",
- "version": "0.0.1",
+ "name": "tadrisino-math-app",
+ "version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "@figma/my-make-file",
- "version": "0.0.1",
+ "name": "tadrisino-math-app",
+ "version": "2.0.0",
"dependencies": {
- "@emotion/react": "11.14.0",
- "@emotion/styled": "11.14.1",
- "@mui/icons-material": "7.3.5",
- "@mui/material": "7.3.5",
- "@popperjs/core": "2.11.8",
- "@radix-ui/react-accordion": "1.2.3",
- "@radix-ui/react-alert-dialog": "1.1.6",
- "@radix-ui/react-aspect-ratio": "1.1.2",
- "@radix-ui/react-avatar": "1.1.3",
- "@radix-ui/react-checkbox": "1.1.4",
- "@radix-ui/react-collapsible": "1.1.3",
- "@radix-ui/react-context-menu": "2.2.6",
- "@radix-ui/react-dialog": "1.1.6",
- "@radix-ui/react-dropdown-menu": "2.1.6",
- "@radix-ui/react-hover-card": "1.1.6",
- "@radix-ui/react-label": "2.1.2",
- "@radix-ui/react-menubar": "1.1.6",
- "@radix-ui/react-navigation-menu": "1.2.5",
- "@radix-ui/react-popover": "1.1.6",
- "@radix-ui/react-progress": "1.1.2",
- "@radix-ui/react-radio-group": "1.2.3",
- "@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.2",
- "@radix-ui/react-switch": "1.1.3",
- "@radix-ui/react-tabs": "1.1.3",
- "@radix-ui/react-toggle": "1.1.2",
- "@radix-ui/react-toggle-group": "1.1.2",
- "@radix-ui/react-tooltip": "1.1.8",
"canvas-confetti": "1.9.4",
- "class-variance-authority": "0.7.1",
- "clsx": "2.1.1",
- "cmdk": "1.1.1",
- "date-fns": "3.6.0",
- "embla-carousel-react": "8.6.0",
- "input-otp": "1.4.2",
"lucide-react": "0.487.0",
"motion": "12.23.24",
- "next-themes": "0.4.6",
- "react-day-picker": "8.10.1",
- "react-dnd": "16.0.1",
- "react-dnd-html5-backend": "16.0.1",
- "react-hook-form": "7.55.0",
- "react-popper": "2.3.0",
- "react-resizable-panels": "2.1.7",
- "react-responsive-masonry": "2.7.1",
- "react-router": "7.13.0",
- "react-slick": "0.31.0",
- "recharts": "2.15.2",
- "sonner": "2.0.3",
- "tailwind-merge": "3.2.0",
- "tw-animate-css": "1.3.8",
- "vaul": "1.1.2"
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "vazirmatn": "33.0.3"
},
"devDependencies": {
"@tailwindcss/vite": "4.1.12",
+ "@types/canvas-confetti": "1.9.0",
+ "@types/react": "18.3.31",
+ "@types/react-dom": "18.3.7",
"@vitejs/plugin-react": "4.7.0",
"tailwindcss": "4.1.12",
+ "typescript": "5.9.3",
"vite": "6.3.5"
- },
- "peerDependencies": {
- "react": "18.3.1",
- "react-dom": "18.3.1"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- }
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.29.7",
@@ -138,17 +82,11 @@
"url": "https://opencollective.com/babel"
}
},
- "node_modules/@babel/core/node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@babel/generator": {
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
"integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.8",
@@ -182,6 +120,7 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
"integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -191,6 +130,7 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
"integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.29.7",
@@ -232,6 +172,7 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -241,6 +182,7 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -271,9 +213,10 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.29.8",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
- "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
+ "version": "7.29.9",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz",
+ "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.8"
@@ -317,19 +260,11 @@
"@babel/core": "^7.0.0-0"
}
},
- "node_modules/@babel/runtime": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
- "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
"node_modules/@babel/template": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
"integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.7",
@@ -344,6 +279,7 @@
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
"integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.7",
@@ -362,6 +298,7 @@
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.29.7",
@@ -371,152 +308,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@emotion/babel-plugin": {
- "version": "11.13.5",
- "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
- "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/helper-module-imports": "^7.16.7",
- "@babel/runtime": "^7.18.3",
- "@emotion/hash": "^0.9.2",
- "@emotion/memoize": "^0.9.0",
- "@emotion/serialize": "^1.3.3",
- "babel-plugin-macros": "^3.1.0",
- "convert-source-map": "^1.5.0",
- "escape-string-regexp": "^4.0.0",
- "find-root": "^1.1.0",
- "source-map": "^0.5.7",
- "stylis": "4.2.0"
- }
- },
- "node_modules/@emotion/cache": {
- "version": "11.14.0",
- "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
- "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
- "license": "MIT",
- "dependencies": {
- "@emotion/memoize": "^0.9.0",
- "@emotion/sheet": "^1.4.0",
- "@emotion/utils": "^1.4.2",
- "@emotion/weak-memoize": "^0.4.0",
- "stylis": "4.2.0"
- }
- },
- "node_modules/@emotion/hash": {
- "version": "0.9.2",
- "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
- "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
- "license": "MIT"
- },
- "node_modules/@emotion/is-prop-valid": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
- "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
- "license": "MIT",
- "dependencies": {
- "@emotion/memoize": "^0.9.0"
- }
- },
- "node_modules/@emotion/memoize": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
- "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
- "license": "MIT"
- },
- "node_modules/@emotion/react": {
- "version": "11.14.0",
- "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
- "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "@emotion/babel-plugin": "^11.13.5",
- "@emotion/cache": "^11.14.0",
- "@emotion/serialize": "^1.3.3",
- "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
- "@emotion/utils": "^1.4.2",
- "@emotion/weak-memoize": "^0.4.0",
- "hoist-non-react-statics": "^3.3.1"
- },
- "peerDependencies": {
- "react": ">=16.8.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@emotion/serialize": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
- "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
- "license": "MIT",
- "dependencies": {
- "@emotion/hash": "^0.9.2",
- "@emotion/memoize": "^0.9.0",
- "@emotion/unitless": "^0.10.0",
- "@emotion/utils": "^1.4.2",
- "csstype": "^3.0.2"
- }
- },
- "node_modules/@emotion/sheet": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
- "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
- "license": "MIT"
- },
- "node_modules/@emotion/styled": {
- "version": "11.14.1",
- "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
- "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.18.3",
- "@emotion/babel-plugin": "^11.13.5",
- "@emotion/is-prop-valid": "^1.3.0",
- "@emotion/serialize": "^1.3.3",
- "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
- "@emotion/utils": "^1.4.2"
- },
- "peerDependencies": {
- "@emotion/react": "^11.0.0-rc.0",
- "react": ">=16.8.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@emotion/unitless": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
- "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
- "license": "MIT"
- },
- "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
- "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
- "license": "MIT",
- "peerDependencies": {
- "react": ">=16.8.0"
- }
- },
- "node_modules/@emotion/utils": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
- "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
- "license": "MIT"
- },
- "node_modules/@emotion/weak-memoize": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
- "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
- "license": "MIT"
- },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -959,44 +750,6 @@
"node": ">=18"
}
},
- "node_modules/@floating-ui/core": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
- "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/utils": "^0.2.12"
- }
- },
- "node_modules/@floating-ui/dom": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
- "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/core": "^1.8.0",
- "@floating-ui/utils": "^0.2.12"
- }
- },
- "node_modules/@floating-ui/react-dom": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz",
- "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/dom": "^1.8.0"
- },
- "peerDependencies": {
- "react": ">=16.8.0",
- "react-dom": ">=16.8.0"
- }
- },
- "node_modules/@floating-ui/utils": {
- "version": "0.2.12",
- "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
- "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
- "license": "MIT"
- },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -1014,6 +767,7 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1035,260 +789,30 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
- "node_modules/@mui/core-downloads-tracker": {
- "version": "7.3.11",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.11.tgz",
- "integrity": "sha512-a7I/b/nBTdXYz2cOSlEmkQ9WWE1x8FHpqMhFPp+Y1VPFxcOw91G5ELOHARQAGSPy5V+UCgJua6K/1x70bAtQPw==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- }
- },
- "node_modules/@mui/icons-material": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.5.tgz",
- "integrity": "sha512-LciL1GLMZ+VlzyHAALSVAR22t8IST4LCXmljcUSx2NOutgO2XnxdIp8ilFbeNf9wpo0iUFbAuoQcB7h+HHIf3A==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.4"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- },
- "peerDependencies": {
- "@mui/material": "^7.3.5",
- "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@mui/material": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz",
- "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.4",
- "@mui/core-downloads-tracker": "^7.3.5",
- "@mui/system": "^7.3.5",
- "@mui/types": "^7.4.8",
- "@mui/utils": "^7.3.5",
- "@popperjs/core": "^2.11.8",
- "@types/react-transition-group": "^4.4.12",
- "clsx": "^2.1.1",
- "csstype": "^3.1.3",
- "prop-types": "^15.8.1",
- "react-is": "^19.2.0",
- "react-transition-group": "^4.4.5"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- },
- "peerDependencies": {
- "@emotion/react": "^11.5.0",
- "@emotion/styled": "^11.3.0",
- "@mui/material-pigment-css": "^7.3.5",
- "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@emotion/react": {
- "optional": true
- },
- "@emotion/styled": {
- "optional": true
- },
- "@mui/material-pigment-css": {
- "optional": true
- },
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@mui/private-theming": {
- "version": "7.3.11",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.11.tgz",
- "integrity": "sha512-9B+YKms0fRHbNrqp9tOT/DNbNnU5gyvJ1o3qAGXfq8GmZcbJnE3At9x07Zr/o0pkhzg4aDdwXVqe4+AcgtOCPA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.6",
- "@mui/utils": "^7.3.11",
- "prop-types": "^15.8.1"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- },
- "peerDependencies": {
- "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@mui/styled-engine": {
- "version": "7.3.10",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz",
- "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.6",
- "@emotion/cache": "^11.14.0",
- "@emotion/serialize": "^1.3.3",
- "@emotion/sheet": "^1.4.0",
- "csstype": "^3.2.3",
- "prop-types": "^15.8.1"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- },
- "peerDependencies": {
- "@emotion/react": "^11.4.1",
- "@emotion/styled": "^11.3.0",
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@emotion/react": {
- "optional": true
- },
- "@emotion/styled": {
- "optional": true
- }
- }
- },
- "node_modules/@mui/system": {
- "version": "7.3.11",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.11.tgz",
- "integrity": "sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.6",
- "@mui/private-theming": "^7.3.11",
- "@mui/styled-engine": "^7.3.10",
- "@mui/types": "^7.4.12",
- "@mui/utils": "^7.3.11",
- "clsx": "^2.1.1",
- "csstype": "^3.2.3",
- "prop-types": "^15.8.1"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- },
- "peerDependencies": {
- "@emotion/react": "^11.5.0",
- "@emotion/styled": "^11.3.0",
- "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@emotion/react": {
- "optional": true
- },
- "@emotion/styled": {
- "optional": true
- },
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@mui/types": {
- "version": "7.4.12",
- "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz",
- "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.6"
- },
- "peerDependencies": {
- "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@mui/utils": {
- "version": "7.3.11",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.11.tgz",
- "integrity": "sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.28.6",
- "@mui/types": "^7.4.12",
- "@types/prop-types": "^15.7.15",
- "clsx": "^2.1.1",
- "prop-types": "^15.8.1",
- "react-is": "^19.2.3"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/mui-org"
- },
- "peerDependencies": {
- "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
- "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
@@ -1309,1316 +833,6 @@
"node": "^22.20 || ^24.12 || >=25"
}
},
- "node_modules/@popperjs/core": {
- "version": "2.11.8",
- "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
- "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
- "license": "MIT",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/popperjs"
- }
- },
- "node_modules/@radix-ui/number": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
- "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==",
- "license": "MIT"
- },
- "node_modules/@radix-ui/primitive": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
- "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
- "license": "MIT"
- },
- "node_modules/@radix-ui/react-accordion": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.3.tgz",
- "integrity": "sha512-RIQ15mrcvqIkDARJeERSuXSry2N8uYnxkdDetpfmalT/+0ntOXLkFOsh9iwlAsCv+qcmhZjbdJogIm6WBa6c4A==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collapsible": "1.1.3",
- "@radix-ui/react-collection": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-alert-dialog": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.6.tgz",
- "integrity": "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dialog": "1.1.6",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-slot": "1.1.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-arrow": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz",
- "integrity": "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.0.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-aspect-ratio": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.2.tgz",
- "integrity": "sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.0.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-avatar": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.3.tgz",
- "integrity": "sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-checkbox": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz",
- "integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-previous": "1.1.0",
- "@radix-ui/react-use-size": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-collapsible": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz",
- "integrity": "sha512-jFSerheto1X03MUC0g6R7LedNW9EEGWdg9W1+MlpkMLwGkgkbUXLPBH/KIuWKXUoeYRVY11llqbTBDzuLg7qrw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-collection": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.2.tgz",
- "integrity": "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-slot": "1.1.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-compose-refs": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
- "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-context": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
- "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-context-menu": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.6.tgz",
- "integrity": "sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-menu": "2.1.6",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-dialog": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz",
- "integrity": "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.5",
- "@radix-ui/react-focus-guards": "1.1.1",
- "@radix-ui/react-focus-scope": "1.1.2",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-portal": "1.1.4",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-slot": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.3"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-direction": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
- "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-dismissable-layer": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.5.tgz",
- "integrity": "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-escape-keydown": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-dropdown-menu": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.6.tgz",
- "integrity": "sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-menu": "2.1.6",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-focus-guards": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
- "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-focus-scope": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.2.tgz",
- "integrity": "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-hover-card": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.6.tgz",
- "integrity": "sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.5",
- "@radix-ui/react-popper": "1.2.2",
- "@radix-ui/react-portal": "1.1.4",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-id": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
- "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-label": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.2.tgz",
- "integrity": "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.0.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-menu": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.6.tgz",
- "integrity": "sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-dismissable-layer": "1.1.5",
- "@radix-ui/react-focus-guards": "1.1.1",
- "@radix-ui/react-focus-scope": "1.1.2",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-popper": "1.2.2",
- "@radix-ui/react-portal": "1.1.4",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-roving-focus": "1.1.2",
- "@radix-ui/react-slot": "1.1.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.3"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-menubar": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.6.tgz",
- "integrity": "sha512-FHq7+3DlXwh/7FOM4i0G4bC4vPjiq89VEEvNF4VMLchGnaUuUbE5uKXMUCjdKaOghEEMeiKa5XCa2Pk4kteWmg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-menu": "2.1.6",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-roving-focus": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-navigation-menu": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.5.tgz",
- "integrity": "sha512-myMHHQUZ3ZLTi8W381/Vu43Ia0NqakkQZ2vzynMmTUtQQ9kNkjzhOwkZC9TAM5R07OZUVIQyHC06f/9JZJpvvA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-dismissable-layer": "1.1.5",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0",
- "@radix-ui/react-use-previous": "1.1.0",
- "@radix-ui/react-visually-hidden": "1.1.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-popover": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz",
- "integrity": "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.5",
- "@radix-ui/react-focus-guards": "1.1.1",
- "@radix-ui/react-focus-scope": "1.1.2",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-popper": "1.2.2",
- "@radix-ui/react-portal": "1.1.4",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-slot": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.3"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-popper": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.2.tgz",
- "integrity": "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==",
- "license": "MIT",
- "dependencies": {
- "@floating-ui/react-dom": "^2.0.0",
- "@radix-ui/react-arrow": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0",
- "@radix-ui/react-use-rect": "1.1.0",
- "@radix-ui/react-use-size": "1.1.0",
- "@radix-ui/rect": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-portal": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.4.tgz",
- "integrity": "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-presence": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
- "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-primitive": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
- "integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-slot": "1.1.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-progress": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.2.tgz",
- "integrity": "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-radio-group": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz",
- "integrity": "sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-roving-focus": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-previous": "1.1.0",
- "@radix-ui/react-use-size": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-roving-focus": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz",
- "integrity": "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-scroll-area": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz",
- "integrity": "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/number": "1.1.0",
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-select": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz",
- "integrity": "sha512-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/number": "1.1.0",
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-dismissable-layer": "1.1.5",
- "@radix-ui/react-focus-guards": "1.1.1",
- "@radix-ui/react-focus-scope": "1.1.2",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-popper": "1.2.2",
- "@radix-ui/react-portal": "1.1.4",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-slot": "1.1.2",
- "@radix-ui/react-use-callback-ref": "1.1.0",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0",
- "@radix-ui/react-use-previous": "1.1.0",
- "@radix-ui/react-visually-hidden": "1.1.2",
- "aria-hidden": "^1.2.4",
- "react-remove-scroll": "^2.6.3"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-separator": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz",
- "integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.0.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-slider": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.3.tgz",
- "integrity": "sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/number": "1.1.0",
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-collection": "1.1.2",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-layout-effect": "1.1.0",
- "@radix-ui/react-use-previous": "1.1.0",
- "@radix-ui/react-use-size": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-slot": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
- "integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "1.1.1"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-switch": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.3.tgz",
- "integrity": "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-use-previous": "1.1.0",
- "@radix-ui/react-use-size": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-tabs": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz",
- "integrity": "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-roving-focus": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-toggle": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.2.tgz",
- "integrity": "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-toggle-group": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.2.tgz",
- "integrity": "sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-direction": "1.1.0",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-roving-focus": "1.1.2",
- "@radix-ui/react-toggle": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.1.0"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-tooltip": {
- "version": "1.1.8",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz",
- "integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/primitive": "1.1.1",
- "@radix-ui/react-compose-refs": "1.1.1",
- "@radix-ui/react-context": "1.1.1",
- "@radix-ui/react-dismissable-layer": "1.1.5",
- "@radix-ui/react-id": "1.1.0",
- "@radix-ui/react-popper": "1.2.2",
- "@radix-ui/react-portal": "1.1.4",
- "@radix-ui/react-presence": "1.1.2",
- "@radix-ui/react-primitive": "2.0.2",
- "@radix-ui/react-slot": "1.1.2",
- "@radix-ui/react-use-controllable-state": "1.1.0",
- "@radix-ui/react-visually-hidden": "1.1.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/react-use-callback-ref": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
- "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-controllable-state": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
- "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-callback-ref": "1.1.0"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-escape-keydown": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
- "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-callback-ref": "1.1.0"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-layout-effect": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
- "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-previous": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz",
- "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==",
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-rect": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
- "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/rect": "1.1.0"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-use-size": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
- "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/@radix-ui/react-visually-hidden": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.2.tgz",
- "integrity": "sha512-1SzA4ns2M1aRlvxErqhLHsBHoS5eI5UUcI2awAMgGUp4LoaoWOKYmvqDY2s/tltuPkh3Yk77YF/r3IRj+Amx4Q==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-primitive": "2.0.2"
- },
- "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
- }
- }
- },
- "node_modules/@radix-ui/rect": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
- "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
- "license": "MIT"
- },
- "node_modules/@react-dnd/asap": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
- "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==",
- "license": "MIT"
- },
- "node_modules/@react-dnd/invariant": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
- "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==",
- "license": "MIT"
- },
- "node_modules/@react-dnd/shallowequal": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
- "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==",
- "license": "MIT"
- },
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -2627,9 +841,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz",
- "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.5.tgz",
+ "integrity": "sha512-J25QJU+B78T4FhhBsNpLJyVWOi31mwtpcMwywHmOKH65Q9IWGA81gPj+dnwlhU8wktVriYE+tFAaQgrnJRzAZg==",
"cpu": [
"arm"
],
@@ -2641,9 +855,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz",
- "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.5.tgz",
+ "integrity": "sha512-LDopB3zuZM5Ux9TT2luNEBJW/tYbGU2g1d+VpKk6I+gSKDb+/7sYE6M225gRQt4RbMX6MSwMsVR/phdjVUgRLg==",
"cpu": [
"arm64"
],
@@ -2655,9 +869,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz",
- "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.5.tgz",
+ "integrity": "sha512-wlJEERGfeuHeBavCL2qVnNacOK43NDoZM4sjkeRPymd04OAE9T1zBqDJgmZ+CIsPTYKwdzpUC8vmOw84dwY4Tg==",
"cpu": [
"arm64"
],
@@ -2669,9 +883,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz",
- "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.5.tgz",
+ "integrity": "sha512-4nJJGg5jbo2wwPP4JP+LfEBA3bvP8rU9CLuhp7jWvq9sxEyhjQFTFdrqi+/dHEin/pd8jpT0vcehIpnZtmEdcQ==",
"cpu": [
"x64"
],
@@ -2683,9 +897,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz",
- "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.5.tgz",
+ "integrity": "sha512-DrZbyCDF1hneuO6jRbvZ2D7+PIBM6yIwYnJpg2vIk58T+wuFpiaGZrfUr59lDWw45bg+IrpTGLPiNi/Fk4w3Cg==",
"cpu": [
"arm64"
],
@@ -2697,9 +911,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz",
- "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.5.tgz",
+ "integrity": "sha512-gqfUVMJMB3mehqywxp6hTBFfgtMQykZY19+cfiaYP0toIJLb/1DZRJHVkQQGP13W4TAwfZDWeg1qBcheTRioXQ==",
"cpu": [
"x64"
],
@@ -2711,9 +925,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz",
- "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.5.tgz",
+ "integrity": "sha512-CFmhpvAwzSaWMlN3VN7UtmoTihlZNzoP0juQib5TQRnYUyDV8dXeWOp29sobWAT6gXl/hQgAClLlEiYozQG3OQ==",
"cpu": [
"arm"
],
@@ -2728,9 +942,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz",
- "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.5.tgz",
+ "integrity": "sha512-Uc9H8eXCOayV6JLTH5bXKMId6qbhNHa818/BgYjm4jrlq3vZquC9cqyvHBw17xy5Mnj5f+I3gFK5JcEf3hSqrw==",
"cpu": [
"arm"
],
@@ -2745,9 +959,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz",
- "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.5.tgz",
+ "integrity": "sha512-VcPr/szv/1BFw112Kt//fxulXt/JPqzzidU84iW68L2DdjnOO8QFUv2zTSYBEPHD6movBD4z+bbr5y60GYM7Jw==",
"cpu": [
"arm64"
],
@@ -2762,9 +976,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz",
- "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.5.tgz",
+ "integrity": "sha512-BnxtJ5/91BrIHYIkGrmjz/lbMhqEHt1dPFqIxIFR+jPn0xVc/oUSCtIT089zfp5ufwGDlYz2UC+Fe1SRBpYFbQ==",
"cpu": [
"arm64"
],
@@ -2779,9 +993,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz",
- "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.5.tgz",
+ "integrity": "sha512-LrYcHZwF+fAMNKHYTOQ5osWM4AZF7YF6D+XtsjDyEvljtt11twc+zHVXBLNEjxVSUnKYsOhvVz4Z213eW02COQ==",
"cpu": [
"loong64"
],
@@ -2796,9 +1010,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz",
- "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.5.tgz",
+ "integrity": "sha512-nj7QKQePAAUpCpJHtg0pR0W/b92A9NO17JS3BAQmHDn/yhmkir2p8llrKY9TOhleKIaSzy1JhxS3T9FVld6coA==",
"cpu": [
"loong64"
],
@@ -2813,9 +1027,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz",
- "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.5.tgz",
+ "integrity": "sha512-5ylkX6dWMeBKge9nTU+Rxfb+ZfaCIJ9lRqIFaK0eAMcWp7OJbYnLveLgXmm0VrvuLKb8qIK+mHyH0qu88RM+iA==",
"cpu": [
"ppc64"
],
@@ -2830,9 +1044,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz",
- "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.5.tgz",
+ "integrity": "sha512-oHK4ZHYFDKjZviK34I+NwgfbGxgI7ztrNxj2hPTSSNFgeq1a/lEd7dHV2fdGAuTH4Iym3RHJg+vAbWaWG4B7Zg==",
"cpu": [
"ppc64"
],
@@ -2847,9 +1061,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz",
- "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.5.tgz",
+ "integrity": "sha512-UcetmHZ6XOXuUByiKZyQmb55ZPr0LABr3Ec/HB9wKZn6CEAFWZkE+hsJErJ9hbPBC7nI0dKuELx7CoV6IM7TMg==",
"cpu": [
"riscv64"
],
@@ -2864,9 +1078,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz",
- "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.5.tgz",
+ "integrity": "sha512-C5CmDPQBtvjVo8cgQsBs+w6WB0JLkiixhgi6hVLV11hERWdn/p0XcPU2OUcZzac9BPOFq7SbaHFa8r3SWEysCQ==",
"cpu": [
"riscv64"
],
@@ -2881,9 +1095,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz",
- "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.5.tgz",
+ "integrity": "sha512-lHVQHJFKsuuxLMi3MQO9XVL8Tje3JR82CzB+QDKC5NWBcsIWuwsn9uIM5e3lBhI+fF1/s63qnyYqsg65+8rV/w==",
"cpu": [
"s390x"
],
@@ -2898,9 +1112,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz",
- "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.5.tgz",
+ "integrity": "sha512-3W9bTFcQNJn71cSJVM9RKIiZOy8DO/XLDii8Uv/Pm6WKqDRj7JV3ZfuXIEfyuy5LXpIzAbB/1M4Ukp9GKNa7nA==",
"cpu": [
"x64"
],
@@ -2915,9 +1129,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz",
- "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.5.tgz",
+ "integrity": "sha512-VDC7rRJlee/scpki96GZ27Omf6yU87s1YXwVTpjE5841faVlDYYT565rgfmoR1U0sqL7z5ivQSDjcsF6VRXyBA==",
"cpu": [
"x64"
],
@@ -2932,9 +1146,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz",
- "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.5.tgz",
+ "integrity": "sha512-z86Ok2p4pTdv5xqCKZsTooO7yBEiaJR/HzU3Wx8RmWsPoLppnMKROhJusQob8B3IE1ghC343kUW9rC2r+Wf3ig==",
"cpu": [
"x64"
],
@@ -2946,9 +1160,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz",
- "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.5.tgz",
+ "integrity": "sha512-IzQmj+xXwQFGhMAMKMQVXkMwMZN3TqkJgAE0nSsqvVwWWciP4AIPMmWRqOQ2GfX7TUDZr+xqGFcBS36CRPGw0g==",
"cpu": [
"arm64"
],
@@ -2960,9 +1174,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz",
- "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.5.tgz",
+ "integrity": "sha512-F6qpTaPc9bwBH85kjy0/BLmLSW1uv7AoOXCoRIkg2arlgCYlWYcAbiMkvZuAcaWk9TpCRG//okznLAqLGshkMw==",
"cpu": [
"arm64"
],
@@ -2974,9 +1188,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz",
- "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.5.tgz",
+ "integrity": "sha512-igoDsTFhhwECBeGbUuLeIk7t8Y1apa+cs6mDWpx2EZ0ch7oEQgzHbFUXN9euoHekCAQzXdXApAGkV6jznS7tWw==",
"cpu": [
"ia32"
],
@@ -2988,9 +1202,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz",
- "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.5.tgz",
+ "integrity": "sha512-U3teMeMbXFmaM5D+OTJpsOXd+wV/qftIeYF9kBKL4v73641qyJmoXFtA28DQLsnmlyayEsTe72xpLHrArq6vHw==",
"cpu": [
"x64"
],
@@ -3002,9 +1216,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz",
- "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.5.tgz",
+ "integrity": "sha512-ypfC34F3RKXvCXBglGqGMsUSMKlgwd1HX9AOAlx9RoZZ6GaI42YHVeKpzg3JG+wpBUJYTG+NNZhqbDWL8tBZkw==",
"cpu": [
"x64"
],
@@ -3349,67 +1563,11 @@
"@babel/types": "^7.28.2"
}
},
- "node_modules/@types/d3-array": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
- "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
- "license": "MIT"
- },
- "node_modules/@types/d3-color": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
- "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
- "license": "MIT"
- },
- "node_modules/@types/d3-ease": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
- "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
- "license": "MIT"
- },
- "node_modules/@types/d3-interpolate": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
- "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-color": "*"
- }
- },
- "node_modules/@types/d3-path": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
- "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
- "license": "MIT"
- },
- "node_modules/@types/d3-scale": {
- "version": "4.0.9",
- "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
- "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-time": "*"
- }
- },
- "node_modules/@types/d3-shape": {
- "version": "3.1.8",
- "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
- "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
- "license": "MIT",
- "dependencies": {
- "@types/d3-path": "*"
- }
- },
- "node_modules/@types/d3-time": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
- "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
- "license": "MIT"
- },
- "node_modules/@types/d3-timer": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
- "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "node_modules/@types/canvas-confetti": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.9.0.tgz",
+ "integrity": "sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/estree": {
@@ -3419,35 +1577,32 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/parse-json": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
- "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
- "license": "MIT"
- },
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
- "version": "19.2.18",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
- "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "version": "18.3.31",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
+ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
+ "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
+ "@types/prop-types": "*",
"csstype": "^3.2.2"
}
},
- "node_modules/@types/react-transition-group": {
- "version": "4.4.12",
- "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
- "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
"license": "MIT",
"peerDependencies": {
- "@types/react": "*"
+ "@types/react": "^18.0.0"
}
},
"node_modules/@vitejs/plugin-react": {
@@ -3471,37 +1626,10 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
- "node_modules/aria-hidden": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
- "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/babel-plugin-macros": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
- "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.12.5",
- "cosmiconfig": "^7.0.0",
- "resolve": "^1.19.0"
- },
- "engines": {
- "node": ">=10",
- "npm": ">=6"
- }
- },
"node_modules/baseline-browser-mapping": {
- "version": "2.11.14",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz",
- "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==",
+ "version": "2.11.26",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.26.tgz",
+ "integrity": "sha512-GLQdD3y6UF8iVuMJl5fHgE4jdn/ua7n+toKfLgNlg3BqQtOZjpy68T8Tup8/wGWZCDlm7KMg7tPb4MPn7oN0TQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -3512,9 +1640,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.28.8",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
- "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
+ "version": "4.29.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.1.tgz",
+ "integrity": "sha512-AUdjuRyCNGUYtqpqfTmWyM4fXay8yIQhmLnvYe/THMGfT9B/34X7xQd3ifKxwyNPPpowVBjLb+64BN9Rn1mizw==",
"dev": true,
"funding": [
{
@@ -3532,11 +1660,11 @@
],
"license": "MIT",
"dependencies": {
- "baseline-browser-mapping": "^2.11.12",
- "caniuse-lite": "^1.0.30001809",
- "electron-to-chromium": "^1.5.402",
- "node-releases": "^2.0.53",
- "update-browserslist-db": "^1.3.0"
+ "baseline-browser-mapping": "^2.11.25",
+ "caniuse-lite": "^1.0.30001810",
+ "electron-to-chromium": "^1.5.438",
+ "node-releases": "^2.0.57",
+ "update-browserslist-db": "^1.3.3"
},
"bin": {
"browserslist": "cli.js"
@@ -3545,19 +1673,10 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/caniuse-lite": {
- "version": "1.0.30001809",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",
- "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",
+ "version": "1.0.30001812",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001812.tgz",
+ "integrity": "sha512-qN+QNNBr93TCmFrmte0bBCjSDMuRvt78VlHT99qIGPszm4QsqCX8lnyWUFkHi8B7ZBAPq8SH+UqyXNy/odMdng==",
"dev": true,
"funding": [
{
@@ -3595,234 +1714,25 @@
"node": ">=18"
}
},
- "node_modules/class-variance-authority": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
- "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
- "license": "Apache-2.0",
- "dependencies": {
- "clsx": "^2.1.1"
- },
- "funding": {
- "url": "https://polar.sh/cva"
- }
- },
- "node_modules/classnames": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
- "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
- "license": "MIT"
- },
- "node_modules/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/cmdk": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
- "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-compose-refs": "^1.1.1",
- "@radix-ui/react-dialog": "^1.1.6",
- "@radix-ui/react-id": "^1.1.0",
- "@radix-ui/react-primitive": "^2.0.2"
- },
- "peerDependencies": {
- "react": "^18 || ^19 || ^19.0.0-rc",
- "react-dom": "^18 || ^19 || ^19.0.0-rc"
- }
- },
"node_modules/convert-source-map": {
- "version": "1.9.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
- "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/cosmiconfig": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
- "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
- "license": "MIT",
- "dependencies": {
- "@types/parse-json": "^4.0.0",
- "import-fresh": "^3.2.1",
- "parse-json": "^5.0.0",
- "path-type": "^4.0.0",
- "yaml": "^1.10.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/cosmiconfig/node_modules/yaml": {
- "version": "1.10.3",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
- "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
- "license": "ISC",
- "engines": {
- "node": ">= 6"
- }
- },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
"license": "MIT"
},
- "node_modules/d3-array": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
- "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
- "license": "ISC",
- "dependencies": {
- "internmap": "1 - 2"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-color": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
- "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-ease": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
- "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-format": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
- "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-interpolate": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
- "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
- "license": "ISC",
- "dependencies": {
- "d3-color": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-path": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
- "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-scale": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
- "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
- "license": "ISC",
- "dependencies": {
- "d3-array": "2.10.0 - 3",
- "d3-format": "1 - 3",
- "d3-interpolate": "1.2.0 - 3",
- "d3-time": "2.1.1 - 3",
- "d3-time-format": "2 - 4"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-shape": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
- "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
- "license": "ISC",
- "dependencies": {
- "d3-path": "^3.1.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
- "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
- "license": "ISC",
- "dependencies": {
- "d3-array": "2 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-time-format": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
- "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
- "license": "ISC",
- "dependencies": {
- "d3-time": "1 - 3"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/d3-timer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
- "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/date-fns": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
- "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/kossnocorp"
- }
- },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -3836,12 +1746,6 @@
}
}
},
- "node_modules/decimal.js-light": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
- "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
- "license": "MIT"
- },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -3852,72 +1756,17 @@
"node": ">=8"
}
},
- "node_modules/detect-node-es": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
- "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
- "license": "MIT"
- },
- "node_modules/dnd-core": {
- "version": "16.0.1",
- "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
- "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
- "license": "MIT",
- "dependencies": {
- "@react-dnd/asap": "^5.0.1",
- "@react-dnd/invariant": "^4.0.1",
- "redux": "^4.2.0"
- }
- },
- "node_modules/dom-helpers": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
- "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.8.7",
- "csstype": "^3.0.2"
- }
- },
"node_modules/electron-to-chromium": {
- "version": "1.5.407",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.407.tgz",
- "integrity": "sha512-4R8XgQOdfxexCd/u63lRm6wCHjECwI45MV9wxAs2ggtfWe2hwlo1ql97jKsju2IcJ+jFSTwBssyYoiWhh7mauQ==",
+ "version": "1.5.439",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.439.tgz",
+ "integrity": "sha512-qu6QIPXhsb+CRcAiTMNjR4A1y/7tCYKkKjr5CZXRVih6qkDf79peZ2BpEU3qoDBNCRrcy3Mra3X9nG5oruuA7Q==",
"dev": true,
"license": "ISC"
},
- "node_modules/embla-carousel": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
- "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
- "license": "MIT"
- },
- "node_modules/embla-carousel-react": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz",
- "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==",
- "license": "MIT",
- "dependencies": {
- "embla-carousel": "8.6.0",
- "embla-carousel-reactive-utils": "8.6.0"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- }
- },
- "node_modules/embla-carousel-reactive-utils": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz",
- "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==",
- "license": "MIT",
- "peerDependencies": {
- "embla-carousel": "8.6.0"
- }
- },
"node_modules/enhanced-resolve": {
- "version": "5.24.5",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
- "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
+ "version": "5.25.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz",
+ "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3928,24 +1777,6 @@
"node": ">=10.13.0"
}
},
- "node_modules/error-ex": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
- "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.2.1"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
@@ -3998,39 +1829,6 @@
"node": ">=6"
}
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/eventemitter3": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
- "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
- "license": "MIT"
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "license": "MIT"
- },
- "node_modules/fast-equals": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz",
- "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==",
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -4049,12 +1847,6 @@
}
}
},
- "node_modules/find-root": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
- "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
- "license": "MIT"
- },
"node_modules/framer-motion": {
"version": "12.43.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz",
@@ -4097,15 +1889,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -4116,15 +1899,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/get-nonce": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
- "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -4132,89 +1906,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/hasown": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
- "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/hoist-non-react-statics": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
- "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "react-is": "^16.7.0"
- }
- },
- "node_modules/hoist-non-react-statics/node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/input-otp": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz",
- "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
- }
- },
- "node_modules/internmap": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
- "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
- "license": "MIT"
- },
- "node_modules/is-core-module": {
- "version": "2.16.2",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
- "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
- "license": "MIT",
- "dependencies": {
- "hasown": "^2.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/jiti": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
@@ -4235,6 +1926,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
@@ -4243,21 +1935,6 @@
"node": ">=6"
}
},
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "license": "MIT"
- },
- "node_modules/json2mq": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
- "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==",
- "license": "MIT",
- "dependencies": {
- "string-convert": "^0.2.0"
- }
- },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -4522,24 +2199,6 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "license": "MIT"
- },
- "node_modules/lodash": {
- "version": "4.18.1",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
- "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
- "license": "MIT"
- },
- "node_modules/lodash.debounce": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
- "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
- "license": "MIT"
- },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -4649,12 +2308,13 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.18",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
- "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"dev": true,
"funding": [
{
@@ -4670,90 +2330,27 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/next-themes": {
- "version": "0.4.6",
- "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
- "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
- }
- },
"node_modules/node-releases": {
- "version": "2.0.53",
- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
- "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
+ "version": "2.0.57",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz",
+ "integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/parse-json": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
- "license": "MIT",
- "dependencies": {
- "@babel/code-frame": "^7.0.0",
- "error-ex": "^1.3.1",
- "json-parse-even-better-errors": "^2.3.0",
- "lines-and-columns": "^1.1.6"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "license": "MIT"
- },
- "node_modules/path-type": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
- "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4764,9 +2361,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.26",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
- "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
"dev": true,
"funding": [
{
@@ -4784,7 +2381,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.17",
+ "nanoid": "^3.3.18",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -4792,29 +2389,11 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "node_modules/prop-types/node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -4822,65 +2401,11 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-day-picker": {
- "version": "8.10.1",
- "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz",
- "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==",
- "license": "MIT",
- "funding": {
- "type": "individual",
- "url": "https://github.com/sponsors/gpbl"
- },
- "peerDependencies": {
- "date-fns": "^2.28.0 || ^3.0.0",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
- }
- },
- "node_modules/react-dnd": {
- "version": "16.0.1",
- "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
- "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
- "license": "MIT",
- "dependencies": {
- "@react-dnd/invariant": "^4.0.1",
- "@react-dnd/shallowequal": "^4.0.1",
- "dnd-core": "^16.0.1",
- "fast-deep-equal": "^3.1.3",
- "hoist-non-react-statics": "^3.3.2"
- },
- "peerDependencies": {
- "@types/hoist-non-react-statics": ">= 3.3.1",
- "@types/node": ">= 12",
- "@types/react": ">= 16",
- "react": ">= 16.14"
- },
- "peerDependenciesMeta": {
- "@types/hoist-non-react-statics": {
- "optional": true
- },
- "@types/node": {
- "optional": true
- },
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/react-dnd-html5-backend": {
- "version": "16.0.1",
- "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
- "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
- "license": "MIT",
- "dependencies": {
- "dnd-core": "^16.0.1"
- }
- },
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -4889,49 +2414,6 @@
"react": "^18.3.1"
}
},
- "node_modules/react-fast-compare": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
- "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
- "license": "MIT"
- },
- "node_modules/react-hook-form": {
- "version": "7.55.0",
- "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.55.0.tgz",
- "integrity": "sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==",
- "license": "MIT",
- "engines": {
- "node": ">=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/react-hook-form"
- },
- "peerDependencies": {
- "react": "^16.8.0 || ^17 || ^18 || ^19"
- }
- },
- "node_modules/react-is": {
- "version": "19.2.8",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz",
- "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==",
- "license": "MIT"
- },
- "node_modules/react-popper": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz",
- "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==",
- "license": "MIT",
- "dependencies": {
- "react-fast-compare": "^3.0.1",
- "warning": "^4.0.2"
- },
- "peerDependencies": {
- "@popperjs/core": "^2.0.0",
- "react": "^16.8.0 || ^17 || ^18",
- "react-dom": "^16.8.0 || ^17 || ^18"
- }
- },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
@@ -4942,248 +2424,10 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-remove-scroll": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
- "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
- "license": "MIT",
- "dependencies": {
- "react-remove-scroll-bar": "^2.3.7",
- "react-style-singleton": "^2.2.3",
- "tslib": "^2.1.0",
- "use-callback-ref": "^1.3.3",
- "use-sidecar": "^1.1.3"
- },
- "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
- }
- }
- },
- "node_modules/react-remove-scroll-bar": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
- "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
- "license": "MIT",
- "dependencies": {
- "react-style-singleton": "^2.2.2",
- "tslib": "^2.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "@types/react": "*",
- "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- }
- }
- },
- "node_modules/react-resizable-panels": {
- "version": "2.1.7",
- "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz",
- "integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
- "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- }
- },
- "node_modules/react-responsive-masonry": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/react-responsive-masonry/-/react-responsive-masonry-2.7.1.tgz",
- "integrity": "sha512-Q+u+nOH87PzjqGFd2PgTcmLpHPZnCmUPREHYoNBc8dwJv6fi51p9U6hqwG8g/T8MN86HrFjrU+uQU6yvETU7cA==",
- "license": "MIT"
- },
- "node_modules/react-router": {
- "version": "7.13.0",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
- "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
- "license": "MIT",
- "dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/react-slick": {
- "version": "0.31.0",
- "resolved": "https://registry.npmjs.org/react-slick/-/react-slick-0.31.0.tgz",
- "integrity": "sha512-zo6VLT8wuSBJffg/TFPbzrw2dEnfZ/cUKmYsKByh3AgatRv29m2LoFbq5vRMa3R3A4wp4d8gwbJKO2fWZFaI3g==",
- "license": "MIT",
- "dependencies": {
- "classnames": "^2.2.5",
- "json2mq": "^0.2.0",
- "lodash.debounce": "^4.0.8",
- "resize-observer-polyfill": "^1.5.0"
- },
- "peerDependencies": {
- "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/react-smooth": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
- "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
- "license": "MIT",
- "dependencies": {
- "fast-equals": "^5.0.1",
- "prop-types": "^15.8.1",
- "react-transition-group": "^4.4.5"
- },
- "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"
- }
- },
- "node_modules/react-style-singleton": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
- "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
- "license": "MIT",
- "dependencies": {
- "get-nonce": "^1.0.0",
- "tslib": "^2.0.0"
- },
- "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
- }
- }
- },
- "node_modules/react-transition-group": {
- "version": "4.4.5",
- "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
- "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@babel/runtime": "^7.5.5",
- "dom-helpers": "^5.0.1",
- "loose-envify": "^1.4.0",
- "prop-types": "^15.6.2"
- },
- "peerDependencies": {
- "react": ">=16.6.0",
- "react-dom": ">=16.6.0"
- }
- },
- "node_modules/recharts": {
- "version": "2.15.2",
- "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.2.tgz",
- "integrity": "sha512-xv9lVztv3ingk7V3Jf05wfAZbM9Q2umJzu5t/cfnAK7LUslNrGT7LPBr74G+ok8kSCeFMaePmWMg0rcYOnczTw==",
- "deprecated": "1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide",
- "license": "MIT",
- "dependencies": {
- "clsx": "^2.0.0",
- "eventemitter3": "^4.0.1",
- "lodash": "^4.17.21",
- "react-is": "^18.3.1",
- "react-smooth": "^4.0.4",
- "recharts-scale": "^0.4.4",
- "tiny-invariant": "^1.3.1",
- "victory-vendor": "^36.6.8"
- },
- "engines": {
- "node": ">=14"
- },
- "peerDependencies": {
- "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
- "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/recharts-scale": {
- "version": "0.4.5",
- "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
- "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
- "license": "MIT",
- "dependencies": {
- "decimal.js-light": "^2.4.1"
- }
- },
- "node_modules/recharts/node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "license": "MIT"
- },
- "node_modules/redux": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
- "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.9.2"
- }
- },
- "node_modules/resize-observer-polyfill": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
- "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==",
- "license": "MIT"
- },
- "node_modules/resolve": {
- "version": "1.22.12",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
- "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "is-core-module": "^2.16.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/rollup": {
- "version": "4.62.4",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz",
- "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==",
+ "version": "4.63.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.5.tgz",
+ "integrity": "sha512-KRWwmNLlPw5M7HcdYfm15oBv9n9LPtjzpzCIxS/phwqvPyxHSoKX6Y2YU3pxSPfy0CLquVgsx/j/hBi6OvH1Nw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5198,31 +2442,31 @@
},
"optionalDependencies": {
"@napi-rs/lzma-linux-x64-gnu": "1.5.1",
- "@rollup/rollup-android-arm-eabi": "4.62.4",
- "@rollup/rollup-android-arm64": "4.62.4",
- "@rollup/rollup-darwin-arm64": "4.62.4",
- "@rollup/rollup-darwin-x64": "4.62.4",
- "@rollup/rollup-freebsd-arm64": "4.62.4",
- "@rollup/rollup-freebsd-x64": "4.62.4",
- "@rollup/rollup-linux-arm-gnueabihf": "4.62.4",
- "@rollup/rollup-linux-arm-musleabihf": "4.62.4",
- "@rollup/rollup-linux-arm64-gnu": "4.62.4",
- "@rollup/rollup-linux-arm64-musl": "4.62.4",
- "@rollup/rollup-linux-loong64-gnu": "4.62.4",
- "@rollup/rollup-linux-loong64-musl": "4.62.4",
- "@rollup/rollup-linux-ppc64-gnu": "4.62.4",
- "@rollup/rollup-linux-ppc64-musl": "4.62.4",
- "@rollup/rollup-linux-riscv64-gnu": "4.62.4",
- "@rollup/rollup-linux-riscv64-musl": "4.62.4",
- "@rollup/rollup-linux-s390x-gnu": "4.62.4",
- "@rollup/rollup-linux-x64-gnu": "4.62.4",
- "@rollup/rollup-linux-x64-musl": "4.62.4",
- "@rollup/rollup-openbsd-x64": "4.62.4",
- "@rollup/rollup-openharmony-arm64": "4.62.4",
- "@rollup/rollup-win32-arm64-msvc": "4.62.4",
- "@rollup/rollup-win32-ia32-msvc": "4.62.4",
- "@rollup/rollup-win32-x64-gnu": "4.62.4",
- "@rollup/rollup-win32-x64-msvc": "4.62.4",
+ "@rollup/rollup-android-arm-eabi": "4.63.5",
+ "@rollup/rollup-android-arm64": "4.63.5",
+ "@rollup/rollup-darwin-arm64": "4.63.5",
+ "@rollup/rollup-darwin-x64": "4.63.5",
+ "@rollup/rollup-freebsd-arm64": "4.63.5",
+ "@rollup/rollup-freebsd-x64": "4.63.5",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.63.5",
+ "@rollup/rollup-linux-arm-musleabihf": "4.63.5",
+ "@rollup/rollup-linux-arm64-gnu": "4.63.5",
+ "@rollup/rollup-linux-arm64-musl": "4.63.5",
+ "@rollup/rollup-linux-loong64-gnu": "4.63.5",
+ "@rollup/rollup-linux-loong64-musl": "4.63.5",
+ "@rollup/rollup-linux-ppc64-gnu": "4.63.5",
+ "@rollup/rollup-linux-ppc64-musl": "4.63.5",
+ "@rollup/rollup-linux-riscv64-gnu": "4.63.5",
+ "@rollup/rollup-linux-riscv64-musl": "4.63.5",
+ "@rollup/rollup-linux-s390x-gnu": "4.63.5",
+ "@rollup/rollup-linux-x64-gnu": "4.63.5",
+ "@rollup/rollup-linux-x64-musl": "4.63.5",
+ "@rollup/rollup-openbsd-x64": "4.63.5",
+ "@rollup/rollup-openharmony-arm64": "4.63.5",
+ "@rollup/rollup-win32-arm64-msvc": "4.63.5",
+ "@rollup/rollup-win32-ia32-msvc": "4.63.5",
+ "@rollup/rollup-win32-x64-gnu": "4.63.5",
+ "@rollup/rollup-win32-x64-msvc": "4.63.5",
"fsevents": "~2.3.2"
}
},
@@ -5231,7 +2475,6 @@
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
@@ -5246,31 +2489,6 @@
"semver": "bin/semver.js"
}
},
- "node_modules/set-cookie-parser": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
- "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
- "license": "MIT"
- },
- "node_modules/sonner": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
- "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
- "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
- }
- },
- "node_modules/source-map": {
- "version": "0.5.7",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
- "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5281,40 +2499,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/string-convert": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
- "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==",
- "license": "MIT"
- },
- "node_modules/stylis": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
- "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
- "license": "MIT"
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/tailwind-merge": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.2.0.tgz",
- "integrity": "sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/dcastil"
- }
- },
"node_modules/tailwindcss": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
@@ -5363,12 +2547,6 @@
"node": ">=18"
}
},
- "node_modules/tiny-invariant": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
- "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
- "license": "MIT"
- },
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
@@ -5392,19 +2570,24 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
- "node_modules/tw-animate-css": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.3.8.tgz",
- "integrity": "sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/Wombosvideo"
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
}
},
"node_modules/update-browserslist-db": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz",
- "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
+ "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
"dev": true,
"funding": [
{
@@ -5432,83 +2615,11 @@
"browserslist": ">= 4.21.0"
}
},
- "node_modules/use-callback-ref": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
- "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.0.0"
- },
- "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
- }
- }
- },
- "node_modules/use-sidecar": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
- "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
- "license": "MIT",
- "dependencies": {
- "detect-node-es": "^1.1.0",
- "tslib": "^2.0.0"
- },
- "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
- }
- }
- },
- "node_modules/vaul": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
- "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==",
- "license": "MIT",
- "dependencies": {
- "@radix-ui/react-dialog": "^1.1.1"
- },
- "peerDependencies": {
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
- "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
- }
- },
- "node_modules/victory-vendor": {
- "version": "36.9.2",
- "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
- "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
- "license": "MIT AND ISC",
- "dependencies": {
- "@types/d3-array": "^3.0.3",
- "@types/d3-ease": "^3.0.0",
- "@types/d3-interpolate": "^3.0.1",
- "@types/d3-scale": "^4.0.2",
- "@types/d3-shape": "^3.1.0",
- "@types/d3-time": "^3.0.0",
- "@types/d3-timer": "^3.0.0",
- "d3-array": "^3.1.6",
- "d3-ease": "^3.0.1",
- "d3-interpolate": "^3.0.1",
- "d3-scale": "^4.0.2",
- "d3-shape": "^3.1.0",
- "d3-time": "^3.0.0",
- "d3-timer": "^3.0.1"
- }
+ "node_modules/vazirmatn": {
+ "version": "33.0.3",
+ "resolved": "https://registry.npmjs.org/vazirmatn/-/vazirmatn-33.0.3.tgz",
+ "integrity": "sha512-fbjNc0CMjazZpIegWzz9OHGzI1APFboT+x7ZecXlUCtDe/nkyx7gtCTZnWS/+eeXG7fbfXymCPKJI8EsnM3iqw==",
+ "license": "OFL"
},
"node_modules/vite": {
"version": "6.3.5",
@@ -5585,15 +2696,6 @@
}
}
},
- "node_modules/warning": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
- "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.0.0"
- }
- },
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
diff --git a/package.json b/package.json
index fad7042..e7fe8ea 100644
--- a/package.json
+++ b/package.json
@@ -1,90 +1,30 @@
{
- "name": "@figma/my-make-file",
+ "name": "tadrisino-math-app",
"private": true,
- "version": "0.0.1",
+ "version": "2.0.0",
"type": "module",
"scripts": {
- "build": "vite build",
- "dev": "vite"
+ "dev": "vite",
+ "build": "tsc --noEmit && vite build",
+ "typecheck": "tsc --noEmit",
+ "preview": "vite preview"
},
"dependencies": {
- "@emotion/react": "11.14.0",
- "@emotion/styled": "11.14.1",
- "@mui/icons-material": "7.3.5",
- "@mui/material": "7.3.5",
- "@popperjs/core": "2.11.8",
- "@radix-ui/react-accordion": "1.2.3",
- "@radix-ui/react-alert-dialog": "1.1.6",
- "@radix-ui/react-aspect-ratio": "1.1.2",
- "@radix-ui/react-avatar": "1.1.3",
- "@radix-ui/react-checkbox": "1.1.4",
- "@radix-ui/react-collapsible": "1.1.3",
- "@radix-ui/react-context-menu": "2.2.6",
- "@radix-ui/react-dialog": "1.1.6",
- "@radix-ui/react-dropdown-menu": "2.1.6",
- "@radix-ui/react-hover-card": "1.1.6",
- "@radix-ui/react-label": "2.1.2",
- "@radix-ui/react-menubar": "1.1.6",
- "@radix-ui/react-navigation-menu": "1.2.5",
- "@radix-ui/react-popover": "1.1.6",
- "@radix-ui/react-progress": "1.1.2",
- "@radix-ui/react-radio-group": "1.2.3",
- "@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.2",
- "@radix-ui/react-switch": "1.1.3",
- "@radix-ui/react-tabs": "1.1.3",
- "@radix-ui/react-toggle-group": "1.1.2",
- "@radix-ui/react-toggle": "1.1.2",
- "@radix-ui/react-tooltip": "1.1.8",
"canvas-confetti": "1.9.4",
- "class-variance-authority": "0.7.1",
- "clsx": "2.1.1",
- "cmdk": "1.1.1",
- "date-fns": "3.6.0",
- "embla-carousel-react": "8.6.0",
- "input-otp": "1.4.2",
"lucide-react": "0.487.0",
"motion": "12.23.24",
- "next-themes": "0.4.6",
- "react-day-picker": "8.10.1",
- "react-dnd": "16.0.1",
- "react-dnd-html5-backend": "16.0.1",
- "react-hook-form": "7.55.0",
- "react-popper": "2.3.0",
- "react-resizable-panels": "2.1.7",
- "react-responsive-masonry": "2.7.1",
- "react-router": "7.13.0",
- "react-slick": "0.31.0",
- "recharts": "2.15.2",
- "sonner": "2.0.3",
- "tailwind-merge": "3.2.0",
- "tw-animate-css": "1.3.8",
- "vaul": "1.1.2"
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "vazirmatn": "33.0.3"
},
"devDependencies": {
"@tailwindcss/vite": "4.1.12",
+ "@types/canvas-confetti": "1.9.0",
+ "@types/react": "18.3.31",
+ "@types/react-dom": "18.3.7",
"@vitejs/plugin-react": "4.7.0",
"tailwindcss": "4.1.12",
+ "typescript": "5.9.3",
"vite": "6.3.5"
- },
- "peerDependencies": {
- "react": "18.3.1",
- "react-dom": "18.3.1"
- },
- "peerDependenciesMeta": {
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- }
- },
- "pnpm": {
- "overrides": {
- "vite": "6.3.5"
- }
}
-}
\ No newline at end of file
+}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
deleted file mode 100644
index d7bfa8a..0000000
--- a/pnpm-workspace.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
-packages:
- - '.'
-supportedArchitectures:
- os:
- - linux
- cpu:
- - x64
- - arm64
- libc:
- - glibc
diff --git a/public/config.js b/public/config.js
new file mode 100644
index 0000000..91378a6
--- /dev/null
+++ b/public/config.js
@@ -0,0 +1,9 @@
+// تنظیماتِ زمانِ اجرای اپ — بعد از build هم روی سرور قابلِ ویرایش است.
+//
+// apiBaseUrl خالی یعنی همان آدرسی که موقعِ build در .env.production بوده.
+// برای جابهجا کردنِ بکاند بدونِ build دوباره، آدرسِ کامل را اینجا بنویسید:
+// apiBaseUrl: "https://api.new-domain.ir/api",
+// اگر CDN (آروان) جلوی سایت است، بعد از ویرایش کشِ این فایل را پاک کنید.
+window.__TADRISINO_CONFIG__ = {
+ apiBaseUrl: "",
+};
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..fadea4c
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1 @@
+Σ
diff --git a/src/api/ConnectionCheck.tsx b/src/api/ConnectionCheck.tsx
deleted file mode 100644
index c78e40e..0000000
--- a/src/api/ConnectionCheck.tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-/**
- * بررسیِ اتصال به بکاند — ابزارِ توسعه، نه بخشی از محصول.
- *
- * برای امتحان کردن، یک خط در App.tsx (یا هر جای دیگر) کافیست:
- *
- * import { ConnectionCheck } from '@/api/ConnectionCheck';
- *
- *
- * هیچ فایلِ دیگری را عوض نمیکند و اگر بکاند بالا نباشد هم اپ را نمیخواباند.
- */
-import { useEffect, useState } from 'react';
-import type { CSSProperties } from 'react';
-import { api, ApiError } from '@/api';
-import type { Grade } from '@/api';
-
-type State =
- | { kind: 'loading' }
- | { kind: 'ok'; grades: Grade[] }
- | { kind: 'error'; message: string; hint: string };
-
-function hintFor(error: unknown): string {
- if (!(error instanceof ApiError)) return 'خطای پیشبینینشده — کنسول را ببینید.';
- if (error.isNetwork) {
- return 'سرور جواب نداد. VITE_API_BASE_URL درست است؟ بکاند بالاست؟';
- }
- if (error.status === 502) {
- return 'nginx بالاست ولی جنگو نه — سرویسِ بکاند روی سرور اجرا نشده.';
- }
- if (error.status === 0) return 'ارتباط برقرار نشد.';
- if (error.isRateLimited) return 'سقفِ نرخ خورد؛ کلاینت خودش دوباره تلاش میکند.';
- if (error.isUnauthorized) return 'نیاز به ورود دارد (این اندپوینت نباید داشته باشد).';
- return `سرور ${error.status} داد.`;
-}
-
-export function ConnectionCheck() {
- const [state, setState] = useState({ kind: 'loading' });
-
- useEffect(() => {
- const controller = new AbortController();
- api
- .grades({ signal: controller.signal })
- .then((grades) => setState({ kind: 'ok', grades }))
- .catch((error: unknown) => {
- if (controller.signal.aborted) return;
- setState({
- kind: 'error',
- message: error instanceof Error ? error.message : String(error),
- hint: hintFor(error),
- });
- });
- return () => controller.abort();
- }, []);
-
- const box: CSSProperties = {
- position: 'fixed', bottom: 12, right: 12, zIndex: 9999,
- maxWidth: 320, padding: '10px 12px', borderRadius: 10,
- font: '12px/1.6 system-ui, sans-serif', direction: 'rtl',
- background: '#fff', border: '1px solid #e5e7eb',
- boxShadow: '0 4px 16px rgba(0,0,0,.08)',
- };
-
- if (state.kind === 'loading') {
- return در حالِ بررسیِ اتصال به بکاند…
;
- }
-
- if (state.kind === 'error') {
- return (
-
-
اتصال برقرار نشد
-
{state.message}
-
{state.hint}
-
{api.http.baseUrl}
-
- );
- }
-
- return (
-
-
اتصال برقرار است
-
- {state.grades.length} پایه از سرور آمد
- {state.grades.length > 0 && `: ${state.grades.map((g) => g.title).join('، ')}`}
-
-
{api.http.baseUrl}
-
- );
-}
diff --git a/src/api/README.md b/src/api/README.md
deleted file mode 100644
index a678b23..0000000
--- a/src/api/README.md
+++ /dev/null
@@ -1,91 +0,0 @@
-# اتصال به بکاند
-
-کلِ ارتباط با سرور از همین پوشه رد میشود. **هیچجای اپ مستقیم `fetch` نزنید** —
-مدیریتِ توکن، تکرارِ درخواست و رفتارِ درستِ خطا اینجا یکجا حل شدهاند.
-
-## راهاندازی
-
-**هیچ تنظیماتی لازم نیست.** بهطورِ پیشفرض به بکاندِ مشترک
-(`https://api.math.tadrisino2.ir/api`) وصل میشود:
-
-```bash
-npm install
-npm run dev
-```
-
-فقط اگر بکاند را روی ماشینِ خودتان بالا آوردهاید:
-
-```bash
-cp .env.example .env # و خطِ VITE_API_BASE_URL را باز کنید
-```
-
-برای اینکه ببینید اتصال برقرار است، یک خط کافیست (بعداً پاکش کنید):
-
-```tsx
-import { ConnectionCheck } from '@/api/ConnectionCheck';
-// داخلِ خروجیِ App:
-
-```
-
-گوشهی صفحه سبز میشود و فهرستِ پایهها را از سرور نشان میدهد، یا قرمز
-میشود و میگوید دقیقاً کجای کار میلنگد.
-
-## استفاده
-
-```tsx
-import { api, ApiError } from '@/api';
-
-const grades = await api.grades();
-const home = await api.home(); // پایهی خودِ کاربر
-const intro = await api.lessons.intro(lessonId);
-const answer = await api.ai.ask('این رو نفهمیدم', { lessonId });
-```
-
-### ورود
-
-```tsx
-await api.auth.guest(deviceId); // «شروع سریع»، بدونِ شماره
-// یا
-const { devOtp } = await api.auth.requestOtp('09120000000');
-await api.auth.login('09120000000', otp); // نامِ فیلد در سرور otp است
-```
-
-توکن خودش در `localStorage` ذخیره میشود و به هر درخواست اضافه میگردد.
-
-### خطاها
-
-```tsx
-try {
- await api.ai.ask(text, { lessonId });
-} catch (error) {
- if (error instanceof ApiError) {
- if (error.isRateLimited) { /* ۴۲۹ — خیلی سریع پرسیده */ }
- if (error.isProviderDown) { /* ۵۰۳ — سرویسِ AI موقتاً قطع */ }
- if (error.isNetwork) { /* اصلاً نرسید یا timeout */ }
- }
-}
-```
-
-## چرا اینقدر ملاحظه در `client.ts`
-
-هر تکهاش برای حالتیست که فقط زیرِ فشار پیش میآید:
-
-| مسئله | اگر نباشد چه میشود |
-|---|---|
-| `timeout` روی هر درخواست | یک بکاندِ کند سوکتها را باز نگه میدارد؛ مرورگر به سقفِ ۶ درخواستِ همزمان میخورد و کلِ اپ قفل میشود |
-| backoff با **jitter** | هزار کلاینت بعد از یک خطا *همزمان* دوباره میزنند و موجِ دوم سرور را میخواباند |
-| تکرارِ خودکار فقط روی `GET` | تکرارِ `POST` یعنی سفارش یا پاسخِ تکراری |
-| `Idempotency-Key` روی ثبتِ پاسخ و سفارش | دابلکلیکِ کاربر دو بار XP یا دو سفارش میسازد |
-| `refresh` تکنسخهای | ۲۰ درخواستِ همزمانِ ۴۰۱ ⇒ ۲۰ بار refresh ⇒ سرور همه را throttle میکند |
-| ادغامِ `GET`های یکسان | دو کامپوننت که با هم یک چیز میخواهند، دو برابر بار میسازند |
-| احترام به هدرِ `Retry-After` | بکاند صریح میگوید چقدر صبر کن؛ نادیده گرفتنش throttle را طولانیتر میکند |
-
-`credentials: 'omit'` هم عمدیست: احرازِ هویت توکنِ Bearer در هدر است و سمتِ
-سرور `CORS_ALLOW_CREDENTIALS=False` تنظیم شده. عوضش نکنید.
-
-## چیزی که هنوز نیست
-
-- صفحهی ورود در اپ وجود ندارد؛ `api.auth` آماده است ولی UI ندارد.
-- `api.home()` ساختارِ واقعیِ درسها را میدهد، ولی وصل کردنش به صفحهها
- هنوز انجام نشده — این PR عمداً `App.tsx` را دست نزده تا با کارِ در جریان
- تصادف نکند.
diff --git a/src/api/client.ts b/src/api/client.ts
deleted file mode 100644
index 584a1bc..0000000
--- a/src/api/client.ts
+++ /dev/null
@@ -1,317 +0,0 @@
-/**
- * هستهی ارتباط با بکاندِ تدریسینو.
- *
- * این فایل عمداً هیچ پکیجی اضافه نمیکند — فقط fetch استاندارد. هر چیزی که
- * اینجاست برای یک حالتِ خراب است که در فشار پیش میآید، نه برای حالتِ خوب:
- *
- * • timeout — بدونِ آن، یک بکاندِ کند سوکتها را باز نگه میدارد تا مرورگر
- * به سقفِ ۶ درخواستِ همزمان بخورد و کلِ اپ قفل شود.
- * • backoff با jitter — وقتی سرور ۴۲۹/۵۰۳ میدهد، تکرارِ فوری فقط بار را
- * بیشتر میکند. jitter لازم است وگرنه هزار کلاینت *همزمان* دوباره میزنند
- * و موجِ بعدی از موجِ اول بدتر است.
- * • فقط GET خودکار تکرار میشود — تکرارِ POST یعنی سفارشِ تکراری.
- * • refresh تکنسخهای — اگر ۲۰ درخواست با هم ۴۰۱ بگیرند، بدونِ این ۲۰ بار
- * refresh صدا زده میشود و سرور همه را throttle میکند.
- * • حذفِ درخواستِ تکراری — دو کامپوننت که همزمان یک GET را میخواهند، یک
- * درخواست میفرستند نه دو تا.
- */
-
-export interface Tokens {
- token: string;
- refreshToken: string;
-}
-
-export class ApiError extends Error {
- constructor(
- readonly status: number,
- message: string,
- readonly payload: Record = {},
- ) {
- super(message);
- this.name = 'ApiError';
- }
- /** ۴۰۱ — توکن نامعتبر یا منقضی. کاربر باید دوباره وارد شود. */
- get isUnauthorized() { return this.status === 401; }
- /** ۴۲۹ — سقفِ نرخ. `retryAfterMs` میگوید چقدر صبر کنید. */
- get isRateLimited() { return this.status === 429; }
- /** ۵۰۳ — سرویسِ بیرونی (هوش مصنوعی/پیامک) موقتاً در دسترس نیست. */
- get isProviderDown() { return this.status === 503; }
- /** درخواست اصلاً به سرور نرسید یا timeout شد. */
- get isNetwork() { return this.status === 0; }
- retryAfterMs?: number;
-}
-
-/* ─── تنظیمات ───────────────────────────────────────────────────────────── */
-
-export interface ClientOptions {
- /** پیشفرض از VITE_API_BASE_URL خوانده میشود. */
- baseUrl?: string;
- /** سقفِ زمانِ هر درخواست. پیشفرض ۱۵ ثانیه. */
- timeoutMs?: number;
- /** بیشترین تعدادِ تلاشِ دوباره برای GET. پیشفرض ۳. */
- maxRetries?: number;
- storage?: TokenStorage;
- /** وقتی نشست بهکل باطل شد (refresh هم شکست خورد) صدا زده میشود. */
- onSignedOut?: () => void;
-}
-
-export interface TokenStorage {
- get(): Tokens | null;
- set(t: Tokens | null): void;
-}
-
-const STORAGE_KEY = 'tadrisino.tokens';
-
-/** localStorage در حالتِ خصوصیِ مرورگر throw میکند؛ هیچکدام نباید اپ را بخواباند. */
-export function localTokenStorage(): TokenStorage {
- return {
- get() {
- try {
- const raw = localStorage.getItem(STORAGE_KEY);
- return raw ? (JSON.parse(raw) as Tokens) : null;
- } catch {
- return null;
- }
- },
- set(t) {
- try {
- if (t) localStorage.setItem(STORAGE_KEY, JSON.stringify(t));
- else localStorage.removeItem(STORAGE_KEY);
- } catch {
- /* نادیده */
- }
- },
- };
-}
-
-/**
- * بکاندِ مشترک. پیشفرض است تا کسی که تازه ریپو را clone میکند بدونِ هیچ
- * تنظیماتی بتواند اپ را اجرا کند و دادهی واقعی ببیند.
- * برای بکاندِ محلی، در فایلِ .env مقدارِ VITE_API_BASE_URL را عوض کنید.
- */
-const DEFAULT_API_BASE_URL = 'https://api.math.tadrisino2.ir/api';
-
-function resolveBaseUrl(explicit?: string): string {
- if (explicit) return explicit.replace(/\/+$/, '');
- const fromEnv = (import.meta as { env?: Record }).env?.VITE_API_BASE_URL;
- if (fromEnv) return fromEnv.replace(/\/+$/, '');
- return DEFAULT_API_BASE_URL;
-}
-
-/* ─── کمکیها ───────────────────────────────────────────────────────────── */
-
-const RETRIABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
-
-/** full jitter — هزار کلاینت نباید همزمان بیدار شوند. */
-function backoffMs(attempt: number, retryAfterMs?: number): number {
- if (retryAfterMs != null) return Math.min(retryAfterMs, 30_000);
- const ceiling = Math.min(1000 * 2 ** attempt, 8000);
- return Math.random() * ceiling;
-}
-
-function parseRetryAfter(header: string | null): number | undefined {
- if (!header) return undefined;
- const seconds = Number(header);
- if (Number.isFinite(seconds)) return seconds * 1000;
- const at = Date.parse(header);
- return Number.isNaN(at) ? undefined : Math.max(0, at - Date.now());
-}
-
-const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
-
-/** برای POSTهایی که چیزی میسازند؛ سرور هدرِ Idempotency-Key را میپذیرد. */
-function newIdempotencyKey(): string {
- const c = globalThis.crypto;
- if (c && 'randomUUID' in c) return c.randomUUID();
- return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
-}
-
-export interface RequestOptions {
- /** هدرهای اضافه. */
- headers?: Record;
- /** برای درخواستهای کند مثل پرسش از دستیار. */
- timeoutMs?: number;
- /** لغو از بیرون (مثلاً کاربر از صفحه رفت). */
- signal?: AbortSignal;
- /** POST را هم قابلِ تکرار میکند — فقط وقتی Idempotency-Key میفرستید. */
- retry?: boolean;
- /** کلیدِ یکتا تا تکرارِ درخواست سفارشِ تکراری نسازد. */
- idempotencyKey?: string;
-}
-
-/* ─── کلاینت ────────────────────────────────────────────────────────────── */
-
-export function createHttpClient(options: ClientOptions = {}) {
- const baseUrl = resolveBaseUrl(options.baseUrl);
- const defaultTimeout = options.timeoutMs ?? 15_000;
- const maxRetries = options.maxRetries ?? 3;
- const storage = options.storage ?? localTokenStorage();
-
- /** درخواستهای GETِ در جریان، تا دو کامپوننت یک چیز را دوبار نگیرند. */
- const inFlight = new Map>();
- /** فقط یک refresh در هر لحظه. */
- let refreshing: Promise | null = null;
-
- async function doRefresh(): Promise {
- const current = storage.get();
- if (!current?.refreshToken) return null;
- if (refreshing) return refreshing;
-
- refreshing = (async () => {
- try {
- const response = await fetch(`${baseUrl}/auth/refresh`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ refreshToken: current.refreshToken }),
- signal: AbortSignal.timeout(defaultTimeout),
- });
- if (!response.ok) return null;
- const data = (await response.json()) as Tokens;
- storage.set({ token: data.token, refreshToken: data.refreshToken });
- return data;
- } catch {
- return null;
- } finally {
- // در میکروتسکِ بعدی آزاد میشود تا همزمانها همین نتیجه را ببینند.
- queueMicrotask(() => { refreshing = null; });
- }
- })();
- return refreshing;
- }
-
- async function once(
- method: string,
- path: string,
- body: unknown,
- opts: RequestOptions,
- allowRefresh: boolean,
- ): Promise {
- const tokens = storage.get();
- const headers: Record = { Accept: 'application/json', ...opts.headers };
- if (body !== undefined) headers['Content-Type'] = 'application/json';
- if (tokens?.token) headers.Authorization = `Bearer ${tokens.token}`;
- if (opts.idempotencyKey) headers['Idempotency-Key'] = opts.idempotencyKey;
-
- // timeoutِ خودمان + لغوِ بیرونی، هر کدام زودتر رسید.
- const timer = AbortSignal.timeout(opts.timeoutMs ?? defaultTimeout);
- const signal = opts.signal ? anySignal([timer, opts.signal]) : timer;
-
- let response: Response;
- try {
- response = await fetch(`${baseUrl}${path}`, {
- method,
- headers,
- body: body === undefined ? undefined : JSON.stringify(body),
- signal,
- // احرازِ هویت هدرِ Bearer است، نه کوکی — سمتِ سرور هم عمداً
- // CORS_ALLOW_CREDENTIALS=False است. اینجا هم نباید عوض شود.
- credentials: 'omit',
- });
- } catch (err) {
- const aborted = err instanceof DOMException && err.name === 'AbortError';
- throw new ApiError(0, aborted ? 'زمانِ درخواست تمام شد' : 'ارتباط با سرور برقرار نشد');
- }
-
- if (response.status === 401 && allowRefresh) {
- const refreshed = await doRefresh();
- if (refreshed) return once(method, path, body, opts, false);
- storage.set(null);
- options.onSignedOut?.();
- }
-
- const text = await response.text();
- let parsed: Record = {};
- if (text) {
- try {
- parsed = JSON.parse(text) as Record;
- } catch {
- // سرور HTML داد (۵۰۲ از nginx مثلاً) — پیامِ خام گمراهکننده است.
- parsed = {};
- }
- }
-
- if (!response.ok) {
- const detail =
- (parsed.detail as string) ??
- (parsed.error as string) ??
- (response.status === 502 ? 'سرور در دسترس نیست' : `خطای ${response.status}`);
- const error = new ApiError(response.status, detail, parsed);
- error.retryAfterMs = parseRetryAfter(response.headers.get('Retry-After'));
- throw error;
- }
- return parsed as T;
- }
-
- async function request(
- method: string,
- path: string,
- body?: unknown,
- opts: RequestOptions = {},
- ): Promise {
- const retriable = opts.retry ?? method === 'GET';
-
- let lastError: ApiError | undefined;
- for (let attempt = 0; attempt <= (retriable ? maxRetries : 0); attempt++) {
- try {
- return await once(method, path, body, opts, true);
- } catch (err) {
- if (!(err instanceof ApiError)) throw err;
- lastError = err;
- const worthRetrying = err.isNetwork || RETRIABLE_STATUS.has(err.status);
- if (!retriable || !worthRetrying || attempt === maxRetries) throw err;
- if (opts.signal?.aborted) throw err;
- await sleep(backoffMs(attempt, err.retryAfterMs));
- }
- }
- throw lastError ?? new ApiError(0, 'خطای نامشخص');
- }
-
- /** GETها با هم ادغام میشوند تا رندرِ همزمانِ چند کامپوننت یک درخواست بدهد. */
- function get(path: string, opts: RequestOptions = {}): Promise {
- // درخواستِ لغوشدنی را ادغام نمیکنیم؛ لغوِ یکی نباید دیگری را بکشد.
- if (opts.signal) return request('GET', path, undefined, opts);
-
- const existing = inFlight.get(path);
- if (existing) return existing as Promise;
-
- const promise = request('GET', path, undefined, opts).finally(() => {
- inFlight.delete(path);
- });
- inFlight.set(path, promise);
- return promise;
- }
-
- return {
- baseUrl,
- storage,
- get,
- post: (path: string, body?: unknown, opts?: RequestOptions) =>
- request('POST', path, body, opts),
- patch: (path: string, body?: unknown, opts?: RequestOptions) =>
- request('PATCH', path, body, opts),
- /** POSTی که چیزی میسازد: با کلیدِ یکتا، پس تکرارش سفارشِ دوم نمیسازد. */
- create: (path: string, body?: unknown, opts: RequestOptions = {}) =>
- request('POST', path, body, {
- ...opts,
- retry: true,
- idempotencyKey: opts.idempotencyKey ?? newIdempotencyKey(),
- }),
- };
-}
-
-export type HttpClient = ReturnType;
-
-/** AbortSignal.any هنوز همهجا نیست؛ این جایگزینِ کوچکش است. */
-function anySignal(signals: AbortSignal[]): AbortSignal {
- const anyFn = (AbortSignal as { any?: (s: AbortSignal[]) => AbortSignal }).any;
- if (anyFn) return anyFn(signals);
- const controller = new AbortController();
- for (const s of signals) {
- if (s.aborted) {
- controller.abort(s.reason);
- break;
- }
- s.addEventListener('abort', () => controller.abort(s.reason), { once: true });
- }
- return controller.signal;
-}
diff --git a/src/api/http.ts b/src/api/http.ts
new file mode 100644
index 0000000..c2be150
--- /dev/null
+++ b/src/api/http.ts
@@ -0,0 +1,339 @@
+/**
+ * هستهی ارتباط با بکاند. فقط fetch استاندارد، بدونِ پکیج.
+ *
+ * هر چیزی که اینجاست برای یک حالتِ خراب است، نه حالتِ خوب:
+ * • timeout — بکاندِ کند نباید سوکتها را تا ابد باز نگه دارد.
+ * • تکرارِ خودکار فقط برای GET، با backoff و jitter؛ تکرارِ POST یعنی ثبتِ دوباره.
+ * • refreshِ تکنسخهای — ده درخواستِ همزمان با ۴۰۱ یک refresh میزنند، نه ده تا.
+ * • ادغامِ GETهای همزمانِ یکسان.
+ * • وقتی نشست بهکل باطل شد، onSignedOut خبر میدهد تا اپ به صفحهی ورود برگردد.
+ */
+
+export interface Tokens {
+ token: string;
+ refreshToken: string;
+}
+
+export class ApiError extends Error {
+ retryAfterMs?: number;
+
+ constructor(
+ readonly status: number,
+ readonly detail: string,
+ readonly body: Record = {},
+ ) {
+ super(detail);
+ this.name = 'ApiError';
+ }
+
+ /** کدِ ماشینیِ خطا، اگر سرور داده باشد (مثلاً "no_hearts"). */
+ get code(): string | undefined {
+ return typeof this.body.code === 'string' ? this.body.code : undefined;
+ }
+ get isNetwork() { return this.status === 0; }
+ get isUnauthorized() { return this.status === 401; }
+ get isRateLimited() { return this.status === 429; }
+ get isProviderDown() { return this.status === 503; }
+}
+
+export interface TokenStorage {
+ get(): Tokens | null;
+ set(tokens: Tokens | null): void;
+}
+
+const STORAGE_KEY = 'tadrisino.tokens';
+
+/** localStorage در حالتِ خصوصیِ بعضی مرورگرها throw میکند؛ اپ نباید بخوابد. */
+export const localTokenStorage: TokenStorage = {
+ get() {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ return raw ? (JSON.parse(raw) as Tokens) : null;
+ } catch {
+ return null;
+ }
+ },
+ set(tokens) {
+ try {
+ if (tokens) localStorage.setItem(STORAGE_KEY, JSON.stringify(tokens));
+ else localStorage.removeItem(STORAGE_KEY);
+ } catch {
+ /* ذخیره نشد؛ نشست فقط تا بستنِ صفحه میماند */
+ }
+ },
+};
+
+export interface RequestOptions {
+ timeoutMs?: number;
+ signal?: AbortSignal;
+ /** POST را هم تکرارپذیر میکند؛ فقط همراهِ idempotencyKey معنا دارد. */
+ retry?: boolean;
+ idempotencyKey?: string;
+}
+
+export interface HttpClientOptions {
+ baseUrl: string;
+ storage?: TokenStorage;
+ timeoutMs?: number;
+ maxRetries?: number;
+}
+
+const RETRIABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
+
+function backoffMs(attempt: number, retryAfterMs?: number): number {
+ if (retryAfterMs != null) return Math.min(retryAfterMs, 30_000);
+ return Math.random() * Math.min(1000 * 2 ** attempt, 8000);
+}
+
+function parseRetryAfter(header: string | null): number | undefined {
+ if (!header) return undefined;
+ const seconds = Number(header);
+ if (Number.isFinite(seconds)) return seconds * 1000;
+ const at = Date.parse(header);
+ return Number.isNaN(at) ? undefined : Math.max(0, at - Date.now());
+}
+
+const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+
+export function newIdempotencyKey(): string {
+ const c = globalThis.crypto;
+ if (c && 'randomUUID' in c) return c.randomUUID();
+ return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
+}
+
+/** AbortSignal.any هنوز همهجا نیست؛ جایگزینِ کوچکش. */
+function anySignal(signals: AbortSignal[]): AbortSignal {
+ const native = (AbortSignal as { any?: (s: AbortSignal[]) => AbortSignal }).any;
+ if (native) return native(signals);
+ const controller = new AbortController();
+ for (const s of signals) {
+ if (s.aborted) {
+ controller.abort(s.reason);
+ break;
+ }
+ s.addEventListener('abort', () => controller.abort(s.reason), { once: true });
+ }
+ return controller.signal;
+}
+
+function timeoutSignal(ms: number): AbortSignal {
+ if ('timeout' in AbortSignal) return AbortSignal.timeout(ms);
+ const controller = new AbortController();
+ setTimeout(() => controller.abort(), ms);
+ return controller.signal;
+}
+
+async function parseBody(response: Response): Promise> {
+ const text = await response.text();
+ if (!text) return {};
+ try {
+ const parsed: unknown = JSON.parse(text);
+ return parsed && typeof parsed === 'object' ? (parsed as Record) : {};
+ } catch {
+ // HTML بهجای JSON: معمولاً ۵۰۲ِ nginx، یا آدرسِ API اشتباه است و
+ // سرورِ فرانت index.html را برگردانده.
+ return {};
+ }
+}
+
+function errorFrom(response: Response, body: Record): ApiError {
+ const detail =
+ (typeof body.detail === 'string' && body.detail) ||
+ (typeof body.error === 'string' && body.error) ||
+ firstFieldError(body) ||
+ (response.status >= 500 ? 'سرور موقتاً در دسترس نیست' : `خطای ${response.status}`);
+ const error = new ApiError(response.status, detail, body);
+ error.retryAfterMs = parseRetryAfter(response.headers.get('Retry-After'));
+ return error;
+}
+
+/** خطای اعتبارسنجیِ DRF به شکلِ {field: ["پیام"]} است. */
+function firstFieldError(body: Record): string | undefined {
+ for (const value of Object.values(body)) {
+ if (Array.isArray(value) && typeof value[0] === 'string') return value[0];
+ }
+ return undefined;
+}
+
+export function createHttpClient(options: HttpClientOptions) {
+ const baseUrl = options.baseUrl.replace(/\/+$/, '');
+ const storage = options.storage ?? localTokenStorage;
+ const defaultTimeout = options.timeoutMs ?? 15_000;
+ const maxRetries = options.maxRetries ?? 2;
+
+ const inFlight = new Map>();
+ const signedOutListeners = new Set<() => void>();
+ let refreshing: Promise | null = null;
+
+ function authHeaders(): Record {
+ const token = storage.get()?.token;
+ return token ? { Authorization: `Bearer ${token}` } : {};
+ }
+
+ function signOut() {
+ storage.set(null);
+ signedOutListeners.forEach((listener) => listener());
+ }
+
+ async function refreshTokens(): Promise {
+ const current = storage.get();
+ if (!current?.refreshToken) return null;
+ if (refreshing) return refreshing;
+
+ refreshing = (async () => {
+ try {
+ const response = await fetch(`${baseUrl}/auth/refresh`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ refreshToken: current.refreshToken }),
+ signal: timeoutSignal(defaultTimeout),
+ credentials: 'omit',
+ });
+ if (!response.ok) return null;
+ const data = (await response.json()) as Tokens;
+ const next = { token: data.token, refreshToken: data.refreshToken || current.refreshToken };
+ storage.set(next);
+ return next;
+ } catch {
+ return null;
+ } finally {
+ queueMicrotask(() => {
+ refreshing = null;
+ });
+ }
+ })();
+ return refreshing;
+ }
+
+ async function send(
+ method: string,
+ path: string,
+ body: unknown,
+ opts: RequestOptions,
+ ): Promise {
+ const headers: Record = { Accept: 'application/json', ...authHeaders() };
+ if (body !== undefined) headers['Content-Type'] = 'application/json';
+ if (opts.idempotencyKey) headers['Idempotency-Key'] = opts.idempotencyKey;
+
+ const timer = timeoutSignal(opts.timeoutMs ?? defaultTimeout);
+ try {
+ return await fetch(`${baseUrl}${path}`, {
+ method,
+ headers,
+ body: body === undefined ? undefined : JSON.stringify(body),
+ signal: opts.signal ? anySignal([timer, opts.signal]) : timer,
+ // احرازِ هویت هدرِ Bearer است، نه کوکی؛ سرور هم CORS را بدونِ credentials دارد.
+ credentials: 'omit',
+ });
+ } catch (err) {
+ if (opts.signal?.aborted) throw err;
+ const timedOut = err instanceof DOMException && (err.name === 'TimeoutError' || err.name === 'AbortError');
+ throw new ApiError(0, timedOut ? 'زمانِ درخواست تمام شد' : 'ارتباط با سرور برقرار نشد');
+ }
+ }
+
+ /** یک درخواست، با یکبار refresh در صورتِ ۴۰۱. */
+ async function sendWithRefresh(
+ method: string,
+ path: string,
+ body: unknown,
+ opts: RequestOptions,
+ ): Promise {
+ const hadToken = Boolean(storage.get()?.token);
+ let response = await send(method, path, body, opts);
+ if (response.status === 401 && hadToken) {
+ const refreshed = await refreshTokens();
+ if (refreshed) response = await send(method, path, body, opts);
+ else signOut();
+ }
+ return response;
+ }
+
+ async function request(method: string, path: string, body?: unknown, opts: RequestOptions = {}): Promise {
+ const retriable = opts.retry ?? method === 'GET';
+ for (let attempt = 0; ; attempt++) {
+ try {
+ const response = await sendWithRefresh(method, path, body, opts);
+ const parsed = await parseBody(response);
+ if (!response.ok) throw errorFrom(response, parsed);
+ return parsed as T;
+ } catch (err) {
+ if (!(err instanceof ApiError)) throw err;
+ const worthRetrying = err.isNetwork || RETRIABLE_STATUS.has(err.status);
+ if (!retriable || !worthRetrying || attempt >= maxRetries || opts.signal?.aborted) throw err;
+ await sleep(backoffMs(attempt, err.retryAfterMs));
+ }
+ }
+ }
+
+ function get(path: string, opts: RequestOptions = {}): Promise {
+ // درخواستِ لغوشدنی ادغام نمیشود؛ لغوِ یکی نباید دیگری را بکشد.
+ if (opts.signal) return request('GET', path, undefined, opts);
+ const existing = inFlight.get(path);
+ if (existing) return existing as Promise;
+ const promise = request('GET', path, undefined, opts).finally(() => inFlight.delete(path));
+ inFlight.set(path, promise);
+ return promise;
+ }
+
+ /**
+ * درخواستِ Server-Sent Events (پاسخِ تکهتکهی دستیار).
+ * رویدادها با خطِ خالی جدا میشوند: `event: <نام>\ndata: `.
+ */
+ async function stream(
+ path: string,
+ body: unknown,
+ onEvent: (event: string, data: Record) => void,
+ opts: RequestOptions = {},
+ ): Promise {
+ const response = await sendWithRefresh('POST', path, body, { timeoutMs: 120_000, ...opts });
+ if (!response.ok || !response.body) {
+ throw errorFrom(response, await parseBody(response));
+ }
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder();
+ let buffer = '';
+ for (;;) {
+ const { value, done } = await reader.read();
+ if (done) break;
+ buffer += decoder.decode(value, { stream: true });
+ const parts = buffer.split('\n\n');
+ buffer = parts.pop() ?? '';
+ for (const part of parts) {
+ const event = /^event: (.+)$/m.exec(part)?.[1];
+ const data = /^data: (.+)$/m.exec(part)?.[1];
+ if (!event || !data) continue;
+ try {
+ onEvent(event, JSON.parse(data) as Record);
+ } catch (err) {
+ if (err instanceof ApiError) throw err;
+ /* تکهی ناقص؛ نادیده */
+ }
+ }
+ }
+ }
+
+ return {
+ baseUrl,
+ storage,
+ get,
+ post: (path: string, body?: unknown, opts?: RequestOptions) => request('POST', path, body, opts),
+ patch: (path: string, body?: unknown, opts?: RequestOptions) => request('PATCH', path, body, opts),
+ /** POSTی که چیزی ثبت میکند: با کلیدِ یکتا، پس تکرارش دوبار ثبت نمیکند. */
+ create: (path: string, body?: unknown, opts: RequestOptions = {}) =>
+ request('POST', path, body, {
+ ...opts,
+ retry: true,
+ idempotencyKey: opts.idempotencyKey ?? newIdempotencyKey(),
+ }),
+ stream,
+ onSignedOut(listener: () => void) {
+ signedOutListeners.add(listener);
+ return () => {
+ signedOutListeners.delete(listener);
+ };
+ },
+ };
+}
+
+export type HttpClient = ReturnType;
diff --git a/src/api/index.ts b/src/api/index.ts
index f1b7da8..3904512 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -1,459 +1,184 @@
/**
- * کلاینتِ API تدریسینو — برای فرانتِ React/TypeScript (MathApp_frontend).
+ * تنها درِ ارتباط با بکاند. هیچ جای دیگرِ اپ fetch نمیزند.
*
- * از روی OpenAPI واقعیِ همین بکاند نوشته شده، نه از روی حدس. نامِ فیلدها
- * دقیقاً همان چیزیست که سرور برمیگرداند (camelCase).
- *
- * نکتههای مهمِ اتصال:
- * - احرازِ هویت توکنِ Bearer است، نه کوکی. پس `credentials: 'include'` لازم
- * نیست و CORS سمتِ سرور هم عمداً بدونِ credentials تنظیم شده.
- * - توکنِ دسترسی امضاشده و کوتاهعمر است؛ با ۴۰۱ باید `refresh` صدا زد.
- * این کلاینت خودش یکبار refresh میکند و درخواست را تکرار.
- * - برای پرسش از دستیار کافیست `lessonId` بدهید؛ سرور خودش میفهمد آن درس
- * مالِ کدام پایه و فصل است و پاسخ را به کتاب مستند میکند.
+ * هر تابع یک اندپوینت است و نامِ فیلدها عیناً همان نامِ سرور است.
*/
+import { resolveApiBaseUrl } from '@/config';
+import { ApiError, createHttpClient, newIdempotencyKey } from './http';
+import type * as T from './types';
-export type Json = Record;
+export { ApiError };
+export type * from './types';
-export interface Tokens {
- token: string;
- refreshToken: string;
+export const http = createHttpClient({ baseUrl: resolveApiBaseUrl() });
+
+const q = (params: Record) => {
+ const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== '');
+ return entries.length ? `?${new URLSearchParams(entries.map(([k, v]) => [k, String(v)]))}` : '';
+};
+
+function saveTokens(data: T.AuthResponse) {
+ http.storage.set({ token: data.token, refreshToken: data.refreshToken ?? '' });
+ return data;
}
-/* ─── انواعِ پاسخ ───────────────────────────────────────────────────────── */
+export const api = {
+ auth: {
+ isSignedIn: () => Boolean(http.storage.get()?.token),
-export type NodeStatus = 'locked' | 'unlocked' | 'in_progress' | 'completed';
+ /** مهمان: فقط با شناسهی دستگاه. refreshToken هم میگیرد، پس بعد از یک ساعت نمیافتد. */
+ guest: (deviceId: string) =>
+ http.post('/auth/session', { type: 'guest', deviceId }).then(saveTokens),
-export interface HomeNode {
- id: string;
- title: string;
- status: NodeStatus;
- lessonIds: number[];
-}
-export interface HomeUnit {
- id: string;
- title: string;
- nodes: HomeNode[];
-}
-export interface HomeResponse {
- units: HomeUnit[];
- stats: { progressPercent: number; xp: number; coins: number };
-}
+ /** گامِ ۱: پیامکِ کد. `devOtp` فقط وقتی سرور در حالتِ توسعه است برمیگردد. */
+ requestOtp: (phone: string) =>
+ http.post<{ success: boolean; devOtp?: string }>('/auth/otp/request', { phone }),
-export interface Grade {
- id: number;
- title: string;
- icon: string;
- color: string;
-}
+ /**
+ * گامِ ۲: تأییدِ کد. اگر الان مهمان باشد، سرور همان کاربر را نگه میدارد و
+ * پیشرفتش از دست نمیرود (توکنِ مهمان همراهِ همین درخواست میرود).
+ */
+ login: (phone: string, otp: string) =>
+ http.post('/auth/login', { phone, otp }).then(saveTokens),
-export interface UserSummary {
- userId: string;
- name: string;
- avatar: string;
- gradeId: number | null;
- xp: number;
- coins: number;
- streak: { current: number; longest: number };
- subscription: string;
-}
-
-export interface UserProfile {
- userId: string;
- phone: string | null;
- name: string;
- avatar: string;
- isGuest: boolean;
- gradeId: number | null;
- birthYear: number | null;
- parentPhone: string;
- bio: string;
-}
-
-export interface LessonIntro {
- lessonId: string;
- title: string;
- introText: string;
- /** برای انتخابِ حالتِ ماسکوت در صفحهی درس */
- mascotAnimation: string;
- /**
- * صفر یعنی متنِ درس هست ولی آزمونش هنوز ساخته نشده — «بهزودی» نشان دهید،
- * نه کویزِ خالی. `complete` هم `totalQuestions: 0` را نمیپذیرد.
- */
- questionCount: number;
-}
-
-export interface QuestionOption {
- id: string;
- value: string;
-}
-export interface Question {
- id: string;
- type: 'multiple_choice' | 'count_objects';
- prompt: string;
- options: QuestionOption[];
-}
-
-export interface AnswerResult {
- correct: boolean;
- correctOptionId: string;
- xpEarned: number;
- heartsRemaining: number;
- /** حالتِ ماسکوت: درست → happy، غلط → encourage */
- mascotMood: string;
-}
-
-export interface LessonComplete {
- stars: number;
- xpEarned: number;
- coinsEarned: number;
- stats: { progressPercent: number; xp: number; coins: number };
-}
-
-export interface Hearts {
- currentHearts: number;
- maxHearts: number;
- nextRefillAt: string | null;
- isUnlimited: boolean;
-}
-
-export interface Character {
- characterId: string;
- slug: string;
- name: string;
- icon: string;
- unlockXp: number;
- unlocked: boolean;
- selected: boolean;
-}
-
-export interface AskResponse {
- conversationId: string;
- messageId: string;
- answer: string;
- suggestedFollowUps: string[];
- remainingQuota: number;
- /** تکههای کتاب که پاسخ رویشان بنا شده؛ خالی یعنی پاسخِ عمومی */
- usedChunks: { title: string; summary: string }[];
-}
-
-export interface Plan {
- id: string;
- title: string;
- price: number;
- durationDays: number;
- features: string[];
-}
-
-/* ─── خطا ───────────────────────────────────────────────────────────────── */
-
-export class ApiError extends Error {
- constructor(
- readonly status: number,
- readonly detail: string,
- readonly body: Json,
- ) {
- super(detail);
- this.name = 'ApiError';
- }
-
- /** ۴۲۹: throttle یا اتمامِ سهمیهی روزانهی دستیار */
- get isRateLimited() {
- return this.status === 429;
- }
- /** ۵۰۳: سرویسِ هوش مصنوعی/پیامک موقتاً در دسترس نیست */
- get isProviderDown() {
- return this.status === 503;
- }
-}
-
-/* ─── کلاینت ────────────────────────────────────────────────────────────── */
-
-export interface ClientOptions {
- baseUrl?: string;
- /** خواندن/نوشتنِ توکن؛ پیشفرض localStorage */
- storage?: {
- get(): Tokens | null;
- set(t: Tokens | null): void;
- };
-}
-
-const STORAGE_KEY = 'tadrisino.tokens';
-
-function defaultStorage() {
- return {
- get(): Tokens | null {
+ /** خروج در سرور (باطل کردنِ refresh token) و بعد پاک کردنِ حافظه. */
+ async logout() {
+ const refreshToken = http.storage.get()?.refreshToken;
try {
- const raw = localStorage.getItem(STORAGE_KEY);
- return raw ? (JSON.parse(raw) as Tokens) : null;
- } catch {
- return null;
+ await http.post('/auth/logout', refreshToken ? { refreshToken } : {});
+ } finally {
+ http.storage.set(null);
}
},
- set(t: Tokens | null) {
- try {
- if (t) localStorage.setItem(STORAGE_KEY, JSON.stringify(t));
- else localStorage.removeItem(STORAGE_KEY);
- } catch {
- /* حالتِ خصوصیِ مرورگر — نادیده */
- }
- },
- };
-}
+ },
-export function createClient(options: ClientOptions = {}) {
- const baseUrl = (options.baseUrl ?? '/api').replace(/\/$/, '');
- const storage = options.storage ?? defaultStorage();
+ grades: () => http.get<{ grades: T.Grade[] }>('/grades').then((r) => r.grades),
- async function raw(
- method: string,
- path: string,
- body?: unknown,
- extraHeaders: Record = {},
- retryOn401 = true,
- ): Promise {
- const tokens = storage.get();
- const headers: Record = { ...extraHeaders };
- if (body !== undefined) headers['Content-Type'] = 'application/json';
- if (tokens?.token) headers['Authorization'] = `Bearer ${tokens.token}`;
+ profile: {
+ get: () => http.get('/user/profile'),
+ update: (patch: Partial>) =>
+ http.patch('/user/profile', patch),
+ setGrade: (gradeId: number) =>
+ http.post<{ success: boolean; gradeId: number }>('/user/grade', { gradeId }),
+ },
- const response = await fetch(`${baseUrl}${path}`, {
- method,
- headers,
- body: body === undefined ? undefined : JSON.stringify(body),
- });
+ summary: () => http.get('/user/summary'),
+ hearts: () => http.get('/user/hearts'),
+ streakCalendar: () => http.get('/user/streak/calendar'),
- if (response.status === 401 && retryOn401 && tokens?.refreshToken) {
- const refreshed = await refresh(tokens.refreshToken).catch(() => null);
- if (refreshed) return raw(method, path, body, extraHeaders, false);
- storage.set(null);
- }
+ /** بدونِ grade، پایهی ذخیرهشدهی خودِ کاربر ملاک است. */
+ home: () => http.get('/home'),
+ nodeLessons: (nodeId: string) => http.get(`/nodes/${nodeId}/lessons`),
- const text = await response.text();
- const parsed: Json = text ? (JSON.parse(text) as Json) : {};
- if (!response.ok) {
- const detail =
- (parsed.detail as string) ??
- (parsed.error as string) ??
- `خطای ${response.status}`;
- throw new ApiError(response.status, detail, parsed);
- }
- return parsed as T;
- }
-
- async function refresh(refreshToken: string) {
- const response = await fetch(`${baseUrl}/auth/refresh`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ refreshToken }),
- });
- if (!response.ok) return null;
- const data = (await response.json()) as Tokens;
- storage.set({ token: data.token, refreshToken: data.refreshToken });
- return data;
- }
-
- return {
- /* ── ورود (صفحهای که فعلاً در فرانت نیست و باید ساخته شود) ────────── */
- auth: {
- /** مهمان: بدونِ شماره، فقط با شناسهی دستگاه. برای «شروع سریع». */
- async guest(deviceId: string) {
- const data = await raw<{ userId: string; token: string; isNewUser: boolean; isGuest: boolean }>(
- 'POST', '/auth/session', { type: 'guest', deviceId },
- );
- storage.set({ token: data.token, refreshToken: '' });
- return data;
- },
- /** گامِ ۱: ارسالِ پیامکِ کد. در حالتِ توسعه `devOtp` هم برمیگردد. */
- requestOtp(phone: string) {
- return raw<{ success: boolean; devOtp?: string; note?: string }>(
- 'POST', '/auth/otp/request', { phone },
- );
- },
- /** گامِ ۲: تأییدِ کد و گرفتنِ توکن. نامِ فیلد در سرور `otp` است. */
- async login(phone: string, otp: string) {
- const data = await raw<{ userId: string } & Tokens>(
- 'POST', '/auth/login', { phone, otp },
- );
- storage.set({ token: data.token, refreshToken: data.refreshToken });
- return data;
- },
- logout() {
- storage.set(null);
- },
- isSignedIn() {
- return Boolean(storage.get()?.token);
- },
- },
-
- /* ── صفحهی «بیا باهم آشنا بشیم» ──────────────────────────────────── */
- profile: {
- get: () => raw('GET', '/user/profile'),
- /** اسم/آواتار/… . فقط فیلدهایی که میفرستید عوض میشوند. */
- update: (patch: Partial>) =>
- raw('PATCH', '/user/profile', patch),
- setGrade: (gradeId: number) =>
- raw<{ success: boolean; gradeId: number }>('POST', '/user/grade', { gradeId }),
- },
-
- /** فهرستِ پایهها را از سرور بگیرید، هاردکد نکنید — پایهها اضافه میشوند. */
- grades: () => raw<{ grades: Grade[] }>('GET', '/grades').then((r) => r.grades),
-
- /* ── صفحهی خانه و نقشهی مهارت ────────────────────────────────────── */
- /** بدونِ gradeId، پایهی ذخیرهشدهی خودِ کاربر ملاک است. */
- home: (gradeId?: number) =>
- raw('GET', gradeId ? `/home?grade=${gradeId}` : '/home'),
- summary: () => raw('GET', '/user/summary'),
- hearts: () => raw('GET', '/user/hearts'),
- streakCalendar: () =>
- raw<{ currentStreak: number; longestStreak: number; activeDays: string[] }>(
- 'GET', '/user/streak/calendar',
+ lesson: {
+ intro: (lessonId: number) => http.get(`/lessons/${lessonId}/intro`),
+ questions: (lessonId: number) =>
+ http.get<{ questions: T.Question[] }>(`/lessons/${lessonId}/questions`).then((r) => r.questions),
+ /**
+ * کلیدِ یکتا را یکبار برای هر جواب بسازید و در تکرار همان را بفرستید تا
+ * امتیاز و قلب دوبار حساب نشود.
+ */
+ answer: (questionId: string, selectedOptionId: string, idempotencyKey = newIdempotencyKey()) =>
+ http.post(
+ `/questions/${questionId}/answer`,
+ { selectedOptionId: Number(selectedOptionId) },
+ { idempotencyKey, retry: true },
),
- leaderboard: (period: 'weekly' | 'all_time' = 'weekly') =>
- raw<{ period: string; entries: unknown[] }>('GET', `/leaderboard?period=${period}`),
+ /** مجموعِ XP و سکهی کاربر اینجا بهروز میشود، نه سرِ هر جواب. */
+ complete: (lessonId: number, result: { correctCount: number; totalQuestions: number }, idempotencyKey?: string) =>
+ http.create(`/lessons/${lessonId}/complete`, result, { idempotencyKey }),
+ report: (questionId: string, reason: T.ReportReason, note?: string) =>
+ http.post<{ success: boolean; reportId: number; alreadyReported: boolean }>(
+ `/questions/${questionId}/report`,
+ { reason, ...(note ? { note } : {}) },
+ ),
+ },
- /** کاتالوگِ کاراکترهای گیمیفیکیشن — مثلِ ماسکاتهای Duolingo، بازشدن بر اساسِ XP. */
- characters: {
- list: () => raw<{ characters: Character[] }>('GET', '/characters').then((r) => r.characters),
- select: (characterId: string) =>
- raw<{ characterId: string; selected: boolean }>('POST', '/characters/select', { characterId }),
+ leaderboard: (period: T.LeaderboardPeriod) =>
+ http.get<{ period: string; entries: T.LeaderboardEntry[] }>(`/leaderboard${q({ period })}`).then((r) => r.entries),
+
+ characters: {
+ list: () => http.get<{ characters: T.Character[] }>('/characters').then((r) => r.characters),
+ select: (characterId: string) =>
+ http.post<{ characterId: string; selected: boolean }>('/characters/select', { characterId }),
+ },
+
+ ai: {
+ /**
+ * پرسش با پاسخِ تکهتکه. `lessonId` پاسخ را به کتابِ همان درس مستند میکند؛
+ * `questionId` به دستیار میگوید دانشآموز کدام سوال را غلط زده.
+ */
+ async ask(
+ input: { userQuestion: string; conversationId?: string; lessonId?: number; questionId?: string },
+ onDelta: (text: string) => void,
+ signal?: AbortSignal,
+ ): Promise {
+ let done: T.AskDone | null = null;
+ await http.stream(
+ '/ai/ask',
+ { ...input, lessonId: input.lessonId?.toString(), stream: true },
+ (event, data) => {
+ if (event === 'chunk') onDelta(String(data.delta ?? ''));
+ else if (event === 'done') done = data as unknown as T.AskDone;
+ else if (event === 'error') throw new ApiError(503, String(data.detail ?? 'دستیار در دسترس نیست'), data);
+ },
+ { signal },
+ );
+ if (!done) throw new ApiError(0, 'پاسخ کامل نرسید');
+ return done;
},
+ feedback: (messageId: string, isHelpful: boolean, reason?: string) =>
+ http.post<{ success: boolean }>(`/ai/messages/${messageId}/feedback`, {
+ isHelpful,
+ ...(reason ? { reason } : {}),
+ }),
+ history: (conversationId: string) =>
+ http.get<{ conversationId: string; lessonId: string | null; messages: T.ConversationMessage[] }>(
+ `/ai/conversations/${conversationId}/messages`,
+ ),
+ },
- /* ── صفحهی درس و سوال ─────────────────────────────────────────────── */
- lesson: {
- intro: (lessonId: number) => raw('GET', `/lessons/${lessonId}/intro`),
- questions: (lessonId: number) =>
- raw<{ questions: Question[] }>('GET', `/lessons/${lessonId}/questions`).then((r) => r.questions),
- /**
- * `idempotencyKey` را بدهید تا اگر شبکه قطع شد و کاربر دوباره زد،
- * امتیاز/قلب دوبار حساب نشود.
- */
- answer: (questionId: number, selectedOptionId: number, idempotencyKey?: string) =>
- raw(
- 'POST', `/questions/${questionId}/answer`, { selectedOptionId },
- idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {},
- ),
- /**
- * پایانِ درس. `correctCount` را سرور با تعدادِ واقعیِ سوالها سقف میزند.
- * مجموعِ XPِ کاربر **اینجا** بهروز میشود، نه سرِ هر پاسخ.
- */
- complete: (
- lessonId: number,
- result: { correctCount: number; totalQuestions: number },
- idempotencyKey?: string,
- ) =>
- raw(
- 'POST', `/lessons/${lessonId}/complete`, result,
- idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {},
- ),
- },
+ notifications: {
+ list: () => http.get<{ items: T.NotificationItem[]; unreadCount: number }>('/notifications'),
+ markRead: (id: number) => http.patch(`/notifications/${id}/read`),
+ markAllRead: () => http.patch('/notifications/read-all'),
+ },
- /* ── صفحهی «از رُبو بپرس» ─────────────────────────────────────────── */
- ai: {
- /**
- * `lessonId` را بدهید تا پاسخ به کتابِ همان درس مستند شود؛ لازم نیست
- * subject/grade/chapter را خودتان حساب کنید.
- * `conversationId` را از پاسخِ قبلی پاس بدهید تا گفتگو ادامه پیدا کند.
- */
- ask: (input: {
- userQuestion: string;
- lessonId?: number | string;
- conversationId?: string;
- questionId?: number | string;
- idempotencyKey?: string;
- }) => {
- const { idempotencyKey, ...body } = input;
- return raw(
- 'POST', '/ai/ask', body,
- idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {},
- );
- },
+ subscription: {
+ plans: () => http.get<{ plans: T.Plan[] }>('/subscription/plans').then((r) => r.plans),
+ status: () => http.get('/subscription/status'),
+ /** سفارش میسازد؛ paymentUrl خودش سراغِ درگاه میرود. */
+ purchase: (planId: string) => http.create('/subscription/purchase', { planId }),
+ /** کدِ تخفیف باید پیش از رفتن به درگاه ثبت شود؛ هر سفارش فقط یک کد. */
+ redeem: (code: string, orderId: string) =>
+ http.post('/discount/redeem', { code, orderId }),
+ order: (orderId: string) => http.get(`/payment/orders/${orderId}`),
+ },
- /**
- * همان ask ولی توکنبهتوکن (SSE). اولین کلمه ~۳.۵ ثانیه بهجای ~۱۰.
- * برای حبابِ چتِ «در حالِ تایپ» همین را استفاده کنید.
- */
- async askStream(
- input: { userQuestion: string; lessonId?: number | string; conversationId?: string },
- onDelta: (text: string) => void,
- ): Promise<{ conversationId?: string; messageId?: string }> {
- const tokens = storage.get();
- const response = await fetch(`${baseUrl}/ai/ask`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- ...(tokens?.token ? { Authorization: `Bearer ${tokens.token}` } : {}),
- },
- body: JSON.stringify({ ...input, stream: true }),
- });
- if (!response.ok || !response.body) {
- throw new ApiError(response.status, 'استریم شروع نشد', {});
- }
+ support: {
+ createTicket: (subject: string, category: string) =>
+ http.create<{ ticketId: string; status: string }>('/support/tickets', { subject, category }),
+ ticket: (ticketId: string) => http.get(`/support/tickets/${ticketId}`),
+ reply: (ticketId: string, text: string) =>
+ http.create<{ messageId: string; success: boolean }>(`/support/tickets/${ticketId}/messages`, { text }),
+ bugReport: (title: string, description: string) =>
+ http.create<{ bugReportId: string; status: string }>('/support/bug-reports', { title, description }),
+ },
- const reader = response.body.getReader();
- const decoder = new TextDecoder();
- let buffer = '';
- let done: { conversationId?: string; messageId?: string } = {};
+ family: {
+ link: () => http.get('/family/link'),
+ invite: (childPhone: string) =>
+ http.post<{ familyLinkId: string; inviteCode: string; status: string }>('/family/link/invite', { childPhone }),
+ accept: (inviteCode: string) =>
+ http.post<{ familyLinkId: string; status: string }>('/family/link/accept', { inviteCode }),
+ weeklyReport: () => http.get('/family/weekly-report'),
+ settings: () => http.get('/family/settings'),
+ updateSettings: (patch: Partial) => http.patch('/family/settings', patch),
+ },
- for (;;) {
- const { value, done: finished } = await reader.read();
- if (finished) break;
- buffer += decoder.decode(value, { stream: true });
-
- // رویدادها با یک خطِ خالی از هم جدا میشوند.
- const parts = buffer.split('\n\n');
- buffer = parts.pop() ?? '';
- for (const part of parts) {
- const event = /^event: (.+)$/m.exec(part)?.[1];
- const dataLine = /^data: (.+)$/m.exec(part)?.[1];
- if (!event || !dataLine) continue;
- const payload = JSON.parse(dataLine) as Json;
- if (event === 'chunk') onDelta(String(payload.delta ?? ''));
- else if (event === 'done') done = payload as typeof done;
- else if (event === 'error') {
- throw new ApiError(503, String(payload.detail ?? 'خطای دستیار'), payload);
- }
- }
- }
- return done;
- },
-
- /** 👍/👎 روی یک پاسخ. `reason` اختیاری ولی خیلی مفید است. */
- feedback: (messageId: string | number, isHelpful: boolean, reason?: string) =>
- raw<{ success: boolean }>('POST', `/ai/messages/${messageId}/feedback`, {
- isHelpful,
- ...(reason ? { reason } : {}),
- }),
-
- history: (conversationId: string | number) =>
- raw<{ conversationId: string; lessonId: string | null; messages: unknown[] }>(
- 'GET', `/ai/conversations/${conversationId}/messages`,
- ),
- },
-
- /* ── صفحهی اشتراک ─────────────────────────────────────────────────── */
- subscription: {
- plans: () => raw<{ plans: Plan[] }>('GET', '/subscription/plans').then((r) => r.plans),
- status: () =>
- raw<{ active: boolean; planId: string | null; expiresAt: string | null }>(
- 'GET', '/subscription/status',
- ),
- /**
- * `paymentUrl` را در مرورگر باز کنید. آن آدرس خودش سراغِ زرینپال
- * میرود؛ اپ نباید مستقیم با درگاه حرف بزند.
- */
- purchase: (planId: string, idempotencyKey?: string) =>
- raw<{ orderId: string; paymentUrl: string }>(
- 'POST', '/subscription/purchase', { planId },
- idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {},
- ),
- },
-
- notifications: {
- list: () => raw<{ items: unknown[]; unreadCount: number }>('GET', '/notifications'),
- markRead: (id: number) => raw('POST', `/notifications/${id}/read`),
- },
- };
-}
-
-export type TadrisinoClient = ReturnType;
+ affiliate: {
+ me: () => http.get('/affiliate/me'),
+ /** کاربرِ تازه (با شماره، نه مهمان) را به صاحبِ کدِ معرف وصل میکند. */
+ track: (referralCode: string) => http.post<{ success: boolean }>('/affiliate/track', { referralCode }),
+ },
+};
diff --git a/src/api/types.ts b/src/api/types.ts
index 904355d..ab4c75a 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -1,24 +1,72 @@
/**
- * تایپهای پاسخِ بکاند — مستقیم از OpenAPI واقعیِ سرویس، نه از حدس.
+ * شکلِ پاسخهای بکاند.
*
- * دو نکته که موقعِ نوشتن غافلگیرکننده بودند و اگر رعایت نشوند درخواستها
- * بیسروصدا شکست میخورند:
- * • شناسهها در پاسخها اغلب رشتهاند، نه عدد.
- * • فیلدِ پرسش در /ai/ask اسمش userQuestion است، نه question.
+ * از روی پاسخِ واقعیِ سرور ثبت شده (۲۰۲۶-۰۹-۲۶)، نه از حدس. قراردادِ کامل در
+ * /api/docs/ همان بکاند است؛ اگر فیلدی آنجا عوض شد، اینجا هم عوض کنید.
+ *
+ * دو نکته که غافلگیر میکنند:
+ * • شناسهها در پاسخها رشتهاند، ولی در بدنهی درخواست گاهی عدد خواسته میشوند.
+ * • birthYear سالِ شمسی است (سرور بیشتر از ۱۵۰۰ را رد میکند).
*/
-export type NodeStatusValue = 'locked' | 'unlocked' | 'in_progress' | 'completed';
+export interface AuthResponse {
+ userId: string;
+ token: string;
+ refreshToken: string;
+ isNewUser?: boolean;
+ isGuest?: boolean;
+}
+
+export interface Grade {
+ id: number;
+ number: number | null;
+ title: string;
+ icon: string;
+ color: string;
+ lessonCount: number;
+}
+
+export interface UserProfile {
+ userId: string;
+ phone: string | null;
+ name: string;
+ avatar: string;
+ isGuest: boolean;
+ gradeId: number | null;
+ birthYear: number | null;
+ parentPhone: string;
+ bio: string;
+}
+
+export interface UserSummary {
+ userId: string;
+ name: string;
+ avatar: string;
+ gradeId: number | null;
+ xp: number;
+ coins: number;
+ streak: { current: number; longest: number };
+ subscription: string | null;
+}
+
+export type NodeStatus = 'locked' | 'unlocked' | 'in_progress' | 'completed';
export interface HomeNode {
id: string;
title: string;
- status: NodeStatusValue;
+ /** نامِ آیکنِ lucide (مثلاً "Grid3x3")؛ ممکن است خالی باشد. */
+ icon: string;
+ color: string;
+ status: NodeStatus;
+ /** درسهای سوالدارِ این جزیره؛ اولی همیشه درسِ بعدی است. */
lessonIds: number[];
+ nextLessonId: number | null;
}
export interface HomeUnit {
id: string;
title: string;
+ blurb: string;
nodes: HomeNode[];
}
@@ -33,24 +81,18 @@ export interface HomeResponse {
stats: HomeStats;
}
-export interface Grade {
- id: number;
+export interface NodeLesson {
+ id: string;
+ kind: 'quiz' | 'video';
title: string;
- icon: string;
- color: string;
+ meta: string;
+ done: boolean;
}
-/** آمارِ بازی (xp/سکه/استریک) اینجا نیست — از /user/summary میآید. */
-export interface UserProfile {
- userId: string;
- phone: string;
- name: string;
- avatar: string;
- isGuest: boolean;
- gradeId: number | null;
- birthYear: number | null;
- parentPhone: string;
- bio: string;
+export interface NodeLessons {
+ nodeId: string;
+ title: string;
+ lessons: NodeLesson[];
}
export interface LessonIntro {
@@ -58,10 +100,16 @@ export interface LessonIntro {
title: string;
introText: string;
mascotAnimation: string;
+ /** صفر یعنی متنِ درس هست ولی سوالش هنوز ساخته نشده. */
questionCount: number;
+ kind: 'quiz' | 'video';
+ example: string;
+ steps: string[];
+ videoUrl: string;
+ durationMinutes: number | null;
}
-export interface ChoiceOption {
+export interface QuestionOption {
id: string;
value: string;
}
@@ -70,11 +118,10 @@ export interface Question {
id: string;
type: string;
prompt: string;
- options: ChoiceOption[];
+ options: QuestionOption[];
}
-/** `mascotMood`: پاسخِ درست → happy، غلط → encourage. */
-export interface AnswerResponse {
+export interface AnswerResult {
correct: boolean;
correctOptionId: string;
xpEarned: number;
@@ -82,41 +129,147 @@ export interface AnswerResponse {
mascotMood: string;
}
+export interface LessonComplete {
+ stars: number;
+ xpEarned: number;
+ coinsEarned: number;
+ stats: HomeStats;
+}
+
+export type ReportReason = 'wrong_answer' | 'unclear' | 'typo' | 'too_hard' | 'other';
+
+export interface Hearts {
+ currentHearts: number;
+ maxHearts: number;
+ nextRefillAt: string | null;
+ fullRefillAt: string | null;
+ isUnlimited: boolean;
+}
+
+export interface StreakCalendar {
+ currentStreak: number;
+ longestStreak: number;
+ /** تاریخهای میلادی به شکلِ YYYY-MM-DD */
+ activeDays: string[];
+}
+
+export type LeaderboardPeriod = 'weekly' | 'all_time';
+
+export interface LeaderboardEntry {
+ rank: number;
+ userId: string;
+ name: string;
+ xpInPeriod: number;
+}
+
+export interface Character {
+ characterId: string;
+ slug: string;
+ name: string;
+ icon: string;
+ unlockXp: number;
+ unlocked: boolean;
+ selected: boolean;
+}
+
export interface UsedChunk {
title: string;
summary: string;
}
-/**
- * `usedChunks` خالی یعنی پاسخ از کتاب پیدا نشده و عمومی است — برای نشان دادنِ
- * برچسبِ «بر اساسِ کتاب» در برابرِ «پاسخِ عمومی» همین کافیست.
- */
-export interface AskResponse {
+/** رویدادِ پایانیِ استریمِ دستیار. */
+export interface AskDone {
conversationId: string;
messageId: string;
- answer: string;
suggestedFollowUps: string[];
remainingQuota: number;
usedChunks: UsedChunk[];
- retrievalDebug?: Record;
+}
+
+export interface ConversationMessage {
+ messageId: string;
+ role: 'user' | 'assistant' | string;
+ text: string;
+}
+
+export interface NotificationItem {
+ id: number;
+ type: string;
+ title: string;
+ body: string;
+ isRead: boolean;
+ createdAt: string;
}
export interface Plan {
id: string;
title: string;
+ /** تومان (currency = "IRT") */
price: number;
currency: string;
popular: boolean;
discountPercent: number;
durationDays: number;
+ features: string[];
+ badge: string;
}
-export type ReportReason =
- | 'wrong_answer' | 'unclear' | 'typo' | 'too_hard' | 'other';
+export interface SubscriptionStatus {
+ active: boolean;
+ planId: string | null;
+ expiresAt: string | null;
+}
-export interface ReportResponse {
+export interface PurchaseResponse {
+ orderId: string;
+ paymentUrl: string;
+}
+
+export interface RedeemResponse {
success: boolean;
- reportId: number;
- alreadyReported: boolean;
+ discountAmount: number;
+ /** "paid" یعنی کد کلِ مبلغ را پوشاند و نباید به درگاه رفت. */
+ orderStatus: string;
+ amount: number;
+}
+
+export interface OrderStatus {
+ orderId: string;
+ status: string;
+ amount: number;
+}
+
+export interface Ticket {
+ ticketId: string;
+ subject: string;
+ status: string;
+ messages: { id: string; sender: string; text: string }[];
+}
+
+export interface FamilyLink {
+ familyLinkId: string;
+ status: string;
+ childName: string;
+}
+
+export interface FamilyWeeklyReport {
+ weekStart: string;
+ xpEarned: number;
+ lessonsCompleted: number;
+ islandsCompleted: number;
+ currentStreak: number;
+}
+
+export interface FamilySettings {
+ dailyLimitMinutes: number;
+ quietHoursStart: string;
+ quietHoursEnd: string;
+}
+
+export interface AffiliateMe {
+ referralCode: string;
+ commissionRate: number;
+ referredCount: number;
+ paidCount: number;
+ totalCommission: number;
}
- */
\ No newline at end of file
diff --git a/src/app/App.tsx b/src/app/App.tsx
index d89123e..04b2676 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -1,3071 +1,148 @@
-import { useState, useEffect, useRef } from "react";
-import { motion } from "motion/react";
-import { AnimatePresence } from "motion/react";
-import {
- Home, Map, BookOpen, Brain, Crown, Bell, Play,
- Lock, Sparkles, MessageCircle, Check, X, Mic, Send,
- ChevronLeft, ChevronRight, Zap, ChevronDown, AlertCircle,
- Grid3x3, TrendingUp, LineChart, Sigma, Compass, Repeat,
- Flame, Star, Trophy, Heart, Users, FileText,
-} from "lucide-react";
-import { createClient, ApiError } from "../api";
-import type {
- Grade as ApiGrade, HomeResponse, HomeUnit, HomeNode, NodeStatus,
- LessonIntro, Question as ApiQuestion, AnswerResult, Plan as ApiPlan,
- Character as ApiCharacter,
-} from "../api";
+import { useCallback, useEffect, useState } from 'react';
+import { AnimatePresence, motion } from 'motion/react';
+import { api, ApiError, http, type Grade, type UserProfile } from '@/api';
+import { AppShell } from '@/components/AppShell';
+import { ToastProvider } from '@/components/Toast';
+import { ErrorState } from '@/components/ui';
+import { KEYS, store } from '@/lib/storage';
+import { askStore } from '@/state/askStore';
+import { LoginScreen } from '@/screens/LoginScreen';
+import { OnboardingScreen } from '@/screens/OnboardingScreen';
+import { SetupScreen } from '@/screens/SetupScreen';
+import { SplashScreen } from '@/screens/SplashScreen';
+import { MainApp } from './MainApp';
+import { launchPayment } from './launchParams';
-// ─── کلاینتِ API — تنها جایی که با بکاند حرف میزنیم ─────────────────────────
-const api = createClient({
- baseUrl: (import.meta as { env?: Record }).env?.VITE_API_BASE_URL ?? "https://api.math.tadrisino2.ir/api",
-});
+type Flow =
+ | { kind: 'boot' }
+ | { kind: 'bootError'; error: unknown }
+ | { kind: 'onboarding' }
+ | { kind: 'login' }
+ | { kind: 'setup'; profile: UserProfile; grades: Grade[] }
+ | { kind: 'main'; profile: UserProfile; grades: Grade[] };
-function getOrCreateDeviceId(): string {
- let id = localStorage.getItem("deviceId");
- if (!id) { id = crypto.randomUUID(); localStorage.setItem("deviceId", id); }
- return id;
-}
+/** کمترین زمانِ نمایشِ اسپلش، تا صفحه چشمک نزند. */
+const SPLASH_MIN_MS = 700;
-// ─── Robot Mascot ─────────────────────────────────────────────────────────────
+/**
+ * جریانِ ورود: اسپلش → (معرفی، فقط بارِ اول) → ورود → تکمیلِ پروفایل → اپ.
+ * خودِ اپ (تبها و صفحهها) در MainApp است.
+ */
+export function App() {
+ const [flow, setFlow] = useState({ kind: 'boot' });
-type RobotMood = "wave" | "point" | "explain" | "think" | "neutral" | "happy" | "encourage";
+ /** پروفایل و پایهها را میگیرد و تصمیم میگیرد کاربر کجا برود. */
+ const enter = useCallback(async () => {
+ const [profile, grades] = await Promise.all([api.profile.get(), api.grades()]);
+ await trackReferral(profile);
+ setFlow(profile.name && profile.gradeId ? { kind: 'main', profile, grades } : { kind: 'setup', profile, grades });
+ }, []);
-function RobotMascot({ mood, size = 120 }: { mood: RobotMood; size?: number }) {
- const eyes: Record = {
- wave: { lw: 10, lh: 10, rw: 10, rh: 10, mouth: "smile" },
- point: { lw: 10, lh: 10, rw: 10, rh: 10, mouth: "smirk" },
- explain: { lw: 9, lh: 12, rw: 12, rh: 12, mouth: "open" },
- think: { lw: 10, lh: 7, rw: 10, rh: 7, mouth: "hmm" },
- neutral: { lw: 9, lh: 9, rw: 9, rh: 9, mouth: "line" },
- happy: { lw: 12, lh: 7, rw: 12, rh: 7, mouth: "grin" },
- encourage:{ lw: 10, lh: 10, rw: 10, rh: 10, mouth: "soft" },
- };
- const e = eyes[mood];
- const lx = 38, ly = 45, rx = 62, ry = 45;
+ const boot = useCallback(async () => {
+ setFlow({ kind: 'boot' });
+ const started = Date.now();
+ let next: Flow | null = null;
+ if (api.auth.isSignedIn()) {
+ try {
+ await enter();
+ } catch (error) {
+ // ۴۰۱ یعنی نشست باطل شده و onSignedOut خودش به ورود میبرد؛
+ // خطای شبکه نباید کاربر را بیدلیل از حساب بیرون بیندازد.
+ if (!(error instanceof ApiError && error.isUnauthorized)) next = { kind: 'bootError', error };
+ else next = { kind: 'login' };
+ }
+ } else {
+ next = store.get(KEYS.onboarded) ? { kind: 'login' } : { kind: 'onboarding' };
+ }
+ const wait = SPLASH_MIN_MS - (Date.now() - started);
+ if (wait > 0) await new Promise((r) => setTimeout(r, wait));
+ if (next) setFlow(next);
+ }, [enter]);
- const mouth = () => {
- switch (e.mouth) {
- case "smile": return ;
- case "smirk": return ;
- case "open": return ;
- case "hmm": return ;
- case "line": return ;
- case "grin": return ;
- case "soft": return ;
- default: return null;
+ useEffect(() => {
+ void boot();
+ }, [boot]);
+
+ // هر جا نشست باطل شد (refresh هم شکست خورد)، برگرد به صفحهی ورود.
+ useEffect(
+ () =>
+ http.onSignedOut(() => {
+ askStore.clear();
+ setFlow({ kind: 'login' });
+ }),
+ [],
+ );
+
+ const afterLogin = async () => {
+ try {
+ await enter();
+ } catch (error) {
+ setFlow({ kind: 'bootError', error });
}
};
- const arms = () => {
- if (mood === "wave") return (
-
-
-
-
-
-
-
- );
- if (mood === "point") return (
-
-
-
-
-
-
- );
- return (
-
-
-
-
- );
- };
-
return (
-
- {/* Antenna */}
-
-
-
- {/* Legs */}
-
-
-
- {/* Body */}
-
-
-
-
-
- {/* Arms */}
- {arms()}
-
- {/* Head shell */}
-
-
- {/* Screen bezel */}
-
-
-
- {/* Glare */}
-
-
- {/* Eyes — left */}
-
-
-
- {/* Eyes — right */}
-
-
-
- {/* Blush */}
- {(mood === "happy" || mood === "encourage") && (
- <>
-
-
- >
- )}
-
- {mouth()}
-
- {/* Happy sparkles */}
- {mood === "happy" && (
- <>
- ⭐
- ✨
- >
- )}
-
- );
-}
-
-// ─── سهیار: مسکاتهای سیگما / دلتا / پی ─────────────────────────────────────
-// موتور تولید SVG سه کاراکتر، هر کدوم با ۵ حالت احساسی + یک حالت عادی.
-// داستان: هر سه از دل یک دفترچهی بینهایت متولد شدن؛ هر کدوم از جنس یک نماد ریاضی.
-// سیگما (Σ) → راهنمای آزمون و کوییز دلتا (Δ) → همتیمیِ تمرینهای سریع
-// پی (π) → همراهِ دلگرمی (بعدِ جواب غلط یا غیبتِ طولانی)
-
-type SeyarChar = "sigma" | "delta" | "pi";
-type SeyarMood = "idle" | "happy" | "excited" | "sad" | "angry" | "depressed";
-
-const SEYAR_PAL: Record> = {
- sigma: { body: "#4C5FD5", inner: "#7A8CFF", belly: "#EAF0FF", ink: "#232355", cheek: "#FFB2C0", mouth: "#FF6B6B", tear: "#8EC9FF", badge: "#FFD23F" },
- delta: { body: "#FF7A3D", inner: "#FFB25E", belly: "#FFE7C7", ink: "#6E2A12", cheek: "#FF8FA3", mouth: "#D6402A", tear: "#8EC9FF", spike: "#FFEB3B" },
- pi: { body: "#9B7BFF", inner: "#C6B3FF", belly: "#F1ECFF", ink: "#3B2B66", cheek: "#FFC2D1", mouth: "#FF6F91", tear: "#5EC8E8" },
-};
-const SEYAR_YSHIFT: Record = { idle: 0, happy: 0, excited: -4, sad: 3, angry: 0, depressed: 9 };
-const SEYAR_ARMDELTA: Record = { idle: 0, happy: 2, excited: 46, sad: -4, angry: -8, depressed: -10 };
-
-function seyarFace(mood: SeyarMood, ex: number, ey: number, mx: number, my: number, ink: string, mouthC: string, tearC: string, cheekC: string, spread: number) {
- const lx = ex, rx = ex + spread;
- let eyes = "", mouth = "", cheeks = "", extra = "";
- if (mood === "idle") {
- eyes = ` `
- + ` `;
- mouth = ` `;
- } else if (mood === "happy") {
- eyes = ` `
- + ` `;
- mouth = ` `;
- cheeks = ` `;
- } else if (mood === "excited") {
- eyes = ` `
- + ` `
- + ` `;
- mouth = ` `;
- cheeks = ` `;
- extra = ` `;
- } else if (mood === "sad") {
- eyes = ` `
- + ` `
- + ` `;
- mouth = ` `;
- extra = ` `;
- } else if (mood === "angry") {
- eyes = ` `
- + ` `
- + ` `;
- mouth = ` `;
- cheeks = ` `;
- extra = ` `;
- } else if (mood === "depressed") {
- eyes = ` `
- + ` `;
- mouth = ` `;
- extra = ` `
- + ` `
- + ` `
- + ``
- + ` `
- + ` `
- + ` `;
- }
- return { eyes, mouth, cheeks, extra };
-}
-
-function seyarSigmaSVG(mood: SeyarMood): string {
- const C = SEYAR_PAL.sigma;
- const f = seyarFace(mood, 80, 86, 100, 114, C.ink, C.mouth, C.tear, C.cheek, 40);
- const a = 12 + SEYAR_ARMDELTA[mood], dy = SEYAR_YSHIFT[mood];
- return `
-
-
-
-
-
-
-
-
-
-
- ${f.extra}${f.eyes} ${f.cheeks}${f.mouth}
-
- Σ
- `;
-}
-
-function seyarDeltaSVG(mood: SeyarMood): string {
- const C = SEYAR_PAL.delta;
- const f = seyarFace(mood, 84, 108, 100, 128, C.ink, C.mouth, C.tear, C.cheek, 32);
- const a = 16 + SEYAR_ARMDELTA[mood], dy = SEYAR_YSHIFT[mood];
- return `
-
-
-
-
-
-
-
-
- ${f.extra}${f.eyes} ${f.cheeks}${f.mouth}
- `;
-}
-
-function seyarPiSVG(mood: SeyarMood): string {
- const C = SEYAR_PAL.pi;
- const f = seyarFace(mood, 80, 112, 100, 138, C.ink, C.mouth, C.tear, C.cheek, 40);
- const a = 18 + SEYAR_ARMDELTA[mood], dy = SEYAR_YSHIFT[mood];
- return `
-
-
-
-
-
-
-
-
-
- ${f.extra}${f.eyes} ${f.cheeks}${f.mouth}
- `;
-}
-
-const SEYAR_SVG: Record string> = {
- sigma: seyarSigmaSVG, delta: seyarDeltaSVG, pi: seyarPiSVG,
-};
-const SEYAR_INFO: Record = {
- sigma: { name: "سیگما", symbol: "Σ", role: "راهنمای آزمون و کوییز" },
- delta: { name: "دلتا", symbol: "Δ", role: "همتیمیِ تمرینهای سریع" },
- pi: { name: "پی", symbol: "π", role: "همراهِ دلگرمی" },
-};
-
-/** اگه slugِ کاراکترِ سرور با یکی از سهیار یکی باشه، از طرحِ خودمون (انیمیشندار) استفاده میکنیم؛
- * وگرنه یه کارتِ عمومی نشون میدیم (پیشنهاد: به بکاند بگید slugها رو sigma/delta/pi بذاره). */
-function seyarSlugMatch(slug: string): SeyarChar | null {
- const s = slug.toLowerCase();
- if (s.includes("sigma") || s.includes("سیگما")) return "sigma";
- if (s.includes("delta") || s.includes("دلتا")) return "delta";
- if (s.includes("pi") || s.includes("پی")) return "pi";
- return null;
-}
-
-function SeyarMascot({ char, mood = "idle", size = 100, bob = false }: { char: SeyarChar; mood?: SeyarMood; size?: number; bob?: boolean }) {
- return (
-
- );
-}
-
-// ─── Types ────────────────────────────────────────────────────────────────────
-
-type AppFlow = "splash" | "onboarding" | "login" | "profile" | "main";
-type MainScreen = "home" | "topics" | "skillmap" | "lessons" | "lesson" | "question" | "askai" | "subscription" | "notifications" | "leaderboard" | "characters";
-interface Profile {
- name: string;
- age: number;
- grade: string;
- gradeName?: string;
-}
-// ─── فصلها و درسها — کاملاً پویا از روی api.home() ────────────────────────
-// دیگه چیزی هاردکد نیست: هر پایه هر چند فصل و هر فصل هر چند ایستگاه که سرور
-// برگردونه همونه. رنگ و آیکون رو چون سرور نمیده، خودمون چرخشی میدیم.
-
-const UNIT_PALETTE = ["#ED6956", "#91ADC6", "#97C0C0", "#9B7BFF", "#FF7A3D", "#F3A9B0", "#4C5FD5", "#5FBF8F", "#E06BAE"];
-const UNIT_ICONS: React.ElementType[] = [Grid3x3, TrendingUp, LineChart, Sigma, Compass, Repeat, BookOpen, Sparkles, Brain];
-
-const unitColor = (i: number) => UNIT_PALETTE[i % UNIT_PALETTE.length];
-const unitIcon = (i: number) => UNIT_ICONS[i % UNIT_ICONS.length];
-
-function unitProgress(unit: HomeUnit) {
- const total = unit.nodes.length;
- const done = unit.nodes.filter((n) => n.status === "completed").length;
- return { done, total, percent: total ? Math.round((done / total) * 100) : 0 };
-}
-
-/** اولین ایستگاهِ ناتمام رو تو کلِ نقشه پیدا میکنه (برای کارتِ «ادامه بده») */
-function findContinueNode(home: HomeResponse | null): { unit: HomeUnit; node: HomeNode; unitIndex: number } | null {
- if (!home) return null;
- for (let i = 0; i < home.units.length; i++) {
- const node = home.units[i].nodes.find((n) => n.status !== "completed");
- if (node) return { unit: home.units[i], node, unitIndex: i };
- }
- return null;
-}
-
-/** چیدمانِ مارپیچِ نقشه، برای هر تعداد ایستگاه که لازم باشه */
-function zigzagPositions(count: number, spacingY = 148, startY = 70): { x: number; y: number }[] {
- return Array.from({ length: count }, (_, i) => ({ x: i % 2 === 0 ? 30 : 74, y: startY + i * spacingY }));
-}
-
-/** حالتِ ماسکوت که سرور با متن میده (مثلاً «happy»/«encourage») رو به حالتِ سهیار نگاشت میکنه */
-function mapMascotMood(server: string | undefined | null): SeyarMood {
- const s = (server ?? "").toLowerCase();
- if (s.includes("excited") || s.includes("celebrat")) return "excited";
- if (s.includes("happy")) return "happy";
- if (s.includes("sad") || s.includes("encourage")) return "sad";
- if (s.includes("angry")) return "angry";
- if (s.includes("depress") || s.includes("tired")) return "depressed";
- return "idle";
-}
-
-// ─── اعلانها ─────────────────────────────────────────────────────────────────
-interface NotificationItem { id: number; type: string; title: string; isRead: boolean }
-
-const NOTIF_STYLE: Record = {
- xp_earned: { icon: Star, color: "#E8A400", bg: "#FFF8E1" },
- lesson_completed: { icon: Check, color: "#5FBF8F", bg: "#EAF8F0" },
- order_paid: { icon: Crown, color: "#9B7BFF", bg: "#F5F1FF" },
- ticket_replied: { icon: MessageCircle, color: "#4C5FD5", bg: "#EEF1FF" },
- family_link_accepted: { icon: Users, color: "#FF7A3D", bg: "#FFF1E6" },
- weekly_report_ready: { icon: FileText, color: "#91ADC6", bg: "#EAF2F6" },
-};
-const notifStyle = (type: string) => NOTIF_STYLE[type] ?? { icon: Bell, color: "#9e9ec0", bg: "#f0eeff" };
-
-// ─── Bottom Nav ───────────────────────────────────────────────────────────────
-
-function BottomNav({ active, onChange }: { active: MainScreen; onChange: (s: MainScreen) => void }) {
- const items: { id: MainScreen; icon: React.ElementType; label: string }[] = [
- { id: "home", icon: Home, label: "خانه" },
- { id: "topics", icon: Map, label: "مهارتها" },
- { id: "lessons", icon: BookOpen, label: "درسها" },
- { id: "askai", icon: Brain, label: "دستیار" },
- { id: "subscription", icon: Crown, label: "اشتراک" },
- ];
- return (
-
- {items.map(({ id, icon: Icon, label }) => (
-
onChange(id)}
- className={`flex flex-col items-center gap-0.5 px-2 py-1 rounded-xl transition-all ${active === id ? "text-[#ED6956]" : "text-[#9e9ec0]"}`}>
-
-
-
- {label}
-
- ))}
-
- );
-}
-
-// ─── Splash Screen ────────────────────────────────────────────────────────────
-
-function SplashScreen({ onNew, onReturning }: { onNew: () => void; onReturning: () => void }) {
- useEffect(() => {
- // Auto-advance after 2.2 s; returning users press button
- const t = setTimeout(onNew, 2200);
- return () => clearTimeout(t);
- }, [onNew]);
-
- return (
-
-
-
-
-
- تدریسینو
- یادگیری ریاضی با هوش مصنوعی
-
-
-
- قبلاً وارد شدم
-
-
-
-
- );
-}
-
-// ─── Onboarding ───────────────────────────────────────────────────────────────
-
-function OnboardingScreen({ onDone }: { onDone: () => void }) {
- const [step, setStep] = useState(0);
- const slides = [
- {
- mood: "wave" as RobotMood, bg: "#ED6956",
- title: "به تدریسینو خوش آمدی! 🤖",
- sub: "دستیار ریاضی هوشمند",
- desc: "با رُبو ریاضی یاد بگیر و هر روز باهوشتر بشو. یادگیری میتونه هم آسون هم جالب باشه!",
- },
- {
- mood: "point" as RobotMood, bg: "#91ADC6",
- title: "مسیر ریاضیت رو بساز",
- sub: "گام به گام پیشرفت کن",
- desc: "از جمع و تفریق شروع کن تا به ضرب، تقسیم و کسرها برسی. رُبو همهجا کنارته!",
- },
- {
- mood: "explain" as RobotMood, bg: "#97C0C0",
- title: "هر چیزی نفهمیدی بپرس!",
- sub: "توضیح ساده و صبورانه",
- desc: "رُبو هر مسئلهای رو با مثالهای جالب برات توضیح میده تا مطمئن بشی خوب یاد گرفتی.",
- },
- ];
- const slide = slides[step];
- return (
-
- {step > 0 && (
-
setStep(s => s - 1)}
- className="absolute top-12 right-5 z-10 w-8 h-8 rounded-full bg-white/15 flex items-center justify-center">
-
-
- )}
-
+
+
-
-
-
+ {flow.kind === 'boot' && }
+ {flow.kind === 'bootError' && (
+
+ void boot()} />
+
+ )}
+ {flow.kind === 'onboarding' && (
+ {
+ store.set(KEYS.onboarded, '1');
+ setFlow({ kind: 'login' });
+ }}
/>
-
-
-
{slide.sub}
-
{slide.title}
-
{slide.desc}
-
+ )}
+ {flow.kind === 'login' && void afterLogin()} />}
+ {flow.kind === 'setup' && (
+ setFlow({ kind: 'main', profile, grades: flow.grades })}
+ />
+ )}
+ {flow.kind === 'main' && (
+ setFlow({ kind: 'login' })}
+ onRegistered={trackReferral}
+ />
+ )}
-
- {/* Dots */}
-
- {slides.map((_, i) => (
-
- ))}
-
-
- {step < slides.length - 1 ? (
- <>
-
- رد کردن
-
- setStep(s => s + 1)}
- style={{ color: slide.bg }}
- className="flex-[2] py-3.5 rounded-2xl bg-white font-black flex items-center justify-center gap-2 active:scale-95 transition-transform shadow-lg shadow-black/20">
- بعدی
-
- >
- ) : (
-
- بزن بریم!
-
- )}
-
-
+
+
);
}
-//-----login screen
-interface AuthData {
- userId: string;
- isGuest: boolean;
-}
-function LoginScreen({ onDone }: { onDone: (auth: AuthData) => void }) {
- const [step, setStep] = useState<"phone" | "otp">("phone");
- const [phone, setPhone] = useState("");
- const [otp, setOtp] = useState("");
- const [devOtp, setDevOtp] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState("");
-
- const phoneValid = /^09\d{9}$/.test(phone);
- const otpValid = otp.trim().length >= 4;
-
- const friendlyError = (err: unknown, fallback: string) => {
- if (err instanceof ApiError) {
- if (err.isRateLimited) return "تعداد تلاش زیاد بوده، کمی صبر کن و دوباره امتحان کن";
- if (err.isProviderDown) return "سرویسِ پیامک موقتاً در دسترس نیست";
- return err.detail || fallback;
- }
- return fallback;
- };
-
- const requestOtp = async () => {
- if (!phoneValid || loading) return;
- setError("");
- setLoading(true);
- try {
- const data = await api.auth.requestOtp(phone);
- setDevOtp(data.devOtp ?? null);
- setStep("otp");
- } catch (err) {
- setError(friendlyError(err, "ارسال کد با خطا مواجه شد، دوباره امتحان کن"));
- } finally {
- setLoading(false);
- }
- };
-
- const verifyOtp = async () => {
- if (!otpValid || loading) return;
- setError("");
- setLoading(true);
- try {
- const data = await api.auth.login(phone, otp.trim());
- onDone({ userId: data.userId, isGuest: false });
- } catch (err) {
- setError(friendlyError(err, "کد وارد شده اشتباهه، دوباره امتحان کن"));
- } finally {
- setLoading(false);
- }
- };
-
- const continueAsGuest = async () => {
- setError("");
- setLoading(true);
+/**
+ * اگر کاربر با لینکِ دعوت آمده بود و حالا با شماره وارد شده، معرفی را ثبت کن.
+ * مهمان را سرور نمیپذیرد، پس کد تا ثبتِ شماره نگه داشته میشود.
+ */
+async function trackReferral(profile: UserProfile) {
+ const code = store.get(KEYS.referralCode);
+ if (!code || profile.isGuest) return;
try {
- const deviceId = getOrCreateDeviceId();
-
- try {
- const data = await api.auth.guest(deviceId);
- onDone({
- userId: data.userId,
- isGuest: true,
- });
- } catch {
- // Backend guest endpoint unavailable → local guest
- onDone({
- userId: `guest-${deviceId}`,
- isGuest: true,
- });
- }
- } finally {
- setLoading(false);
+ await api.affiliate.track(code);
+ store.remove(KEYS.referralCode);
+ } catch (err) {
+ // کدِ نامعتبر یا معرفیِ خود: دوباره امتحان کردن فایده ندارد.
+ if (err instanceof ApiError && err.status >= 400 && err.status < 500) store.remove(KEYS.referralCode);
}
-};
-
- return (
-
-
-
-
-
ورود به تدریسینو
-
- {step === "phone" ? "شماره موبایلت رو وارد کن" : "کد پیامکشده رو وارد کن"}
-
-
-
-
-
-
-
-
-
- {step === "phone" ? (
-
-
شماره موبایل
-
setPhone(e.target.value.replace(/\D/g, "").slice(0, 11))}
- placeholder="09xxxxxxxxx"
- dir="ltr"
- inputMode="numeric"
- className={`w-full px-4 py-4 rounded-2xl text-sm bg-white border-2 outline-none text-[#1a1a2e] placeholder:text-[#b0b0c8] transition-all font-medium text-left ${
- error ? "border-red-400 bg-red-50" : phone ? "border-[#ED6956]" : "border-[#e8e4ff]"
- }`}
- />
- {error && (
-
- )}
-
- ) : (
-
کد تایید
-
setOtp(e.target.value.replace(/\D/g, "").slice(0, 6))}
- placeholder="----"
- dir="ltr"
- inputMode="numeric"
- className={`w-full px-4 py-4 rounded-2xl text-lg tracking-[0.5em] text-center bg-white border-2 outline-none text-[#1a1a2e] placeholder:text-[#b0b0c8] transition-all font-bold ${
- error ? "border-red-400 bg-red-50" : otp ? "border-[#ED6956]" : "border-[#e8e4ff]"
- }`}
- />
- {error && (
-
- )}
- {devOtp && (
-
- کد تست محیط توسعه: {devOtp} (یا ۰۰۰۰)
-
- )}
-
{ setStep("phone"); setOtp(""); setError(""); setDevOtp(null); }}
- className="text-[#ED6956] text-xs font-bold mt-3">
- اصلاح شماره موبایل
-
-
- )}
-
-
- {loading ? "در حال ارسال..." : step === "phone" ? "دریافت کد تایید" : "ورود"}
-
-
-
- ادامه به عنوان مهمان
-
-
-
- );
-}
-// ─── Profile Setup ────────────────────────────────────────────────────────────
-
-const AGES = Array.from({ length: 7 }, (_, i) => i + 12);
-
-const toPersianNumber = (value: number | string) =>
- String(value).replace(
- /\d/g,
- (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]
- );
-
-function ProfileSetupScreen({
- onDone,
-}: {
- onDone: (p: Profile) => void;
-}) {
-
- const [name, setName] = useState("");
- const [age, setAge] = useState(15);
- const [grade, setGrade] = useState("");
- const [touched, setTouched] = useState(false);
- const [grades, setGrades] = useState([]);
- const [gradesError, setGradesError] = useState(false);
- const [saving, setSaving] = useState(false);
- const [saveError, setSaveError] = useState("");
-
- useEffect(() => {
- api.grades().then(setGrades).catch(() => setGradesError(true));
- }, []);
-
- const nameErr = touched && !name.trim();
- const gradeErr = touched && !grade;
-
-
- // ─────────────────────────────────────────────────────────────────────
- // Scroll sound
- // ─────────────────────────────────────────────────────────────────────
-
- const playTick = () => {
-
- try {
-
- const AudioCtx =
- window.AudioContext ||
- (window as any).webkitAudioContext;
-
- if (!AudioCtx) return;
-
- const ctx = new AudioCtx();
-
- if (ctx.state === "suspended") {
- ctx.resume();
- }
-
- const oscillator = ctx.createOscillator();
- const gain = ctx.createGain();
-
- oscillator.type = "sine";
- oscillator.frequency.setValueAtTime(720, ctx.currentTime);
-
- gain.gain.setValueAtTime(0.0001, ctx.currentTime);
- gain.gain.exponentialRampToValueAtTime(
- 0.045,
- ctx.currentTime + 0.005
- );
- gain.gain.exponentialRampToValueAtTime(
- 0.0001,
- ctx.currentTime + 0.045
- );
-
- oscillator.connect(gain);
- gain.connect(ctx.destination);
-
- oscillator.start();
- oscillator.stop(ctx.currentTime + 0.05);
-
- setTimeout(() => {
- ctx.close();
- }, 100);
-
- } catch {
- // Audio is optional; don't break the form if unavailable.
- }
- };
-
-
- // ─────────────────────────────────────────────────────────────────────
- // Age wheel
- // ─────────────────────────────────────────────────────────────────────
-const changeAge = (direction: number) => {
- setAge((current) => {
- const next = Math.max(
- AGES[0],
- Math.min(AGES[AGES.length - 1], current + direction)
- );
-
- if (next !== current) {
- playTick();
- }
-
- return next;
- });
-};
- const handleSubmit = async () => {
-
- setTouched(true);
-
- if (!name.trim() || !grade || saving) return;
-
- setSaving(true);
- setSaveError("");
- try {
- await api.profile.update({ name: name.trim() });
- await api.profile.setGrade(Number(grade));
- const g = grades.find((x) => String(x.id) === grade);
- onDone({
- name: name.trim(),
- age,
- grade,
- gradeName: g?.title ?? "",
- });
- } catch (err) {
- setSaveError(err instanceof ApiError ? err.detail : "ذخیره نشد، دوباره امتحان کن");
- } finally {
- setSaving(false);
- }
- };
-
-
- return (
-
-
- {/* ───────────────── HEADER ───────────────── */}
-
-
-
- {/* decorative circles */}
-
-
-
-
-
-
-
-
-
-
- فقط چندتا سؤال کوچیک
-
-
-
- بیا باهم آشنا بشیم!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* ───────────────── FORM ───────────────── */}
-
-
-
-
-
-
- {/* ───────── NAME ───────── */}
-
-
-
-
- اسمت چیه؟
-
-
-
setName(e.target.value)}
- placeholder="مثلاً سارا یا علی"
- dir="rtl"
- className={`
- w-full px-4 py-4 rounded-2xl text-sm
- bg-white border-2 outline-none
- text-[#1a1a2e]
- placeholder:text-[#b0b0c8]
- transition-all font-medium
- ${
- nameErr
- ? "border-red-400 bg-red-50"
- : name
- ? "border-[#ED6956]"
- : "border-[#e8e4ff]"
- }
- `}
- />
-
- {nameErr && (
-
- )}
-
-
- {/* ───────── AGE ───────── */}
-
-
- چند سالته؟
-
-
-
-
- {/* selection highlight - ثابت */}
-
-
- {/* fade بالا */}
-
-
- {/* fade پایین */}
-
-
- {/* اسکرول واقعی */}
-
{
- const el = e.currentTarget;
-
- const itemHeight = 36;
- const center = el.clientHeight / 2;
-
- const index = Math.round(
- (el.scrollTop + center - 67) / itemHeight
- );
-
- const newAge = AGES[Math.max(0, Math.min(AGES.length - 1, index))];
-
- if (newAge !== age) {
- setAge(newAge);
- playTick();
- }
- }}
- >
-
-
- {AGES.map((value) => {
- const distance = Math.abs(value - age);
-
- return (
-
{
- setAge(value);
- playTick();
-
- const container =
- document.getElementById("age-wheel");
-
- if (container) {
- const index = value - AGES[0];
-
- container.scrollTo({
- top: index * 36,
- behavior: "smooth",
- });
- }
- }}
- className={`
- w-full h-[36px]
- flex-shrink-0
- flex items-center justify-center
- snap-center
- select-none
- transition-all duration-150
- relative z-20
- ${
- value === age
- ? "text-[#ED6956] text-xl font-black"
- : distance === 1
- ? "text-[#777792] text-base font-bold"
- : "text-[#c5c4d2] text-sm font-semibold"
- }
- `}
- >
- {toPersianNumber(value)}
-
- );
- })}
-
- {/* فضای خالی پایین */}
-
-
-
-
- {/* ───────── GRADE ───────── */}
-
-
-
-
- کدوم پایهای؟
-
-
- {grades.length === 0 && !gradesError && (
-
- {[0, 1, 2, 3].map((i) => (
-
- ))}
-
- )}
-
- {gradesError && (
-
-
فهرستِ پایهها نیومد؛ اتصالت رو چک کن و دوباره باز کن.
-
- )}
-
-
-
- {grades.map((g) => {
-
- const selected = grade === String(g.id);
-
- return (
-
-
setGrade(String(g.id))}
- className={`
- relative overflow-hidden
- flex items-center gap-3
- px-4 py-4
- rounded-2xl
- border-2
- transition-all
- active:scale-[0.97]
- ${
- selected
- ? "bg-[#ED6956] border-[#ED6956] text-white shadow-lg shadow-[#ED6956]/25"
- : gradeErr
- ? "bg-red-50 border-red-300 text-[#3a3a5c]"
- : "bg-white border-[#e8e4ff] text-[#3a3a5c]"
- }
- `}
- >
-
-
- {toPersianNumber(g.id)}
-
-
-
- {g.title}
-
-
- {selected && (
-
- )}
-
-
-
- );
-
- })}
-
-
-
-
- {gradeErr && (
-
-
- پایه تحصیلیت رو انتخاب کن
-
- )}
-
-
- {saveError && (
-
- )}
-
- {/* ───────── START ───────── */}
-
-
- {saving ? "در حال ذخیره..." : "شروع یادگیری"}
-
-
-
-
-
-
-
-
-
-
- );
-}
-// ─── Home Screen ──────────────────────────────────────────────────────────────
-
-function HomeScreen({
- profile,
- home,
- summary,
- unreadCount,
- onNavigate,
- onContinue,
-}: {
- profile: Profile;
- home: HomeResponse | null;
- summary: { xp: number; coins: number; streak: { current: number; longest: number } } | null;
- unreadCount: number;
- onNavigate: (s: MainScreen) => void;
- onContinue: (unitId: string) => void;
-}) {
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
- const cont = findContinueNode(home);
- const cUnit = cont?.unit;
- const cProg = cUnit ? unitProgress(cUnit) : { done: 0, total: 0, percent: 0 };
- const cColor = cont ? unitColor(cont.unitIndex) : "#ED6956";
- const xp = summary?.xp ?? 0;
- const level = Math.floor(xp / 100) + 1;
- return (
-
-
- {/* ── Header ─────────────────────────────────────────────────────────── */}
-
- {/* decorative blobs */}
-
-
-
- {/* Greeting row */}
-
-
-
سلام، {profile.name}!
-
- آمادهای ریاضی رو شروع کنیم؟
-
- {profile.gradeName && (
-
- {profile.gradeName}
-
- )}
-
-
- onNavigate("characters")}
- className="w-10 h-10 rounded-full bg-white/15 flex items-center justify-center">
-
-
- onNavigate("leaderboard")}
- className="w-10 h-10 rounded-full bg-white/15 flex items-center justify-center">
-
-
- onNavigate("notifications")}
- className="relative w-10 h-10 rounded-full bg-white/15 flex items-center justify-center">
-
- {unreadCount > 0 && (
-
- {unreadCount > 9 ? "۹+" : String(unreadCount).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)])}
-
- )}
-
-
-
-
-
- {/* ── Bold stat bar — overlaps the header for more visual weight ─────── */}
-
-
{[
- { icon: Flame, val: toFa(summary?.streak.current ?? 0), label: "روز پیاپی", fg: "#FF7A3D", bg: "#FFF1E6" },
- { icon: Star, val: toFa(xp), label: "امتیاز", fg: "#E8A400", bg: "#FFF8E1" },
- { icon: Trophy, val: toFa(level), label: "سطح", fg: "#9B7BFF", bg: "#F5F1FF" },
- ].map(({ icon: Icon, val, label, fg, bg }) => (
-
-
-
-
-
{val}
-
{label}
-
- ))}
-
-
-
- {/* ── Scrollable content ──────────────────────────────────────────────── */}
-
-
- {/* Continue card */}
- {cont && cUnit ? (
-
-
-
-
ادامه از جایی که موندی
-
{cont.node.title}
-
{cUnit.title} · {toFa(cProg.done)} از {toFa(cProg.total)} · {toFa(cProg.percent)}٪ تکمیل
-
-
-
-
-
- {/* Progress bar */}
-
-
onContinue(cUnit.id)}
- className="w-full py-3 text-white rounded-2xl font-bold text-sm flex items-center justify-center gap-2 active:scale-[0.98] transition-transform"
- style={{ backgroundColor: cColor }}>
- ادامه درس
-
-
- ) : (
-
-
-
- {home ? "همهی فصلهای این پایه رو تموم کردی! 🎉" : "در حال آوردنِ مسیر یادگیری..."}
-
-
- )}
-
- {/* Quick access grid */}
-
-
onNavigate("topics")}
- className="bg-white rounded-2xl p-4 text-right shadow-sm border border-[#ede9ff] active:scale-95 transition-transform">
-
-
-
- مهارتها
- ببین کجا هستی
-
-
onNavigate("askai")}
- className="bg-[#9B7BFF] rounded-2xl p-4 text-right shadow-md shadow-[#9B7BFF]/30 active:scale-95 transition-transform">
-
-
-
- از پی بپرس
- هر سوالی داری
-
-
-
-
- );
-}
-
-// // ─── Skill Map ────────────────────────────────────────────────────────────────
-
-// function SkillMapScreen({ onNavigate }: { onNavigate: (s: MainScreen) => void }) {
-// // هر آیتم یا ویدیوی درس است یا آزمون آن؛ ترتیب آرایه همان ترتیب واقعی خواندن دانشآموز است
-// const items: { id: number; type: "video" | "quiz"; title: string; meta: string; done: boolean }[] = [
-// { id: 1, type: "video", title: "آشنایی با اعداد", meta: "۸ دقیقه", done: true },
-// { id: 2, type: "quiz", title: "آزمون اعداد", meta: "۵ سوال", done: true },
-// { id: 3, type: "video", title: "جمع اعداد", meta: "۱۰ دقیقه", done: true },
-// { id: 4, type: "quiz", title: "آزمون جمع", meta: "۵ سوال", done: false },
-// { id: 5, type: "video", title: "تفریق اعداد", meta: "۹ دقیقه", done: false },
-// { id: 6, type: "quiz", title: "آزمون تفریق", meta: "۵ سوال", done: false },
-// { id: 7, type: "video", title: "ضرب مقدماتی", meta: "۱۱ دقیقه", done: false },
-// { id: 8, type: "quiz", title: "آزمون پایانی", meta: "۱۰ سوال", done: false },
-// ];
-
-// const doneCount = items.filter((it) => it.done).length;
-// const percent = Math.round((doneCount / items.length) * 100);
-// const percentFa = String(percent).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
-
-// return (
-//
-//
-//
-//
-//
مسیر ریاضی
-//
{percentFa}٪ مسیر طی شده
-//
-//
-//
-//
-//
-//
-//
-// {/* خط تایملاین که از وسط آیکنها رد میشود */}
-//
-//
-// {items.map((item, i) => {
-// const locked = i > 0 && !items[i - 1].done;
-// const isCurrent = !locked && !item.done;
-// const Icon = item.type === "video" ? Play : Brain;
-// return (
-//
!locked && onNavigate("lesson")}
-// className="relative flex items-center gap-3 text-right disabled:cursor-not-allowed">
-//
-// {locked ? :
-// item.done ? :
-// }
-//
-//
-//
-//
{item.title}
-//
-//
-// {item.type === "video" ? "ویدیو" : "آزمون"} · {item.meta}
-//
-//
-// {isCurrent && (
-//
ادامه
-// )}
-//
-//
-// );
-// })}
-//
-//
-//
-//
-// );
-// }
-
-// ─── Topic Select ───────────────────────────────────────────────────────────
-
-// چیدمانِ فصلها روی نقشه — بهشکلِ مارپیچ، مثلِ نقشهی مهارت ولی برای کلِ فصلها
-function TopicSelectScreen({ home, onSelect }: { home: HomeResponse | null; onSelect: (unitId: string) => void }) {
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
- const units = home?.units ?? [];
- const positions = zigzagPositions(units.length);
- const mapHeight = positions.length ? positions[positions.length - 1].y + 190 : 400;
-
- const overallPct = home?.stats.progressPercent ?? 0;
-
- return (
-
- {/* Header */}
-
-
-
-
-
نقشهی فصلها
-
{toFa(overallPct)}٪ از کل مسیر رو رفتی
-
-
0 ? "happy" : "idle"} size={64}/>
-
-
-
-
- {/* Map */}
-
- {!home ? (
-
-
- در حال آوردنِ فصلها...
-
- ) : units.length === 0 ? (
-
-
- هنوز فصلی برای این پایه ثبت نشده
-
- ) : (
-
-
- {/* مسیرِ اتصال بینِ خونههای فصلها */}
-
- {positions.slice(0, -1).map((p1, i) => {
- const p2 = positions[i + 1];
- const midY = (p1.y + p2.y) / 2;
- const d = `M ${p1.x} ${p1.y} C ${p1.x} ${midY}, ${p2.x} ${midY}, ${p2.x} ${p2.y}`;
- const started = units[i]?.nodes.some((n) => n.status !== "locked");
- return (
-
- );
- })}
-
-
- {/* خونههای فصلها */}
- {units.map((unit, i) => {
- const pos = positions[i];
- if (!pos) return null;
- const prog = unitProgress(unit);
- const complete = prog.percent === 100;
- const color = unitColor(i);
- const Icon = unitIcon(i);
- return (
-
-
- onSelect(unit.id)}
- className="relative rounded-[26px] flex items-center justify-center transition-all duration-150 active:translate-y-1"
- style={{
- width: 84, height: 84,
- backgroundColor: complete ? color : "#ffffff",
- border: complete ? "none" : `4px solid ${color}`,
- boxShadow: complete ? `0 6px 0 ${color}99` : `0 6px 0 ${color}40, 0 0 0 5px white`,
- }}>
- {complete
- ?
- : }
- {!complete && prog.done > 0 && (
-
- {toFa(prog.percent)}٪
-
- )}
-
-
-
-
{unit.title}
-
{toFa(prog.done)} از {toFa(prog.total)} ایستگاه
-
-
- );
- })}
-
- {/* پی، منتظرِ انتهای مسیر */}
-
-
-
- {overallPct === 100 ? "همه رو تموم کردی! 🎉" : "همینجا منتظرتم"}
-
-
-
-
- )}
-
-
- );
-}
-
-// ─── Skill Map (مسیر مبحث، شبیه دولینگو) ────────────────────────────────────
-
-// ─── Beautiful Skill Map ─────────────────────────────────────────────────────
-
-const MAP_NODE = 76;
-
-const MAP_POSITIONS = [
- { x: 50, y: 70 },
- { x: 72, y: 190 },
- { x: 43, y: 310 },
- { x: 25, y: 430 },
- { x: 55, y: 550 },
- { x: 76, y: 670 },
- { x: 48, y: 790 },
- { x: 28, y: 910 },
- { x: 58, y: 1030 },
- { x: 75, y: 1150 },
-];
-
-function SkillMapScreen({
- unitId,
- home,
- onOpenLesson,
- onBack,
-}: {
- unitId: string;
- home: HomeResponse | null;
- onOpenLesson: (lessonId: number) => void;
- onBack: () => void;
-}) {
- const unitIndex = home?.units.findIndex((u) => u.id === unitId) ?? -1;
- const unit = unitIndex >= 0 ? home!.units[unitIndex] : undefined;
- const items = unit?.nodes ?? [];
- const color = unitIndex >= 0 ? unitColor(unitIndex) : "#91ADC6";
-
- const doneCount = items.filter((it) => it.status === "completed").length;
- const percent = items.length
- ? Math.round((doneCount / items.length) * 100)
- : 0;
-
- const percentFa = String(percent).replace(
- /\d/g,
- (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]
- );
-
- const positions = items.map((_, i) => {
- if (MAP_POSITIONS[i]) return MAP_POSITIONS[i];
-
- const pattern = [
- { x: 50, y: 70 },
- { x: 72, y: 190 },
- { x: 43, y: 310 },
- { x: 25, y: 430 },
- ];
-
- const p = pattern[i % pattern.length];
-
- return {
- x: p.x,
- y: p.y + Math.floor(i / pattern.length) * 480,
- };
- });
-
- const mapHeight =
- positions.length > 0
- ? positions[positions.length - 1].y + 150
- : 500;
-
- return (
-
-
- {/* ───────── Header ───────── */}
-
-
-
- {/* Decorative circles */}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- مسیر یادگیری
-
-
-
- {unit?.title ?? "..."}
-
-
-
- {percentFa}٪ مسیر رو رفتی
-
-
-
-
= 50 ? "happy" : "idle"}
- size={76}
- />
-
-
-
- {/* Progress */}
-
-
-
-
- پیشرفت تو
- {percentFa}٪
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* ───────── Map ───────── */}
-
-
-
-
-
- {/* Background decorative dots */}
-
-
-
- {[...Array(18)].map((_, i) => (
-
-
-
- ))}
-
-
-
-
- {/* ───────── Curved Path ───────── */}
-
-
-
- {/* Shadow path */}
-
- {positions.slice(0, -1).map((p1, i) => {
-
- const p2 = positions[i + 1];
-
- const midY = (p1.y + p2.y) / 2;
-
- const d = `
- M ${p1.x} ${p1.y}
- C ${p1.x} ${midY},
- ${p2.x} ${midY},
- ${p2.x} ${p2.y}
- `;
-
- return (
-
- );
-
- })}
-
-
- {/* Main path */}
-
- {positions.slice(0, -1).map((p1, i) => {
-
- const p2 = positions[i + 1];
-
- const midY = (p1.y + p2.y) / 2;
-
- const d = `
- M ${p1.x} ${p1.y}
- C ${p1.x} ${midY},
- ${p2.x} ${midY},
- ${p2.x} ${p2.y}
- `;
-
- const completed = items[i]?.status === "completed";
-
- return (
-
- );
-
- })}
-
-
-
-
- {/* ───────── Nodes ───────── */}
-
- {items.map((item, i) => {
-
- const position = positions[i];
-
- const locked = item.status === "locked";
-
- const current = item.status === "unlocked" || item.status === "in_progress";
-
- const completed = item.status === "completed";
-
- const Icon = Play;
-
- return (
-
-
-
- {/* Current lesson label */}
-
- {current && (
-
-
-
-
-
- {item.title}
-
- {/* little arrow */}
-
-
-
-
-
-
-
- )}
-
-
- {/* Current glow */}
-
- {current && (
-
-
-
- )}
-
-
- {/* Node */}
-
-
- !locked && item.lessonIds[0] != null && onOpenLesson(item.lessonIds[0])
- }
- className="relative z-10 rounded-full flex items-center justify-center transition-all duration-150 active:translate-y-1"
- style={{
- width: MAP_NODE,
- height: MAP_NODE,
-
- backgroundColor:
- locked
- ? "#e5e4ec"
- : completed
- ? color
- : "#ffffff",
-
- border:
- locked
- ? "none"
- : completed
- ? "none"
- : `4px solid ${color}`,
-
- boxShadow:
- locked
- ? "0 6px 0 #cccbd6"
- : completed
- ? `0 7px 0 ${color}99`
- : `0 7px 0 ${color}55, 0 0 0 5px white`,
- }}
- >
-
- {locked ? (
-
-
-
- ) : completed ? (
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
-
-
- );
-
- })}
-
-
- {/* Finish decoration */}
-
- {items.length > 0 && (
-
-
-
-
- ادامه بده!
-
-
-
-
-
-
-
-
- )}
-
-
-
-
-
-
- );
-}
-// ─── Lessons Hub (تب «درسها») ──────────────────────────────────────────────
-// دستهبندیشده و مناسبِ دانشآموزِ دبیرستانی: کارتِ ادامه، سه دستهی درسی،
-// و یه دعوتِ کوچیک از دلتا برای یه چالشِ سریع.
-
-function LessonsHubScreen({
- home,
- onOpenUnit,
- onNavigate,
-}: {
- home: HomeResponse | null;
- onOpenUnit: (unitId: string) => void;
- onNavigate: (s: MainScreen) => void;
-}) {
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
-
- // اولین ایستگاهِ ناتمام رو برای کارتِ «ادامه بده» پیدا میکنیم
- const cont = findContinueNode(home);
- const cUnit = cont?.unit;
- const cProg = cUnit ? unitProgress(cUnit) : { done: 0, total: 0, percent: 0 };
- const cColor = cont ? unitColor(cont.unitIndex) : "#97C0C0";
- const units = home?.units ?? [];
-
- return (
-
- {/* Header */}
-
-
-
-
-
درسها
-
با سهیار، قدمبهقدم جلو برو
-
-
-
-
-
-
-
- {/* Continue card */}
- {cont && cUnit ? (
-
-
-
-
ادامه از جایی که موندی
-
{cUnit.title}
-
{cont.node.title} · {toFa(cProg.done)} از {toFa(cProg.total)} درس
-
-
-
-
-
-
-
onOpenUnit(cUnit.id)}
- className="w-full py-3 text-white rounded-2xl font-bold text-sm flex items-center justify-center gap-2 active:scale-[0.98] transition-transform"
- style={{ backgroundColor: cColor }}>
- ادامه بده
-
-
- ) : (
-
-
-
- {home ? "همهی فصلها تموم شدن! 🎉" : "در حال آوردنِ درسها..."}
-
-
- )}
-
- {/* همهی فصلها */}
- {units.length > 0 && (
-
-
-
فصلهای این پایه
- {toFa(units.length)} فصل
-
-
- {units.map((unit, i) => {
- const prog = unitProgress(unit);
- const color = unitColor(i);
- const Icon = unitIcon(i);
- return (
-
onOpenUnit(unit.id)}
- className="flex-shrink-0 w-[136px] rounded-2xl p-3.5 text-right bg-white border border-[#ede9ff] shadow-sm active:scale-95 transition-transform">
-
-
-
- {unit.title}
- {toFa(prog.done)} از {toFa(prog.total)} درس
-
-
- );
- })}
-
-
- )}
-
- {/* دعوتِ دلتا به یه چالشِ سریع */}
-
onNavigate("question")}
- className="bg-[#FF7A3D] rounded-3xl p-4 flex items-center gap-3 text-right shadow-md shadow-[#FF7A3D]/25 active:scale-[0.98] transition-transform">
-
-
-
یه سؤال دیگه... فقط یکی!
-
یه چالشِ سریع با دلتا
-
-
-
-
-
-
- );
-}
-
-// ─── Lesson Screen ────────────────────────────────────────────────────────────
-
-function LessonScreen({
- unitId,
- lessonId,
- home,
- onNavigate,
- onBack,
-}: {
- unitId: string;
- lessonId: number;
- home: HomeResponse | null;
- onNavigate: (s: MainScreen) => void;
- onBack: () => void;
-}) {
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
- const unitIndex = home?.units.findIndex((u) => u.id === unitId) ?? -1;
- const unit = unitIndex >= 0 ? home!.units[unitIndex] : undefined;
- const color = unitIndex >= 0 ? unitColor(unitIndex) : "#ED6956";
-
- const [intro, setIntro] = useState(null);
- const [loadErr, setLoadErr] = useState(false);
-
- useEffect(() => {
- setIntro(null);
- setLoadErr(false);
- api.lesson.intro(lessonId).then(setIntro).catch(() => setLoadErr(true));
- }, [lessonId]);
-
- const idx = unit ? Math.max(0, unit.nodes.findIndex((n) => n.lessonIds.includes(lessonId))) : 0;
-
- return (
-
-
-
-
-
-
-
- {unit && (
-
- ایستگاه {toFa(idx + 1)} از {toFa(unit.nodes.length)} · {unit.title}
-
- )}
-
{intro?.title ?? "..."}
-
-
-
-
-
-
-
-
- {/* توضیحِ سیگما، بهجای پنلِ متنیِ خشک */}
-
-
-
-
-
- {loadErr ? (
-
این درس الان بارگذاری نشد. یه بار دیگه امتحان کن.
- ) : !intro ? (
-
در حال آوردنِ توضیح درس...
- ) : (
-
{intro.introText}
- )}
-
-
-
- {intro && intro.questionCount === 0 ? (
-
- آزمونِ این درس بهزودی اضافه میشه
-
- ) : (
-
onNavigate("question")} disabled={!intro}
- className="w-full py-4 text-white rounded-2xl font-black flex items-center justify-center gap-2 shadow-lg active:scale-[0.98] transition-transform disabled:opacity-50"
- style={{ backgroundColor: color, boxShadow: `0 10px 24px -6px ${color}66` }}>
- شروع تمرین {intro ? `(${toFa(intro.questionCount)} سوال)` : ""}
-
- )}
-
-
- );
-}
-
-// ─── Question Screen ──────────────────────────────────────────────────────────
-
-function QuestionScreen({ lessonId, onNavigate, onBack, onComplete }: { lessonId: number; onNavigate: (s: MainScreen) => void; onBack: () => void; onComplete?: () => void }) {
- const [questions, setQuestions] = useState(null);
- const [loadErr, setLoadErr] = useState(false);
- const [qIdx, setQIdx] = useState(0);
- const [sel, setSel] = useState(null);
- const [result, setResult] = useState(null);
- const [answering, setAnswering] = useState(false);
- const [lives, setLives] = useState(5);
- const [correctCount, setCorrectCount] = useState(0);
- const outOfLives = lives <= 0;
-
- useEffect(() => {
- api.hearts().then((h) => setLives(h.currentHearts)).catch(() => {});
- }, []);
-
- useEffect(() => {
- setQuestions(null);
- setLoadErr(false);
- api.lesson.questions(lessonId).then(setQuestions).catch(() => setLoadErr(true));
- }, [lessonId]);
-
- const q = questions?.[qIdx];
- const correct = result?.correct ?? false;
- // سهیار: قبل از جواب سیگما راهنماست، جوابِ درست یعنی دلتا، جوابِ غلط یعنی پی
- const seyarChar: SeyarChar = sel === null ? "sigma" : correct ? "delta" : "pi";
- const seyarMood: SeyarMood = sel === null ? "idle" : correct ? "excited" : outOfLives ? "depressed" : "sad";
-
- const optCls = (optId: string) => {
- if (!result) return "bg-white border-2 border-[#e8e4ff] text-[#1a1a2e]";
- if (optId === result.correctOptionId) return "bg-[#e5fdf0] border-2 border-green-400 text-green-700";
- if (optId === sel) return "bg-[#fff0f0] border-2 border-red-300 text-red-500";
- return "bg-white border-2 border-[#e8e4ff] text-[#b0b0c8]";
- };
-
- const selectAnswer = async (optId: string) => {
- if (sel !== null || !q || answering) return;
- setSel(optId);
- setAnswering(true);
- try {
- const r = await api.lesson.answer(Number(q.id), Number(optId), crypto.randomUUID());
- setResult(r);
- setLives(r.heartsRemaining);
- if (r.correct) setCorrectCount((c) => c + 1);
- } catch {
- setLoadErr(true);
- } finally {
- setAnswering(false);
- }
- };
-
- const next = async () => {
- if (!questions) return;
- if (outOfLives) {
- setQIdx(0); setSel(null); setResult(null); setCorrectCount(0);
- api.hearts().then((h) => setLives(h.currentHearts)).catch(() => {});
- return;
- }
- if (qIdx < questions.length - 1) {
- setQIdx((i) => i + 1); setSel(null); setResult(null);
- } else {
- try {
- await api.lesson.complete(lessonId, { correctCount, totalQuestions: questions.length }, crypto.randomUUID());
- onComplete?.();
- } catch { /* اگه ثبتِ پایان شکست خورد، بازم برگرد خونه — بعداً دوباره میشه امتحان کرد */ }
- onNavigate("home");
- }
- };
-
- if (loadErr) {
- return (
-
-
- این سوالها الان بارگذاری نشدن.
- برگشت
-
- );
- }
-
- if (!questions || !q) {
- return (
-
-
- در حال آوردنِ سوالها...
-
- );
- }
-
- return (
-
- {/* Top bar */}
-
-
-
-
-
-
-
{qIdx + 1}/{questions.length}
-
-
- {/* جونها (قلبها) */}
-
- {Array.from({ length: 5 }).map((_, i) => {
- const filled = i < lives;
- return (
-
-
-
- {filled && (
-
-
-
- )}
-
-
- );
- })}
-
-
-
-
- {/* سهیار bubble */}
-
-
-
-
-
-
-
- {sel === null ? "جواب درست رو انتخاب کن! " :
- correct ? "آفرین! درست بود ⚡ ادامه بده!" :
- outOfLives ? "جونات تموم شد؛ نگران نباش، از اول با هم شروع میکنیم." :
- "اشکالی نداره، بیا با هم دوباره نگاهش کنیم."}
-
-
-
- {/* Problem card */}
-
-
سوال {qIdx + 1}
-
{q.prompt}
-
-
- {/* Options grid */}
-
- {q.options.map((opt) => (
- selectAnswer(opt.id)}
- whileTap={{ scale: sel === null ? 0.93 : 1 }}
- className={`py-4 rounded-2xl text-xl font-black flex items-center justify-center gap-1.5 transition-all ${optCls(opt.id)}`}>
- {opt.value}
- {result && opt.id === result.correctOptionId && }
- {sel === opt.id && result && !result.correct && }
-
- ))}
-
-
- {result && (
-
- {outOfLives ? <>دوباره تلاش کن > :
- qIdx < questions.length - 1 ? <>سوال بعدی > :
- <>پایان تمرین >}
-
- )}
-
-
- );
-}
-
-// ─── Ask AI ───────────────────────────────────────────────────────────────────
-
-function AskAIScreen({ profile }: { profile: Profile }) {
- const [input, setInput] = useState("");
- const [loading, setLoading] = useState(false);
- const [streamingText, setStreamingText] = useState(null);
- const [conversationId, setConversationId] = useState(undefined);
- const [messages, setMessages] = useState([
- { from: "bot", text: `سلام ${profile.name}! من پیام 🙂 هر سوالی داری بپرس، قضاوتی هم در کار نیست!` },
- ]);
-
- const send = async () => {
- if (!input.trim() || loading) return;
- const txt = input;
- setInput("");
- setMessages(m => [...m, { from: "user", text: txt }]);
- setLoading(true);
- setStreamingText("");
- let finalText = "";
- try {
- const res = await api.ai.askStream({ userQuestion: txt, conversationId }, (delta) => {
- finalText += delta;
- setStreamingText(finalText);
- });
- if (res.conversationId) setConversationId(res.conversationId);
- setMessages(m => [...m, { from: "bot", text: finalText }]);
- } catch (err) {
- const msg = err instanceof ApiError
- ? (err.isRateLimited ? "یهکم زیاد سوال پرسیدی؛ چند لحظه صبر کن." :
- err.isProviderDown ? "دستیار الان در دسترس نیست، بعداً دوباره امتحان کن." :
- err.detail || "پاسخ نیومد، دوباره امتحان کن.")
- : "پاسخ نیومد، دوباره امتحان کن.";
- setMessages(m => [...m, { from: "bot", text: msg }]);
- } finally {
- setStreamingText(null);
- setLoading(false);
- }
- };
-
- return (
-
-
-
-
-
از پی بپرس
-
دستیار هوشمند ریاضی
-
-
-
-
-
-
-
-
- {messages.map((msg, i) => (
-
- {msg.from === "bot" && (
-
-
-
- )}
-
{msg.text}
-
- ))}
- {loading && (
-
-
-
-
- {streamingText ? (
-
- {streamingText}
-
- ) : (
-
- {[0, 1, 2].map(i => (
-
- ))}
-
- )}
-
- )}
-
-
-
-
-
-
-
- setInput(e.target.value)}
- onKeyDown={e => e.key === "Enter" && send()}
- placeholder="سوالت رو بنویس..." dir="rtl"
- className="flex-1 h-10 bg-[#f0eeff] rounded-full px-4 text-sm outline-none text-[#1a1a2e] placeholder:text-[#b0b0c8]"/>
-
-
-
-
-
-
- );
-}
-
-// ─── Notifications Screen ──────────────────────────────────────────────────────
-
-function NotificationsScreen({
- items,
- loading,
- loadErr,
- onBack,
- onMarkRead,
- onMarkAllRead,
-}: {
- items: NotificationItem[] | null;
- loading: boolean;
- loadErr: boolean;
- onBack: () => void;
- onMarkRead: (id: number) => void;
- onMarkAllRead: () => void;
-}) {
- const unreadCount = items?.filter((n) => !n.isRead).length ?? 0;
-
- return (
-
-
-
-
-
-
-
اعلانها
- {unreadCount > 0 && (
-
- همه خوانده شد
-
- )}
-
-
-
-
- {loading ? (
-
-
- در حال آوردنِ اعلانها...
-
- ) : loadErr ? (
-
-
- اعلانها الان نیومدن، بعداً دوباره امتحان کن.
-
- ) : !items || items.length === 0 ? (
-
- ) : (
-
- {items.map((n) => {
- const style = notifStyle(n.type);
- const Icon = style.icon;
- return (
-
!n.isRead && onMarkRead(n.id)}
- className={`flex items-center gap-3 p-3.5 rounded-2xl text-right transition-colors ${
- n.isRead ? "bg-white border border-[#ede9ff]" : "bg-[#EEF1FF] border border-[#4C5FD5]/20"
- }`}>
-
-
-
- {n.title}
- {!n.isRead && }
-
- );
- })}
-
- )}
-
-
- );
-}
-
-// ─── Leaderboard Screen ─────────────────────────────────────────────────────────
-// توجه: کلاینتِ api.ts پارامترِ period رو "weekly" | "monthly" تایپ کرده، ولی
-// بکاندِ واقعی فقط "weekly" | "all_time" رو قبول میکنه (سریالایزرِ خودشون رو
-// دیدم). اینجا از مقادیرِ واقعیِ بکاند استفاده میکنیم.
-
-interface LeaderboardEntry { rank: number; userId: string; name: string; xpInPeriod: number }
-type LeaderboardPeriod = "weekly" | "all_time";
-
-const RANK_MEDAL: Record = {
- 1: { bg: "#FFD23F", fg: "#8a6d00" },
- 2: { bg: "#D9DEE8", fg: "#5a6270" },
- 3: { bg: "#E8B27A", fg: "#7a4a1a" },
-};
-
-function LeaderboardScreen({
- entries,
- period,
- loading,
- loadErr,
- myUserId,
- onChangePeriod,
- onBack,
-}: {
- entries: LeaderboardEntry[] | null;
- period: LeaderboardPeriod;
- loading: boolean;
- loadErr: boolean;
- myUserId?: string;
- onChangePeriod: (p: LeaderboardPeriod) => void;
- onBack: () => void;
-}) {
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
- const top3 = entries?.slice(0, 3) ?? [];
- const rest = entries?.slice(3) ?? [];
-
- return (
-
- {/* Header */}
-
-
-
-
-
-
-
-
جدولِ امتیازها
-
-
-
- {/* Period toggle */}
-
- {([["weekly", "این هفته"], ["all_time", "کل زمان"]] as [LeaderboardPeriod, string][]).map(([p, label]) => (
- onChangePeriod(p)}
- className={`flex-1 py-2 rounded-xl text-xs font-bold transition-colors ${
- period === p ? "bg-white text-[#FF7A3D]" : "text-white/80"
- }`}>
- {label}
-
- ))}
-
-
-
-
-
- {loading ? (
-
-
- در حال آوردنِ جدول...
-
- ) : loadErr ? (
-
-
- جدول الان نیومد، بعداً دوباره امتحان کن.
-
- ) : !entries || entries.length === 0 ? (
-
-
- هنوز کسی تو این جدول نیست
-
- ) : (
- <>
- {/* سکوی سه نفرِ برتر */}
- {top3.length > 0 && (
-
- {[top3[1], top3[0], top3[2]].filter(Boolean).map((e) => {
- const isMe = e.userId === myUserId;
- const medal = RANK_MEDAL[e.rank];
- const podiumH = e.rank === 1 ? 92 : e.rank === 2 ? 72 : 56;
- return (
-
-
- {toFa(e.rank)}
-
-
{isMe ? "تو" : e.name}
-
{toFa(e.xpInPeriod)} XP
-
-
- );
- })}
-
- )}
-
- {/* بقیهی رتبهها */}
-
- {rest.map((e) => {
- const isMe = e.userId === myUserId;
- return (
-
-
{toFa(e.rank)}
-
- {e.name ? e.name[0] : "؟"}
-
-
{isMe ? "تو" : e.name}
-
{toFa(e.xpInPeriod)} XP
-
- );
- })}
-
- >
- )}
-
-
- );
-}
-
-
-
-// ─── Characters Screen (کاتالوگِ گیمیفیکیشنِ بکاند) ─────────────────────────
-// کاراکترها رو خودِ بکاند مدیریت میکنه (مثلِ subscription.Plan)، پس تعدادشون
-// میتونه هر چقدر باشه. اگه slug با سیگما/دلتا/پی جور دربیاد، طرحِ سهیارِ
-// خودمون (انیمیشندار) نشون داده میشه؛ وگرنه یه کارتِ عمومی.
-
-function CharactersScreen({
- characters,
- loading,
- loadErr,
- selecting,
- onSelect,
- onBack,
-}: {
- characters: ApiCharacter[] | null;
- loading: boolean;
- loadErr: boolean;
- selecting: string | null;
- onSelect: (characterId: string) => void;
- onBack: () => void;
-}) {
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
-
- return (
-
-
-
-
-
-
-
-
-
همراهت رو انتخاب کن
-
با کسبِ امتیاز، کاراکترهای بیشتری باز میشن
-
-
-
-
-
- {loading ? (
-
-
- در حال آوردنِ کاراکترها...
-
- ) : loadErr ? (
-
-
- لیست الان نیومد، بعداً دوباره امتحان کن.
-
- ) : !characters || characters.length === 0 ? (
-
-
- فعلاً کاراکتری تعریف نشده
-
- ) : (
-
- {characters.map((c) => {
- const seyar = seyarSlugMatch(c.slug);
- const isBusy = selecting === c.characterId;
- return (
-
onSelect(c.characterId)}
- className={`relative rounded-3xl p-4 flex flex-col items-center gap-2 border-2 transition-all ${
- c.selected ? "bg-[#F5F1FF] border-[#9B7BFF]" :
- c.unlocked ? "bg-white border-[#ede9ff] active:scale-95" :
- "bg-[#f4f3fa] border-[#ede9ff] opacity-60"
- }`}>
- {c.selected && (
-
-
-
- )}
-
- {seyar ? (
-
- ) : (
-
-
-
- )}
- {!c.unlocked && (
-
-
-
- )}
-
- {c.name}
-
- {c.unlocked ? (c.selected ? "انتخابشده" : "بازشده") : `با ${toFa(c.unlockXp)} امتیاز باز میشه`}
-
-
- );
- })}
-
- )}
-
-
- );
-}
-
-// ─── Subscription ─────────────────────────────────────────────────────────────
-
-const PLAN_STYLES = [
- { card: "bg-white border border-[#ede9ff]", titleCls: "text-[#1a1a2e]", subCls: "text-[#9e9ec0]", fCls: "text-[#3a3a5c]", checkCls: "text-[#ED6956]", btnCls: "border-2 border-[#ED6956] text-[#ED6956]" },
- { card: "bg-[#FF7A3D] border-2 border-white/40", titleCls: "text-white", subCls: "text-white/60", fCls: "text-white/85", checkCls: "text-white", btnCls: "bg-white text-[#FF7A3D] font-bold" },
- { card: "bg-[#91ADC6] border-2 border-white/30", titleCls: "text-white", subCls: "text-white/60", fCls: "text-white/88", checkCls: "text-white", btnCls: "bg-white text-[#91ADC6] font-bold" },
-];
-
-function durationLabel(days: number): string {
- if (days === 0) return "";
- if (days === 30) return "ماهانه";
- if (days >= 360 && days <= 370) return "سالانه";
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
- return `${toFa(days)} روزه`;
-}
-
-function SubscriptionScreen() {
- const [plans, setPlans] = useState(null);
- const [loadErr, setLoadErr] = useState(false);
- const [purchasingId, setPurchasingId] = useState(null);
- const toFa = (n: number) => String(n).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
-
- useEffect(() => {
- api.subscription.plans().then(setPlans).catch(() => setLoadErr(true));
- }, []);
-
- const buy = async (planId: string) => {
- if (purchasingId) return;
- setPurchasingId(planId);
- try {
- const res = await api.subscription.purchase(planId, crypto.randomUUID());
- window.open(res.paymentUrl, "_blank");
- } catch {
- /* اگه پرداخت شروع نشد، همینجا میمونه تا دوباره امتحان کنه */
- } finally {
- setPurchasingId(null);
- }
- };
-
- return (
-
-
-
-
اشتراک ویژه
-
یادگیری ریاضی رو جدی بگیر
-
-
-
- {loadErr && (
-
-
پلنها الان نیومدن؛ اتصالت رو چک کن.
-
- )}
-
- {!plans && !loadErr && (
-
-
- در حال آوردنِ پلنها...
-
- )}
-
- {/* Plan cards */}
- {plans?.map((plan, i) => {
- const style = PLAN_STYLES[i % PLAN_STYLES.length];
- const isFree = plan.price === 0;
- const dur = durationLabel(plan.durationDays);
- return (
-
-
-
-
-
{plan.title}
- {dur &&
{dur}
}
-
-
-
- {isFree ? "رایگان" : `${toFa(plan.price.toLocaleString())} تومان`}
-
-
-
-
- {plan.features.map(f => (
-
-
- {f}
-
- ))}
-
-
!isFree && buy(plan.id)} disabled={isFree || purchasingId === plan.id}
- className={`w-full py-3 rounded-2xl text-sm active:scale-[0.98] transition-transform disabled:opacity-60 ${style.btnCls}`}>
- {isFree ? "پلن فعلی" : purchasingId === plan.id ? "در حال اتصال به درگاه..." : "انتخاب این پلن"}
-
-
-
- );
- })}
-
- {plans &&
هر زمان میتوانید لغو کنید · بدون تعهد
}
-
-
- );
-}
-
-// ─── Root ─────────────────────────────────────────────────────────────────────
-
-export default function App() {
- const [flow, setFlow] = useState("splash");
- const [screen, setScreen] = useState("home");
- const [profile, setProfile] = useState(null);
- const [auth, setAuth] = useState(null);
- const [unitId, setUnitId] = useState("");
- const [lessonId, setLessonId] = useState(1);
- const [home, setHome] = useState(null);
- const [summary, setSummary] = useState<{ xp: number; coins: number; streak: { current: number; longest: number } } | null>(null);
- const [notifications, setNotifications] = useState(null);
- const [notifLoading, setNotifLoading] = useState(false);
- const [notifErr, setNotifErr] = useState(false);
- const unreadCount = notifications?.filter((n) => !n.isRead).length ?? 0;
-
- const refreshHome = () => {
- api.home().then((h) => {
- setHome(h);
- if (!unitId && h.units[0]) setUnitId(h.units[0].id);
- }).catch(() => {});
- api.summary().then((s: any) => setSummary(s)).catch(() => {});
- };
-
- const refreshNotifications = () => {
- setNotifLoading(true);
- setNotifErr(false);
- api.notifications.list()
- .then((r) => setNotifications(r.items as unknown as NotificationItem[]))
- .catch(() => setNotifErr(true))
- .finally(() => setNotifLoading(false));
- };
-
- const markNotifRead = (id: number) => {
- setNotifications((list) => list?.map((n) => (n.id === id ? { ...n, isRead: true } : n)) ?? list);
- api.notifications.markRead(id).catch(() => {});
- };
- const markAllNotifRead = () => {
- const unread = notifications?.filter((n) => !n.isRead) ?? [];
- setNotifications((list) => list?.map((n) => ({ ...n, isRead: true })) ?? list);
- unread.forEach((n) => api.notifications.markRead(n.id).catch(() => {}));
- };
-
- const [lbEntries, setLbEntries] = useState(null);
- const [lbPeriod, setLbPeriod] = useState("weekly");
- const [lbLoading, setLbLoading] = useState(false);
- const [lbErr, setLbErr] = useState(false);
-
- const refreshLeaderboard = (period: LeaderboardPeriod) => {
- setLbLoading(true);
- setLbErr(false);
- api.leaderboard(period)
- .then((r) => setLbEntries(r.entries as unknown as LeaderboardEntry[]))
- .catch(() => setLbErr(true))
- .finally(() => setLbLoading(false));
- };
-
- const [characters, setCharacters] = useState(null);
- const [charLoading, setCharLoading] = useState(false);
- const [charErr, setCharErr] = useState(false);
- const [charSelecting, setCharSelecting] = useState(null);
-
- const refreshCharacters = () => {
- setCharLoading(true);
- setCharErr(false);
- api.characters.list()
- .then(setCharacters)
- .catch(() => setCharErr(true))
- .finally(() => setCharLoading(false));
- };
-
- const selectCharacter = (characterId: string) => {
- setCharSelecting(characterId);
- api.characters.select(characterId)
- .then(() => setCharacters((list) => list?.map((c) => ({ ...c, selected: c.characterId === characterId })) ?? list))
- .catch(() => {})
- .finally(() => setCharSelecting(null));
- };
-
- useEffect(() => {
- if (flow === "main") { refreshHome(); refreshNotifications(); }
- }, [flow]);
-
- useEffect(() => {
- if (flow === "main" && screen === "leaderboard" && !lbEntries) refreshLeaderboard(lbPeriod);
- }, [flow, screen]);
-
- useEffect(() => {
- if (flow === "main" && screen === "characters" && !characters) refreshCharacters();
- }, [flow, screen]);
-
- // ─── تاریخچهی صفحات، برای دکمهی «بازگشت» ────────────────────────────────
- // navigate: رفتن به جلو (صفحهی فعلی رو تو تاریخچه نگه میداره)
- // goBack: برگشت به صفحهی قبلی (یا خانه، اگه چیزی تو تاریخچه نبود)
- // switchTab: تعویضِ تبِ پایین؛ تاریخچه رو پاک میکنه چون یه مسیرِ تازهست
- const [history, setHistory] = useState([]);
-
- const navigate = (s: MainScreen) => {
- setHistory(h => [...h, screen]);
- setScreen(s);
- };
- const goBack = () => {
- setHistory(h => {
- if (h.length === 0) { setScreen("home"); return h; }
- setScreen(h[h.length - 1]);
- return h.slice(0, -1);
- });
- };
- const switchTab = (s: MainScreen) => {
- setHistory([]);
- setScreen(s);
- };
-
- useEffect(() => {
- // اگه قبلاً واردِ اپ شده بودیم (توکن هنوز معتبره)، مستقیم بریم تو خانه
- if (api.auth.isSignedIn()) {
- Promise.all([api.profile.get(), api.grades().catch(() => [])])
- .then(([p, gs]) => {
- const gName = gs.find((g) => g.id === p.gradeId)?.title ?? "";
- setProfile({ name: p.name || "", age: 15, grade: p.gradeId ? String(p.gradeId) : "", gradeName: gName });
- setFlow(p.name && p.gradeId ? "main" : "profile");
- })
- .catch(() => { /* توکن خراب بود؛ همون اسپلش بمونه، کاربر از اول وارد میشه */ });
- }
- }, []);
-
- const handleReturning = () => setFlow("login");
- const handleOnboardingDone = () => setFlow("login");
-
- const handleLoginDone = async (a: AuthData) => {
- setAuth(a);
- try {
- const [p, gs] = await Promise.all([api.profile.get(), api.grades().catch(() => [])]);
- if (p.name && p.gradeId) {
- const gName = gs.find((g) => g.id === p.gradeId)?.title ?? "";
- setProfile({ name: p.name, age: 15, grade: String(p.gradeId), gradeName: gName });
- setFlow("main"); setScreen("home");
- return;
- }
- } catch {
- /* پروفایلِ تازه یا خطای موقت — بریم مرحلهی تکمیلِ پروفایل */
- }
- setFlow("profile");
- };
-
- const handleProfileDone = (p: Profile) => {
- setProfile(p);
- setFlow("main");
- setScreen("home");
- };
-
- const showNav = flow === "main";
-
- const isLightHeader = flow === "main" && screen === "question";
-
- return (
-
- {/* Phone frame */}
-
-
- {/* Status bar */}
-
-
- {/* Screen area */}
-
-
-
- {flow === "splash" && setFlow("onboarding")} onReturning={handleReturning}/>}
- {flow === "onboarding"&& }
- {flow === "profile" && }
- {flow === "main" && screen === "lesson" && }
- {flow === "main" && screen === "question" && }
- {flow === "main" && screen === "askai" && }
- {flow === "main" && screen === "subscription" && }
- {flow === "login" && }
- {flow === "main" && screen === "home" && { setUnitId(id); navigate("skillmap"); }}/>}
- {flow === "main" && screen === "notifications" && }
- {flow === "main" && screen === "leaderboard" && { setLbPeriod(p); refreshLeaderboard(p); }} onBack={goBack}/>}
- {flow === "main" && screen === "characters" && }
- {flow === "main" && screen === "topics" && { setUnitId(id); navigate("skillmap"); }}/>}
- {flow === "main" && screen === "lessons" && { setUnitId(id); navigate("skillmap"); }} onNavigate={navigate}/>}
- {flow === "main" && screen === "skillmap" && { setLessonId(id); navigate("lesson"); }} onBack={goBack}/>}
-
-
-
-
- {showNav &&
}
-
-
- );
}
diff --git a/src/app/MainApp.tsx b/src/app/MainApp.tsx
new file mode 100644
index 0000000..5d4a071
--- /dev/null
+++ b/src/app/MainApp.tsx
@@ -0,0 +1,149 @@
+import { useEffect, useState } from 'react';
+import { AnimatePresence, motion } from 'motion/react';
+import { CheckCircle2, XCircle } from 'lucide-react';
+import { api, type Grade, type UserProfile } from '@/api';
+import { AppDataProvider, useAppData } from '@/state/AppData';
+import { NavigatorProvider, useNav } from '@/nav/Navigator';
+import { isTab, type Route } from '@/nav/routes';
+import { BottomNav } from '@/components/BottomNav';
+import { Sheet } from '@/components/Sheet';
+import { useToast } from '@/components/Toast';
+import { PrimaryButton } from '@/components/ui';
+import { AskScreen } from '@/screens/AskScreen';
+import { CharactersScreen } from '@/screens/CharactersScreen';
+import { FamilyScreen } from '@/screens/FamilyScreen';
+import { HomeScreen } from '@/screens/HomeScreen';
+import { InviteScreen } from '@/screens/InviteScreen';
+import { LeaderboardScreen } from '@/screens/LeaderboardScreen';
+import { LessonScreen } from '@/screens/LessonScreen';
+import { LoginScreen } from '@/screens/LoginScreen';
+import { MapScreen } from '@/screens/MapScreen';
+import { NotificationsScreen } from '@/screens/NotificationsScreen';
+import { ProfileScreen } from '@/screens/ProfileScreen';
+import { QuizScreen } from '@/screens/QuizScreen';
+import { StreakScreen } from '@/screens/StreakScreen';
+import { SubscriptionScreen } from '@/screens/SubscriptionScreen';
+import { SupportScreen } from '@/screens/SupportScreen';
+import { UnitScreen } from '@/screens/UnitScreen';
+import type { PaymentReturn } from './launchParams';
+
+interface Props {
+ profile: UserProfile;
+ grades: Grade[];
+ payment: PaymentReturn | null;
+ onSignedOut: () => void;
+ onRegistered: (profile: UserProfile) => Promise;
+}
+
+/** اپِ اصلی بعد از ورود: تبها، صفحهها و دادهی مشترک. */
+export function MainApp({ profile, grades, payment, onSignedOut, onRegistered }: Props) {
+ return (
+
+
+
+ {payment && }
+
+
+ );
+}
+
+function Screens({ onSignedOut, onRegistered }: Pick) {
+ const nav = useNav();
+ const { route } = nav;
+ const showNav = isTab(route.name) && nav.tab === route.name;
+
+ return (
+ <>
+
+ {showNav && }
+ >
+ );
+}
+
+function RouteView({ route, onSignedOut, onRegistered }: { route: Route } & Pick) {
+ switch (route.name) {
+ case 'home': return ;
+ case 'map': return ;
+ case 'ask': return ;
+ case 'subscription': return ;
+ case 'profile': return ;
+ case 'unit': return ;
+ case 'lesson': return ;
+ case 'quiz': return ;
+ case 'notifications': return ;
+ case 'leaderboard': return ;
+ case 'characters': return ;
+ case 'streak': return ;
+ case 'support': return ;
+ case 'family': return ;
+ case 'invite': return ;
+ case 'register': return ;
+ }
+}
+
+/** مهمانی که شماره ثبت میکند؛ همان کاربر میماند و فقط پروفایل تازه میشود. */
+function RegisterView({ onRegistered }: Pick) {
+ const nav = useNav();
+ const { setProfile } = useAppData();
+ const toast = useToast();
+ return (
+ {
+ try {
+ const profile = await api.profile.get();
+ setProfile(profile);
+ await onRegistered(profile);
+ } catch {
+ /* ورود انجام شده؛ پروفایل با بازگشت به اپ تازه میشود */
+ }
+ toast('شمارهت ثبت شد! پیشرفتت امنه.', 'success');
+ nav.back();
+ }}
+ />
+ );
+}
+
+/** نتیجهی برگشت از درگاه؛ وضعیتِ سفارش از خودِ سرور هم دوباره خوانده میشود. */
+function PaymentResult({ payment }: { payment: PaymentReturn }) {
+ const { refreshSummary } = useAppData();
+ const [open, setOpen] = useState(true);
+ const [paid, setPaid] = useState(payment.status === 'paid');
+
+ useEffect(() => {
+ api.subscription
+ .order(payment.orderId)
+ .then((order) => setPaid(order.status === 'paid'))
+ .catch(() => {});
+ void refreshSummary();
+ }, [payment.orderId, refreshSummary]);
+
+ return (
+ setOpen(false)}>
+
+ {paid ?
:
}
+
{paid ? 'پرداخت موفق بود! 🎉' : 'پرداخت انجام نشد'}
+
+ {paid
+ ? 'اشتراکت فعال شد؛ از الان قلبهات نامحدوده.'
+ : 'اگه مبلغی از حسابت کم شده، نگران نباش؛ طیِ چند ساعت خودکار برمیگرده یا اشتراکت فعال میشه.'}
+
+
setOpen(false)}>باشه
+
+
+ );
+}
diff --git a/src/app/components/figma/ImageWithFallback.tsx b/src/app/components/figma/ImageWithFallback.tsx
deleted file mode 100644
index 0e26139..0000000
--- a/src/app/components/figma/ImageWithFallback.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import React, { useState } from 'react'
-
-const ERROR_IMG_SRC =
- 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg=='
-
-export function ImageWithFallback(props: React.ImgHTMLAttributes) {
- const [didError, setDidError] = useState(false)
-
- const handleError = () => {
- setDidError(true)
- }
-
- const { src, alt, style, className, ...rest } = props
-
- return didError ? (
-
-
-
-
-
- ) : (
-
- )
-}
diff --git a/src/app/components/ui/accordion.tsx b/src/app/components/ui/accordion.tsx
deleted file mode 100644
index bd6b1e3..0000000
--- a/src/app/components/ui/accordion.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as AccordionPrimitive from "@radix-ui/react-accordion";
-import { ChevronDownIcon } from "lucide-react";
-
-import { cn } from "./utils";
-
-function Accordion({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function AccordionItem({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AccordionTrigger({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
- svg]:rotate-180",
- className,
- )}
- {...props}
- >
- {children}
-
-
-
- );
-}
-
-function AccordionContent({
- className,
- children,
- ...props
-}: React.ComponentProps) {
- return (
-
- {children}
-
- );
-}
-
-export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/src/app/components/ui/alert-dialog.tsx b/src/app/components/ui/alert-dialog.tsx
deleted file mode 100644
index 875b8df..0000000
--- a/src/app/components/ui/alert-dialog.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
-
-import { cn } from "./utils";
-import { buttonVariants } from "./button";
-
-function AlertDialog({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-function AlertDialogTrigger({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AlertDialogPortal({
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AlertDialogOverlay({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AlertDialogContent({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
-
-
-
- );
-}
-
-function AlertDialogHeader({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function AlertDialogFooter({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function AlertDialogTitle({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AlertDialogDescription({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AlertDialogAction({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AlertDialogCancel({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-export {
- AlertDialog,
- AlertDialogPortal,
- AlertDialogOverlay,
- AlertDialogTrigger,
- AlertDialogContent,
- AlertDialogHeader,
- AlertDialogFooter,
- AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogAction,
- AlertDialogCancel,
-};
diff --git a/src/app/components/ui/alert.tsx b/src/app/components/ui/alert.tsx
deleted file mode 100644
index 9c35976..0000000
--- a/src/app/components/ui/alert.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import * as React from "react";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "./utils";
-
-const alertVariants = cva(
- "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
- {
- variants: {
- variant: {
- default: "bg-card text-card-foreground",
- destructive:
- "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-function Alert({
- className,
- variant,
- ...props
-}: React.ComponentProps<"div"> & VariantProps) {
- return (
-
- );
-}
-
-function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function AlertDescription({
- className,
- ...props
-}: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-export { Alert, AlertTitle, AlertDescription };
diff --git a/src/app/components/ui/aspect-ratio.tsx b/src/app/components/ui/aspect-ratio.tsx
deleted file mode 100644
index c16d6bc..0000000
--- a/src/app/components/ui/aspect-ratio.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-"use client";
-
-import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
-
-function AspectRatio({
- ...props
-}: React.ComponentProps) {
- return ;
-}
-
-export { AspectRatio };
diff --git a/src/app/components/ui/avatar.tsx b/src/app/components/ui/avatar.tsx
deleted file mode 100644
index c990451..0000000
--- a/src/app/components/ui/avatar.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as AvatarPrimitive from "@radix-ui/react-avatar";
-
-import { cn } from "./utils";
-
-function Avatar({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AvatarImage({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-function AvatarFallback({
- className,
- ...props
-}: React.ComponentProps) {
- return (
-
- );
-}
-
-export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/app/components/ui/badge.tsx b/src/app/components/ui/badge.tsx
deleted file mode 100644
index 2ccc2c4..0000000
--- a/src/app/components/ui/badge.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "./utils";
-
-const badgeVariants = cva(
- "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
- {
- variants: {
- variant: {
- default:
- "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
- secondary:
- "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
- destructive:
- "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
- outline:
- "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-function Badge({
- className,
- variant,
- asChild = false,
- ...props
-}: React.ComponentProps<"span"> &
- VariantProps & { asChild?: boolean }) {
- const Comp = asChild ? Slot : "span";
-
- return (
-
- );
-}
-
-export { Badge, badgeVariants };
diff --git a/src/app/components/ui/breadcrumb.tsx b/src/app/components/ui/breadcrumb.tsx
deleted file mode 100644
index 8f84d7e..0000000
--- a/src/app/components/ui/breadcrumb.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { ChevronRight, MoreHorizontal } from "lucide-react";
-
-import { cn } from "./utils";
-
-function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
- return ;
-}
-
-function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
- return (
-
- );
-}
-
-function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
- return (
-
- );
-}
-
-function BreadcrumbLink({
- asChild,
- className,
- ...props
-}: React.ComponentProps<"a"> & {
- asChild?: boolean;
-}) {
- const Comp = asChild ? Slot : "a";
-
- return (
-
- );
-}
-
-function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
- return (
-
- );
-}
-
-function BreadcrumbSeparator({
- children,
- className,
- ...props
-}: React.ComponentProps<"li">) {
- return (
- svg]:size-3.5", className)}
- {...props}
- >
- {children ?? }
-
- );
-}
-
-function BreadcrumbEllipsis({
- className,
- ...props
-}: React.ComponentProps<"span">) {
- return (
-
-
- More
-
- );
-}
-
-export {
- Breadcrumb,
- BreadcrumbList,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbPage,
- BreadcrumbSeparator,
- BreadcrumbEllipsis,
-};
diff --git a/src/app/components/ui/button.tsx b/src/app/components/ui/button.tsx
deleted file mode 100644
index 40ef7aa..0000000
--- a/src/app/components/ui/button.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import * as React from "react";
-import { Slot } from "@radix-ui/react-slot";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "./utils";
-
-const buttonVariants = cva(
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
- {
- variants: {
- variant: {
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
- destructive:
- "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
- outline:
- "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
- secondary:
- "bg-secondary text-secondary-foreground hover:bg-secondary/80",
- ghost:
- "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
- link: "text-primary underline-offset-4 hover:underline",
- },
- size: {
- default: "h-9 px-4 py-2 has-[>svg]:px-3",
- sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
- lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
- icon: "size-9 rounded-md",
- },
- },
- defaultVariants: {
- variant: "default",
- size: "default",
- },
- },
-);
-
-function Button({
- className,
- variant,
- size,
- asChild = false,
- ...props
-}: React.ComponentProps<"button"> &
- VariantProps & {
- asChild?: boolean;
- }) {
- const Comp = asChild ? Slot : "button";
-
- return (
-
- );
-}
-
-export { Button, buttonVariants };
diff --git a/src/app/components/ui/calendar.tsx b/src/app/components/ui/calendar.tsx
deleted file mode 100644
index ee7b73f..0000000
--- a/src/app/components/ui/calendar.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { ChevronLeft, ChevronRight } from "lucide-react";
-import { DayPicker } from "react-day-picker";
-
-import { cn } from "./utils";
-import { buttonVariants } from "./button";
-
-function Calendar({
- className,
- classNames,
- showOutsideDays = true,
- ...props
-}: React.ComponentProps) {
- return (
- .day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
- : "[&:has([aria-selected])]:rounded-md",
- ),
- day: cn(
- buttonVariants({ variant: "ghost" }),
- "size-8 p-0 font-normal aria-selected:opacity-100",
- ),
- day_range_start:
- "day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground",
- day_range_end:
- "day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground",
- day_selected:
- "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
- day_today: "bg-accent text-accent-foreground",
- day_outside:
- "day-outside text-muted-foreground aria-selected:text-muted-foreground",
- day_disabled: "text-muted-foreground opacity-50",
- day_range_middle:
- "aria-selected:bg-accent aria-selected:text-accent-foreground",
- day_hidden: "invisible",
- ...classNames,
- }}
- components={{
- IconLeft: ({ className, ...props }) => (
-
- ),
- IconRight: ({ className, ...props }) => (
-
- ),
- }}
- {...props}
- />
- );
-}
-
-export { Calendar };
diff --git a/src/app/components/ui/card.tsx b/src/app/components/ui/card.tsx
deleted file mode 100644
index 5f9d58a..0000000
--- a/src/app/components/ui/card.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import * as React from "react";
-
-import { cn } from "./utils";
-
-function Card({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function CardAction({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function CardContent({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
- return (
-
- );
-}
-
-export {
- Card,
- CardHeader,
- CardFooter,
- CardTitle,
- CardAction,
- CardDescription,
- CardContent,
-};
diff --git a/src/app/components/ui/carousel.tsx b/src/app/components/ui/carousel.tsx
deleted file mode 100644
index bb5ab13..0000000
--- a/src/app/components/ui/carousel.tsx
+++ /dev/null
@@ -1,241 +0,0 @@
-"use client";
-
-import * as React from "react";
-import useEmblaCarousel, {
- type UseEmblaCarouselType,
-} from "embla-carousel-react";
-import { ArrowLeft, ArrowRight } from "lucide-react";
-
-import { cn } from "./utils";
-import { Button } from "./button";
-
-type CarouselApi = UseEmblaCarouselType[1];
-type UseCarouselParameters = Parameters;
-type CarouselOptions = UseCarouselParameters[0];
-type CarouselPlugin = UseCarouselParameters[1];
-
-type CarouselProps = {
- opts?: CarouselOptions;
- plugins?: CarouselPlugin;
- orientation?: "horizontal" | "vertical";
- setApi?: (api: CarouselApi) => void;
-};
-
-type CarouselContextProps = {
- carouselRef: ReturnType[0];
- api: ReturnType[1];
- scrollPrev: () => void;
- scrollNext: () => void;
- canScrollPrev: boolean;
- canScrollNext: boolean;
-} & CarouselProps;
-
-const CarouselContext = React.createContext(null);
-
-function useCarousel() {
- const context = React.useContext(CarouselContext);
-
- if (!context) {
- throw new Error("useCarousel must be used within a ");
- }
-
- return context;
-}
-
-function Carousel({
- orientation = "horizontal",
- opts,
- setApi,
- plugins,
- className,
- children,
- ...props
-}: React.ComponentProps<"div"> & CarouselProps) {
- const [carouselRef, api] = useEmblaCarousel(
- {
- ...opts,
- axis: orientation === "horizontal" ? "x" : "y",
- },
- plugins,
- );
- const [canScrollPrev, setCanScrollPrev] = React.useState(false);
- const [canScrollNext, setCanScrollNext] = React.useState(false);
-
- const onSelect = React.useCallback((api: CarouselApi) => {
- if (!api) return;
- setCanScrollPrev(api.canScrollPrev());
- setCanScrollNext(api.canScrollNext());
- }, []);
-
- const scrollPrev = React.useCallback(() => {
- api?.scrollPrev();
- }, [api]);
-
- const scrollNext = React.useCallback(() => {
- api?.scrollNext();
- }, [api]);
-
- const handleKeyDown = React.useCallback(
- (event: React.KeyboardEvent) => {
- if (event.key === "ArrowLeft") {
- event.preventDefault();
- scrollPrev();
- } else if (event.key === "ArrowRight") {
- event.preventDefault();
- scrollNext();
- }
- },
- [scrollPrev, scrollNext],
- );
-
- React.useEffect(() => {
- if (!api || !setApi) return;
- setApi(api);
- }, [api, setApi]);
-
- React.useEffect(() => {
- if (!api) return;
- onSelect(api);
- api.on("reInit", onSelect);
- api.on("select", onSelect);
-
- return () => {
- api?.off("select", onSelect);
- };
- }, [api, onSelect]);
-
- return (
-
-
- {children}
-
-
- );
-}
-
-function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
- const { carouselRef, orientation } = useCarousel();
-
- return (
-
- );
-}
-
-function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
- const { orientation } = useCarousel();
-
- return (
-
- );
-}
-
-function CarouselPrevious({
- className,
- variant = "outline",
- size = "icon",
- ...props
-}: React.ComponentProps) {
- const { orientation, scrollPrev, canScrollPrev } = useCarousel();
-
- return (
-
-
- Previous slide
-
- );
-}
-
-function CarouselNext({
- className,
- variant = "outline",
- size = "icon",
- ...props
-}: React.ComponentProps) {
- const { orientation, scrollNext, canScrollNext } = useCarousel();
-
- return (
-
-
- Next slide
-
- );
-}
-
-export {
- type CarouselApi,
- Carousel,
- CarouselContent,
- CarouselItem,
- CarouselPrevious,
- CarouselNext,
-};
diff --git a/src/app/components/ui/chart.tsx b/src/app/components/ui/chart.tsx
deleted file mode 100644
index b49bc36..0000000
--- a/src/app/components/ui/chart.tsx
+++ /dev/null
@@ -1,353 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as RechartsPrimitive from "recharts";
-
-import { cn } from "./utils";
-
-// Format: { THEME_NAME: CSS_SELECTOR }
-const THEMES = { light: "", dark: ".dark" } as const;
-
-export type ChartConfig = {
- [k in string]: {
- label?: React.ReactNode;
- icon?: React.ComponentType;
- } & (
- | { color?: string; theme?: never }
- | { color?: never; theme: Record }
- );
-};
-
-type ChartContextProps = {
- config: ChartConfig;
-};
-
-const ChartContext = React.createContext(null);
-
-function useChart() {
- const context = React.useContext(ChartContext);
-
- if (!context) {
- throw new Error("useChart must be used within a ");
- }
-
- return context;
-}
-
-function ChartContainer({
- id,
- className,
- children,
- config,
- ...props
-}: React.ComponentProps<"div"> & {
- config: ChartConfig;
- children: React.ComponentProps<
- typeof RechartsPrimitive.ResponsiveContainer
- >["children"];
-}) {
- const uniqueId = React.useId();
- const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
-
- return (
-
-
-
-
- {children}
-
-
-
- );
-}
-
-const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
- const colorConfig = Object.entries(config).filter(
- ([, config]) => config.theme || config.color,
- );
-
- if (!colorConfig.length) {
- return null;
- }
-
- return (
-