From d972f9b3dc7983f06c451e1f847948e1d987638e Mon Sep 17 00:00:00 2001 From: RUI <298977887@qq.com> Date: Wed, 14 May 2025 00:30:11 +0800 Subject: [PATCH] 0514.1 --- .cursor/rules/nextjs.mdc | 50 + .dockerignore | 32 + Dockerfile | 49 + README-API.md | 482 ++ README-DB.md | 236 + README-DOCKER-IMPORT.md | 63 + README-DOCKER.md | 93 + README-TEAM-DB.md | 74 + README-UI.md | 54 + README-hooks.md | 312 + README.md | 46 +- docker-compose.yml | 43 + next.config.ts | 15 + package-lock.json | 6712 ----------------- package.json | 33 +- pnpm-lock.yaml | 5660 ++++++++++++++ public/robots.txt | 20 + src/app/(auth)/layout.tsx | 193 + src/app/(auth)/login/page.tsx | 256 + src/app/(auth)/register/page.tsx | 650 ++ src/app/api/auth/login/route.ts | 138 + src/app/api/auth/register/route.ts | 351 + src/app/api/auth/verify-invite/route.ts | 142 + src/app/api/cron/init/route.ts | 391 + .../api/team/[teamCode]/brands/[id]/route.ts | 287 + src/app/api/team/[teamCode]/brands/route.ts | 251 + .../team/[teamCode]/categories/[id]/route.ts | 371 + .../api/team/[teamCode]/categories/route.ts | 259 + .../team/[teamCode]/customers/[id]/route.ts | 444 ++ .../api/team/[teamCode]/customers/route.ts | 300 + .../team/[teamCode]/logistics/query/route.ts | 212 + .../api/team/[teamCode]/logistics/route.ts | 483 ++ .../logistics/update-status/route.ts | 192 + .../payment-platforms/[id]/route.ts | 387 + .../[teamCode]/payment-platforms/route.ts | 152 + .../team/[teamCode]/products/[id]/route.ts | 474 ++ src/app/api/team/[teamCode]/products/route.ts | 424 ++ .../team/[teamCode]/sales-records/route.ts | 598 ++ .../shop-follower-growth/[id]/route.ts | 404 + .../[teamCode]/shop-follower-growth/route.ts | 324 + .../shop-follower-growth/statistics/route.ts | 186 + .../[teamCode]/shop-sales-analysis/route.ts | 139 + .../api/team/[teamCode]/shops/[id]/route.ts | 436 ++ src/app/api/team/[teamCode]/shops/route.ts | 385 + .../team/[teamCode]/suppliers/[id]/route.ts | 396 + .../api/team/[teamCode]/suppliers/route.ts | 300 + src/app/api/team/users/route.ts | 39 + .../teams/[teamCode]/init-database/route.ts | 252 + src/app/api/teams/[teamCode]/route.ts | 114 + src/app/api/teams/route.ts | 389 + src/app/api/test/connection/route.ts | 42 + src/app/api/test/users/route.ts | 61 + src/app/api/tools/images/[...path]/route.ts | 180 + src/app/api/tools/parseAddress/route.ts | 118 + src/app/api/tools/upload/route.ts | 157 + src/app/api/workspace/invite/route.ts | 241 + src/app/api/workspace/members/route.ts | 88 + src/app/api/workspace/teams/route.ts | 73 + src/app/globals.css | 605 +- src/app/layout.tsx | 42 +- src/app/page.tsx | 443 +- .../team/[teamCode]/brands/brand-modal.tsx | 239 + src/app/team/[teamCode]/brands/page.tsx | 417 + .../[teamCode]/categories/category-modal.tsx | 261 + src/app/team/[teamCode]/categories/page.tsx | 474 ++ .../components/CustomerAnalytics.tsx | 428 ++ .../[teamCode]/customers/customer-modal.tsx | 562 ++ src/app/team/[teamCode]/customers/page.tsx | 616 ++ src/app/team/[teamCode]/logistics/page.tsx | 748 ++ src/app/team/[teamCode]/page.tsx | 232 + .../[teamCode]/payment-platforms/page.tsx | 503 ++ .../payment-platform-modal.tsx | 272 + .../products/components/ProductFilters.tsx | 217 + src/app/team/[teamCode]/products/page.tsx | 693 ++ .../[teamCode]/products/product-modal.tsx | 912 +++ .../sales-records/ProductInfoModal.module.css | 156 + .../sales-records/ProductInfoModal.tsx | 342 + .../components/SalesRecordModal.tsx | 948 +++ .../sales-records/components/ShipModal.tsx | 598 ++ .../[teamCode]/sales-records/page.module.css | 974 +++ .../team/[teamCode]/sales-records/page.tsx | 861 +++ .../sales/components/CustomerSelector.tsx | 221 + .../sales/components/CustomerSelectorInfo.tsx | 135 + .../sales/components/ProductModalAdapter.tsx | 92 + .../sales/components/ProductSelector.tsx | 127 + .../sales/components/SelectedProductList.tsx | 198 + src/app/team/[teamCode]/sales/page.tsx | 903 +++ src/app/team/[teamCode]/sales/sales.d.ts | 25 + .../sales2/components/AddCustomer.tsx | 590 ++ .../sales2/components/CustomerSelector.tsx | 397 + .../sales2/components/ProductSelector.tsx | 157 + src/app/team/[teamCode]/sales2/page.tsx | 712 ++ .../components/ControlPanel.tsx | 103 + .../components/ShopDataRow.tsx | 182 + .../components/TableHeader.tsx | 132 + .../[teamCode]/shop-follower-growth/page.tsx | 542 ++ .../services/apiService.ts | 260 + .../[teamCode]/shop-follower-growth/types.ts | 52 + .../shop-follower-growth/utils/dataUtils.ts | 41 + .../shop-follower-growth/utils/dateUtils.ts | 60 + .../shop-sales-analysis/page.module.css | 34 + .../[teamCode]/shop-sales-analysis/page.tsx | 241 + src/app/team/[teamCode]/shops/page.tsx | 543 ++ src/app/team/[teamCode]/shops/shop-modal.tsx | 360 + src/app/team/[teamCode]/suppliers/page.tsx | 641 ++ .../[teamCode]/suppliers/supplier-modal.tsx | 456 ++ src/app/team/layout.tsx | 388 + src/app/test/page.module.css | 95 + src/app/test/page.tsx | 137 + src/app/test/ui/page.tsx | 293 + src/app/test/usehook/page.module.css | 110 + src/app/test/usehook/page.tsx | 208 + src/app/workspace/create-team/page.tsx | 417 + src/app/workspace/page.tsx | 532 ++ src/components/AddMemberModal.tsx | 506 ++ src/components/InviteModal.tsx | 318 + src/components/LogisticsScheduler.tsx | 122 + src/components/ThemeProvider.tsx | 155 + src/components/UploadImage.tsx | 438 ++ src/components/icon/IconifyPicker.tsx | 248 + src/components/icon/icon-button.tsx | 25 + src/components/icon/iconify-icon.tsx | 24 + src/components/icon/index.ts | 5 + src/components/icon/svg-icon.tsx | 39 + src/components/tooltip/MyTooltip.tsx | 143 + src/components/ui/Button.tsx | 199 + src/components/ui/Card.tsx | 179 + src/components/ui/Input.tsx | 153 + src/components/ui/Modal.tsx | 281 + src/components/ui/Notification.tsx | 546 ++ src/hooks/index.ts | 33 + src/hooks/useSettings.ts | 119 + src/hooks/useTeam.tsx | 171 + src/hooks/useUser.ts | 95 + src/lib/auth/index.ts | 121 + src/lib/db/config.ts | 23 + src/lib/db/connect-system.ts | 73 + src/lib/db/connect-team.ts | 133 + src/lib/db/db-singleton.ts | 279 + src/lib/db/index.ts | 20 + src/lib/db/init-system-db.ts | 247 + src/lib/db/init-team-db.ts | 798 ++ src/lib/db/teamdb-singleton.ts | 337 + src/lib/db/types.ts | 29 + src/models/system/SystemUserModel.ts | 291 + src/models/system/UserModel.ts | 270 + src/models/system/WorkspaceModel.ts | 100 + src/models/system/index.ts | 11 + src/models/system/types/ISystemUser.ts | 97 + src/models/system/types/ITeam.ts | 74 + src/models/system/types/IUserRole.ts | 58 + src/models/system/types/IUserTeamRelation.ts | 51 + src/models/system/types/IWorkspace.ts | 42 + src/models/system/types/index.ts | 12 + src/models/team/BrandModel.ts | 115 + src/models/team/SupplierModel.ts | 101 + src/models/team/TeamModel.ts | 155 + src/models/team/index.ts | 27 + src/models/team/types/IAfterSalesRecord.ts | 74 + src/models/team/types/IBrand.ts | 27 + src/models/team/types/ICategory.ts | 27 + src/models/team/types/ICustomer.ts | 46 + src/models/team/types/ILogisticsRecord.ts | 52 + src/models/team/types/IPaymentPlatform.ts | 39 + src/models/team/types/IProduct.ts | 70 + src/models/team/types/ISalesRecord.ts | 63 + src/models/team/types/IShop.ts | 49 + src/models/team/types/IShopFollowerGrowth.ts | 38 + src/models/team/types/IShopTrafficExpense.ts | 39 + src/models/team/types/ISupplier.ts | 75 + src/models/team/types/index.ts | 20 + src/models/team/types/old/brand.ts | 54 + src/models/team/types/old/category.ts | 54 + src/models/team/types/old/customer.ts | 118 + src/models/team/types/old/payment-platform.ts | 68 + src/models/team/types/old/product.ts | 121 + src/models/team/types/old/sales.ts | 131 + src/models/team/types/old/shop.ts | 89 + src/models/team/types/old/supplier.ts | 92 + src/store/settingStore.ts | 223 + src/store/userStore.ts | 193 + src/styles/antd-glass.css | 398 + src/types/enum.ts | 42 + src/utils/date.utils.ts | 281 + src/utils/getAccessToken.ts | 33 + src/utils/index.ts | 80 + src/utils/querySFExpress.ts | 116 + src/utils/string.utils.ts | 156 + src/utils/validation.utils.ts | 289 + tsconfig.json | 26 +- .../20250513_CZYUTC.jpeg | Bin 0 -> 164521 bytes .../20250513_PHC2f0.png | Bin 0 -> 38141 bytes .../20250512_Mwk-TW.jpeg | Bin 0 -> 164521 bytes .../20250512_P6WytO.jpeg | Bin 0 -> 153520 bytes .../20250512_Pi7PZg.png | Bin 0 -> 42847 bytes .../20250513_QjJwMJ.png | Bin 0 -> 79117 bytes .../20250513_XSPCpr.png | Bin 0 -> 107808 bytes .../20250513_bmM9jR.png | Bin 0 -> 55614 bytes .../20250513_brqRWt.png | Bin 0 -> 207788 bytes .../20250513_efMl7A.jpeg | Bin 0 -> 164521 bytes .../20250513_la0rHD.png | Bin 0 -> 106169 bytes .../20250513_u8S5FJ.png | Bin 0 -> 106169 bytes .../20250513_xeStQ0.png | Bin 0 -> 111144 bytes .../20250514_NzOu9u.png | Bin 0 -> 78424 bytes 模型定义-团队级.txt | 372 + 模型定义-系统级.txt | 126 + 206 files changed, 50243 insertions(+), 6881 deletions(-) create mode 100644 .cursor/rules/nextjs.mdc create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 README-API.md create mode 100644 README-DB.md create mode 100644 README-DOCKER-IMPORT.md create mode 100644 README-DOCKER.md create mode 100644 README-TEAM-DB.md create mode 100644 README-UI.md create mode 100644 README-hooks.md create mode 100644 docker-compose.yml delete mode 100644 package-lock.json create mode 100644 pnpm-lock.yaml create mode 100644 public/robots.txt create mode 100644 src/app/(auth)/layout.tsx create mode 100644 src/app/(auth)/login/page.tsx create mode 100644 src/app/(auth)/register/page.tsx create mode 100644 src/app/api/auth/login/route.ts create mode 100644 src/app/api/auth/register/route.ts create mode 100644 src/app/api/auth/verify-invite/route.ts create mode 100644 src/app/api/cron/init/route.ts create mode 100644 src/app/api/team/[teamCode]/brands/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/brands/route.ts create mode 100644 src/app/api/team/[teamCode]/categories/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/categories/route.ts create mode 100644 src/app/api/team/[teamCode]/customers/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/customers/route.ts create mode 100644 src/app/api/team/[teamCode]/logistics/query/route.ts create mode 100644 src/app/api/team/[teamCode]/logistics/route.ts create mode 100644 src/app/api/team/[teamCode]/logistics/update-status/route.ts create mode 100644 src/app/api/team/[teamCode]/payment-platforms/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/payment-platforms/route.ts create mode 100644 src/app/api/team/[teamCode]/products/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/products/route.ts create mode 100644 src/app/api/team/[teamCode]/sales-records/route.ts create mode 100644 src/app/api/team/[teamCode]/shop-follower-growth/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/shop-follower-growth/route.ts create mode 100644 src/app/api/team/[teamCode]/shop-follower-growth/statistics/route.ts create mode 100644 src/app/api/team/[teamCode]/shop-sales-analysis/route.ts create mode 100644 src/app/api/team/[teamCode]/shops/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/shops/route.ts create mode 100644 src/app/api/team/[teamCode]/suppliers/[id]/route.ts create mode 100644 src/app/api/team/[teamCode]/suppliers/route.ts create mode 100644 src/app/api/team/users/route.ts create mode 100644 src/app/api/teams/[teamCode]/init-database/route.ts create mode 100644 src/app/api/teams/[teamCode]/route.ts create mode 100644 src/app/api/teams/route.ts create mode 100644 src/app/api/test/connection/route.ts create mode 100644 src/app/api/test/users/route.ts create mode 100644 src/app/api/tools/images/[...path]/route.ts create mode 100644 src/app/api/tools/parseAddress/route.ts create mode 100644 src/app/api/tools/upload/route.ts create mode 100644 src/app/api/workspace/invite/route.ts create mode 100644 src/app/api/workspace/members/route.ts create mode 100644 src/app/api/workspace/teams/route.ts create mode 100644 src/app/team/[teamCode]/brands/brand-modal.tsx create mode 100644 src/app/team/[teamCode]/brands/page.tsx create mode 100644 src/app/team/[teamCode]/categories/category-modal.tsx create mode 100644 src/app/team/[teamCode]/categories/page.tsx create mode 100644 src/app/team/[teamCode]/customers/components/CustomerAnalytics.tsx create mode 100644 src/app/team/[teamCode]/customers/customer-modal.tsx create mode 100644 src/app/team/[teamCode]/customers/page.tsx create mode 100644 src/app/team/[teamCode]/logistics/page.tsx create mode 100644 src/app/team/[teamCode]/page.tsx create mode 100644 src/app/team/[teamCode]/payment-platforms/page.tsx create mode 100644 src/app/team/[teamCode]/payment-platforms/payment-platform-modal.tsx create mode 100644 src/app/team/[teamCode]/products/components/ProductFilters.tsx create mode 100644 src/app/team/[teamCode]/products/page.tsx create mode 100644 src/app/team/[teamCode]/products/product-modal.tsx create mode 100644 src/app/team/[teamCode]/sales-records/ProductInfoModal.module.css create mode 100644 src/app/team/[teamCode]/sales-records/ProductInfoModal.tsx create mode 100644 src/app/team/[teamCode]/sales-records/components/SalesRecordModal.tsx create mode 100644 src/app/team/[teamCode]/sales-records/components/ShipModal.tsx create mode 100644 src/app/team/[teamCode]/sales-records/page.module.css create mode 100644 src/app/team/[teamCode]/sales-records/page.tsx create mode 100644 src/app/team/[teamCode]/sales/components/CustomerSelector.tsx create mode 100644 src/app/team/[teamCode]/sales/components/CustomerSelectorInfo.tsx create mode 100644 src/app/team/[teamCode]/sales/components/ProductModalAdapter.tsx create mode 100644 src/app/team/[teamCode]/sales/components/ProductSelector.tsx create mode 100644 src/app/team/[teamCode]/sales/components/SelectedProductList.tsx create mode 100644 src/app/team/[teamCode]/sales/page.tsx create mode 100644 src/app/team/[teamCode]/sales/sales.d.ts create mode 100644 src/app/team/[teamCode]/sales2/components/AddCustomer.tsx create mode 100644 src/app/team/[teamCode]/sales2/components/CustomerSelector.tsx create mode 100644 src/app/team/[teamCode]/sales2/components/ProductSelector.tsx create mode 100644 src/app/team/[teamCode]/sales2/page.tsx create mode 100644 src/app/team/[teamCode]/shop-follower-growth/components/ControlPanel.tsx create mode 100644 src/app/team/[teamCode]/shop-follower-growth/components/ShopDataRow.tsx create mode 100644 src/app/team/[teamCode]/shop-follower-growth/components/TableHeader.tsx create mode 100644 src/app/team/[teamCode]/shop-follower-growth/page.tsx create mode 100644 src/app/team/[teamCode]/shop-follower-growth/services/apiService.ts create mode 100644 src/app/team/[teamCode]/shop-follower-growth/types.ts create mode 100644 src/app/team/[teamCode]/shop-follower-growth/utils/dataUtils.ts create mode 100644 src/app/team/[teamCode]/shop-follower-growth/utils/dateUtils.ts create mode 100644 src/app/team/[teamCode]/shop-sales-analysis/page.module.css create mode 100644 src/app/team/[teamCode]/shop-sales-analysis/page.tsx create mode 100644 src/app/team/[teamCode]/shops/page.tsx create mode 100644 src/app/team/[teamCode]/shops/shop-modal.tsx create mode 100644 src/app/team/[teamCode]/suppliers/page.tsx create mode 100644 src/app/team/[teamCode]/suppliers/supplier-modal.tsx create mode 100644 src/app/team/layout.tsx create mode 100644 src/app/test/page.module.css create mode 100644 src/app/test/page.tsx create mode 100644 src/app/test/ui/page.tsx create mode 100644 src/app/test/usehook/page.module.css create mode 100644 src/app/test/usehook/page.tsx create mode 100644 src/app/workspace/create-team/page.tsx create mode 100644 src/app/workspace/page.tsx create mode 100644 src/components/AddMemberModal.tsx create mode 100644 src/components/InviteModal.tsx create mode 100644 src/components/LogisticsScheduler.tsx create mode 100644 src/components/ThemeProvider.tsx create mode 100644 src/components/UploadImage.tsx create mode 100644 src/components/icon/IconifyPicker.tsx create mode 100644 src/components/icon/icon-button.tsx create mode 100644 src/components/icon/iconify-icon.tsx create mode 100644 src/components/icon/index.ts create mode 100644 src/components/icon/svg-icon.tsx create mode 100644 src/components/tooltip/MyTooltip.tsx create mode 100644 src/components/ui/Button.tsx create mode 100644 src/components/ui/Card.tsx create mode 100644 src/components/ui/Input.tsx create mode 100644 src/components/ui/Modal.tsx create mode 100644 src/components/ui/Notification.tsx create mode 100644 src/hooks/index.ts create mode 100644 src/hooks/useSettings.ts create mode 100644 src/hooks/useTeam.tsx create mode 100644 src/hooks/useUser.ts create mode 100644 src/lib/auth/index.ts create mode 100644 src/lib/db/config.ts create mode 100644 src/lib/db/connect-system.ts create mode 100644 src/lib/db/connect-team.ts create mode 100644 src/lib/db/db-singleton.ts create mode 100644 src/lib/db/index.ts create mode 100644 src/lib/db/init-system-db.ts create mode 100644 src/lib/db/init-team-db.ts create mode 100644 src/lib/db/teamdb-singleton.ts create mode 100644 src/lib/db/types.ts create mode 100644 src/models/system/SystemUserModel.ts create mode 100644 src/models/system/UserModel.ts create mode 100644 src/models/system/WorkspaceModel.ts create mode 100644 src/models/system/index.ts create mode 100644 src/models/system/types/ISystemUser.ts create mode 100644 src/models/system/types/ITeam.ts create mode 100644 src/models/system/types/IUserRole.ts create mode 100644 src/models/system/types/IUserTeamRelation.ts create mode 100644 src/models/system/types/IWorkspace.ts create mode 100644 src/models/system/types/index.ts create mode 100644 src/models/team/BrandModel.ts create mode 100644 src/models/team/SupplierModel.ts create mode 100644 src/models/team/TeamModel.ts create mode 100644 src/models/team/index.ts create mode 100644 src/models/team/types/IAfterSalesRecord.ts create mode 100644 src/models/team/types/IBrand.ts create mode 100644 src/models/team/types/ICategory.ts create mode 100644 src/models/team/types/ICustomer.ts create mode 100644 src/models/team/types/ILogisticsRecord.ts create mode 100644 src/models/team/types/IPaymentPlatform.ts create mode 100644 src/models/team/types/IProduct.ts create mode 100644 src/models/team/types/ISalesRecord.ts create mode 100644 src/models/team/types/IShop.ts create mode 100644 src/models/team/types/IShopFollowerGrowth.ts create mode 100644 src/models/team/types/IShopTrafficExpense.ts create mode 100644 src/models/team/types/ISupplier.ts create mode 100644 src/models/team/types/index.ts create mode 100644 src/models/team/types/old/brand.ts create mode 100644 src/models/team/types/old/category.ts create mode 100644 src/models/team/types/old/customer.ts create mode 100644 src/models/team/types/old/payment-platform.ts create mode 100644 src/models/team/types/old/product.ts create mode 100644 src/models/team/types/old/sales.ts create mode 100644 src/models/team/types/old/shop.ts create mode 100644 src/models/team/types/old/supplier.ts create mode 100644 src/store/settingStore.ts create mode 100644 src/store/userStore.ts create mode 100644 src/styles/antd-glass.css create mode 100644 src/types/enum.ts create mode 100644 src/utils/date.utils.ts create mode 100644 src/utils/getAccessToken.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/querySFExpress.ts create mode 100644 src/utils/string.utils.ts create mode 100644 src/utils/validation.utils.ts create mode 100644 uploads/products_team_atgv_5940/20250513_CZYUTC.jpeg create mode 100644 uploads/products_team_atgv_5940/20250513_PHC2f0.png create mode 100644 uploads/products_team_jipl_1635/20250512_Mwk-TW.jpeg create mode 100644 uploads/products_team_jipl_1635/20250512_P6WytO.jpeg create mode 100644 uploads/products_team_jipl_1635/20250512_Pi7PZg.png create mode 100644 uploads/products_team_jipl_1635/20250513_QjJwMJ.png create mode 100644 uploads/products_team_jipl_1635/20250513_XSPCpr.png create mode 100644 uploads/products_team_jipl_1635/20250513_bmM9jR.png create mode 100644 uploads/products_team_jipl_1635/20250513_brqRWt.png create mode 100644 uploads/products_team_jipl_1635/20250513_efMl7A.jpeg create mode 100644 uploads/products_team_jipl_1635/20250513_la0rHD.png create mode 100644 uploads/products_team_jipl_1635/20250513_u8S5FJ.png create mode 100644 uploads/products_team_jipl_1635/20250513_xeStQ0.png create mode 100644 uploads/products_team_jipl_1635/20250514_NzOu9u.png create mode 100644 模型定义-团队级.txt create mode 100644 模型定义-系统级.txt diff --git a/.cursor/rules/nextjs.mdc b/.cursor/rules/nextjs.mdc new file mode 100644 index 0000000..3505db5 --- /dev/null +++ b/.cursor/rules/nextjs.mdc @@ -0,0 +1,50 @@ +--- +description: +globs: +alwaysApply: true +--- +Always respond in 中文 + +## 注意事项 + +### 相关规范文档(项目根目录下,需要时请查看) +- 数据模型结构:模型定义-团队级.txt 和 模型定义-系统级.txt +- 自定义钩子使用文档:README-hooks.md +- 数据库连接模块使用指南:README-DB.md +- API请求规范与最佳实践:README-API.md +- UI设计规范:README-UI.md +- 工具函数已在src\utils\index.ts中导出 +- 钩子在src\hooks\index.ts,请查看相关使用方式,禁止使用src\store + +### 开发规范 +- 现在已启用TypeScript严格模式,不要出现类型错误,禁止出现未使用变量 +- UI使用Ant Design 5.X 注意兼容性问题 +- api路由不需要auth,禁止使用auth!!! +- 前端UI、API请求都要符合我们现有的规范(可查看相关文件) +- 开发时,单文件代码严格限制在700行以内(含注释) 不要画蛇添足! +- 禁止使用any类型! +- 必须包含三级注释体系: + ▸ 文件头注释(作者:阿瑞/功能/版本) + ▸ 模块级注释(逻辑分段说明) + ▸ 关键代码行注释(复杂逻辑解释) +- 当前在Windows 11环境下使用PowerShell终端进行开发,禁用类Unix命令,使用`Get-ChildItem` 替代ls ; +- 已使用pnpm run dev启动了项目,禁止重复启动 +- 当修改超过300行的文件时,确保每次修改代码行数 ≤ 100行,分多次修改。 + +## 补充说明 + +1. 适当使用useMemo、React.memo来优化性能和防止不必要的重新渲染; +2. 尽量少使用第三方库,优先使用原生功能或已有依赖; +3. 创建模块化样式文件(Modular Stylesheet),命名遵循[name].module.css约定; +4. 创建新组件时,遵循项目的目录组织结构和命名规范; +5. 最好是模块化能够复用的; +6. 创建前端页面时,复杂样式时应分离样式文件,创建`page.module.css`使用`import styles from './page.module.css';`引入样式; +7. 已有UI组件已在`src\components\ui`路径下,使用时可查看具体代码获取使用方法; +8. 不使用`import { toast } from 'sonner';`这个第三方组件,使用`src\components\ui\Notification.tsx`这个组件; +9. 数据模型定义在`src\models\system`(系统级)和`src\models\team(团队及)文件中; +10. 避免重复定义类型!!!数据模型接口定义在`src\models\system\types`(系统级)和`src\models\team\types`(团队级)文件中; +11. 当执行终端命令时,先解释这个命令的作用;如果要使用pnpm安装包时,先解释为什么需要这个包; + +## 数据库说明 + +数据库是docker中搭建的,可以使用这个命令进入数据库: docker exec -it my-mysql mysql -uroot -p"aiwoQwo520.." \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3d55e61 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,32 @@ +# 开发环境文件 +node_modules +.git +.github +.next +.vscode +*.log +*.md +!README-*.md + +# 测试文件 +__tests__ +coverage +jest.config.js + +# 本地开发文件 +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Docker相关文件 +Dockerfile +docker-compose.yml +.dockerignore + +# 其他文件 +.DS_Store +*.zip +*.tar.gz +.cursor \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..60be1a9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# 基础镜像阶段 - 使用 Node.js 22 作为基础镜像 +FROM node:22-alpine AS base + +# 安装依赖阶段 +FROM base AS deps +# 使用 Alpine 的包管理器安装必要的系统依赖 +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# 复制 package.json 和 package-lock.json +COPY package.json package-lock.json* ./ +# 使用 npm 安装项目依赖 +RUN npm install + +# 构建阶段 +FROM base AS builder +WORKDIR /app +# 从依赖阶段复制 node_modules +COPY --from=deps /app/node_modules ./node_modules +# 复制所有项目文件 +COPY . . + +# 构建应用 +RUN npm run build + +# 生产运行阶段 +FROM base AS runner +WORKDIR /app + +# 设置为生产环境 +ENV NODE_ENV production + +# 创建非 root 用户运行应用,提高安全性 +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs +USER nextjs + +# 从构建阶段复制必要文件 +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# 设置环境变量 +ENV PORT 3000 +# 暴露端口 +EXPOSE 3000 + +# 启动命令 +CMD ["node", "server.js"] \ No newline at end of file diff --git a/README-API.md b/README-API.md new file mode 100644 index 0000000..7f1dc2c --- /dev/null +++ b/README-API.md @@ -0,0 +1,482 @@ +# API请求规范与最佳实践 + +## 目录 + +- [基本规范](#基本规范) + - [响应格式](#响应格式) + - [错误处理](#错误处理) + - [参数验证](#参数验证) + - [安全性考虑](#安全性考虑) +- [API路由实现](#api路由实现) + - [基础API路由](#基础api路由) + - [动态路由实现](#动态路由实现) + - [Next.js 15.3+路由处理器](#nextjs-153路由处理器) +- [最佳实践](#最佳实践) +- [常见问题](#常见问题) + +## 基本规范 + +### 响应格式 + +所有API响应应遵循统一的格式: + +```typescript +// 成功响应 +{ + success: true, + data?: any, // 通用数据字段 + [key: string]: any, // 或使用特定业务字段名(如users, products等) + message?: string // 可选,成功消息 +} + +// 错误响应 +{ + success: false, + error: string, // 错误消息 + code?: string // 可选,错误代码 +} +``` + +**示例:通用数据字段** +```typescript +return NextResponse.json({ + success: true, + data: rows +}); +``` + +**示例:特定业务字段** +```typescript +return NextResponse.json({ + success: true, + users: rows // 使用更具描述性的字段名 +}); +``` + +### 错误处理 + +始终使用try-catch处理API操作,并返回适当的错误信息: + +```typescript +try { + // API操作 +} catch (error) { + console.error('操作描述失败:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : '未知错误' + }, + { status: 500 } + ); +} +``` + +### 参数验证 + +在执行操作前验证输入参数: + +```typescript +// 获取参数 +const { id } = params; + +// 验证参数 +if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的ID参数' }, + { status: 400 } + ); +} + +// 继续操作 +``` + +### 安全性考虑 + +- 使用参数化查询防止SQL注入 +- 不要在响应中返回敏感信息(如密码、令牌) +- 对数据库操作使用最小权限原则 +- 验证用户权限和身份认证信息 + +```typescript +// 良好实践 - 使用参数化查询 +const [user] = await req.db.query( + 'SELECT id, username FROM users WHERE id = ?', + [userId] +); + +// 不良实践 - 容易遭受SQL注入 +const [user] = await req.db.query( + `SELECT * FROM users WHERE id = ${userId}` +); +``` + +## API路由实现 + +### 基础API路由 + +使用数据库连接中间件的API路由基本实现: + +```typescript +// API路由文件(app/api/your-route/route.ts) +import { NextResponse } from 'next/server'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; + +async function handler(req: RequestWithDB) { + try { + // 使用req.db访问数据库连接 + const [rows] = await req.db.query('SELECT * FROM your_table'); + + return NextResponse.json({ + success: true, + data: rows + }); + } catch (error) { + console.error('操作失败:', error); + return NextResponse.json( + { success: false, error: '数据库操作失败' }, + { status: 500 } + ); + } +} + +// 导出使用中间件包装的处理函数 +export const GET = connectSystemDB(handler); +``` + +### 必要的导入说明 + +在Next.js的API路由中,通常需要以下两个核心导入: + +```typescript +import { NextRequest, NextResponse } from 'next/server'; +``` + +- **NextRequest**: 扩展了原生Request对象,提供了额外的便利方法和属性,如: + - `nextUrl`: 获取解析后的URL对象,可访问`pathname`、`searchParams`等 + - `cookies`: 访问和操作请求的Cookie + - `headers`: 获取请求头 + - `json()`: 解析JSON请求体 + +- **NextResponse**: 用于创建和返回响应,提供了多种便利方法: + - `NextResponse.json()`: 创建JSON响应,自动设置Content-Type + - `NextResponse.redirect()`: 创建重定向响应 + - `NextResponse.rewrite()`: 创建重写响应 + - `NextResponse.next()`: 继续请求处理链 + +这两个对象是构建API路由的基础,几乎在所有API路由实现中都需要使用。 + +### 动态路由实现 + +在Next.js的动态路由中,必须**正确处理动态参数**。在13.4以后的版本中,动态路由参数需要使用`await`来获取: + +```typescript +// 正确的动态路由实现 +// API路由文件(app/api/your-route/[param]/route.ts) +import { NextRequest, NextResponse } from 'next/server'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; + +export const GET = async ( + req: NextRequest, + context: { params: { param: string } } +) => { + try { + // 正确获取动态参数 - 使用await + const params = await context.params; + const param = params.param; + + // 验证参数 + if (!param) { + return NextResponse.json( + { success: false, error: '无效的参数' }, + { status: 400 } + ); + } + + // 处理函数 + const handler = async (dbReq: RequestWithDB) => { + // 数据库操作 + const [rows] = await dbReq.db.query( + 'SELECT * FROM table WHERE id = ?', + [param] + ); + + return NextResponse.json({ + success: true, + data: rows + }); + }; + + // 执行处理函数 + return await connectSystemDB(handler)(req); + + } catch (error) { + console.error('操作失败:', error); + return NextResponse.json( + { success: false, error: '操作失败' }, + { status: 500 } + ); + } +}; +``` + +**注意事项**: +1. 使用箭头函数定义路由处理器:`export const GET = async (...) => {...}` +2. 正确获取动态参数:`const params = await context.params;` +3. 使用ES6解构后再使用参数:`const param = params.param;` +4. 不要直接解构:`const { param } = context.params;` - 这会导致错误 + +#### 路由处理函数参数说明 + +在Next.js App Router中,路由处理函数(如GET、POST、PUT、DELETE等)需要接收两个参数: + +1. **第一个参数**:`request: NextRequest` - 包含请求信息的对象,包括请求头、URL、搜索参数等 +2. **第二个参数**:`context: { params: { [key: string]: string } }` - 包含动态路由参数的上下文对象 + +对于动态路由如`/api/users/[id]`,路由处理函数会接收如下参数: + +```typescript +export async function GET( + request: NextRequest, + context: { params: { id: string } } +) { + const userId = context.params.id; + // 使用userId处理请求... +} +``` + +这种双参数模式对于获取动态路由参数非常有用,但在Next.js 15.3+版本中已被弃用,转而使用单参数模式,从URL中提取参数(见下文)。 + +### Next.js 15.3+路由处理器 + +在Next.js 15.3+版本中,路由处理器的类型定义发生了变化。为避免类型错误,应使用以下方式实现API路由: + +#### 单参数路由处理器模式 + +对于动态路由,应使用单参数函数并从URL中提取路径参数,避免使用带context的双参数函数: + +```typescript +/** + * 符合Next.js 15.3+的路由处理器签名 + */ +export async function GET(request: NextRequest) { + try { + // 从URL路径中提取动态参数 + const pathname = request.nextUrl.pathname; + const parts = pathname.split('/'); + const paramValue = parts[3]; // 例如: /api/path/[param] + + // 验证参数 + if (!paramValue) { + return NextResponse.json( + { success: false, error: '参数不能为空' }, + { status: 400 } + ); + } + + // 处理逻辑 + // ... + + } catch (error) { + // 错误处理 + } +} +``` + +#### 路由处理器函数声明方式 + +推荐使用函数声明而非箭头函数: + +```typescript +// 推荐 ✅ +export async function GET(request: NextRequest) { + // 处理逻辑 +} + +// 不推荐 ❌ +export const GET = async (request: NextRequest) => { + // 处理逻辑 +}; +``` + +#### 修复类型错误示例 + +如果构建时出现以下类型错误: + +``` +Type '{ __tag__: "GET"; __param_position__: "second"; __param_type__: { params: { paramName: string; }; }; }' +does not satisfy the constraint 'ParamCheck'. +``` + +应将路由函数从: + +```typescript +// 可能导致类型错误的实现 ❌ +export async function GET( + request: NextRequest, + { params }: { params: { paramName: string } } +) { + const value = params.paramName; + // ... +} +``` + +修改为: + +```typescript +// 修复后的实现 ✅ +export async function GET(request: NextRequest) { + // 从URL中提取参数 + const pathname = request.nextUrl.pathname; + const paramName = pathname.split('/')[3]; // 适当调整索引位置 + + // ...继续处理 +} +``` + +## 最佳实践 + +1. **使用标准格式**:始终使用统一的响应格式 +2. **错误处理**:所有API路由都应包含try-catch块 +3. **参数验证**:在处理前验证所有输入参数 +4. **日志记录**:记录关键操作和错误信息 +5. **权限验证**:确保用户有权限执行请求的操作 +6. **状态码使用**:使用正确的HTTP状态码 + - 200: 成功 + - 201: 创建成功 + - 400: 请求错误/参数无效 + - 401: 未授权 + - 403: 禁止访问 + - 404: 资源不存在 + - 500: 服务器内部错误 +7. **客户端组件中使用useSearchParams**:始终将使用useSearchParams的组件包裹在Suspense边界中,避免构建警告 + +## 常见问题 + +### 问:如何处理文件上传? + +**答**:在Next.js的App Router中,使用`formData`来处理文件上传: + +```typescript +export const POST = async (req: NextRequest) => { + try { + const formData = await req.formData(); + const file = formData.get('file') as File; + + // 处理文件上传 + // ... + + return NextResponse.json({ success: true }); + } catch (error) { + return NextResponse.json( + { success: false, error: '文件上传失败' }, + { status: 500 } + ); + } +}; +``` + +### 问:如何实现分页API? + +**答**:使用查询参数实现分页: + +```typescript +export const GET = async (req: NextRequest) => { + try { + const { searchParams } = new URL(req.url); + const page = parseInt(searchParams.get('page') || '1'); + const limit = parseInt(searchParams.get('limit') || '10'); + const offset = (page - 1) * limit; + + // 查询数据 + // ... + + return NextResponse.json({ + success: true, + data: rows, + pagination: { + page, + limit, + total: totalCount + } + }); + } catch (error) { + // 错误处理 + } +}; +``` + +### 问:如何处理动态路由中的错误? + +**答**:确保正确使用await获取参数,并使用try-catch处理可能的错误: + +```typescript +try { + const params = await context.params; + // 使用params... +} catch (error) { + console.error('参数获取失败:', error); + return NextResponse.json( + { success: false, error: '参数处理错误' }, + { status: 400 } + ); +} +``` + +### 问:如何修复Next.js 15.3+中的路由处理器类型错误? + +**答**:使用单参数路由处理器,从请求URL中提取路径参数: + +```typescript +export async function GET(request: NextRequest) { + try { + // 从URL路径中提取动态参数 + const pathname = request.nextUrl.pathname; + const paramValue = pathname.split('/')[3]; // 根据路径结构调整索引 + + // 继续处理... + } catch (error) { + // 错误处理 + } +} +``` + +### 问:如何处理useSearchParams导致的构建警告? + +**答**:将使用useSearchParams的组件包裹在Suspense边界中: + +```tsx +// 正确处理useSearchParams的方式 +'use client'; + +import { Suspense } from 'react'; +import { useSearchParams } from 'next/navigation'; + +// 创建一个独立的组件处理搜索参数 +function SearchParamsHandler({ onParamsChange }) { + const searchParams = useSearchParams(); + + // 使用searchParams的逻辑 + // ... + + return null; +} + +export default function Page() { + // 页面主要内容 + return ( +
+ {/* 包裹在Suspense中 */} + + + + + {/* 页面其他内容 */} +
+ ); +} +``` + +--- + +文档由阿瑞创建和维护。如有问题,请联系系统管理员。 \ No newline at end of file diff --git a/README-DB.md b/README-DB.md new file mode 100644 index 0000000..c62890f --- /dev/null +++ b/README-DB.md @@ -0,0 +1,236 @@ +# 数据库连接模块使用指南 + +## 目录 + +- [简介](#简介) +- [文件结构](#文件结构) +- [基本用法](#基本用法) + - [系统数据库连接](#系统数据库连接) + - [团队数据库连接](#团队数据库连接) +- [数据库查询最佳实践](#数据库查询最佳实践) +- [性能考虑](#性能考虑) +- [常见问题](#常见问题) + +## 简介 + +数据库连接模块提供了一套高效的数据库连接管理系统,基于连接池实现,用于管理系统级数据库和团队级数据库的连接。主要特点: + +- 使用高阶函数中间件模式,易于集成到API路由 +- 基于`mysql2/promise`的连接池,提供高性能的连接复用 +- 自动管理连接的获取和释放,防止连接泄漏 +- 分离的系统和团队数据库连接逻辑,支持多租户架构 +- 完善的日志系统,方便调试和监控 + +## 文件结构 + +``` +src/lib/db/ +├── config.ts # 数据库配置 +├── connect-system.ts # 系统数据库连接 +├── connect-team.ts # 团队数据库连接 +└── index.ts # 统一导出入口 +``` + +## 基本用法 + +### 系统数据库连接 + +系统数据库用于存储全局数据,如用户账户、工作空间和团队信息。 + +```typescript +// 导入系统数据库连接 +import { connectSystemDB, RequestWithDB } from '@/lib/db'; + +// 定义处理器函数 +async function handler(req: RequestWithDB) { + try { + // 使用req.db访问数据库连接 + const [rows] = await req.db.query('SELECT * FROM your_table'); + + // 处理结果 + return rows; + } catch (error) { + console.error('数据库操作失败:', error); + throw error; + } +} + +// 使用中间件包装处理函数 +export const processData = connectSystemDB(handler); +``` + +### 团队数据库连接 + +团队数据库用于存储特定团队的数据,每个团队可以使用独立的数据库。 + +```typescript +// 导入团队数据库连接 +import { connectTeamDB, RequestWithDB } from '@/lib/db'; + +// 假设已获取团队数据库配置 +const teamDbConfig = { + host: 'localhost', + name: 'team_db', + user: 'team_user', + password: 'team_password' +}; + +// 定义处理器函数 +async function handler(req: RequestWithDB) { + try { + // 使用req.db访问数据库连接 + const [rows] = await req.db.query('SELECT * FROM team_data'); + + // 处理结果 + return rows; + } catch (error) { + console.error('数据库操作失败:', error); + throw error; + } +} + +// 使用中间件包装处理函数并创建处理函数 +export const processTeamData = connectTeamDB( + teamDbConfig.host, + teamDbConfig.name, + teamDbConfig.user, + teamDbConfig.password +)(handler); +``` + +## 数据库查询最佳实践 + +1. **使用参数化查询** + +参数化查询是防止SQL注入的最佳方式: + +```typescript +// 良好实践 - 使用参数化查询 +const [user] = await req.db.query( + 'SELECT id, username FROM users WHERE id = ?', + [userId] +); + +// 不良实践 - 容易遭受SQL注入 +const [user] = await req.db.query( + `SELECT * FROM users WHERE id = ${userId}` +); +``` + +2. **只查询需要的列** + +为提高性能,只查询实际需要的列: + +```typescript +// 良好实践 - 只查询需要的列 +const [rows] = await req.db.query( + 'SELECT id, name, email FROM users' +); + +// 不良实践 - 查询所有列 +const [rows] = await req.db.query( + 'SELECT * FROM users' +); +``` + +3. **使用事务保证数据一致性** + +当需要进行多个相关操作时,使用事务确保数据一致性: + +```typescript +try { + await req.db.beginTransaction(); + + // 执行多个查询 + await req.db.query('INSERT INTO orders (user_id, total) VALUES (?, ?)', [userId, total]); + const [result] = await req.db.query('SELECT LAST_INSERT_ID() as id'); + const orderId = result[0].id; + + for (const item of items) { + await req.db.query( + 'INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)', + [orderId, item.productId, item.quantity] + ); + } + + await req.db.commit(); + return orderId; +} catch (error) { + await req.db.rollback(); + throw error; +} +``` + +## 性能考虑 + +连接池已经配置为最佳性能,但仍需注意: + +- 系统数据库连接池:最大10个连接 +- 团队数据库连接池:每个团队最大5个连接 +- 长时间运行的查询可能会耗尽连接池 +- 确保查询有合适的索引 +- 对大型结果集使用分页 + +### 连接池监控 + +连接池状态可以在日志中查看。成功的连接操作会显示为绿色圆点(🟢),失败的操作会显示为红色圆点(🔴)。 + +``` +🟢 MySQL 已连接: localhost:3306/saas_master +🟢 MySQL 系统数据库连接已获取 +🟢 MySQL 系统数据库连接已释放 +``` + +## 常见问题 + +### 问:如何执行事务? + +**答**:使用`req.db.beginTransaction()`、`req.db.commit()`和`req.db.rollback()`: + +```typescript +try { + await req.db.beginTransaction(); + + // 执行多个查询 + await req.db.query('INSERT INTO ...'); + await req.db.query('UPDATE ...'); + + await req.db.commit(); +} catch (error) { + await req.db.rollback(); + throw error; +} +``` + +### 问:如何处理大量并发请求? + +**答**:当前连接池配置应该足以处理中等负载。如果需要处理高并发,考虑: +1. 增加连接池大小(修改`connectionLimit`) +2. 添加缓存层减少数据库查询 +3. 实现请求节流或批处理 + +### 问:连接池中的连接是如何管理的? + +**答**:连接池自动管理连接的创建、分配和回收: +- 首次请求时,创建连接并添加到池中 +- 处理完请求后,连接被释放回池中(而不是关闭) +- 连接有最大空闲时间,超时会被关闭 +- 连接池有最大连接数限制,超过限制的请求会等待 + +```typescript +// 不需要手动关闭连接 +// 中间件会自动处理释放连接 +const connection = await systemPool.getConnection(); +try { + // 使用连接... + const [rows] = await connection.query('SELECT ...'); + return rows; +} finally { + // 中间件会自动调用这一行 + connection.release(); +} +``` + +--- + +文档由阿瑞创建和维护。如有问题,请联系系统管理员。 \ No newline at end of file diff --git a/README-DOCKER-IMPORT.md b/README-DOCKER-IMPORT.md new file mode 100644 index 0000000..ef0196c --- /dev/null +++ b/README-DOCKER-IMPORT.md @@ -0,0 +1,63 @@ +# Docker 镜像导入导出指南 + +## 镜像信息 + +- 名称: saas +- 版本: 25.5.12 +- 文件: saas-25.5.12.tar +- 大小: 67.4 MB +- 导出日期: 2025/5/12 + +## 导入镜像 + +使用以下命令将镜像导入 Docker: + +```powershell +# 导入镜像 +docker load -i D:\DockerImages\saas-25.5.12.tar +``` + +## 验证导入 + +导入后,使用以下命令确认镜像已成功加载: + +```powershell +# 查看镜像列表 +docker images | findstr saas +``` + +## 运行容器 + +使用以下命令从镜像运行容器: + +```powershell +# 基本运行命令 +docker run -d -p 3000:3000 --name saas-app saas:25.5.12 + +# 或者使用 docker-compose +docker-compose up -d +``` + +## 导出说明 + +此镜像是使用以下命令导出的: + +```powershell +# 导出镜像命令 +docker save -o D:\DockerImages\saas-25.5.12.tar saas:25.5.12 +``` + +## 镜像构建说明 + +此镜像通过以下命令构建: + +```powershell +# 构建命令 +docker build -t saas:25.5.12 -f Dockerfile . +``` + +## 注意事项 + +1. 导入/导出过程可能需要几分钟时间,取决于系统性能 +2. 确保有足够的磁盘空间用于导入操作 +3. 镜像基于 Node.js 22 Alpine,优化用于生产环境 \ No newline at end of file diff --git a/README-DOCKER.md b/README-DOCKER.md new file mode 100644 index 0000000..3395cde --- /dev/null +++ b/README-DOCKER.md @@ -0,0 +1,93 @@ +# Docker 部署说明 + +## 项目 Docker 化说明 + +本项目使用 Docker 和 Docker Compose 进行容器化部署,包含两个主要服务: +- Next.js 应用服务(基于 Node.js 22) +- MySQL 数据库服务 + +## 前提条件 + +- 安装 [Docker](https://www.docker.com/get-started) +- 安装 [Docker Compose](https://docs.docker.com/compose/install/) + +## 部署步骤 + +### 1. 构建并启动服务 + +```bash +# 构建并在后台启动所有服务 +docker-compose up -d + +# 只构建不启动 +docker-compose build + +# 查看服务日志 +docker-compose logs -f +``` + +### 2. 访问应用 + +应用将在以下地址可用: +- 前端应用: http://localhost:3000 + +### 3. 数据库访问 + +MySQL 数据库信息: +- 主机: localhost:3306 +- 用户名: root +- 密码: aiwoQwo520.. +- 数据库名: saas_db + +可以使用以下命令连接数据库: + +```bash +docker exec -it my-mysql mysql -uroot -p"aiwoQwo520.." +``` + +### 4. 停止服务 + +```bash +# 停止所有服务但不删除容器 +docker-compose stop + +# 停止并删除容器和网络 +docker-compose down + +# 停止并删除容器、网络和数据卷(谨慎使用,会删除数据库数据) +docker-compose down -v +``` + +## 文件说明 + +- `Dockerfile`: 用于构建 Next.js 应用的镜像(使用 Node.js 22 和 npm) +- `docker-compose.yml`: 定义和配置服务 +- `.dockerignore`: 指定不包含在 Docker 构建上下文中的文件 + +## 构建流程说明 + +Dockerfile 使用多阶段构建以优化最终镜像大小: + +1. 基础阶段:使用 Node.js 22 Alpine 镜像作为基础 +2. 依赖阶段:安装项目依赖 + - 使用 `npm install` 安装依赖 +3. 构建阶段:构建 Next.js 应用 + - 使用 `npm run build` 构建应用 +4. 运行阶段:配置生产环境并运行应用 + - 使用 standalone 输出模式优化部署 + +## 环境变量 + +可在 `docker-compose.yml` 文件中修改环境变量: + +- `DB_HOST`: 数据库主机名 +- `DB_USER`: 数据库用户名 +- `DB_PASSWORD`: 数据库密码 +- `DB_NAME`: 数据库名称 +- `DB_PORT`: 数据库端口 + +## 注意事项 + +1. 数据库数据存储在 Docker 卷 `mysql_data` 中,即使容器被删除也会保留 +2. 上传的文件保存在宿主机的 `./uploads` 目录,并映射到容器内的 `/app/uploads` +3. 初始化脚本位于 `./scripts` 目录,会在 MySQL 容器首次启动时执行 \ No newline at end of file diff --git a/README-TEAM-DB.md b/README-TEAM-DB.md new file mode 100644 index 0000000..7f89966 --- /dev/null +++ b/README-TEAM-DB.md @@ -0,0 +1,74 @@ +# 团队数据库连接工具使用指南 + +## 简介 + +团队数据库连接工具提供了一种便捷的方式来连接和管理团队级别的数据库。该工具会从请求中自动获取团队信息,并连接到对应的数据库。 + +## 基本用法 + +使用团队数据库连接工具非常简单,只需直接将处理函数传递给`connectTeamDB`: + +```typescript +import { connectTeamDB, RequestWithDB } from '@/lib/db'; + +export const GET = connectTeamDB(async (req: RequestWithDB) => { + // 数据库连接已经附加到req.db上 + const [rows] = await req.db.query('SELECT * FROM your_table'); + return NextResponse.json({ data: rows }); +}); +``` + +使用时,需要确保请求中包含团队标识,可以通过以下方式之一提供: +- HTTP头:`x-team-id` +- URL查询参数:`?teamId=xxx` + +## 连接池管理 + +团队数据库连接工具会自动为每个不同的数据库维护一个连接池,并在适当的时候复用它们。你不需要手动管理连接池的创建和销毁。 + +## 最佳实践 + +1. **正确处理错误**:始终在查询中使用try-catch来捕获和处理可能的数据库错误。 + +2. **不要手动释放连接**:中间件会自动管理连接的获取和释放,你不需要手动调用`connection.release()`。 + +## 示例 + +### 基本查询示例 +```typescript +export const GET = connectTeamDB(async (req: RequestWithDB) => { + try { + const [users] = await req.db.query('SELECT * FROM users WHERE status = ?', ['active']); + return NextResponse.json({ users }); + } catch (error) { + console.error('查询失败:', error); + return NextResponse.json({ error: '查询失败' }, { status: 500 }); + } +}); +``` + +### 事务示例 +```typescript +export const POST = connectTeamDB(async (req: RequestWithDB) => { + // 开始事务 + await req.db.beginTransaction(); + + try { + const { userId, amount } = await req.json(); + + // 执行多个查询 + await req.db.query('UPDATE accounts SET balance = balance - ? WHERE user_id = ?', [amount, userId]); + await req.db.query('INSERT INTO transactions (user_id, amount) VALUES (?, ?)', [userId, amount]); + + // 提交事务 + await req.db.commit(); + + return NextResponse.json({ success: true }); + } catch (error) { + // 回滚事务 + await req.db.rollback(); + console.error('事务失败:', error); + return NextResponse.json({ error: '交易失败' }, { status: 500 }); + } +}); +``` \ No newline at end of file diff --git a/README-UI.md b/README-UI.md new file mode 100644 index 0000000..af311ca --- /dev/null +++ b/README-UI.md @@ -0,0 +1,54 @@ +# 现代毛玻璃UI效果 + +一个基于Next.js和React开发的现代化SaaS管理平台,使用最新的毛玻璃UI设计风格,提供明亮通透的用户界面体验。 + +## 项目特点 + +- **现代毛玻璃UI效果**:采用最新流行的毛玻璃(Glassmorphism)设计风格,提供通透、现代的用户界面 +- **主题切换功能**:支持明亮/深色两种主题模式,满足不同用户偏好和使用场景 +- **主题持久化存储**:使用 zustand 管理状态,localStorage 保存主题偏好,刷新页面后仍保持设置 +- **响应式设计**:完全适配移动端和桌面端的各种屏幕尺寸 +- **动态视觉元素**:使用多种动画效果增强用户体验,包括背景气泡、渐变动画等 +- **模块化组件**:基于组件化思想构建,便于维护和扩展 + +## 技术栈 + +- **前端框架**:Next.js 15.x + React 19 +- **状态管理**:Zustand +- **样式解决方案**:Tailwind CSS 4.x(准备弃用)改为antd5.X +- **字体**:Geist字体家族(提供现代简约风格) +- **动画**:CSS原生动画 +- **开发语言**:TypeScript + +## 界面预览 + +项目提供了多种精美的UI组件和效果: + +- **毛玻璃导航栏**:半透明模糊效果,随着主题变化而调整 +- **功能展示卡片**:带有微妙悬浮效果的信息卡片 +- **数据统计面板**:展示关键数据指标的可视化组件 +- **主题切换开关**:允许用户在明亮/深色主题间切换,并记住用户选择 + +## 设计说明 + +### 色彩系统 + +项目使用了一套鲜明而和谐的色彩系统: + +- **主色调**: + - 蓝色 (#2d7ff9) + - 紫色 (#8e6bff) + - 青色 (#06d7b2) + - 粉色 (#ff66c2) + - 橙色 (#ff9640) + +- **明亮主题背景**:明亮的淡蓝色,配合多彩渐变气泡 +- **暗色主题背景**:深蓝色调,带有鲜艳的强调色点缀 + +### 毛玻璃效果参数 + +精心调整的毛玻璃效果参数,确保最佳视觉体验: + +- **背景模糊**:`backdrop-blur-xl`确保适当的模糊程度 +- **透明度**:卡片背景透明度在0.25-0.6之间 +- **边框**:微妙的半透明边框提升层次感 \ No newline at end of file diff --git a/README-hooks.md b/README-hooks.md new file mode 100644 index 0000000..16b930b --- /dev/null +++ b/README-hooks.md @@ -0,0 +1,312 @@ +# SaaS项目自定义钩子使用文档 + +## 简介 + +本文档介绍了SaaS项目中的自定义React钩子(Custom Hooks),这些钩子封装了常用的状态管理逻辑,使组件代码更加简洁、可读和可维护。 + +## 目录结构 + +``` +src/ +├── hooks/ +│ ├── index.ts # 统一导出所有钩子 +│ ├── useUser.ts # 用户相关钩子 +│ └── useSettings.ts # 设置相关钩子 +└── store/ + ├── userStore.ts # 用户状态管理 + └── settingStore.ts # 设置状态管理 +``` + +## 安装和导入 + +钩子已集成到项目中,无需额外安装。使用时只需从hooks目录导入: + +```tsx +// 导入单个钩子 +import { useTheme } from '@/hooks'; + +// 或导入多个钩子 +import { useTheme, useAuth, useUserProfile } from '@/hooks'; +``` + +## 用户相关钩子 + +### useUserInfo + +获取当前登录用户的基本信息。 + +```tsx +import { useUserInfo } from '@/hooks'; + +function UserInfoComponent() { + const userInfo = useUserInfo(); + + return ( +
+

用户名: {userInfo?.username}

+

邮箱: {userInfo?.email}

+
+ ); +} +``` + +### useUserProfile + +获取格式化后的用户信息,适用于UI显示。 + +```tsx +import { useUserProfile } from '@/hooks'; + +function ProfileComponent() { + const profile = useUserProfile(); + + return ( +
+

用户名: {profile.username}

+

邮箱: {profile.email}

+

电话: {profile.phone}

+

角色: {profile.roleName}

+
+ ); +} +``` + +### useIsAdmin + +检查当前用户是否为管理员。 + +```tsx +import { useIsAdmin } from '@/hooks'; + +function AdminSection() { + const isAdmin = useIsAdmin(); + + if (!isAdmin) { + return

您没有访问此页面的权限

; + } + + return
管理员面板内容
; +} +``` + +### useHasRole + +检查当前用户是否拥有特定角色。 + +```tsx +import { useHasRole } from '@/hooks'; + +function RoleBasedComponent() { + const hasRole = useHasRole(); + + return ( +
+ {hasRole('editor') && } + {hasRole('admin') && } +
+ ); +} +``` + +### useAuth + +管理用户登录状态和认证操作。 + +```tsx +import { useAuth } from '@/hooks'; + +function LoginComponent() { + const { isAuthenticated, login, logout } = useAuth(); + + const handleLogin = async () => { + const response = await fetch('/api/login', { /* 登录请求 */ }); + const data = await response.json(); + + if (data.success) { + login(data.token, data.user); + } + }; + + return ( +
+ {isAuthenticated ? ( + + ) : ( + + )} +
+ ); +} +``` + +## 设置相关钩子 + +### useTheme + +管理主题模式(亮色/暗色)和相关操作。 + +```tsx +import { useTheme } from '@/hooks'; + +function ThemeToggleComponent() { + const { + themeMode, + isDarkMode, + toggleThemeMode, + enableLightMode, + enableDarkMode + } = useTheme(); + + return ( +
+

当前主题: {themeMode}

+ + + +
+ ); +} +``` + +### useApplyTheme + +自动将当前主题应用到DOM。适用于自定义布局组件或页面。 + +```tsx +import { useApplyTheme } from '@/hooks'; + +function Layout({ children }) { + // 将当前主题应用到DOM + useApplyTheme(); + + return ( +
+ {children} +
+ ); +} +``` + +### useSettings + +获取所有系统设置。 + +```tsx +import { useSettings } from '@/hooks'; + +function SettingsComponent() { + const settings = useSettings(); + + return ( +
+

主题模式: {settings.themeMode}

+ {/* 其他设置项 */} +
+ ); +} +``` + +## 实际使用示例 + +下面是一个综合使用多个钩子的组件示例: + +```tsx +import { + useTheme, + useApplyTheme, + useUserProfile, + useIsAdmin, + useAuth +} from '@/hooks'; + +function DashboardPage() { + // 应用主题到DOM + useApplyTheme(); + + // 获取主题状态和操作 + const { isDarkMode, toggleThemeMode } = useTheme(); + + // 获取用户信息和权限 + const userProfile = useUserProfile(); + const isAdmin = useIsAdmin(); + const { isAuthenticated, logout } = useAuth(); + + if (!isAuthenticated) { + return ; + } + + return ( +
+
+
+

欢迎, {userProfile.username}

+ +
+ +
+ +
+ {/* 普通用户内容 */} + + + {/* 仅管理员可见内容 */} + {isAdmin && } +
+
+ ); +} +``` + +## 测试页面 + +项目中包含一个专门的测试页面,用于演示各种钩子的使用方法: + +- 路径: `/test/usehook` +- 源代码: `src/app/test/usehook/page.tsx` + +在这个页面上可以测试以下功能: +- 主题切换 +- 用户登录/登出 +- 角色权限检查 +- 用户信息显示 + +## 最佳实践 + +1. **避免重复导入基础store**:使用自定义钩子而不是直接导入store,减少对底层实现的依赖。 + +2. **优先使用语义化钩子**:例如使用`useIsAdmin()`而不是`useUserInfo().roleType === 'admin'`。 + +3. **组合使用多个钩子**:根据需要组合使用不同的钩子,构建复杂功能。 + +4. **使用useMemo和useCallback**:在性能敏感的场景中,确保不会导致不必要的重新渲染。 + +## 扩展自定义钩子 + +如需扩展现有钩子或添加新钩子,请遵循以下步骤: + +1. 在适当的文件中添加新钩子(`useUser.ts`或`useSettings.ts`) +2. 在`hooks/index.ts`中导出新钩子 +3. 确保包含适当的注释和类型定义 +4. 遵循三级注释规范(文件头注释、模块级注释、关键代码行注释) + +## 常见问题 + +**Q: 为什么推荐使用自定义钩子而不是直接使用store?** + +A: 自定义钩子提供了更高级别的抽象,隐藏了状态管理的复杂性,使组件代码更加简洁和易于维护。同时,它们可以更轻松地进行单元测试。 + +**Q: 如何处理钩子之间的依赖关系?** + +A: 当一个钩子需要依赖另一个钩子时,应在钩子内部导入并使用依赖的钩子,而不是在组件中手动管理这些依赖关系。 + +**Q: 如何确保钩子在正确的上下文中使用?** + +A: React钩子只能在React函数组件或其他自定义钩子中使用。确保不在类组件、普通函数或条件语句中直接调用钩子。 \ No newline at end of file diff --git a/README.md b/README.md index e215bc4..873afdd 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,18 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# SaaS 多租户管理系统 -## Getting Started +## 项目概述 -First, run the development server: +本项目是一个基于Next.js的多租户SaaS管理系统,支持工作空间、用户和团队的管理,采用MySQL作为数据存储。 -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +## 系统架构 + +### 创建数据库 ``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. + sudo docker run --name my-mysql \ + -p 3306:3306 \ + -v /vol1/1000/SSD/mysql:/var/lib/mysql \ + -e MYSQL_ROOT_PASSWORD=aiwoQwo520.. \ + -e MYSQL_DATABASE=myapp \ + -e TZ=Asia/Shanghai \ + -d mysql:latest \ + --default-time-zone='+08:00' \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..35068e9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,43 @@ +version: '3.8' + +services: + # Next.js 应用服务 + nextjs-app: + build: + context: . + dockerfile: Dockerfile + container_name: nextjs-saas-app + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - DB_HOST=mysql + - DB_USER=root + - DB_PASSWORD=aiwoQwo520.. + - DB_NAME=saas_db + - DB_PORT=3306 + depends_on: + - mysql + restart: unless-stopped + volumes: + - ./uploads:/app/uploads + + # MySQL 数据库服务 + mysql: + image: mysql:8.0 + container_name: my-mysql + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=aiwoQwo520.. + - MYSQL_DATABASE=saas_db + - MYSQL_USER=saas_user + - MYSQL_PASSWORD=saas_password + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./scripts:/docker-entrypoint-initdb.d + +# 定义持久化卷 +volumes: + mysql_data: \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index e9ffa30..542f394 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,21 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ + reactStrictMode: true, + // 配置开发服务器选项,降低热重载频率 + onDemandEntries: { + // 期间页面在内存中保持的时间(毫秒) + maxInactiveAge: 25 * 1000, + // 同时保持的页面数 + pagesBufferLength: 2, + }, + // 添加 standalone 输出模式,优化 Docker 部署 + output: 'standalone', + // 禁用 ESLint 检查 + eslint: { + // 在构建时不进行 ESLint 检查 + ignoreDuringBuilds: true, + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 87bccac..0000000 --- a/package-lock.json +++ /dev/null @@ -1,6712 +0,0 @@ -{ - "name": "saas", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "saas", - "version": "0.1.0", - "dependencies": { - "next": "15.3.2", - "react": "^19.0.0", - "react-dom": "^19.0.0" - }, - "devDependencies": { - "@eslint/eslintrc": "^3", - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "^9", - "eslint-config-next": "15.3.2", - "tailwindcss": "^4", - "typescript": "^5" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.6", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", - "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.13.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz", - "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.1.0" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.1.tgz", - "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.1.0" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.1.0.tgz", - "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.1.0.tgz", - "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.1.0.tgz", - "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.1.0.tgz", - "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.1.0.tgz", - "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", - "cpu": [ - "ppc64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.1.0.tgz", - "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", - "cpu": [ - "s390x" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.1.0.tgz", - "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.1.0.tgz", - "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.1.0.tgz", - "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.1.tgz", - "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==", - "cpu": [ - "arm" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.1.0" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.1.tgz", - "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.1.0" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.1.tgz", - "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==", - "cpu": [ - "s390x" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.1.0" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.1.tgz", - "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.1.0" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.1.tgz", - "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==", - "cpu": [ - "arm64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.1.0" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.1.tgz", - "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.1.0" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.1.tgz", - "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==", - "cpu": [ - "wasm32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.4.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.1.tgz", - "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==", - "cpu": [ - "ia32" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.1.tgz", - "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==", - "cpu": [ - "x64" - ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz", - "integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "cors": "^2.8.5", - "cross-spawn": "^7.0.3", - "eventsource": "^3.0.2", - "express": "^5.0.1", - "express-rate-limit": "^7.5.0", - "pkce-challenge": "^5.0.0", - "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.9.tgz", - "integrity": "sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.0", - "@emnapi/runtime": "^1.4.0", - "@tybys/wasm-util": "^0.9.0" - } - }, - "node_modules/@next/env": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz", - "integrity": "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==", - "license": "MIT" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.2.tgz", - "integrity": "sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "3.3.1" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz", - "integrity": "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz", - "integrity": "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz", - "integrity": "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz", - "integrity": "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz", - "integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz", - "integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz", - "integrity": "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz", - "integrity": "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz", - "integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tailwindcss/node": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.6.tgz", - "integrity": "sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.3.0", - "enhanced-resolve": "^5.18.1", - "jiti": "^2.4.2", - "lightningcss": "1.29.2", - "magic-string": "^0.30.17", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.6" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.6.tgz", - "integrity": "sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.6", - "@tailwindcss/oxide-darwin-arm64": "4.1.6", - "@tailwindcss/oxide-darwin-x64": "4.1.6", - "@tailwindcss/oxide-freebsd-x64": "4.1.6", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.6", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.6", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.6", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.6", - "@tailwindcss/oxide-linux-x64-musl": "4.1.6", - "@tailwindcss/oxide-wasm32-wasi": "4.1.6", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.6", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.6" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz", - "integrity": "sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz", - "integrity": "sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz", - "integrity": "sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz", - "integrity": "sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz", - "integrity": "sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz", - "integrity": "sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz", - "integrity": "sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz", - "integrity": "sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz", - "integrity": "sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz", - "integrity": "sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@emnapi/wasi-threads": "^1.0.2", - "@napi-rs/wasm-runtime": "^0.2.9", - "@tybys/wasm-util": "^0.9.0", - "tslib": "^2.8.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz", - "integrity": "sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz", - "integrity": "sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/postcss": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.6.tgz", - "integrity": "sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.6", - "@tailwindcss/oxide": "4.1.6", - "postcss": "^8.4.41", - "tailwindcss": "4.1.6" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.17.46", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.46.tgz", - "integrity": "sha512-0PQHLhZPWOxGW4auogW0eOQAuNIlCYvibIpG67ja0TOJ6/sehu+1en7sfceUn+QQtx4Rk3GxbLNwPh0Cav7TWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/react": { - "version": "19.1.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz", - "integrity": "sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.1.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.3.tgz", - "integrity": "sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz", - "integrity": "sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.32.0", - "@typescript-eslint/type-utils": "8.32.0", - "@typescript-eslint/utils": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.0.tgz", - "integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.32.0", - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/typescript-estree": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.0.tgz", - "integrity": "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.0.tgz", - "integrity": "sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "8.32.0", - "@typescript-eslint/utils": "8.32.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.0.tgz", - "integrity": "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.0.tgz", - "integrity": "sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/visitor-keys": "8.32.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.0.tgz", - "integrity": "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.32.0", - "@typescript-eslint/types": "8.32.0", - "@typescript-eslint/typescript-estree": "8.32.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.0.tgz", - "integrity": "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.32.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.2.tgz", - "integrity": "sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.2.tgz", - "integrity": "sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.2.tgz", - "integrity": "sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.2.tgz", - "integrity": "sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.2.tgz", - "integrity": "sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.2.tgz", - "integrity": "sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.2.tgz", - "integrity": "sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.2.tgz", - "integrity": "sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.2.tgz", - "integrity": "sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.2.tgz", - "integrity": "sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.2.tgz", - "integrity": "sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.2.tgz", - "integrity": "sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.2.tgz", - "integrity": "sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.2.tgz", - "integrity": "sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.9" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.2.tgz", - "integrity": "sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.2.tgz", - "integrity": "sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.2.tgz", - "integrity": "sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", - "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001717", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", - "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "optional": true, - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", - "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, - "license": "MIT" - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "license": "MIT" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/es-abstract": { - "version": "1.23.9", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", - "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.0", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-regex": "^1.2.1", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.0", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.18" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.26.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", - "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.13.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.26.0", - "@eslint/plugin-kit": "^0.2.8", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@modelcontextprotocol/sdk": "^1.8.0", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "zod": "^3.24.2" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-config-next": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.3.2.tgz", - "integrity": "sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@next/eslint-plugin-next": "15.3.2", - "@rushstack/eslint-patch": "^1.10.3", - "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.31.0", - "eslint-plugin-jsx-a11y": "^6.10.0", - "eslint-plugin-react": "^7.37.0", - "eslint-plugin-react-hooks": "^5.0.0" - }, - "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", - "typescript": ">=3.3.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", - "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.4.0", - "get-tsconfig": "^4.10.0", - "is-bun-module": "^2.0.0", - "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.13", - "unrs-resolver": "^1.6.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint-import-resolver-typescript" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*", - "eslint-plugin-import-x": "*" - }, - "peerDependenciesMeta": { - "eslint-plugin-import": { - "optional": true - }, - "eslint-plugin-import-x": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", - "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "aria-query": "^5.3.2", - "array-includes": "^3.1.8", - "array.prototype.flatmap": "^1.3.2", - "ast-types-flow": "^0.0.8", - "axe-core": "^4.10.0", - "axobject-query": "^4.1.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "hasown": "^2.0.2", - "jsx-ast-utils": "^3.3.5", - "language-tags": "^1.0.9", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.1" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.14.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventsource": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/eventsource-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", - "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.0", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", - "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": "^4.11 || 5 || ^5.0.0-beta.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", - "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", - "optional": true - }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bun-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", - "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.7.1" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/jiti": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", - "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/language-subtag-registry": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", - "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/language-tags": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", - "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", - "dev": true, - "license": "MIT", - "dependencies": { - "language-subtag-registry": "^0.3.20" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", - "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-darwin-arm64": "1.29.2", - "lightningcss-darwin-x64": "1.29.2", - "lightningcss-freebsd-x64": "1.29.2", - "lightningcss-linux-arm-gnueabihf": "1.29.2", - "lightningcss-linux-arm64-gnu": "1.29.2", - "lightningcss-linux-arm64-musl": "1.29.2", - "lightningcss-linux-x64-gnu": "1.29.2", - "lightningcss-linux-x64-musl": "1.29.2", - "lightningcss-win32-arm64-msvc": "1.29.2", - "lightningcss-win32-x64-msvc": "1.29.2" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", - "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", - "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", - "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", - "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", - "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", - "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", - "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", - "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", - "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", - "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/napi-postinstall": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.3.tgz", - "integrity": "sha512-Mi7JISo/4Ij2tDZ2xBE2WH+/KvVlkhA6juEjpEeRAVPNCpN3nxJo/5FhDNKgBcdmcmhaH6JjgST4xY/23ZYK0w==", - "dev": true, - "license": "MIT", - "bin": { - "napi-postinstall": "lib/cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/napi-postinstall" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/next": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz", - "integrity": "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==", - "license": "MIT", - "dependencies": { - "@next/env": "15.3.2", - "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.15", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "15.3.2", - "@next/swc-darwin-x64": "15.3.2", - "@next/swc-linux-arm64-gnu": "15.3.2", - "@next/swc-linux-arm64-musl": "15.3.2", - "@next/swc-linux-x64-gnu": "15.3.2", - "@next/swc-linux-x64-musl": "15.3.2", - "@next/swc-win32-arm64-msvc": "15.3.2", - "@next/swc-win32-x64-msvc": "15.3.2", - "sharp": "^0.34.1" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next/node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "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==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pkce-challenge": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", - "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.20.0" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", - "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", - "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.26.0" - }, - "peerDependencies": { - "react": "^19.1.0" - } - }, - "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==", - "dev": true, - "license": "MIT" - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "devOptional": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "license": "ISC" - }, - "node_modules/sharp": { - "version": "0.34.1", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz", - "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==", - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.3", - "semver": "^7.7.1" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.1", - "@img/sharp-darwin-x64": "0.34.1", - "@img/sharp-libvips-darwin-arm64": "1.1.0", - "@img/sharp-libvips-darwin-x64": "1.1.0", - "@img/sharp-libvips-linux-arm": "1.1.0", - "@img/sharp-libvips-linux-arm64": "1.1.0", - "@img/sharp-libvips-linux-ppc64": "1.1.0", - "@img/sharp-libvips-linux-s390x": "1.1.0", - "@img/sharp-libvips-linux-x64": "1.1.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.1.0", - "@img/sharp-libvips-linuxmusl-x64": "1.1.0", - "@img/sharp-linux-arm": "0.34.1", - "@img/sharp-linux-arm64": "0.34.1", - "@img/sharp-linux-s390x": "0.34.1", - "@img/sharp-linux-x64": "0.34.1", - "@img/sharp-linuxmusl-arm64": "0.34.1", - "@img/sharp-linuxmusl-x64": "0.34.1", - "@img/sharp-wasm32": "0.34.1", - "@img/sharp-win32-ia32": "0.34.1", - "@img/sharp-win32-x64": "0.34.1" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "optional": true, - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stable-hash": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", - "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string.prototype.includes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", - "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwindcss": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.6.tgz", - "integrity": "sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unrs-resolver": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.2.tgz", - "integrity": "sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "napi-postinstall": "^0.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/JounQin" - }, - "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.2", - "@unrs/resolver-binding-darwin-x64": "1.7.2", - "@unrs/resolver-binding-freebsd-x64": "1.7.2", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.2", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.2", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.2", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.2", - "@unrs/resolver-binding-linux-x64-musl": "1.7.2", - "@unrs/resolver-binding-wasm32-wasi": "1.7.2", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.2", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.2", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.2" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.24.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", - "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-to-json-schema": { - "version": "3.24.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", - "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", - "dev": true, - "license": "ISC", - "peerDependencies": { - "zod": "^3.24.1" - } - } - } -} diff --git a/package.json b/package.json index 95a3e26..470fdb2 100644 --- a/package.json +++ b/package.json @@ -6,22 +6,43 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "init-db": "ts-node scripts/init-db.ts" }, "dependencies": { + "@ant-design/icons": "^6.0.0", + "@ant-design/v5-patch-for-react-19": "^1.0.3", + "@iconify/react": "^6.0.0", + "@types/node-cron": "^3.0.11", + "antd": "^5.25.1", + "bcryptjs": "^3.0.2", + "dayjs": "^1.11.13", + "echarts": "^5.6.0", + "jsonwebtoken": "^9.0.2", + "mysql2": "^3.14.1", + "nanoid": "^5.1.5", + "next": "15.3.2", + "next-auth": "^4.24.11", + "node-cron": "^4.0.4", + "qrcode.react": "^4.2.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "next": "15.3.2" + "react-icons": "^5.5.0", + "styled-components": "^6.0.9", + "uuid": "^11.1.0", + "zustand": "^5.0.3" }, "devDependencies": { - "typescript": "^5", + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "@types/bcryptjs": "^3.0.0", + "@types/jsonwebtoken": "^9.0.9", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4", "eslint": "^9", "eslint-config-next": "15.3.2", - "@eslint/eslintrc": "^3" + "tailwindcss": "^4", + "typescript": "^5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..e683b8a --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5660 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ant-design/icons': + specifier: ^6.0.0 + version: 6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/v5-patch-for-react-19': + specifier: ^1.0.3 + version: 1.0.3(antd@5.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@iconify/react': + specifier: ^6.0.0 + version: 6.0.0(react@19.1.0) + '@types/node-cron': + specifier: ^3.0.11 + version: 3.0.11 + antd: + specifier: ^5.25.1 + version: 5.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + bcryptjs: + specifier: ^3.0.2 + version: 3.0.2 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 + echarts: + specifier: ^5.6.0 + version: 5.6.0 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 + mysql2: + specifier: ^3.14.1 + version: 3.14.1 + nanoid: + specifier: ^5.1.5 + version: 5.1.5 + next: + specifier: 15.3.2 + version: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-auth: + specifier: ^4.24.11 + version: 4.24.11(next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + node-cron: + specifier: ^4.0.4 + version: 4.0.4 + qrcode.react: + specifier: ^4.2.0 + version: 4.2.0(react@19.1.0) + react: + specifier: ^19.0.0 + version: 19.1.0 + react-dom: + specifier: ^19.0.0 + version: 19.1.0(react@19.1.0) + react-icons: + specifier: ^5.5.0 + version: 5.5.0(react@19.1.0) + styled-components: + specifier: ^6.0.9 + version: 6.1.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + uuid: + specifier: ^11.1.0 + version: 11.1.0 + zustand: + specifier: ^5.0.3 + version: 5.0.4(@types/react@19.1.3)(react@19.1.0) + devDependencies: + '@eslint/eslintrc': + specifier: ^3 + version: 3.3.1 + '@tailwindcss/postcss': + specifier: ^4 + version: 4.1.6 + '@types/bcryptjs': + specifier: ^3.0.0 + version: 3.0.0 + '@types/jsonwebtoken': + specifier: ^9.0.9 + version: 9.0.9 + '@types/node': + specifier: ^20 + version: 20.17.46 + '@types/react': + specifier: ^19 + version: 19.1.3 + '@types/react-dom': + specifier: ^19 + version: 19.1.3(@types/react@19.1.3) + eslint: + specifier: ^9 + version: 9.26.0(jiti@2.4.2) + eslint-config-next: + specifier: 15.3.2 + version: 15.3.2(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + tailwindcss: + specifier: ^4 + version: 4.1.6 + typescript: + specifier: ^5 + version: 5.8.3 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@ant-design/colors@7.2.0': + resolution: {integrity: sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==} + + '@ant-design/colors@8.0.0': + resolution: {integrity: sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==} + + '@ant-design/cssinjs-utils@1.1.3': + resolution: {integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@ant-design/cssinjs@1.23.0': + resolution: {integrity: sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@2.0.6': + resolution: {integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==} + engines: {node: '>=8.x'} + + '@ant-design/fast-color@3.0.0': + resolution: {integrity: sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==} + + '@ant-design/icons@5.6.1': + resolution: {integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/icons@6.0.0': + resolution: {integrity: sha512-o0aCCAlHc1o4CQcapAwWzHeaW2x9F49g7P3IDtvtNXgHowtRWYb7kiubt8sQPFvfVIVU/jLw2hzeSlNt0FU+Uw==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@1.1.2': + resolution: {integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==} + peerDependencies: + react: '>=16.9.0' + + '@ant-design/v5-patch-for-react-19@1.0.3': + resolution: {integrity: sha512-iWfZuSUl5kuhqLUw7jJXUQFMMkM7XpW7apmKzQBQHU0cpifYW4A79xIBt9YVO5IBajKpPG5UKP87Ft7Yrw1p/w==} + engines: {node: '>=12.x'} + peerDependencies: + antd: '>=5.22.6' + react: '>=19.0.0' + react-dom: '>=19.0.0' + + '@babel/runtime@7.27.1': + resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==} + engines: {node: '>=6.9.0'} + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} + + '@emotion/is-prop-valid@1.2.2': + resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} + + '@emotion/memoize@0.8.1': + resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} + + '@emotion/unitless@0.8.1': + resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.2': + resolution: {integrity: sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.13.0': + resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.26.0': + resolution: {integrity: sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.8': + resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@iconify/react@6.0.0': + resolution: {integrity: sha512-eqNscABVZS8eCpZLU/L5F5UokMS9mnCf56iS1nM9YYHdH8ZxqZL9zyjSwW60IOQFsXZkilbBiv+1paMXBhSQnw==} + peerDependencies: + react: '>=16' + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@img/sharp-darwin-arm64@0.34.1': + resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.1': + resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.1.0': + resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.1.0': + resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.1.0': + resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.1.0': + resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.1.0': + resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.1.0': + resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.1.0': + resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.1': + resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.1': + resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.34.1': + resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.1': + resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.1': + resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.1': + resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.1': + resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.34.1': + resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.1': + resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@modelcontextprotocol/sdk@1.11.1': + resolution: {integrity: sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@0.2.9': + resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==} + + '@next/env@15.3.2': + resolution: {integrity: sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==} + + '@next/eslint-plugin-next@15.3.2': + resolution: {integrity: sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==} + + '@next/swc-darwin-arm64@15.3.2': + resolution: {integrity: sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.3.2': + resolution: {integrity: sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.3.2': + resolution: {integrity: sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@15.3.2': + resolution: {integrity: sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@15.3.2': + resolution: {integrity: sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@15.3.2': + resolution: {integrity: sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@15.3.2': + resolution: {integrity: sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.3.2': + resolution: {integrity: sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + + '@rc-component/async-validator@5.0.4': + resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==} + engines: {node: '>=14.x'} + + '@rc-component/color-picker@2.0.1': + resolution: {integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@1.4.0': + resolution: {integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==} + engines: {node: '>=8.x'} + + '@rc-component/mutate-observer@1.1.0': + resolution: {integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/portal@1.1.2': + resolution: {integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.0.0': + resolution: {integrity: sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tour@1.15.1': + resolution: {integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/trigger@2.2.6': + resolution: {integrity: sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/util@1.2.1': + resolution: {integrity: sha512-AUVu6jO+lWjQnUOOECwu8iR0EdElQgWW5NBv5vP/Uf9dWbAX3udhMutRlkVXjuac2E40ghkFy+ve00mc/3Fymg==} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@rushstack/eslint-patch@1.11.0': + resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.1.6': + resolution: {integrity: sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==} + + '@tailwindcss/oxide-android-arm64@4.1.6': + resolution: {integrity: sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.6': + resolution: {integrity: sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.6': + resolution: {integrity: sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.6': + resolution: {integrity: sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.6': + resolution: {integrity: sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.6': + resolution: {integrity: sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.6': + resolution: {integrity: sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.6': + resolution: {integrity: sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.6': + resolution: {integrity: sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.6': + resolution: {integrity: sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.6': + resolution: {integrity: sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.6': + resolution: {integrity: sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.6': + resolution: {integrity: sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.6': + resolution: {integrity: sha512-ELq+gDMBuRXPJlpE3PEen+1MhnHAQQrh2zF0dI1NXOlEWfr2qWf2CQdr5jl9yANv8RErQaQ2l6nIFO9OSCVq/g==} + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/bcryptjs@3.0.0': + resolution: {integrity: sha512-WRZOuCuaz8UcZZE4R5HXTco2goQSI2XxjGY3hbM/xDvwmqFWd4ivooImsMx65OKM6CtNKbnZ5YL+YwAwK7c1dg==} + deprecated: This is a stub types definition. bcryptjs provides its own type definitions, so you do not need this installed. + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/jsonwebtoken@9.0.9': + resolution: {integrity: sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node-cron@3.0.11': + resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} + + '@types/node@20.17.46': + resolution: {integrity: sha512-0PQHLhZPWOxGW4auogW0eOQAuNIlCYvibIpG67ja0TOJ6/sehu+1en7sfceUn+QQtx4Rk3GxbLNwPh0Cav7TWw==} + + '@types/react-dom@19.1.3': + resolution: {integrity: sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.3': + resolution: {integrity: sha512-dLWQ+Z0CkIvK1J8+wrDPwGxEYFA4RAyHoZPxHVGspYmFVnwGSNT24cGIhFJrtfRnWVuW8X7NO52gCXmhkVUWGQ==} + + '@types/stylis@4.2.5': + resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==} + + '@typescript-eslint/eslint-plugin@8.32.0': + resolution: {integrity: sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/parser@8.32.0': + resolution: {integrity: sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/scope-manager@8.32.0': + resolution: {integrity: sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.32.0': + resolution: {integrity: sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/types@8.32.0': + resolution: {integrity: sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.32.0': + resolution: {integrity: sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.32.0': + resolution: {integrity: sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.32.0': + resolution: {integrity: sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-darwin-arm64@1.7.2': + resolution: {integrity: sha512-vxtBno4xvowwNmO/ASL0Y45TpHqmNkAaDtz4Jqb+clmcVSSl8XCG/PNFFkGsXXXS6AMjP+ja/TtNCFFa1QwLRg==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.7.2': + resolution: {integrity: sha512-qhVa8ozu92C23Hsmv0BF4+5Dyyd5STT1FolV4whNgbY6mj3kA0qsrGPe35zNR3wAN7eFict3s4Rc2dDTPBTuFQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.7.2': + resolution: {integrity: sha512-zKKdm2uMXqLFX6Ac7K5ElnnG5VIXbDlFWzg4WJ8CGUedJryM5A3cTgHuGMw1+P5ziV8CRhnSEgOnurTI4vpHpg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.2': + resolution: {integrity: sha512-8N1z1TbPnHH+iDS/42GJ0bMPLiGK+cUqOhNbMKtWJ4oFGzqSJk/zoXFzcQkgtI63qMcUI7wW1tq2usZQSb2jxw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.7.2': + resolution: {integrity: sha512-tjYzI9LcAXR9MYd9rO45m1s0B/6bJNuZ6jeOxo1pq1K6OBuRMMmfyvJYval3s9FPPGmrldYA3mi4gWDlWuTFGA==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.7.2': + resolution: {integrity: sha512-jon9M7DKRLGZ9VYSkFMflvNqu9hDtOCEnO2QAryFWgT6o6AXU8du56V7YqnaLKr6rAbZBWYsYpikF226v423QA==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.7.2': + resolution: {integrity: sha512-c8Cg4/h+kQ63pL43wBNaVMmOjXI/X62wQmru51qjfTvI7kmCy5uHTJvK/9LrF0G8Jdx8r34d019P1DVJmhXQpA==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': + resolution: {integrity: sha512-A+lcwRFyrjeJmv3JJvhz5NbcCkLQL6Mk16kHTNm6/aGNc4FwPHPE4DR9DwuCvCnVHvF5IAd9U4VIs/VvVir5lg==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': + resolution: {integrity: sha512-hQQ4TJQrSQW8JlPm7tRpXN8OCNP9ez7PajJNjRD1ZTHQAy685OYqPrKjfaMw/8LiHCt8AZ74rfUVHP9vn0N69Q==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.7.2': + resolution: {integrity: sha512-NoAGbiqrxtY8kVooZ24i70CjLDlUFI7nDj3I9y54U94p+3kPxwd2L692YsdLa+cqQ0VoqMWoehDFp21PKRUoIQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.7.2': + resolution: {integrity: sha512-KaZByo8xuQZbUhhreBTW+yUnOIHUsv04P8lKjQ5otiGoSJ17ISGYArc+4vKdLEpGaLbemGzr4ZeUbYQQsLWFjA==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.7.2': + resolution: {integrity: sha512-dEidzJDubxxhUCBJ/SHSMJD/9q7JkyfBMT77Px1npl4xpg9t0POLvnWywSk66BgZS/b2Hy9Y1yFaoMTFJUe9yg==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.7.2': + resolution: {integrity: sha512-RvP+Ux3wDjmnZDT4XWFfNBRVG0fMsc+yVzNFUqOflnDfZ9OYujv6nkh+GOr+watwrW4wdp6ASfG/e7bkDradsw==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.7.2': + resolution: {integrity: sha512-y797JBmO9IsvXVRCKDXOxjyAE4+CcZpla2GSoBQ33TVb3ILXuFnMrbR/QQZoauBYeOFuu4w3ifWLw52sdHGz6g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.7.2': + resolution: {integrity: sha512-gtYTh4/VREVSLA+gHrfbWxaMO/00y+34htY7XpioBTy56YN2eBjkPrY1ML1Zys89X3RJDKVaogzwxlM1qU7egg==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.7.2': + resolution: {integrity: sha512-Ywv20XHvHTDRQs12jd3MY8X5C8KLjDbg/jyaal/QLKx3fAShhJyD4blEANInsjxW3P7isHx1Blt56iUDDJO3jg==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.7.2': + resolution: {integrity: sha512-friS8NEQfHaDbkThxopGk+LuE5v3iY0StruifjQEt7SLbA46OnfgMO15sOTkbpJkol6RB+1l1TYPXh0sCddpvA==} + cpu: [x64] + os: [win32] + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + antd@5.25.1: + resolution: {integrity: sha512-4KC7KuPCjr0z3Vuw9DsF+ceqJaPLbuUI3lOX1sY8ix25ceamp+P8yxOmk3Y2JHCD2ZAhq+5IQ/DTJRN2adWYKQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.8: + resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + + axe-core@4.10.3: + resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bcryptjs@3.0.2: + resolution: {integrity: sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==} + hasBin: true + + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelize@1.0.1: + resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} + + caniuse-lite@1.0.30001717: + resolution: {integrity: sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-color-keywords@1.0.0: + resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} + engines: {node: '>=4'} + + css-to-react-native@3.2.0: + resolution: {integrity: sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@15.3.2: + resolution: {integrity: sha512-FerU4DYccO4FgeYFFglz0SnaKRe1ejXQrDb8kWUkTAg036YWi+jUsgg4sIGNCDhAsDITsZaL4MzBWKB6f4G1Dg==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.0: + resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.26.0: + resolution: {integrity: sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.1: + resolution: {integrity: sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@7.5.0: + resolution: {integrity: sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==} + engines: {node: '>= 16'} + peerDependencies: + express: ^4.11 || 5 || ^5.0.0-beta.1 + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.4.4: + resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.0: + resolution: {integrity: sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + jose@4.15.9: + resolution: {integrity: sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.29.2: + resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.2: + resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.2: + resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.2: + resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.2: + resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.29.2: + resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.29.2: + resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.29.2: + resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.29.2: + resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.2: + resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.2: + resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} + engines: {node: '>= 12.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + lru.min@1.1.2: + resolution: {integrity: sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mysql2@3.14.1: + resolution: {integrity: sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==} + engines: {node: '>= 8.0'} + + named-placeholders@1.1.3: + resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} + engines: {node: '>=12.0.0'} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.5: + resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} + engines: {node: ^18 || >=20} + hasBin: true + + napi-postinstall@0.2.3: + resolution: {integrity: sha512-Mi7JISo/4Ij2tDZ2xBE2WH+/KvVlkhA6juEjpEeRAVPNCpN3nxJo/5FhDNKgBcdmcmhaH6JjgST4xY/23ZYK0w==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + next-auth@4.24.11: + resolution: {integrity: sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==} + peerDependencies: + '@auth/core': 0.34.2 + next: ^12.2.5 || ^13 || ^14 || ^15 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 || ^19 + react-dom: ^17.0.2 || ^18 || ^19 + peerDependenciesMeta: + '@auth/core': + optional: true + nodemailer: + optional: true + + next@15.3.2: + resolution: {integrity: sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-cron@4.0.4: + resolution: {integrity: sha512-Z7015tX5SFQnDWYXLOiDR+ls5I7AEimbDOn77aiss4oaWyxKqzCBVreJXspHCraUejjMhessvp2l4ISYaRrQSg==} + engines: {node: '>=6.0.0'} + + oauth@0.9.15: + resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + oidc-token-hash@5.1.0: + resolution: {integrity: sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==} + engines: {node: ^10.13.0 || >=12.0.0} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + openid-client@5.7.1: + resolution: {integrity: sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@8.2.0: + resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} + engines: {node: '>=16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} + engines: {node: '>=16.20.0'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + + preact-render-to-string@5.2.6: + resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} + peerDependencies: + preact: '>=10' + + preact@10.26.6: + resolution: {integrity: sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qrcode.react@4.2.0: + resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.0: + resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==} + engines: {node: '>= 0.8'} + + rc-cascader@3.34.0: + resolution: {integrity: sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-checkbox@3.5.0: + resolution: {integrity: sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-collapse@3.9.0: + resolution: {integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dialog@9.6.0: + resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-drawer@7.2.0: + resolution: {integrity: sha512-9lOQ7kBekEJRdEpScHvtmEtXnAsy+NGDXiRWc2ZVC7QXAazNVbeT4EraQKYwCME8BJLa8Bxqxvs5swwyOepRwg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dropdown@4.2.1: + resolution: {integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + rc-field-form@2.7.0: + resolution: {integrity: sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-image@7.12.0: + resolution: {integrity: sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input-number@9.5.0: + resolution: {integrity: sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input@1.8.0: + resolution: {integrity: sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-mentions@2.20.0: + resolution: {integrity: sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-menu@9.16.1: + resolution: {integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-motion@2.9.5: + resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-notification@5.6.4: + resolution: {integrity: sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-overflow@1.4.1: + resolution: {integrity: sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-pagination@5.1.0: + resolution: {integrity: sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-picker@4.11.3: + resolution: {integrity: sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==} + engines: {node: '>=8.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + rc-progress@4.0.0: + resolution: {integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-rate@2.13.1: + resolution: {integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@1.4.3: + resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-segmented@2.7.0: + resolution: {integrity: sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-select@14.16.7: + resolution: {integrity: sha512-lT9kO5gFHQdJzu9a0btcOtNaJHkhenSl8H5mcpgXN9VIMXP59rnkpbdHmPrteixWs1D5zFOTyoTYX3b7joADIQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-slider@11.1.8: + resolution: {integrity: sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-steps@6.0.1: + resolution: {integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-switch@4.1.0: + resolution: {integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-table@7.50.5: + resolution: {integrity: sha512-FDZu8aolhSYd3v9KOc3lZOVAU77wmRRu44R0Wfb8Oj1dXRUsloFaXMSl6f7yuWZUxArJTli7k8TEOX2mvhDl4A==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tabs@15.6.1: + resolution: {integrity: sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-textarea@1.10.0: + resolution: {integrity: sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tooltip@6.4.0: + resolution: {integrity: sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tree-select@5.27.0: + resolution: {integrity: sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==} + peerDependencies: + react: '*' + react-dom: '*' + + rc-tree@5.13.1: + resolution: {integrity: sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + rc-upload@4.9.0: + resolution: {integrity: sha512-pAzlPnyiFn1GCtEybEG2m9nXNzQyWXqWV2xFYCmDxjN9HzyjS5Pz2F+pbNdYw8mMJsixLEKLG0wVy9vOGxJMJA==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@5.44.4: + resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-virtual-list@3.18.6: + resolution: {integrity: sha512-TQ5SsutL3McvWmmxqQtMIbfeoE3dGjJrRSfKekgby7WQMpPIFvv4ghytp5Z0s3D8Nik9i9YNOCqHBfk86AwgAA==} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} + peerDependencies: + react: '*' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + sharp@0.34.1: + resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-components@6.1.18: + resolution: {integrity: sha512-Mvf3gJFzZCkhjY2Y/Fx9z1m3dxbza0uI9H1CbNZm/jSHCojzJhQ0R7bByrlFJINnMzz/gPulpoFFGymNwrsMcw==} + engines: {node: '>= 16'} + peerDependencies: + react: '>= 16.8.0' + react-dom: '>= 16.8.0' + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylis@4.3.2: + resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@4.1.6: + resolution: {integrity: sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} + engines: {node: '>=12.22'} + + tinyglobby@0.2.13: + resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unrs-resolver@1.7.2: + resolution: {integrity: sha512-BBKpaylOW8KbHsu378Zky/dGh4ckT/4NW/0SHRABdqRLcQJ2dAOjDo9g97p04sWflm0kqPqpUatxReNV/dqI5A==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-to-json-schema@3.24.5: + resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} + peerDependencies: + zod: ^3.24.1 + + zod@3.24.4: + resolution: {integrity: sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==} + + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + + zustand@5.0.4: + resolution: {integrity: sha512-39VFTN5InDtMd28ZhjLyuTnlytDr9HfwO512Ai4I8ZABCoyAj4F1+sr7sD1jP/+p7k77Iko0Pb5NhgBFDCX0kQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@ant-design/colors@7.2.0': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/colors@8.0.0': + dependencies: + '@ant-design/fast-color': 3.0.0 + + '@ant-design/cssinjs-utils@1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@babel/runtime': 7.27.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/cssinjs@1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.1.3 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + stylis: 4.3.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.27.1 + + '@ant-design/fast-color@3.0.0': {} + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/colors': 7.2.0 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/icons@6.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/colors': 8.0.0 + '@ant-design/icons-svg': 4.4.2 + '@rc-component/util': 1.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@ant-design/react-slick@1.1.2(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + json2mq: 0.2.0 + react: 19.1.0 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.2 + + '@ant-design/v5-patch-for-react-19@1.0.3(antd@5.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + antd: 5.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@babel/runtime@7.27.1': {} + + '@emnapi/core@1.4.3': + dependencies: + '@emnapi/wasi-threads': 1.0.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emotion/hash@0.8.0': {} + + '@emotion/is-prop-valid@1.2.2': + dependencies: + '@emotion/memoize': 0.8.1 + + '@emotion/memoize@0.8.1': {} + + '@emotion/unitless@0.7.5': {} + + '@emotion/unitless@0.8.1': {} + + '@eslint-community/eslint-utils@4.7.0(eslint@9.26.0(jiti@2.4.2))': + dependencies: + eslint: 9.26.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.2': {} + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.26.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@iconify/react@6.0.0(react@19.1.0)': + dependencies: + '@iconify/types': 2.0.0 + react: 19.1.0 + + '@iconify/types@2.0.0': {} + + '@img/sharp-darwin-arm64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.1.0 + optional: true + + '@img/sharp-darwin-x64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.1.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-darwin-x64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-arm@1.1.0': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.1.0': + optional: true + + '@img/sharp-libvips-linux-s390x@1.1.0': + optional: true + + '@img/sharp-libvips-linux-x64@1.1.0': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.1.0': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.1.0': + optional: true + + '@img/sharp-linux-arm64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.1.0 + optional: true + + '@img/sharp-linux-arm@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.1.0 + optional: true + + '@img/sharp-linux-s390x@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.1.0 + optional: true + + '@img/sharp-linux-x64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.1.0 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.1': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + optional: true + + '@img/sharp-wasm32@0.34.1': + dependencies: + '@emnapi/runtime': 1.4.3 + optional: true + + '@img/sharp-win32-ia32@0.34.1': + optional: true + + '@img/sharp-win32-x64@0.34.1': + optional: true + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@modelcontextprotocol/sdk@1.11.1': + dependencies: + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + express: 5.1.0 + express-rate-limit: 7.5.0(express@5.1.0) + pkce-challenge: 5.0.0 + raw-body: 3.0.0 + zod: 3.24.4 + zod-to-json-schema: 3.24.5(zod@3.24.4) + transitivePeerDependencies: + - supports-color + + '@napi-rs/wasm-runtime@0.2.9': + dependencies: + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 + optional: true + + '@next/env@15.3.2': {} + + '@next/eslint-plugin-next@15.3.2': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@15.3.2': + optional: true + + '@next/swc-darwin-x64@15.3.2': + optional: true + + '@next/swc-linux-arm64-gnu@15.3.2': + optional: true + + '@next/swc-linux-arm64-musl@15.3.2': + optional: true + + '@next/swc-linux-x64-gnu@15.3.2': + optional: true + + '@next/swc-linux-x64-musl@15.3.2': + optional: true + + '@next/swc-win32-arm64-msvc@15.3.2': + optional: true + + '@next/swc-win32-x64-msvc@15.3.2': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@panva/hkdf@1.2.1': {} + + '@rc-component/async-validator@5.0.4': + dependencies: + '@babel/runtime': 7.27.1 + + '@rc-component/color-picker@2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@ant-design/fast-color': 2.0.6 + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/context@1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.27.1 + + '@rc-component/mutate-observer@1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/portal@1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/qrcode@1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/tour@1.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/trigger@2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@rc-component/util@1.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-is: 18.3.1 + + '@rtsao/scc@1.1.0': {} + + '@rushstack/eslint-patch@1.11.0': {} + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.6': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + lightningcss: 1.29.2 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.6 + + '@tailwindcss/oxide-android-arm64@4.1.6': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.6': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.6': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.6': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.6': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.6': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.6': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.6': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.6': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.6': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.6': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.6': + optional: true + + '@tailwindcss/oxide@4.1.6': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.6 + '@tailwindcss/oxide-darwin-arm64': 4.1.6 + '@tailwindcss/oxide-darwin-x64': 4.1.6 + '@tailwindcss/oxide-freebsd-x64': 4.1.6 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.6 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.6 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.6 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.6 + '@tailwindcss/oxide-linux-x64-musl': 4.1.6 + '@tailwindcss/oxide-wasm32-wasi': 4.1.6 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.6 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.6 + + '@tailwindcss/postcss@4.1.6': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.6 + '@tailwindcss/oxide': 4.1.6 + postcss: 8.5.3 + tailwindcss: 4.1.6 + + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/bcryptjs@3.0.0': + dependencies: + bcryptjs: 3.0.2 + + '@types/estree@1.0.7': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/jsonwebtoken@9.0.9': + dependencies: + '@types/ms': 2.1.0 + '@types/node': 20.17.46 + + '@types/ms@2.1.0': {} + + '@types/node-cron@3.0.11': {} + + '@types/node@20.17.46': + dependencies: + undici-types: 6.19.8 + + '@types/react-dom@19.1.3(@types/react@19.1.3)': + dependencies: + '@types/react': 19.1.3 + + '@types/react@19.1.3': + dependencies: + csstype: 3.1.3 + + '@types/stylis@4.2.5': {} + + '@typescript-eslint/eslint-plugin@8.32.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.32.0 + '@typescript-eslint/type-utils': 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.32.0 + eslint: 9.26.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.32.0 + '@typescript-eslint/types': 8.32.0 + '@typescript-eslint/typescript-estree': 8.32.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.32.0 + debug: 4.4.0 + eslint: 9.26.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.32.0': + dependencies: + '@typescript-eslint/types': 8.32.0 + '@typescript-eslint/visitor-keys': 8.32.0 + + '@typescript-eslint/type-utils@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.32.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.0 + eslint: 9.26.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.32.0': {} + + '@typescript-eslint/typescript-estree@8.32.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.32.0 + '@typescript-eslint/visitor-keys': 8.32.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.32.0 + '@typescript-eslint/types': 8.32.0 + '@typescript-eslint/typescript-estree': 8.32.0(typescript@5.8.3) + eslint: 9.26.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.32.0': + dependencies: + '@typescript-eslint/types': 8.32.0 + eslint-visitor-keys: 4.2.0 + + '@unrs/resolver-binding-darwin-arm64@1.7.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.7.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.7.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.7.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.7.2': + dependencies: + '@napi-rs/wasm-runtime': 0.2.9 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.7.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.7.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.7.2': + optional: true + + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + antd@5.25.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@ant-design/colors': 7.2.0 + '@ant-design/cssinjs': 1.23.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/fast-color': 2.0.6 + '@ant-design/icons': 5.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@ant-design/react-slick': 1.1.2(react@19.1.0) + '@babel/runtime': 7.27.1 + '@rc-component/color-picker': 2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/mutate-observer': 1.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/qrcode': 1.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/tour': 1.15.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.13 + rc-cascader: 3.34.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-checkbox: 3.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-collapse: 3.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-dialog: 9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-drawer: 7.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-dropdown: 4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-field-form: 2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-image: 7.12.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-input-number: 9.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-mentions: 2.20.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-notification: 5.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-pagination: 5.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-picker: 4.11.3(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-progress: 4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-rate: 2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-segmented: 2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-select: 14.16.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-slider: 11.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-steps: 6.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-switch: 4.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-table: 7.50.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tabs: 15.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-textarea: 1.10.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tooltip: 6.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree-select: 5.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-upload: 4.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + ast-types-flow@0.0.8: {} + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + aws-ssl-profiles@1.1.2: {} + + axe-core@4.10.3: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + bcryptjs@3.0.2: {} + + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.0 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.0 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + buffer-equal-constant-time@1.0.1: {} + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelize@1.0.1: {} + + caniuse-lite@1.0.30001717: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chownr@3.0.0: {} + + classnames@2.5.1: {} + + client-only@0.0.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + compute-scroll-into-view@3.1.1: {} + + concat-map@0.0.1: {} + + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-color-keywords@1.0.0: {} + + css-to-react-native@3.2.0: + dependencies: + camelize: 1.0.1 + css-color-keywords: 1.0.0 + postcss-value-parser: 4.2.0 + + csstype@3.1.3: {} + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.13: {} + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + denque@2.1.0: {} + + depd@2.0.0: {} + + detect-libc@2.0.4: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + + ee-first@1.1.1: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-next@15.3.2(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@next/eslint-plugin-next': 15.3.2 + '@rushstack/eslint-patch': 1.11.0 + '@typescript-eslint/eslint-plugin': 8.32.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.26.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.26.0(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.26.0(jiti@2.4.2)) + eslint-plugin-react: 7.37.5(eslint@9.26.0(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.26.0(jiti@2.4.2)) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.0 + eslint: 9.26.0(jiti@2.4.2) + get-tsconfig: 4.10.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.13 + unrs-resolver: 1.7.2 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.26.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.26.0(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.26.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.26.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.26.0(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.26.0(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.26.0(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.32.0(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.26.0(jiti@2.4.2)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.10.3 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.26.0(jiti@2.4.2) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@5.2.0(eslint@9.26.0(jiti@2.4.2)): + dependencies: + eslint: 9.26.0(jiti@2.4.2) + + eslint-plugin-react@7.37.5(eslint@9.26.0(jiti@2.4.2)): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.26.0(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.26.0(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.26.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.2 + '@eslint/core': 0.13.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.26.0 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@modelcontextprotocol/sdk': 1.11.1 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + zod: 3.24.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.0.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.1 + + express-rate-limit@7.5.0(express@5.1.0): + dependencies: + express: 5.1.0 + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.1 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.4.4(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.10.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + ipaddr.js@1.9.1: {} + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.3.2: + optional: true + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.1 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-promise@4.0.0: {} + + is-property@1.0.2: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jiti@2.4.2: {} + + jose@4.15.9: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.1 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.8 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.29.2: + optional: true + + lightningcss-darwin-x64@1.29.2: + optional: true + + lightningcss-freebsd-x64@1.29.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.2: + optional: true + + lightningcss-linux-arm64-gnu@1.29.2: + optional: true + + lightningcss-linux-arm64-musl@1.29.2: + optional: true + + lightningcss-linux-x64-gnu@1.29.2: + optional: true + + lightningcss-linux-x64-musl@1.29.2: + optional: true + + lightningcss-win32-arm64-msvc@1.29.2: + optional: true + + lightningcss-win32-x64-msvc@1.29.2: + optional: true + + lightningcss@1.29.2: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.2 + lightningcss-darwin-x64: 1.29.2 + lightningcss-freebsd-x64: 1.29.2 + lightningcss-linux-arm-gnueabihf: 1.29.2 + lightningcss-linux-arm64-gnu: 1.29.2 + lightningcss-linux-arm64-musl: 1.29.2 + lightningcss-linux-x64-gnu: 1.29.2 + lightningcss-linux-x64-musl: 1.29.2 + lightningcss-win32-arm64-msvc: 1.29.2 + lightningcss-win32-x64-msvc: 1.29.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + long@5.3.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-cache@7.18.3: {} + + lru.min@1.1.2: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.54.0: {} + + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + ms@2.1.3: {} + + mysql2@3.14.1: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.6.3 + long: 5.3.2 + lru.min: 1.1.2 + named-placeholders: 1.1.3 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + + named-placeholders@1.1.3: + dependencies: + lru-cache: 7.18.3 + + nanoid@3.3.11: {} + + nanoid@5.1.5: {} + + napi-postinstall@0.2.3: {} + + natural-compare@1.4.0: {} + + negotiator@1.0.0: {} + + next-auth@4.24.11(next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@panva/hkdf': 1.2.1 + cookie: 0.7.2 + jose: 4.15.9 + next: 15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + oauth: 0.9.15 + openid-client: 5.7.1 + preact: 10.26.6 + preact-render-to-string: 5.2.6(preact@10.26.6) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + uuid: 8.3.2 + + next@15.3.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@next/env': 15.3.2 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001717 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.3.2 + '@next/swc-darwin-x64': 15.3.2 + '@next/swc-linux-arm64-gnu': 15.3.2 + '@next/swc-linux-arm64-musl': 15.3.2 + '@next/swc-linux-x64-gnu': 15.3.2 + '@next/swc-linux-x64-musl': 15.3.2 + '@next/swc-win32-arm64-msvc': 15.3.2 + '@next/swc-win32-x64-msvc': 15.3.2 + sharp: 0.34.1 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-cron@4.0.4: {} + + oauth@0.9.15: {} + + object-assign@4.1.1: {} + + object-hash@2.2.0: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + oidc-token-hash@5.1.0: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + openid-client@5.7.1: + dependencies: + jose: 4.15.9 + lru-cache: 6.0.0 + object-hash: 2.2.0 + oidc-token-hash: 5.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-to-regexp@8.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pkce-challenge@5.0.0: {} + + possible-typed-array-names@1.1.0: {} + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.4.49: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact-render-to-string@5.2.6(preact@10.26.6): + dependencies: + preact: 10.26.6 + pretty-format: 3.8.0 + + preact@10.26.6: {} + + prelude-ls@1.2.1: {} + + pretty-format@3.8.0: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + qrcode.react@4.2.0(react@19.1.0): + dependencies: + react: 19.1.0 + + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@3.0.0: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + unpipe: 1.0.0 + + rc-cascader@3.34.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-select: 14.16.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-checkbox@3.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-collapse@3.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-dialog@9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-drawer@7.2.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-dropdown@4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-field-form@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/async-validator': 5.0.4 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-image@7.12.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/portal': 1.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-dialog: 9.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-input-number@9.5.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/mini-decimal': 1.1.0 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-input@1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-mentions@2.20.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-textarea: 1.10.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-menu@9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-motion@2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-notification@5.6.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-overflow@1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-pagination@5.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-picker@4.11.3(dayjs@1.11.13)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + dayjs: 1.11.13 + + rc-progress@4.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-rate@2.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-resize-observer@1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + resize-observer-polyfill: 1.5.1 + + rc-segmented@2.7.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-select@14.16.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-overflow: 1.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-slider@11.1.8(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-steps@6.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-switch@4.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-table@7.50.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/context': 1.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tabs@15.6.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-dropdown: 4.2.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-menu: 9.16.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-textarea@1.10.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tooltip@6.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + '@rc-component/trigger': 2.2.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tree-select@5.27.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-select: 14.16.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-tree: 5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-tree@5.13.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-virtual-list: 3.18.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-upload@4.9.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + rc-util@5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-is: 18.3.1 + + rc-virtual-list@3.18.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/runtime': 7.27.1 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + rc-util: 5.44.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react-icons@5.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react@19.1.0: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resize-observer-polyfill@1.5.1: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + router@2.2.0: + dependencies: + debug: 4.4.0 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.2.0 + transitivePeerDependencies: + - supports-color + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + scheduler@0.26.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + semver@6.3.1: {} + + semver@7.7.1: {} + + send@1.2.0: + dependencies: + debug: 4.4.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + seq-queue@0.0.5: {} + + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + setprototypeof@1.2.0: {} + + shallowequal@1.1.0: {} + + sharp@0.34.1: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.1 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.1 + '@img/sharp-darwin-x64': 0.34.1 + '@img/sharp-libvips-darwin-arm64': 1.1.0 + '@img/sharp-libvips-darwin-x64': 1.1.0 + '@img/sharp-libvips-linux-arm': 1.1.0 + '@img/sharp-libvips-linux-arm64': 1.1.0 + '@img/sharp-libvips-linux-ppc64': 1.1.0 + '@img/sharp-libvips-linux-s390x': 1.1.0 + '@img/sharp-libvips-linux-x64': 1.1.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 + '@img/sharp-libvips-linuxmusl-x64': 1.1.0 + '@img/sharp-linux-arm': 0.34.1 + '@img/sharp-linux-arm64': 0.34.1 + '@img/sharp-linux-s390x': 0.34.1 + '@img/sharp-linux-x64': 0.34.1 + '@img/sharp-linuxmusl-arm64': 0.34.1 + '@img/sharp-linuxmusl-x64': 0.34.1 + '@img/sharp-wasm32': 0.34.1 + '@img/sharp-win32-ia32': 0.34.1 + '@img/sharp-win32-x64': 0.34.1 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + + source-map-js@1.2.1: {} + + sqlstring@2.3.3: {} + + stable-hash@0.0.5: {} + + statuses@2.0.1: {} + + streamsearch@1.1.0: {} + + string-convert@0.2.1: {} + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.9 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + styled-components@6.1.18(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@emotion/is-prop-valid': 1.2.2 + '@emotion/unitless': 0.8.1 + '@types/stylis': 4.2.5 + css-to-react-native: 3.2.0 + csstype: 3.1.3 + postcss: 8.4.49 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + shallowequal: 1.1.0 + stylis: 4.3.2 + tslib: 2.6.2 + + styled-jsx@5.1.6(react@19.1.0): + dependencies: + client-only: 0.0.1 + react: 19.1.0 + + stylis@4.3.2: {} + + stylis@4.3.6: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@4.1.6: {} + + tapable@2.2.1: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + throttle-debounce@5.0.2: {} + + tinyglobby@0.2.13: + dependencies: + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + toidentifier@1.0.1: {} + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.3.0: {} + + tslib@2.6.2: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.8.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.19.8: {} + + unpipe@1.0.0: {} + + unrs-resolver@1.7.2: + dependencies: + napi-postinstall: 0.2.3 + optionalDependencies: + '@unrs/resolver-binding-darwin-arm64': 1.7.2 + '@unrs/resolver-binding-darwin-x64': 1.7.2 + '@unrs/resolver-binding-freebsd-x64': 1.7.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.7.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.7.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.7.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.7.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.7.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.7.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.7.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.7.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.7.2 + '@unrs/resolver-binding-linux-x64-musl': 1.7.2 + '@unrs/resolver-binding-wasm32-wasi': 1.7.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.7.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.7.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.7.2 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + uuid@11.1.0: {} + + uuid@8.3.2: {} + + vary@1.1.2: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + yallist@4.0.0: {} + + yallist@5.0.0: {} + + yocto-queue@0.1.0: {} + + zod-to-json-schema@3.24.5(zod@3.24.4): + dependencies: + zod: 3.24.4 + + zod@3.24.4: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 + + zustand@5.0.4(@types/react@19.1.3)(react@19.1.0): + optionalDependencies: + '@types/react': 19.1.3 + react: 19.1.0 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..4fffdbd --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,20 @@ +# SaaS平台robots.txt +User-agent: * +Allow: / + +# 禁止访问的路径 +Disallow: /api/ +Disallow: /admin/ +Disallow: /dashboard/private/ +Disallow: /uploads/private/ + +# 允许访问的特定路径 +Allow: /public/ +Allow: /blog/ +Allow: /docs/ + +# 站点地图 +Sitemap: https://yourdomain.com/sitemap.xml + +# 抓取频率设置 +Crawl-delay: 10 diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx new file mode 100644 index 0000000..267b93e --- /dev/null +++ b/src/app/(auth)/layout.tsx @@ -0,0 +1,193 @@ +/** + * 认证页面布局 + * 作者: 阿瑞 + * 功能: 提供登录和注册页面的统一布局,左侧显示应用简介,右侧显示认证界面 + * 版本: 1.4 + */ + +'use client'; + +import React from 'react'; +import { useIsDarkMode } from "@/components/ThemeProvider"; + +/** + * 认证布局组件 + * 提供左右分栏布局,左侧展示应用介绍,右侧展示登录/注册组件 + */ +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + {children} + ); +} + +/** + * 认证页面内容组件 + * 使用ThemeContext获取主题状态 + */ +function AuthContent({ children }: { children: React.ReactNode }) { + // 使用Context API获取主题状态,无需水合逻辑 + const { isDarkMode, isMounted } = useIsDarkMode(); + + // 如果未水合完成,交由ThemeProvider处理加载状态 + if (!isMounted) return null; + + return ( +
+ {/* 动态背景元素 */} +
+
+
+
+
+ + {/* 装饰小球元素 */} +
+
+
+
+
+ + {/* 左侧应用简介 - 占据2/3空间 */} +
+
+
+
+
+ + + +
+

+ 多租户SaaS管理系统 +

+

+ 基于Next.js的现代化私域管理平台
助力企业高效管理团队与资源 +

+
+ +
+
+
+
+ + + +
+

+ 多租户架构 +

+
+

+ 支持多工作空间独立管理,为每个企业提供专属环境,数据隔离安全可靠 +

+
+ +
+
+
+ + + +
+

+ 精细权限控制 +

+
+

+ 基于角色的访问控制系统,确保用户只能访问其权限范围内的资源和功能 +

+
+ +
+
+
+ + + +
+

+ 现代UI设计 +

+
+

+ 采用流行的毛玻璃设计风格,提供明亮通透的用户界面和流畅的交互体验 +

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

+ 高效数据管理 +

+
+

+ 智能数据库连接池,支持自动初始化和维护,提供完整的团队和工作空间数据隔离 +

+
+
+ + {/* 系统特点 */} +
+

系统特点

+
+
+ + + + 工作空间和团队管理 +
+
+ + + + 基于JWT的认证机制 +
+
+ + + + 响应式设计,全设备支持 +
+
+ + + + 深色/明亮主题切换 +
+
+ + + + 成员邀请与注册 +
+
+ + + + 基于Next.js的现代技术栈 +
+
+
+ +
+
+
+ + {/* 右侧登录/注册界面 - 占据1/3空间 */} +
+ {children} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx new file mode 100644 index 0000000..eedf543 --- /dev/null +++ b/src/app/(auth)/login/page.tsx @@ -0,0 +1,256 @@ +/** + * 登录页面 + * 作者: 阿瑞 + * 功能: 提供用户登录界面和功能,使用毛玻璃UI效果 + * 版本: 1.6 + */ + +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import Link from 'next/link'; +import { useAuth, useTheme, useApplyTheme } from '@/hooks'; +import { useNotification } from '@/components/ui/Notification'; + +/** + * 登录页面组件 + * 处理用户登录表单提交和验证 + */ +export default function LoginPage() { + // 状态管理 + const [formData, setFormData] = useState({ + username: '', + password: '', + }); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + // 添加认证加载状态 + const [authChecked, setAuthChecked] = useState(false); + + // 路由 + const router = useRouter(); + const notification = useNotification(); + + // 应用主题 + useApplyTheme(); + + // 使用自定义钩子 + const { isAuthenticated, login } = useAuth(); + const { isDarkMode } = useTheme(); + + /** + * 检查认证状态并设置标志 + */ + useEffect(() => { + // 确保认证状态已经从持久化存储中加载完成 + const timer = setTimeout(() => { + setAuthChecked(true); + }, 100); + + return () => clearTimeout(timer); + }, []); + + /** + * 已登录用户重定向 + * 如果用户已经登录,直接跳转到工作空间页面 + */ + useEffect(() => { + // 只有在认证状态检查完成后才执行重定向 + if (!authChecked) return; + + console.log('登录状态检查 isAuthenticated:', isAuthenticated); + if (isAuthenticated) { + console.log('用户已登录,跳转到工作空间'); + router.replace('/workspace'); // 使用replace代替push进行强制导航 + } + }, [isAuthenticated, router, authChecked]); + + /** + * 表单输入变更处理 + */ + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + // 清除错误提示 + if (error) setError(''); + }; + + /** + * 表单提交处理 + */ + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setLoading(true); + console.log('提交登录表单,用户名:', formData.username); + + try { + // 验证表单 + if (!formData.username || !formData.password) { + throw new Error('用户名和密码不能为空'); + } + + // 发送登录请求 + console.log('发送登录请求...'); + const response = await fetch('/api/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(formData), + }); + + // 处理响应 + const data = await response.json(); + console.log('登录响应状态:', response.status, response.ok); + + if (!response.ok) { + throw new Error(data.error || '登录失败,请检查用户名和密码'); + } + + // 显示登录成功提示 + console.log('登录成功,获取到令牌和用户信息'); + notification.success('登录成功,正在跳转...'); + + // 使用自定义钩子进行登录 + await login(data.accessToken, data.user); + + // 短暂延迟后强制导航到工作空间 + console.log('准备跳转到工作空间页面...'); + setTimeout(() => { + console.log('执行强制跳转'); + window.location.href = '/workspace'; // 使用原生导航,绕过任何Route拦截 + }, 500); + + } catch (error: Error | unknown) { + console.error('登录失败详情:', error); + const errorMessage = error instanceof Error ? error.message : '登录过程中发生错误'; + setError(errorMessage); + notification.error(errorMessage); + } finally { + setLoading(false); + } + }; + + // 认证状态检查中显示加载界面 + if (!authChecked) { + return ( +
+
+
+
+
+ ); + } + + return ( +
+
+

+ 登录账户 +

+

+ 或{' '} + + 注册新账户 + +

+
+ +
+
+
+ {/* 错误提示 */} + {error && ( +
+
+
+ +
+
+

{error}

+
+
+
+ )} + + {/* 用户名输入 */} +
+ +
+ +
+
+ + {/* 密码输入 */} +
+ +
+ +
+
+ + {/* 记住我和忘记密码 */} +
+
+ + +
+ + +
+ + {/* 提交按钮 */} +
+ +
+
+
+
+
+ ); +} diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx new file mode 100644 index 0000000..1524290 --- /dev/null +++ b/src/app/(auth)/register/page.tsx @@ -0,0 +1,650 @@ +'use client'; + +/** + * 用户注册页面 + * 作者: 阿瑞 + * 功能: 提供用户注册和创建工作空间功能,支持邀请链接注册,使用毛玻璃UI效果 + * 版本: 1.4 + */ + +import { useState, FormEvent, useEffect, useRef, useCallback } from 'react'; +import { useRouter, useSearchParams } from 'next/navigation'; +import Link from 'next/link'; +import { useTheme } from '@/hooks'; +import { useNotification } from '@/components/ui/Notification'; + +// 缓存已验证的令牌结果,避免重复请求 +const verifiedTokenCache = new Map(); + +/** + * 注册表单状态接口 + */ +interface RegisterFormState { + username: string; + email: string; + phone: string; + password: string; + confirmPassword: string; + workspaceName: string; + isSubmitting: boolean; +} + +/** + * 表单验证错误接口 + */ +interface FormErrors { + username?: string; + email?: string; + phone?: string; + password?: string; + confirmPassword?: string; + workspaceName?: string; + general?: string; +} + +/** + * 注册页面组件 + */ +export default function RegisterPage() { + const router = useRouter(); + const searchParams = useSearchParams(); + const notification = useNotification(); + + // 获取邀请令牌 + const inviteToken = searchParams?.get('token'); + + // 用于确保请求只发送一次的标志 + const tokenFetchAttemptedRef = useRef(false); + + // 使用主题钩子(移除了不必要的useApplyTheme调用) + const { isDarkMode } = useTheme(); + + // 添加注册完成状态标志 + const [registrationCompleted, setRegistrationCompleted] = useState(false); + + /** + * 表单状态 + */ + const [formState, setFormState] = useState({ + username: '', + email: '', + phone: '', + password: '', + confirmPassword: '', + workspaceName: '', + isSubmitting: false + }); + + /** + * 表单验证错误 + */ + const [errors, setErrors] = useState({}); + + /** + * 邀请信息状态 + */ + const [inviteInfo, setInviteInfo] = useState<{ + isValid: boolean; + workspaceName?: string; + inviterName?: string; + isLoading: boolean; + error?: string; + used?: boolean; + usedBy?: string; + usedAt?: string; + }>({ + isValid: false, + isLoading: !!inviteToken + }); + + /** + * 验证邀请令牌的函数 - 使用useCallback避免重复创建 + */ + const verifyInviteToken = useCallback(async (token: string) => { + // 如果令牌已缓存,直接使用缓存结果 + if (verifiedTokenCache.has(token)) { + const cachedData = verifiedTokenCache.get(token); + setInviteInfo(cachedData); + return; + } + + try { + const response = await fetch(`/api/auth/verify-invite?token=${token}`); + + let resultData; + + if (response.ok) { + const data = await response.json(); + resultData = { + isValid: true, + workspaceName: data.workspaceName, + inviterName: data.inviterName, + isLoading: false + }; + setInviteInfo(resultData); + } else { + const errorData = await response.json(); + + // 增加对已使用令牌的处理 + if (errorData.used) { + resultData = { + isValid: false, + isLoading: false, + error: `该邀请链接已被${errorData.usedBy}使用`, + used: true, + usedBy: errorData.usedBy, + usedAt: errorData.usedAt + }; + setInviteInfo(resultData); + notification.error(`该邀请链接已被使用,请联系管理员获取新的邀请链接`); + } else { + resultData = { + isValid: false, + isLoading: false, + error: errorData.error || '无效的邀请链接' + }; + setInviteInfo(resultData); + notification.error('邀请链接无效或已过期'); + } + } + + // 缓存结果 + verifiedTokenCache.set(token, resultData); + + } catch (error) { + console.error('验证邀请令牌失败:', error); + const resultData = { + isValid: false, + isLoading: false, + error: '验证邀请链接失败' + }; + setInviteInfo(resultData); + notification.error('验证邀请链接失败'); + + // 即使是错误也缓存,避免重复请求 + verifiedTokenCache.set(token, resultData); + } + }, [notification]); + + /** + * 初始化验证邀请令牌 + */ + useEffect(() => { + // 只有在有令牌且尚未完成注册的情况下处理 + if (inviteToken && !registrationCompleted && !tokenFetchAttemptedRef.current) { + // 标记已尝试获取 + tokenFetchAttemptedRef.current = true; + // 验证邀请令牌 + verifyInviteToken(inviteToken); + } else if (!inviteToken && inviteInfo.isLoading) { + // 如果没有令牌但状态仍为加载中,重置状态 + setInviteInfo(prev => ({...prev, isLoading: false})); + } + // 添加inviteInfo.isLoading到依赖数组 + }, [inviteToken, registrationCompleted, verifyInviteToken, inviteInfo.isLoading]); + + /** + * 处理表单输入变化 + * @param e 输入事件 + */ + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormState(prev => ({ + ...prev, + [name]: value + })); + + // 清除相应字段的错误 + if (errors[name as keyof FormErrors]) { + setErrors(prev => ({ + ...prev, + [name]: undefined + })); + } + }; + + /** + * 验证表单输入 + * @returns 表单是否有效 + */ + const validateForm = (): boolean => { + const newErrors: FormErrors = {}; + + // 验证用户名 + if (!formState.username.trim()) { + newErrors.username = '请输入用户名'; + } else if (formState.username.length < 3) { + newErrors.username = '用户名长度至少为3个字符'; + } + + // 验证邮箱 + if (!formState.email.trim()) { + newErrors.email = '请输入电子邮箱'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formState.email)) { + newErrors.email = '请输入有效的电子邮箱'; + } + + // 验证手机号 + if (!formState.phone.trim()) { + newErrors.phone = '请输入手机号码'; + } else if (!/^1\d{10}$/.test(formState.phone)) { + newErrors.phone = '请输入有效的手机号码'; + } + + // 验证密码 + if (!formState.password) { + newErrors.password = '请输入密码'; + } else if (formState.password.length < 6) { + newErrors.password = '密码长度至少为6个字符'; + } + + // 验证确认密码 + if (!formState.confirmPassword) { + newErrors.confirmPassword = '请确认密码'; + } else if (formState.confirmPassword !== formState.password) { + newErrors.confirmPassword = '两次输入的密码不一致'; + } + + // 验证工作空间名称(仅在没有邀请令牌时需要) + if (!inviteToken && !formState.workspaceName.trim()) { + newErrors.workspaceName = '请输入工作空间名称'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + /** + * 处理表单提交 + * @param e 表单提交事件 + */ + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + + // 表单验证 + if (!validateForm()) { + return; + } + + setFormState(prev => ({ ...prev, isSubmitting: true })); + setErrors({}); + + try { + const payload = { + username: formState.username, + email: formState.email, + phone: formState.phone, + password: formState.password, + ...(inviteToken + ? { inviteToken } + : { workspaceName: formState.workspaceName }) + }; + + const response = await fetch('/api/auth/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || '注册失败'); + } + + // 设置注册完成状态 + setRegistrationCompleted(true); + + // 如果注册成功并使用了邀请令牌,从缓存中删除该令牌 + if (inviteToken) { + verifiedTokenCache.delete(inviteToken); + } + + // 注册成功提示 + notification.success('注册成功!正在跳转到登录页面...'); + + // 使用setTimeout确保通知显示后再跳转 + setTimeout(() => { + try { + // 首先尝试使用Next.js路由 + router.replace('/login'); + + // 如果在500ms内没有跳转成功,使用window.location强制跳转 + setTimeout(() => { + if (document.location.pathname.includes('register')) { + console.log('使用window.location强制跳转到登录页'); + window.location.href = '/login'; + } + }, 500); + } catch (error) { + console.error('路由跳转失败:', error); + // 如果路由跳转失败,使用window.location作为备选方案 + window.location.href = '/login'; + } + }, 100); // 给予通知足够的时间显示 + + } catch (error: Error | unknown) { + console.error('注册失败:', error); + + setErrors({ + general: error instanceof Error ? error.message : '注册失败,请稍后再试' + }); + notification.error('注册失败:' + (error instanceof Error ? error.message : '未知错误')); + } finally { + // 无论成功还是失败,都重置提交状态 + setFormState(prev => ({ ...prev, isSubmitting: false })); + } + }; + + // 如果正在加载邀请信息,显示加载状态 + if (inviteToken && inviteInfo.isLoading) { + return ( +
+
+

验证邀请链接中...

+
+
+
+ ); + } + + // 如果有邀请令牌但无效,显示错误信息 + if (inviteToken && !inviteInfo.isValid && !inviteInfo.isLoading) { + // 显示自定义的错误消息,包括令牌已使用的情况 + const errorMessage = inviteInfo.used + ? `该邀请链接已于 ${new Date(inviteInfo.usedAt || Date.now()).toLocaleString()} 被${inviteInfo.usedBy || '其他用户'}使用` + : (inviteInfo.error || '该邀请链接可能已过期或无效。请联系邀请人获取新的邀请链接。'); + + return ( +
+
+

无效的邀请链接

+

+ {errorMessage} +

+ + 返回登录 + +
+
+ ); + } + + return ( +
+
+
+

+ {inviteToken ? '接受邀请并注册' : '注册新账号'} +

+

+ {inviteToken && inviteInfo.workspaceName + ? `您被邀请加入"${inviteInfo.workspaceName}"工作空间` + : '创建您的账号和工作空间'} +

+
+ +
+ {/* 通用错误提示 */} + {errors.general && ( +
+
+
+ + + +
+
+

+ {errors.general} +

+
+
+
+ )} + +
+ {/* 用户名 */} +
+ +
+ + {errors.username && ( +
+ {errors.username} +
+ )} +
+
+ + {/* 电子邮箱 */} +
+ +
+ + {errors.email && ( +
+ {errors.email} +
+ )} +
+
+ + {/* 手机号码 */} +
+ +
+ + {errors.phone && ( +
+ {errors.phone} +
+ )} +
+
+ + {/* 密码 */} +
+ +
+ + {errors.password && ( +
+ {errors.password} +
+ )} +
+
+ + {/* 确认密码 */} +
+ +
+ + {errors.confirmPassword && ( +
+ {errors.confirmPassword} +
+ )} +
+
+ + {/* 工作空间名称(仅当没有邀请令牌时显示) */} + {!inviteToken && ( +
+ +
+ + {errors.workspaceName && ( +
+ {errors.workspaceName} +
+ )} +
+
+ )} + + {/* 显示邀请的工作空间信息(如果有) */} + {inviteToken && inviteInfo.isValid && ( +
+

+ 您将加入工作空间: {inviteInfo.workspaceName} +

+ {inviteInfo.inviterName && ( +

+ 邀请人: {inviteInfo.inviterName} +

+ )} +
+ )} + + {/* 提交按钮 */} +
+ +
+ + {/* 登录链接 */} +
+

+ 已有账号?{' '} + + 去登录 + +

+
+
+
+
+
+ ); +} diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..be28755 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,138 @@ +/** + * 登录API路由 + * 作者: 阿瑞 + * 功能: 处理用户登录验证并返回JWT令牌 + * 版本: 1.1 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; +import { SystemUserModel, WorkspaceModel } from '@/models/system'; +import { AuthUtils } from '@/lib/auth'; +import { UserStatus } from '@/models/system/types'; +import bcrypt from 'bcryptjs'; + +/** + * 处理登录请求 + * @param req 带数据库连接的请求对象 + */ +async function handler(req: RequestWithDB) { + try { + // 解析请求体 + const body = await req.json(); + const { username, password } = body; + + // 验证请求数据 + if (!username || !password) { + return NextResponse.json( + { + success: false, + error: '用户名和密码不能为空' + }, + { status: 400 } + ); + } + + // 查询用户信息 + const user = await SystemUserModel.getByUsername(username); + + // 用户不存在 + if (!user) { + return NextResponse.json( + { + success: false, + error: '用户名或密码错误' + }, + { status: 401 } + ); + } + + // 验证密码 + if (!user.password) { + return NextResponse.json( + { + success: false, + error: '用户密码数据异常' + }, + { status: 500 } + ); + } + + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + return NextResponse.json( + { + success: false, + error: '用户名或密码错误' + }, + { status: 401 } + ); + } + + // 检查用户状态是否正常 + if (user.status !== UserStatus.ENABLED) { + return NextResponse.json( + { + success: false, + error: '账户已被禁用,请联系管理员' + }, + { status: 403 } + ); + } + + // 获取工作空间信息 + const workspace = await WorkspaceModel.getById(user.workspace_id); + if (!workspace) { + return NextResponse.json( + { + success: false, + error: '用户工作空间不存在或已被删除' + }, + { status: 403 } + ); + } + + // 更新最后登录时间 + await SystemUserModel.updateLastLogin(user.id); + + // 创建用户信息对象(排除敏感字段) + const userInfo = AuthUtils.sanitizeUser(user); + + // 补充工作空间信息 + userInfo.workspace.name = workspace.name; + userInfo.workspace.status = workspace.status; + + // 生成JWT访问令牌 + const accessToken = await AuthUtils.createToken(user); + + // 返回成功响应 + return NextResponse.json({ + success: true, + message: '登录成功', + user: userInfo, + accessToken + }); + } catch (error: unknown) { + console.error('登录处理失败:', error); + + const errorMessage = error instanceof Error ? error.message : '登录处理过程中发生错误'; + + // 返回错误响应 + return NextResponse.json( + { + success: false, + error: errorMessage + }, + { status: 500 } + ); + } +} + +/** + * 处理POST请求 + * 符合Next.js 15.3+的路由处理器签名 + */ +export async function POST(request: NextRequest) { + const wrappedHandler = connectSystemDB(handler); + return wrappedHandler(request); +} \ No newline at end of file diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts new file mode 100644 index 0000000..2c9576b --- /dev/null +++ b/src/app/api/auth/register/route.ts @@ -0,0 +1,351 @@ +/** + * 用户注册API路由 + * 作者: 阿瑞 + * 功能: 处理用户注册请求,支持普通注册和邀请注册 + * 版本: 3.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import bcrypt from 'bcryptjs'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; +import { SystemUserModel } from '@/models/system'; +import { WorkspaceStatus, UserStatus } from '@/models/system/types'; + +/** + * MySQL查询结果接口 + */ +interface MySQLRow { + [key: string]: string | number | boolean | Date | null; +} + +/** + * MySQL插入结果接口 + */ +interface MySQLInsertResult { + insertId: number; + affectedRows: number; +} + +/** + * 邀请信息接口 + */ +interface InvitationInfo { + id: number; + token: string; + workspace_id: number; + role?: string; + role_type?: string; + role_name?: string; + is_custom_role?: boolean; + workspace_name: string; +} + +/** + * 数据库错误接口,扩展了Error接口添加了code属性 + * MySQL错误会包含错误码 + */ +interface DbError extends Error { + code?: string; + message: string; +} + +/** + * 处理用户注册请求 + * @param req 带数据库连接的请求对象 + */ +async function handler(req: RequestWithDB) { + try { + // 解析请求体 + const body = await req.json(); + const { username, email, phone, password, workspaceName, inviteToken } = body; + + // 验证必填字段 + if (!username || !email || !phone || !password) { + return NextResponse.json( + { + success: false, + error: '用户名、邮箱、手机号和密码为必填字段' + }, + { status: 400 } + ); + } + + // 如果没有提供邀请令牌,则需要工作空间名称 + if (!inviteToken && !workspaceName) { + return NextResponse.json( + { + success: false, + error: '工作空间名称为必填字段' + }, + { status: 400 } + ); + } + + // 检查用户名是否已存在 + const existingUserByUsername = await SystemUserModel.getByUsername(username); + if (existingUserByUsername) { + return NextResponse.json( + { + success: false, + error: '用户名已存在' + }, + { status: 409 } + ); + } + + // 检查邮箱是否已存在 + const existingUserByEmail = await SystemUserModel.getByEmail(email); + if (existingUserByEmail) { + return NextResponse.json( + { + success: false, + error: '邮箱已被注册' + }, + { status: 409 } + ); + } + + // 检查手机号是否已存在 + const [existingPhones] = await req.db.query( + 'SELECT id FROM system_users WHERE phone = ?', + [phone] + ); + + if (existingPhones && (existingPhones as MySQLRow[]).length > 0) { + return NextResponse.json( + { + success: false, + error: '手机号已被注册' + }, + { status: 409 } + ); + } + + // 对密码进行加密 + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password, salt); + + // 开始注册流程,使用事务确保数据一致性 + try { + // 开始事务 + await req.db.beginTransaction(); + + // 初始化变量 + let workspaceId: number | null = null; + let roleType = "user"; // 默认角色类型 + let roleName = "普通用户"; // 默认角色名称 + let isCustomRole = false; // 默认不是自定义角色 + let invitationId: number | null = null; + let returnWorkspaceName = workspaceName || ''; + const isInviteFlow = Boolean(inviteToken && inviteToken.trim() !== ''); + + if (isInviteFlow) { + // 查询邀请信息 + const [rows] = await req.db.query( + `SELECT + i.id, + i.token, + i.workspace_id, + i.role, + i.role_type, + i.role_name, + i.is_custom_role, + w.name as workspace_name + FROM workspace_invitations i + JOIN workspaces w ON i.workspace_id = w.id + WHERE i.token = ? AND i.used_by IS NULL AND i.expires_at > NOW()`, + [inviteToken] + ); + + // 检查是否有结果 + if (!rows || !(rows as MySQLRow[]).length) { + await req.db.rollback(); + return NextResponse.json( + { + success: false, + error: '无效的邀请令牌或已过期' + }, + { status: 400 } + ); + } + + // 获取原始邀请记录 + const invitation = (rows as InvitationInfo[])[0]; + + // 提取邀请信息 + invitationId = Number(invitation.id); + workspaceId = Number(invitation.workspace_id); + returnWorkspaceName = String(invitation.workspace_name); + + // 获取角色信息 + if (invitation.role_type !== undefined) { + // 使用新角色系统 + roleType = String(invitation.role_type || 'user'); + roleName = String(invitation.role_name || '普通用户'); + isCustomRole = Boolean(invitation.is_custom_role || false); + } else { + // 兼容旧角色系统 + roleType = String(invitation.role || 'user'); + roleName = roleType === 'admin' ? '管理员' : '普通用户'; + isCustomRole = false; + } + + // 验证工作空间ID + if (!workspaceId || isNaN(workspaceId)) { + await req.db.rollback(); + return NextResponse.json( + { + success: false, + error: '邀请中的工作空间ID无效' + }, + { status: 400 } + ); + } + } else { + // 普通注册流程,创建新工作空间 + const [result] = await req.db.query( + `INSERT INTO workspaces (name, creator_id, status, created_at) VALUES (?, ?, ?, ?)`, + [workspaceName, 0, WorkspaceStatus.ACTIVE, new Date()] + ); + + // 从插入结果中获取ID + workspaceId = (result as MySQLInsertResult).insertId; + + // 管理员角色 + roleType = "admin"; + roleName = "管理员"; + isCustomRole = false; + + if (!workspaceId) { + await req.db.rollback(); + return NextResponse.json( + { + success: false, + error: '创建工作空间失败' + }, + { status: 500 } + ); + } + } + + // 最终检查工作空间ID + if (workspaceId === null || isNaN(workspaceId) || workspaceId === 0) { + await req.db.rollback(); + return NextResponse.json( + { + success: false, + error: '无法确定工作空间ID' + }, + { status: 500 } + ); + } + + // 创建用户并关联到工作空间 + const [userResult] = await req.db.query( + `INSERT INTO system_users + (username, password, email, phone, workspace_id, role_type, role_name, is_custom_role, status, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + username, + hashedPassword, + email, + phone, + workspaceId, + roleType, + roleName, + isCustomRole, + UserStatus.ENABLED, + new Date() + ] + ); + + const userId = (userResult as MySQLInsertResult).insertId; + + if (!userId) { + await req.db.rollback(); + return NextResponse.json( + { + success: false, + error: '创建用户失败' + }, + { status: 500 } + ); + } + + // 如果是普通注册(创建了新工作空间),更新工作空间的创建者ID + if (!isInviteFlow) { + await req.db.query( + `UPDATE workspaces SET creator_id = ?, updated_at = ? WHERE id = ?`, + [userId, new Date(), workspaceId] + ); + } + + // 如果是邀请注册,更新邀请记录 + if (isInviteFlow && invitationId !== null) { + await req.db.query( + `UPDATE workspace_invitations SET used_by = ?, used_at = NOW() WHERE id = ?`, + [userId, invitationId] + ); + } + + // 提交事务 + await req.db.commit(); + + // 返回注册成功响应 + return NextResponse.json( + { + success: true, + message: '注册成功', + user: { id: userId, username, email }, + workspace: { id: workspaceId, name: returnWorkspaceName } + }, + { status: 201 } + ); + } catch (error) { + // 回滚事务 + await req.db.rollback(); + throw error; // 重新抛出错误以便外层catch处理 + } + } catch (error: unknown) { + console.error('注册失败:', error); + + let statusCode = 500; + let errorMessage = '注册失败,请稍后再试'; + + // 处理特定类型的错误 + const dbError = error as DbError; + if (dbError.code === 'ER_DUP_ENTRY') { + statusCode = 409; + errorMessage = '用户名、邮箱或手机号已存在'; + } else if (error instanceof Error) { + errorMessage = error.message; + } + + return NextResponse.json( + { + success: false, + error: errorMessage + }, + { status: statusCode } + ); + } +} + +/** + * 处理用户注册请求 + * POST /api/auth/register + */ +export async function POST(request: NextRequest) { + try { + return await connectSystemDB(handler)(request); + } catch (error) { + console.error('注册请求处理失败:', error); + return NextResponse.json( + { + success: false, + error: '处理注册请求失败' + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/auth/verify-invite/route.ts b/src/app/api/auth/verify-invite/route.ts new file mode 100644 index 0000000..6f15839 --- /dev/null +++ b/src/app/api/auth/verify-invite/route.ts @@ -0,0 +1,142 @@ +/** + * 邀请令牌验证API路由 + * 作者: 阿瑞 + * 功能: 验证邀请令牌的有效性并返回相关信息 + * 版本: 1.3 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; + +/** + * 验证邀请令牌 + */ +export async function GET(request: NextRequest) { + try { + // 获取令牌参数 + const { searchParams } = new URL(request.url); + const token = searchParams.get('token'); + + // 验证参数 + if (!token) { + return NextResponse.json( + { + success: false, + error: '缺少令牌参数' + }, + { status: 400 } + ); + } + + console.log('验证邀请令牌:', token); + + // 使用连接数据库的模式 + const handler = async (req: RequestWithDB) => { + // 先查询是否是已使用的令牌 + const [usedRows] = await req.db.query( + `SELECT i.id, u.username, i.used_at + FROM workspace_invitations i + LEFT JOIN system_users u ON i.used_by = u.id + WHERE i.token = ? AND i.used_by IS NOT NULL`, + [token] + ); + + // 转换为纯JavaScript对象 + const usedInvitations = JSON.parse(JSON.stringify(usedRows)); + + // 如果令牌已被使用,返回更明确的信息 + if (usedInvitations && usedInvitations.length > 0) { + const usedInvitation = usedInvitations[0]; + console.log('邀请令牌已被使用:', usedInvitation); + + return NextResponse.json( + { + success: false, + error: '该邀请链接已被使用', + used: true, + usedBy: usedInvitation.username || '其他用户', + usedAt: usedInvitation.used_at + }, + { status: 400 } + ); + } + + // 查询令牌 + const [rows] = await req.db.query( + `SELECT i.*, w.name as workspace_name, u.username as inviter_name + FROM workspace_invitations i + JOIN workspaces w ON i.workspace_id = w.id + JOIN system_users u ON i.created_by = u.id + WHERE i.token = ? AND i.used_by IS NULL AND i.expires_at > NOW()`, + [token] + ); + + // 转换为纯JavaScript对象 + const invitations = JSON.parse(JSON.stringify(rows)); + + // 打印查询结果,用于调试 + console.log('邀请记录查询结果:', JSON.stringify(invitations && invitations.length > 0 ? invitations[0] : {}, null, 2)); + + // 如果未找到有效令牌 + if (!invitations || invitations.length === 0) { + console.log('未找到有效的邀请令牌'); + return NextResponse.json( + { + success: false, + error: '无效的邀请令牌或已过期' + }, + { status: 404 } + ); + } + + const invitation = invitations[0]; + console.log('找到有效的邀请令牌:', invitation.id); + + // 检查是否有新的角色字段 + const hasNewRoleFields = invitation.hasOwnProperty('role_type') && + invitation.hasOwnProperty('role_name') && + invitation.hasOwnProperty('is_custom_role'); + + // 确保字段名称统一 + const responseData = { + success: true, + valid: true, + workspaceId: invitation.workspace_id, + workspaceName: invitation.workspace_name, + inviterId: invitation.created_by, + inviterName: invitation.inviter_name, + role: invitation.role, + // 新角色系统字段 + role_type: hasNewRoleFields ? invitation.role_type : invitation.role, + role_name: hasNewRoleFields ? invitation.role_name : (invitation.role === 'admin' ? '管理员' : '普通用户'), + is_custom_role: hasNewRoleFields ? invitation.is_custom_role : false, + expiresAt: invitation.expires_at, + // 增加额外的字段格式,保证前端能够正确读取 + workspace_id: invitation.workspace_id, + workspace_name: invitation.workspace_name, + created_by: invitation.created_by, + inviter_name: invitation.inviter_name + }; + + console.log('响应数据:', responseData); + + // 返回邀请相关信息 + return NextResponse.json(responseData); + }; + + // 执行处理函数 + const wrappedHandler = connectSystemDB(handler); + return await wrappedHandler(request); + + } catch (error: unknown) { + console.error('验证邀请令牌失败:', error); + + return NextResponse.json( + { + success: false, + error: '验证邀请令牌失败: ' + (error instanceof Error ? error.message : String(error)) + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/cron/init/route.ts b/src/app/api/cron/init/route.ts new file mode 100644 index 0000000..5f2d83a --- /dev/null +++ b/src/app/api/cron/init/route.ts @@ -0,0 +1,391 @@ +/** + * 物流定时任务初始化API路由 + * 作者: 阿瑞 + * 功能: 初始化物流查询定时任务 + * 版本: 1.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import * as nodeCron from 'node-cron'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { querySFExpress } from '@/utils/querySFExpress'; +import { RowDataPacket, Connection } from 'mysql2/promise'; + +// 存储正在运行的cron任务 +const runningCronTasks: Record = {}; + +// 存储任务状态 +const cronTaskStatus: Record = {}; + +/** + * 查询需要更新的物流记录 + */ +async function fetchPendingLogistics(db: Connection, teamCode: string) { + try { + // 查询满足条件的物流记录:顺丰快递、可查询的、状态不是已签收 + const [records] = await db.query(` + SELECT + id, + tracking_number, + customer_tail_number, + company, + status + FROM + logistics_records + WHERE + company = '顺丰速运' + AND is_queryable = 1 + AND (status IS NULL OR status != '已签收') + LIMIT 100 + `) as [RowDataPacket[], unknown]; + + return records; + } catch (error) { + console.error(`[${teamCode}] 查询待更新物流记录失败:`, error); + return []; + } +} + +/** + * 更新物流记录状态 + */ +async function updateLogisticsStatus(db: Connection, record: Record, sfResponse: Record, teamCode: string) { + try { + // 解析API返回的物流信息 + const resultData = typeof sfResponse.apiResultData === 'string' + ? JSON.parse(sfResponse.apiResultData) + : sfResponse.apiResultData; + + // 获取路由信息(兼容新旧格式) + const routeInfo = resultData?.msgData?.routeResps?.[0] || {}; + + // 提取状态信息 + let status = null; + let details = null; + + // 处理有路由数据的情况 + if (routeInfo.routes && Array.isArray(routeInfo.routes) && routeInfo.routes.length > 0) { + // 保存完整的物流详情 + details = JSON.stringify(routeInfo); + + // 分析最新的物流状态 + const latestRoute = routeInfo.routes[0]; + const content = latestRoute.remark || latestRoute.content || ''; + + // 根据描述内容识别状态 + if (content.includes('已签收') || content.includes('签收')) { + status = '已签收'; + } else if (content.includes('派送') || content.includes('派件')) { + status = '派送中'; + } else if (content.includes('运输') || content.includes('发往')) { + status = '运输中'; + } else if (content.includes('已揽收') || content.includes('揽件') || content.includes('已收取')) { + status = '已揽件'; + } else { + status = '已填单'; + } + } + + // 更新数据库记录 + if (status || details) { + console.log(`[${teamCode}] 更新物流状态: ID=${record.id}, 状态=${status}`); + + const updateFields = []; + const updateValues = []; + + if (status) { + updateFields.push('status = ?'); + updateValues.push(status); + } + + if (details) { + updateFields.push('details = ?'); + updateValues.push(details); + } + + if (updateFields.length > 0) { + updateFields.push('updated_at = CURRENT_TIMESTAMP'); + + const updateQuery = ` + UPDATE logistics_records + SET ${updateFields.join(', ')} + WHERE id = ? + `; + + await db.query(updateQuery, [...updateValues, record.id]); + return true; + } + } + + return false; + } catch (error) { + console.error(`[${teamCode}] 更新物流记录状态失败:`, error); + return false; + } +} + +/** + * 执行物流记录更新 + */ +async function executeUpdate(teamCode: string) { + console.log(`[${teamCode}] 开始自动查询物流记录...`); + + // 更新状态 + cronTaskStatus[teamCode] = { + ...cronTaskStatus[teamCode] || {}, + currentStatus: 'running', + lastRunTime: new Date().toISOString() + }; + + let successCount = 0; + let errorCount = 0; + + try { + // 使用connectTeamDB创建带连接的请求 + // 创建正确的请求对象,确保包含团队标识 + const url = new URL(`http://localhost/api/cron/init`); + url.searchParams.set('teamId', teamCode); + + const headers = new Headers(); + headers.set('x-team-id', teamCode); + + const request = new NextRequest(url, { + method: 'GET', + headers + }); + + return await connectTeamDB(async (req: RequestWithDB) => { + const db = req.db; + + // 查询需要更新的物流记录 + const records = await fetchPendingLogistics(db, teamCode); + console.log(`[${teamCode}] 找到 ${records.length} 条需更新的物流记录`); + + // 逐个更新物流记录 + for (const record of records) { + try { + const trackingNumber = record.tracking_number; + const phoneLast4Digits = record.customer_tail_number; + + // 跳过无效记录 + if (!trackingNumber || !phoneLast4Digits) { + errorCount++; + continue; + } + + // 调用顺丰API查询物流 + console.log(`[${teamCode}] 查询物流: ${trackingNumber}, 手机尾号: ${phoneLast4Digits}`); + const sfResponse = await querySFExpress(trackingNumber, phoneLast4Digits); + + // 处理API错误 + if (sfResponse.apiResultCode !== '0000' && sfResponse.apiResultCode !== 'S0000') { + console.error(`[${teamCode}] 物流API错误: ${sfResponse.apiErrorMsg}`); + errorCount++; + continue; + } + + // 更新物流状态 + const updated = await updateLogisticsStatus(db, record, sfResponse, teamCode); + if (updated) { + successCount++; + } + } catch (error) { + console.error(`[${teamCode}] 处理物流记录失败:`, error); + errorCount++; + } + } + + console.log(`[${teamCode}] 物流记录更新完成: 成功=${successCount}, 失败=${errorCount}`); + + // 更新任务状态 + cronTaskStatus[teamCode] = { + ...cronTaskStatus[teamCode], + currentStatus: 'completed', + runCount: (cronTaskStatus[teamCode]?.runCount || 0) + 1 + }; + + return NextResponse.json({ + success: true, + message: '物流记录查询完成', + results: { + successCount, + errorCount, + totalProcessed: records.length + } + }); + })(request); + } catch (error) { + console.error(`[${teamCode}] 执行物流更新失败:`, error); + + // 更新任务状态为失败 + cronTaskStatus[teamCode] = { + ...cronTaskStatus[teamCode], + currentStatus: 'failed' + }; + + return NextResponse.json( + { success: false, error: '执行物流更新失败' }, + { status: 500 } + ); + } +} + +/** + * 启动定时更新任务 + */ +function startCronTask(teamCode: string) { + // 检查是否已存在任务 + if (runningCronTasks[teamCode]) { + console.log(`[${teamCode}] 物流定时任务已在运行中`); + return false; + } + + console.log(`[${teamCode}] 启动物流定时任务`); + + try { + // 创建定时任务 - 每2小时执行一次 + runningCronTasks[teamCode] = nodeCron.schedule('0 */2 * * *', async () => { + await executeUpdate(teamCode); + }); + + // 计算下次执行时间 + const now = new Date(); + const currentHour = now.getHours(); + const nextHour = currentHour + (currentHour % 2 === 0 ? 2 : 1); + const nextRunDate = new Date(now); + nextRunDate.setHours(nextHour, 0, 0, 0); + + // 初始化任务状态 + cronTaskStatus[teamCode] = { + lastRunTime: null, + nextScheduledTime: nextRunDate.toISOString(), + currentStatus: 'idle', + runCount: 0 + }; + + console.log(`[${teamCode}] 物流定时任务已启动,下次执行时间: ${nextRunDate.toISOString()}`); + return true; + } catch (error) { + console.error(`[${teamCode}] 启动物流定时任务失败:`, error); + return false; + } +} + +/** + * GET 路由处理函数 - 获取任务状态或初始化任务 + */ +export async function GET(request: NextRequest) { + try { + // 从查询参数中获取团队代码 + const { searchParams } = new URL(request.url); + const teamCode = searchParams.get('teamCode'); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '缺少团队代码参数' }, + { status: 400 } + ); + } + + // 将teamCode添加到请求头和查询参数,用于数据库连接 + const headers = new Headers(request.headers); + headers.set('x-team-id', teamCode); + + // 克隆请求并添加团队标识 + const url = new URL(request.url); + url.searchParams.set('teamId', teamCode); + + // 检查是否需要初始化 + const shouldInit = searchParams.get('init') === 'true'; + const runNow = searchParams.get('runNow') === 'true'; + + // 根据参数决定启动还是获取状态 + if (shouldInit) { + const initialized = startCronTask(teamCode); + + if (initialized) { + // 如果需要立即执行一次 + if (runNow) { + // 直接使用executeUpdate函数 + await executeUpdate(teamCode); + } + + return NextResponse.json({ + success: true, + message: '物流定时任务已初始化', + status: cronTaskStatus[teamCode] + }); + } else { + return NextResponse.json({ + success: true, + message: '物流定时任务已在运行中', + status: cronTaskStatus[teamCode] + }); + } + } + + // 返回任务状态 + return NextResponse.json({ + success: true, + taskExists: !!runningCronTasks[teamCode], + status: cronTaskStatus[teamCode] || { + lastRunTime: null, + nextScheduledTime: null, + currentStatus: 'idle', + runCount: 0 + } + }); + } catch (error) { + console.error('物流定时任务初始化/查询失败:', error); + return NextResponse.json( + { success: false, error: '物流定时任务初始化/查询失败' }, + { status: 500 } + ); + } +} + +/** + * POST 路由处理函数 - 手动触发更新 + */ +export async function POST(request: NextRequest) { + try { + // 从查询参数中获取团队代码 + const { searchParams } = new URL(request.url); + const teamCode = searchParams.get('teamCode'); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '缺少团队代码参数' }, + { status: 400 } + ); + } + + // 将teamCode添加到请求头和查询参数,用于数据库连接 + const headers = new Headers(request.headers); + headers.set('x-team-id', teamCode); + + // 克隆请求并添加团队标识 + const url = new URL(request.url); + url.searchParams.set('teamId', teamCode); + + // 确保任务已初始化 + if (!runningCronTasks[teamCode]) { + startCronTask(teamCode); + } + + // 立即执行更新 + return await executeUpdate(teamCode); + + } catch (error) { + console.error('手动触发物流更新失败:', error); + return NextResponse.json( + { success: false, error: '手动触发物流更新失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/brands/[id]/route.ts b/src/app/api/team/[teamCode]/brands/[id]/route.ts new file mode 100644 index 0000000..2e05ffd --- /dev/null +++ b/src/app/api/team/[teamCode]/brands/[id]/route.ts @@ -0,0 +1,287 @@ +/** + * 品牌详情API路由 + * 作者: 阿瑞 + * 功能: 提供单个品牌数据的获取、更新和删除API + * 版本: 2.1.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { connectTeamDB, RequestWithDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + // 不复制body,因为它可能已被读取 + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/brands/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,品牌ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * 获取单个品牌详情 + * GET /api/team/[teamCode]/brands/[id] + */ +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + // 验证参数 + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的品牌ID' }, + { status: 400 } + ); + } + + // 处理函数 + const handler = async (dbReq: RequestWithDB) => { + // 执行查询 + const [brands] = await dbReq.db.query( + 'SELECT id, `order`, name, description, created_at, updated_at FROM brands WHERE id = ?', + [id] + ); + + // 检查是否找到品牌 + if (!Array.isArray(brands) || brands.length === 0) { + return NextResponse.json( + { success: false, error: '品牌不存在' }, + { status: 404 } + ); + } + + return NextResponse.json({ + success: true, + brand: brands[0] || null + }); + }; + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return await connectTeamDB(handler)(teamReq); + + } catch (error) { + console.error('获取品牌详情失败:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : '未知错误' + }, + { status: 500 } + ); + } +} + +/** + * 更新品牌信息 + * PUT /api/team/[teamCode]/brands/[id] + */ +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + // 验证参数 + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的品牌ID' }, + { status: 400 } + ); + } + + // 获取请求体数据 + let body; + try { + // 克隆请求,确保body只被读取一次 + const clonedReq = req.clone(); + body = await clonedReq.json(); + + // 验证必填字段 + if (!body.name || body.name.trim() === '') { + return NextResponse.json( + { success: false, error: '品牌名称不能为空' }, + { status: 400 } + ); + } + } catch (error) { + console.error('解析请求数据失败:', error); + return NextResponse.json( + { success: false, error: '无效的请求数据' }, + { status: 400 } + ); + } + + // 处理函数 + const handler = async (dbReq: RequestWithDB) => { + // 检查品牌是否存在 + const [existingBrands] = await dbReq.db.query( + 'SELECT id FROM brands WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingBrands) || existingBrands.length === 0) { + return NextResponse.json( + { success: false, error: '品牌不存在' }, + { status: 404 } + ); + } + + // 准备更新数据 + const brandData = { + name: body.name.trim(), + order: typeof body.order === 'number' ? body.order : 0, + description: body.description ? body.description.trim() : null + }; + + // 更新数据 + await dbReq.db.query( + 'UPDATE brands SET `order` = ?, name = ?, description = ? WHERE id = ?', + [brandData.order, brandData.name, brandData.description, id] + ); + + // 获取更新后的品牌数据 + const [updatedBrands] = await dbReq.db.query( + 'SELECT id, `order`, name, description, created_at, updated_at FROM brands WHERE id = ?', + [id] + ); + + return NextResponse.json({ + success: true, + brand: Array.isArray(updatedBrands) && updatedBrands.length > 0 ? updatedBrands[0] : null, + message: '品牌更新成功' + }); + }; + + // 添加团队信息到请求(不包含body) + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return await connectTeamDB(handler)(teamReq); + + } catch (error) { + console.error('更新品牌失败:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : '未知错误' + }, + { status: 500 } + ); + } +} + +/** + * 删除品牌 + * DELETE /api/team/[teamCode]/brands/[id] + */ +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + // 验证参数 + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的品牌ID' }, + { status: 400 } + ); + } + + // 处理函数 + const handler = async (dbReq: RequestWithDB) => { + // 检查品牌是否存在 + const [existingBrands] = await dbReq.db.query( + 'SELECT id FROM brands WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingBrands) || existingBrands.length === 0) { + return NextResponse.json( + { success: false, error: '品牌不存在' }, + { status: 404 } + ); + } + + // 删除品牌 + await dbReq.db.query('DELETE FROM brands WHERE id = ?', [id]); + + return NextResponse.json({ + success: true, + message: '品牌删除成功' + }); + }; + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return await connectTeamDB(handler)(teamReq); + + } catch (error) { + console.error('删除品牌失败:', error); + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : '未知错误' + }, + { status: 500 } + ); + } +}; \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/brands/route.ts b/src/app/api/team/[teamCode]/brands/route.ts new file mode 100644 index 0000000..06e0b6b --- /dev/null +++ b/src/app/api/team/[teamCode]/brands/route.ts @@ -0,0 +1,251 @@ +/** + * 品牌API接口 + * 作者: 阿瑞 + * 功能: 提供品牌数据的查询和创建接口 + * 版本: 2.3.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * GET 获取品牌列表 + */ +const getBrands = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]品牌列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(name LIKE ? OR description LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = `SELECT COUNT(*) as total FROM brands ${whereClause}`; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = (totalRows as Array<{ total: number }>)[0].total; + + // 查询分页数据 + const querySql = ` + SELECT + id, \`order\`, name, description, + created_at as createdAt, updated_at as updatedAt + FROM brands ${whereClause} + ORDER BY \`order\` ASC, id ASC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + console.log(`查询团队[${teamCode}]品牌列表成功, 总数:${total}`); + + return NextResponse.json({ + total, + brands: Array.isArray(rows) ? rows : [] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]品牌列表失败:`, error); + return NextResponse.json( + { error: '获取品牌列表失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 创建品牌 + */ +const createBrand = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取请求数据 + const data = await req.json(); + + // 验证必填字段 + if (!data.name) { + return NextResponse.json({ error: '品牌名称为必填字段' }, { status: 400 }); + } + + try { + console.log(`开始创建团队[${teamCode}]品牌, 名称:${data.name}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查品牌名称是否已存在 + const [existingBrands] = await connection.query( + 'SELECT id FROM brands WHERE name = ?', + [data.name] + ); + + if (Array.isArray(existingBrands) && existingBrands.length > 0) { + await connection.rollback(); + return NextResponse.json({ error: '该品牌名称已存在' }, { status: 409 }); + } + + // 插入品牌记录 + const insertSql = ` + INSERT INTO brands ( + \`order\`, name, description, created_at, updated_at + ) VALUES (?, ?, ?, NOW(), NOW()) + `; + + const [result] = await connection.query(insertSql, [ + data.order || 0, + data.name, + data.description || null + ]); + + const insertId = (result as { insertId: number }).insertId; + + // 查询新插入的品牌信息 + const [newBrandRows] = await connection.query( + `SELECT + id, \`order\`, name, description, + created_at as createdAt, updated_at as updatedAt + FROM brands WHERE id = ?`, + [insertId] + ); + + // 提交事务 + await connection.commit(); + + console.log(`创建团队[${teamCode}]品牌成功, ID:${insertId}`); + + return NextResponse.json({ + message: '品牌创建成功', + brand: Array.isArray(newBrandRows) && newBrandRows.length > 0 ? newBrandRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`创建团队[${teamCode}]品牌失败:`, error); + return NextResponse.json( + { error: '创建品牌失败' }, + { status: 500 } + ); + } +}; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/brands + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引2的位置 + return pathParts[3] || ''; +}; + +// 导出处理函数,使用单参数路由处理器 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getBrands)(teamReq, { teamCode }); + } catch (error) { + console.error('处理品牌列表请求失败:', error); + return NextResponse.json( + { error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(createBrand)(teamReq, { teamCode }); + } catch (error) { + console.error('处理品牌创建请求失败:', error); + return NextResponse.json( + { error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/categories/[id]/route.ts b/src/app/api/team/[teamCode]/categories/[id]/route.ts new file mode 100644 index 0000000..3b1960f --- /dev/null +++ b/src/app/api/team/[teamCode]/categories/[id]/route.ts @@ -0,0 +1,371 @@ +/** + * 单个品类API路由 + * 作者: 阿瑞 + * 功能: 提供单个品类的查询、更新和删除接口 + * 版本: 2.3.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/categories/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,品类ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * 获取单个品类 + */ +const getCategory = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的品类ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始查询团队[${teamCode}]品类, ID:${id}`); + + const [rows] = await req.db.query( + `SELECT + id, name, description, icon, + created_at as createdAt, updated_at as updatedAt + FROM categories WHERE id = ?`, + [id] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应品类' }, + { status: 404 } + ); + } + + console.log(`查询团队[${teamCode}]品类成功, ID:${id}`); + + return NextResponse.json({ + success: true, + category: rows[0] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]品类详情失败:`, error); + return NextResponse.json( + { success: false, error: '获取品类详情失败' }, + { status: 500 } + ); + } +}; + +/** + * 更新品类 + */ +const updateCategory = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的品类ID' }, + { status: 400 } + ); + } + + // 获取请求数据或使用预解析的数据 + const data = req.parsedBody || await req.json(); + + // 验证是否有需要更新的字段 + if (!data.name && !data.description && !data.icon) { + return NextResponse.json( + { success: false, error: '缺少需要更新的字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始更新团队[${teamCode}]品类, ID:${id}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查品类是否存在 + const [existingCategory] = await connection.query( + 'SELECT id FROM categories WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingCategory) || existingCategory.length === 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '未找到相应品类' }, + { status: 404 } + ); + } + + // 如果更新名称,检查名称是否已存在 + if (data.name) { + const [nameCheck] = await connection.query( + 'SELECT id FROM categories WHERE name = ? AND id != ?', + [data.name, id] + ); + + if (Array.isArray(nameCheck) && nameCheck.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该品类名称已存在' }, + { status: 409 } + ); + } + } + + // 构建更新SQL + const updateFields = []; + const updateParams = []; + + if (data.name) { + updateFields.push('name = ?'); + updateParams.push(data.name); + } + + if (data.description !== undefined) { + updateFields.push('description = ?'); + updateParams.push(data.description || null); + } + + if (data.icon !== undefined) { + updateFields.push('icon = ?'); + updateParams.push(data.icon || null); + } + + updateFields.push('updated_at = NOW()'); + + // 添加ID作为WHERE条件参数 + updateParams.push(id); + + const updateSql = ` + UPDATE categories + SET ${updateFields.join(', ')} + WHERE id = ? + `; + + await connection.query(updateSql, updateParams); + + // 查询更新后的品类信息 + const [updatedCategoryRows] = await connection.query( + `SELECT + id, name, description, icon, + created_at as createdAt, updated_at as updatedAt + FROM categories WHERE id = ?`, + [id] + ); + + // 提交事务 + await connection.commit(); + + console.log(`更新团队[${teamCode}]品类成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '品类更新成功', + category: Array.isArray(updatedCategoryRows) && updatedCategoryRows.length > 0 ? updatedCategoryRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`更新团队[${teamCode}]品类失败:`, error); + return NextResponse.json( + { success: false, error: '更新品类失败' }, + { status: 500 } + ); + } +}; + +/** + * 删除品类 + */ +const deleteCategory = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的品类ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始删除团队[${teamCode}]品类, ID:${id}`); + + // 检查品类是否存在 + const [existingCategory] = await req.db.query( + 'SELECT id FROM categories WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingCategory) || existingCategory.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应品类' }, + { status: 404 } + ); + } + + // 直接删除品类记录 + await req.db.query('DELETE FROM categories WHERE id = ?', [id]); + + console.log(`删除团队[${teamCode}]品类成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '品类删除成功' + }); + } catch (error) { + console.error(`删除团队[${teamCode}]品类失败:`, error); + return NextResponse.json( + { success: false, error: '删除品类失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getCategory)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理品类详情请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 克隆请求,确保body只被读取一次 + const clonedReq = req.clone(); + + // 预先读取请求体 + let body; + try { + body = await clonedReq.json(); + } catch { + // 如果无法解析JSON,继续处理 + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 传递body数据到teamReq的headers中,避免重复读取body + if (body) { + teamReq.headers.set('x-request-body', JSON.stringify(body)); + } + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(updateCategory)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理品类更新请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(deleteCategory)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理品类删除请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/categories/route.ts b/src/app/api/team/[teamCode]/categories/route.ts new file mode 100644 index 0000000..c765c4b --- /dev/null +++ b/src/app/api/team/[teamCode]/categories/route.ts @@ -0,0 +1,259 @@ +/** + * 品类API接口 + * 作者: 阿瑞 + * 功能: 提供品类数据的查询和创建接口 + * 版本: 2.3.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/categories + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * GET 获取品类列表 + */ +const getCategories = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]品类列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(name LIKE ? OR description LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = `SELECT COUNT(*) as total FROM categories ${whereClause}`; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = (totalRows as Array<{ total: number }>)[0].total; + + // 查询分页数据 + const querySql = ` + SELECT + id, name, description, icon, + created_at as createdAt, updated_at as updatedAt + FROM categories ${whereClause} + ORDER BY id ASC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + console.log(`查询团队[${teamCode}]品类列表成功, 总数:${total}`); + + return NextResponse.json({ + success: true, + total, + categories: Array.isArray(rows) ? rows : [] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]品类列表失败:`, error); + return NextResponse.json( + { success: false, error: '获取品类列表失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 创建品类 + */ +const createCategory = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取请求数据 + const data = await req.json(); + + // 验证必填字段 + if (!data.name) { + return NextResponse.json( + { success: false, error: '品类名称为必填字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始创建团队[${teamCode}]品类, 名称:${data.name}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查品类名称是否已存在 + const [existingCategories] = await connection.query( + 'SELECT id FROM categories WHERE name = ?', + [data.name] + ); + + if (Array.isArray(existingCategories) && existingCategories.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该品类名称已存在' }, + { status: 409 } + ); + } + + // 插入品类记录 + const insertSql = ` + INSERT INTO categories ( + name, description, icon, created_at, updated_at + ) VALUES (?, ?, ?, NOW(), NOW()) + `; + + const [result] = await connection.query(insertSql, [ + data.name, + data.description || null, + data.icon || null + ]); + + const insertId = (result as { insertId: number }).insertId; + + // 查询新插入的品类信息 + const [newCategoryRows] = await connection.query( + `SELECT + id, name, description, icon, + created_at as createdAt, updated_at as updatedAt + FROM categories WHERE id = ?`, + [insertId] + ); + + // 提交事务 + await connection.commit(); + + console.log(`创建团队[${teamCode}]品类成功, ID:${insertId}`); + + return NextResponse.json({ + success: true, + message: '品类创建成功', + category: Array.isArray(newCategoryRows) && newCategoryRows.length > 0 ? newCategoryRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`创建团队[${teamCode}]品类失败:`, error); + return NextResponse.json( + { success: false, error: '创建品类失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getCategories)(teamReq, { teamCode }); + } catch (error) { + console.error('处理品类列表请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(createCategory)(teamReq, { teamCode }); + } catch (error) { + console.error('处理品类创建请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/customers/[id]/route.ts b/src/app/api/team/[teamCode]/customers/[id]/route.ts new file mode 100644 index 0000000..187bd1e --- /dev/null +++ b/src/app/api/team/[teamCode]/customers/[id]/route.ts @@ -0,0 +1,444 @@ +/** + * 客户详情API路由 + * 作者: 阿瑞 + * 功能: 提供单个客户数据的获取、更新和删除API + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + // 不复制body,因为它可能已被读取 + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/customers/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,客户ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * 获取单个客户详情 + */ +const getCustomerDetail = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的客户ID' }, + { status: 400 } + ); + } + + try { + // 查询客户信息 + const [rows] = await req.db.query( + `SELECT + id, name, phone, gender, wechat, + address, birthday, follow_date as followDate, balance, + created_at as createdAt, updated_at as updatedAt + FROM customers + WHERE id = ? AND deleted_at IS NULL`, + [id] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return NextResponse.json( + { success: false, error: '客户不存在' }, + { status: 404 } + ); + } + + return NextResponse.json({ + success: true, + customer: rows[0] + }); + } catch (error) { + console.error('获取客户详情失败:', error); + return NextResponse.json( + { success: false, error: '获取客户详情失败' }, + { status: 500 } + ); + } +}; + +/** + * 更新客户信息 + */ +interface CustomerRequestBody { + id?: number; + name?: string; + phone?: string; + gender?: number; + wechat?: string; + birthday?: string; + followDate?: string; + balance?: number; + address?: string | null | { + province?: string | null; + city?: string | null; + district?: string | null; + county?: string | null; + detail?: string | null; + }; + [key: string]: unknown; // 索引签名允许动态访问属性 +} + +const updateCustomer = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const data = params?.requestBody as CustomerRequestBody; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的客户ID' }, + { status: 400 } + ); + } + + if (!data) { + return NextResponse.json( + { success: false, error: '请求数据为空' }, + { status: 400 } + ); + } + + // 验证ID是否匹配 + if (data.id !== undefined && data.id !== Number(id)) { + return NextResponse.json( + { success: false, error: '客户ID不匹配' }, + { status: 400 } + ); + } + + try { + // 检查客户是否存在 + const [existingCustomers] = await req.db.query( + 'SELECT id FROM customers WHERE id = ? AND deleted_at IS NULL', + [id] + ); + + if (!Array.isArray(existingCustomers) || existingCustomers.length === 0) { + return NextResponse.json( + { success: false, error: '客户不存在' }, + { status: 404 } + ); + } + + // 如果更新手机号,检查是否与其他客户重复 + if (data.phone) { + const [phoneCheck] = await req.db.query( + 'SELECT id FROM customers WHERE phone = ? AND id != ? AND deleted_at IS NULL', + [data.phone, id] + ); + + if (Array.isArray(phoneCheck) && phoneCheck.length > 0) { + return NextResponse.json( + { success: false, error: '该手机号已被其他客户使用' }, + { status: 409 } + ); + } + } + + // 处理地址字段,确保正确存储结构化地址 + let addressJson: string | null = null; + if (data.address !== undefined) { + if (data.address === null) { + addressJson = null; + } else { + // 标准化地址对象结构 + const addressObj: { + province: string | null; + city: string | null; + district: string | null; + detail: string | null; + } = { + province: null, + city: null, + district: null, + detail: null + }; + + if (typeof data.address === 'string') { + // 如果是字符串,作为详细地址存储 + addressObj.detail = data.address; + } else if (typeof data.address === 'object' && data.address !== null) { + // 将提供的地址对象结构化 + const addrObj = data.address as { + province?: string | null; + city?: string | null; + district?: string | null; + county?: string | null; + detail?: string | null; + }; + if (addrObj.province) addressObj.province = addrObj.province; + if (addrObj.city) addressObj.city = addrObj.city; + if (addrObj.district || addrObj.county) { + addressObj.district = addrObj.district || addrObj.county || null; + } + if (addrObj.detail) addressObj.detail = addrObj.detail; + } + + // 只有当至少有一个地址字段有值时才存储地址 + if (addressObj.province || addressObj.city || addressObj.district || addressObj.detail) { + addressJson = JSON.stringify(addressObj); + } else { + addressJson = null; + } + } + } + + // 构建更新字段 + const updateFields: string[] = []; + const updateValues: (string | number | null)[] = []; + + // 可更新的字段列表 + const fieldMapping: Record = { + 'name': 'name', + 'phone': 'phone', + 'gender': 'gender', + 'wechat': 'wechat', + 'birthday': 'birthday', + 'followDate': 'follow_date', + 'balance': 'balance' + }; + + // 特殊处理地址字段 + if (addressJson !== undefined) { + updateFields.push('address = ?'); + updateValues.push(addressJson); + } + + // 遍历请求数据,构建更新语句 + Object.entries(fieldMapping).forEach(([clientField, dbField]) => { + if (data[clientField] !== undefined) { + updateFields.push(`${dbField} = ?`); + + // 特殊处理日期字段,空字符串转为NULL + if ((clientField === 'birthday' || clientField === 'followDate') && data[clientField] === '') { + updateValues.push(null); + } else { + const value = data[clientField]; + // 类型安全处理:确保值为string、number或null + if (typeof value === 'object' && value !== null) { + updateValues.push(null); + } else { + updateValues.push(value as string | number | null); + } + } + } + }); + + // 添加更新时间 + updateFields.push('updated_at = NOW()'); + + // 如果没有要更新的字段,返回成功 + if (updateFields.length === 0) { + return NextResponse.json({ + success: true, + message: '无需更新' + }); + } + + // 构建并执行更新SQL + const updateSql = `UPDATE customers SET ${updateFields.join(', ')} WHERE id = ?`; + updateValues.push(id); + + await req.db.query(updateSql, updateValues); + + // 获取更新后的客户信息 + const [updatedRows] = await req.db.query( + `SELECT + id, name, phone, gender, wechat, + address, birthday, follow_date as followDate, balance, + created_at as createdAt, updated_at as updatedAt + FROM customers + WHERE id = ?`, + [id] + ); + + return NextResponse.json({ + success: true, + message: '客户更新成功', + customer: Array.isArray(updatedRows) && updatedRows.length > 0 ? updatedRows[0] : null + }); + } catch (error) { + console.error('更新客户失败:', error); + return NextResponse.json( + { success: false, error: '更新客户失败' }, + { status: 500 } + ); + } +}; + +/** + * 删除客户 + */ +const deleteCustomer = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的客户ID' }, + { status: 400 } + ); + } + + try { + // 检查客户是否存在 + const [existingCustomers] = await req.db.query( + 'SELECT id FROM customers WHERE id = ? AND deleted_at IS NULL', + [id] + ); + + if (!Array.isArray(existingCustomers) || existingCustomers.length === 0) { + return NextResponse.json( + { success: false, error: '客户不存在' }, + { status: 404 } + ); + } + + // 软删除:设置deleted_at字段 + await req.db.query( + 'UPDATE customers SET deleted_at = NOW(), updated_at = NOW() WHERE id = ?', + [id] + ); + + return NextResponse.json({ + success: true, + message: '客户删除成功' + }); + } catch (error) { + console.error('删除客户失败:', error); + return NextResponse.json( + { success: false, error: '删除客户失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getCustomerDetail)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理客户详情请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 先读取请求体 + let requestBody; + try { + // 克隆请求,确保body只被读取一次 + const clonedReq = req.clone(); + requestBody = await clonedReq.json(); + } catch (error) { + console.error('解析请求数据失败:', error); + return NextResponse.json( + { success: false, error: '无效的请求数据格式' }, + { status: 400 } + ); + } + + // 添加团队信息到请求(不传递body) + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(updateCustomer)(teamReq, { teamCode, id, requestBody }); + } catch (error) { + console.error('处理客户更新请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(deleteCustomer)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理客户删除请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/customers/route.ts b/src/app/api/team/[teamCode]/customers/route.ts new file mode 100644 index 0000000..9f1e63c --- /dev/null +++ b/src/app/api/team/[teamCode]/customers/route.ts @@ -0,0 +1,300 @@ +/** + * 客户API接口 + * 作者: 阿瑞 + * 功能: 提供客户数据的查询和创建接口 + * 版本: 2.3.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/customers + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * GET 获取客户列表 + */ +const getCustomers = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]客户列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(name LIKE ? OR phone LIKE ? OR wechat LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`); + } + + // 只查询未删除的客户 + conditions.push('deleted_at IS NULL'); + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = `SELECT COUNT(*) as total FROM customers ${whereClause}`; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = (totalRows as Array<{ total: number }>)[0].total; + + // 查询分页数据 + const querySql = ` + SELECT + id, name, phone, gender, wechat, + address, birthday, follow_date as followDate, balance, + created_at as createdAt, updated_at as updatedAt + FROM customers ${whereClause} + ORDER BY created_at DESC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + console.log(`查询团队[${teamCode}]客户列表成功, 总数:${total}`); + + return NextResponse.json({ + success: true, + total, + customers: Array.isArray(rows) ? rows : [] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]客户列表失败:`, error); + return NextResponse.json( + { success: false, error: '获取客户列表失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 创建客户 + */ +const createCustomer = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取请求数据 + const data = await req.json(); + + // 验证必填字段 + if (!data.name || !data.phone) { + return NextResponse.json( + { success: false, error: '姓名和手机号为必填字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始创建团队[${teamCode}]客户, 姓名:${data.name}, 手机:${data.phone}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查手机号是否已存在 + const [existingCustomers] = await connection.query( + 'SELECT id FROM customers WHERE phone = ? AND deleted_at IS NULL', + [data.phone] + ); + + if (Array.isArray(existingCustomers) && existingCustomers.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该手机号已被注册' }, + { status: 409 } + ); + } + + // 处理地址字段,确保正确存储结构化地址 + let addressJson = null; + if (data.address) { + // 标准化地址对象结构 + const addressObj = { + province: null, + city: null, + district: null, + detail: null + }; + + if (typeof data.address === 'string') { + // 如果是字符串,作为详细地址存储 + addressObj.detail = data.address; + } else if (typeof data.address === 'object') { + // 将提供的地址对象结构化 + if (data.address.province) addressObj.province = data.address.province; + if (data.address.city) addressObj.city = data.address.city; + if (data.address.district || data.address.county) { + addressObj.district = data.address.district || data.address.county; + } + if (data.address.detail) addressObj.detail = data.address.detail; + } + + // 只有当至少有一个地址字段有值时才存储地址 + if (addressObj.province || addressObj.city || addressObj.district || addressObj.detail) { + addressJson = JSON.stringify(addressObj); + } + } + + // 插入客户记录 + const insertSql = ` + INSERT INTO customers ( + name, phone, gender, wechat, address, birthday, + follow_date, balance, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `; + + const [result] = await connection.query(insertSql, [ + data.name, + data.phone, + data.gender || null, + data.wechat || null, + addressJson, + data.birthday || null, + data.followDate || null, + data.balance || 0 + ]); + + const insertId = (result as { insertId: number }).insertId; + + // 查询新插入的客户信息 + const [newCustomerRows] = await connection.query( + `SELECT + id, name, phone, gender, wechat, + address, birthday, follow_date as followDate, balance, + created_at as createdAt, updated_at as updatedAt + FROM customers WHERE id = ?`, + [insertId] + ); + + // 提交事务 + await connection.commit(); + + console.log(`创建团队[${teamCode}]客户成功, ID:${insertId}`); + + return NextResponse.json({ + success: true, + message: '客户创建成功', + customer: Array.isArray(newCustomerRows) && newCustomerRows.length > 0 ? newCustomerRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`创建团队[${teamCode}]客户失败:`, error); + return NextResponse.json( + { success: false, error: '创建客户失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getCustomers)(teamReq, { teamCode }); + } catch (error) { + console.error('处理客户列表请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(createCustomer)(teamReq, { teamCode }); + } catch (error) { + console.error('处理客户创建请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/logistics/query/route.ts b/src/app/api/team/[teamCode]/logistics/query/route.ts new file mode 100644 index 0000000..873311a --- /dev/null +++ b/src/app/api/team/[teamCode]/logistics/query/route.ts @@ -0,0 +1,212 @@ +/** + * 物流查询API路由 + * 作者: 阿瑞 + * 功能: 查询物流单号状态 + * 版本: 1.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { querySFExpress } from '@/utils/querySFExpress'; +import { RowDataPacket } from 'mysql2/promise'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 物流记录数据接口 + */ +interface LogisticsRecord extends RowDataPacket { + id: number; + tracking_number: string; + is_queryable: boolean; + customer_tail_number: string; + company: string; + details?: string; + status?: string; + record_id: number; + record_type: string; + created_at: string; + updated_at: string; +} + +/** + * 查询物流信息处理函数 + */ +const queryLogistics = async (req: RequestWithDB, params?: Record) => { + try { + // 获取团队代码 + const teamCode = params?.teamCode as string; + + if (!teamCode) { + return NextResponse.json( + { error: '缺少团队代码参数' }, + { status: 400 } + ); + } + + // 获取并验证请求参数 + const body = await req.json(); + const { id, trackingNumber, phoneLast4Digits } = body; + + if (!id) { + return NextResponse.json( + { error: '缺少记录ID' }, + { status: 400 } + ); + } + + if (!trackingNumber) { + return NextResponse.json( + { error: '缺少物流单号' }, + { status: 400 } + ); + } + + if (!phoneLast4Digits) { + return NextResponse.json( + { error: '缺少手机尾号' }, + { status: 400 } + ); + } + + // 调用顺丰物流查询API + const sfResult = await querySFExpress(trackingNumber, phoneLast4Digits); + + // 记录API调用结果,用于调试 + console.log(`物流查询结果 - 单号: ${trackingNumber}, 状态码: ${sfResult.apiResultCode}`); + + if (sfResult.apiResultCode !== '0000' && sfResult.apiResultCode !== 'S0000') { + // 详细记录错误信息 + console.error(`顺丰API查询失败: ${JSON.stringify({ + id, + trackingNumber, + phoneLast4Digits, + errorCode: sfResult.apiResultCode, + errorMsg: sfResult.apiErrorMsg, + originalError: sfResult.error + }, null, 2)}`); + + return NextResponse.json( + { + error: `查询物流失败: ${sfResult.apiErrorMsg}`, + details: sfResult, + params: { + id, + trackingNumber, + phoneLast4DigitsMasked: phoneLast4Digits ? `****${phoneLast4Digits.substring(Math.max(0, phoneLast4Digits.length - 4))}` : null + } + }, + { status: 500 } + ); + } + + // 解析API返回的物流信息 + // 根据修改后的querySFExpress,结果可能直接是解析后的对象 + const resultData = typeof sfResult.apiResultData === 'string' + ? JSON.parse(sfResult.apiResultData) + : sfResult.apiResultData; + + // 获取路由信息(兼容新旧格式) + const routeInfo = resultData?.msgData?.routeResps?.[0] || {}; + + // 确保有有效的物流路由数据 + if (!routeInfo.routes || !Array.isArray(routeInfo.routes)) { + console.log('顺丰API没有返回有效的物流路由数据'); + } else { + console.log(`顺丰API返回了${routeInfo.routes.length}条物流路由记录`); + } + + // 提取状态信息 + let status = null; + if (routeInfo.routes && routeInfo.routes.length > 0) { + const latestRoute = routeInfo.routes[0]; + + // 根据内容识别状态 + const content = latestRoute.remark || ''; + if (content.includes('已签收')) { + status = '已签收'; + } else if (content.includes('派送中') || content.includes('派件中')) { + status = '运输中'; + } else if (content.includes('已收取') || content.includes('已揽件')) { + status = '已揽件'; + } + } + + // 获取数据库连接 + const connection = req.db; + + // 更新数据库中的物流记录 + const details = JSON.stringify({ + routes: routeInfo.routes || [], + routeType: routeInfo.routeType, + mailNo: routeInfo.mailNo, + queryTime: new Date().toISOString() + }); + + console.log('即将更新物流记录:', { + id, + status, + hasDetails: !!details + }); + + await connection.query( + `UPDATE logistics_records + SET status = ?, + details = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [status, details, id] + ); + + // 获取完整的物流记录信息 + const [logisticsRecords] = await connection.query( + `SELECT * FROM logistics_records WHERE id = ?`, + [id] + ); + + if (logisticsRecords.length === 0) { + return NextResponse.json( + { error: '物流记录不存在' }, + { status: 404 } + ); + } + + const logisticsRecord = logisticsRecords[0]; + + // 不需要查询关联产品,直接返回物流信息即可 + + // 返回成功响应 + return NextResponse.json({ + success: true, + message: '查询物流信息成功', + data: logisticsRecord, + sfResponse: sfResult + }); + + } catch (error) { + console.error('查询物流API错误:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : '查询物流失败' }, + { status: 500 } + ); + } +}; + +/** + * 处理POST请求 + */ +export async function POST(request: NextRequest) { + try { + // 从URL路径中提取团队代码 + const pathname = request.nextUrl.pathname; + const teamCode = pathname.split('/')[3]; // /api/team/[teamCode]/... + + return connectTeamDB((dbReq: RequestWithDB) => { + return queryLogistics(dbReq, { teamCode }); + })(request); + } catch (error) { + console.error('处理POST请求失败:', error); + return NextResponse.json( + { error: '处理请求失败', details: (error as Error).message }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/logistics/route.ts b/src/app/api/team/[teamCode]/logistics/route.ts new file mode 100644 index 0000000..16b00f1 --- /dev/null +++ b/src/app/api/team/[teamCode]/logistics/route.ts @@ -0,0 +1,483 @@ +/** + * 物流记录API路由 + * 作者: 阿瑞 + * 功能: 提供物流记录的查询和创建接口 + * 版本: 1.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RowDataPacket, ResultSetHeader } from 'mysql2/promise'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RecordType } from '@/models/team/types/ILogisticsRecord'; + +/** + * 物流记录请求参数接口 + */ +interface LogisticsRequestParams { + salesRecordId: number; + trackingNumber: string; + company: string; + customerTailNumber: string; + isQueryable: boolean; + products: { + productId: number; + quantity: number; + }[]; +} + +/** + * 物流记录数据接口 + */ +interface LogisticsRecord extends RowDataPacket { + id: number; + tracking_number: string; + is_queryable: boolean; + customer_tail_number: string; + company: string; + status?: string; + record_id: number; + record_type: RecordType; + created_at: string; + updated_at: string; +} + +/** + * 物流产品数据接口 + */ +interface LogisticsProduct extends RowDataPacket { + id: number; + logistics_id: number; + product_id: number; + quantity: number; + created_at: string; +} + +/** + * 创建物流记录 + */ +const createLogisticsRecord = async (req: RequestWithDB, params?: Record) => { + try { + // 正确处理动态路由参数 + const teamCode = params?.teamCode as string; + + if (!teamCode) { + return NextResponse.json( + { error: '缺少团队代码参数' }, + { status: 400 } + ); + } + + // 解析请求数据 + const requestData: LogisticsRequestParams = await req.json(); + const { + salesRecordId, + trackingNumber, + company, + customerTailNumber, + isQueryable, + products + } = requestData; + + // 数据验证 + if (!salesRecordId) { + return NextResponse.json( + { error: '销售记录ID不能为空' }, + { status: 400 } + ); + } + + if (!trackingNumber) { + return NextResponse.json( + { error: '物流单号不能为空' }, + { status: 400 } + ); + } + + if (!company) { + return NextResponse.json( + { error: '物流公司不能为空' }, + { status: 400 } + ); + } + + if (!customerTailNumber) { + return NextResponse.json( + { error: '客户手机尾号不能为空' }, + { status: 400 } + ); + } + + if (!products || !Array.isArray(products) || products.length === 0) { + return NextResponse.json( + { error: '发货产品不能为空' }, + { status: 400 } + ); + } + + // 获取数据库连接 + const connection = req.db; + + // 检查销售记录是否存在 + const [salesRecords] = await connection.query( + 'SELECT id, order_status FROM sales_records WHERE id = ?', + [salesRecordId] + ); + + if (salesRecords.length === 0) { + return NextResponse.json( + { error: '销售记录不存在' }, + { status: 404 } + ); + } + + // 检查销售记录是否已有物流记录 + const [existingLogistics] = await connection.query( + `SELECT id FROM logistics_records + WHERE record_id = ? AND record_type = ?`, + [salesRecordId, RecordType.SALES_RECORD] + ); + + let logisticsId: number; + let isUpdate = false; + + if (existingLogistics.length > 0) { + // 更新现有物流记录 + isUpdate = true; + logisticsId = existingLogistics[0].id; + + await connection.query( + `UPDATE logistics_records + SET tracking_number = ?, + is_queryable = ?, + customer_tail_number = ?, + company = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [ + trackingNumber, + isQueryable, + customerTailNumber, + company, + logisticsId + ] + ); + + // 删除旧的产品关联 + await connection.query( + `DELETE FROM logistics_products WHERE logistics_id = ?`, + [logisticsId] + ); + + // 更新记录时不改变订单状态 + } else { + // 创建新物流记录 + const [logisticsResult] = await connection.query( + `INSERT INTO logistics_records + (tracking_number, is_queryable, customer_tail_number, company, status, record_id, record_type) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + [ + trackingNumber, + isQueryable, + customerTailNumber, + company, + '已填单', // 初始状态改为已填单 + salesRecordId, + RecordType.SALES_RECORD + ] + ); + + logisticsId = logisticsResult.insertId; + + // 更新销售记录的订单状态(仅对新记录) + // 确保初始状态为"已填单" + await connection.query( + `UPDATE sales_records + SET order_status = + CASE + WHEN order_status IS NULL THEN JSON_ARRAY('已填单') + ELSE order_status + END + WHERE id = ?`, + [salesRecordId] + ); + } + + // 批量插入物流产品关联记录 + if (products.length > 0) { + const logisticsProductValues = products.map(product => [ + logisticsId, + product.productId, + product.quantity + ]); + + await connection.query( + `INSERT INTO logistics_products (logistics_id, product_id, quantity) VALUES ?`, + [logisticsProductValues] + ); + } + + // 查询更新后的物流记录详情 + const [logisticsRecords] = await connection.query( + `SELECT * FROM logistics_records WHERE id = ?`, + [logisticsId] + ); + + const createdLogistics = logisticsRecords[0]; + + // 查询关联的产品信息 + const [logisticsProducts] = await connection.query( + `SELECT + lp.id, lp.logistics_id, lp.product_id, lp.quantity, lp.created_at, + p.name as product_name, p.code as product_code, p.image + FROM logistics_products lp + JOIN products p ON lp.product_id = p.id + WHERE lp.logistics_id = ?`, + [logisticsId] + ); + + return NextResponse.json({ + success: true, + message: isUpdate ? '物流记录更新成功' : '物流记录创建成功', + data: { + ...createdLogistics, + products: logisticsProducts + } + }); + + } catch (error) { + console.error('创建物流记录失败:', error); + return NextResponse.json( + { error: '创建物流记录失败', details: (error as Error).message }, + { status: 500 } + ); + } +}; + +/** + * 获取物流记录列表 + */ +const getLogisticsRecords = async (req: RequestWithDB, params?: Record) => { + try { + // 正确处理动态路由参数 + const teamCode = params?.teamCode as string; + + if (!teamCode) { + return NextResponse.json( + { error: '缺少团队代码参数' }, + { status: 400 } + ); + } + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const recordId = url.searchParams.get('recordId') || ''; + const recordType = url.searchParams.get('recordType') || ''; + const trackingNumber = url.searchParams.get('trackingNumber') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (recordId) { + conditions.push('lr.record_id = ?'); + queryParams.push(recordId); + } + + if (recordType) { + conditions.push('lr.record_type = ?'); + queryParams.push(recordType); + } + + if (trackingNumber) { + conditions.push('lr.tracking_number LIKE ?'); + queryParams.push(`%${trackingNumber}%`); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = ` + SELECT COUNT(*) as total + FROM logistics_records lr + ${whereClause} + `; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = totalRows[0].total as number; + + // 查询分页数据 + const querySql = ` + SELECT + lr.id, lr.tracking_number, lr.is_queryable, lr.customer_tail_number, + lr.company, lr.status, lr.record_id, lr.record_type, + lr.created_at, lr.updated_at + FROM logistics_records lr + ${whereClause} + ORDER BY lr.created_at DESC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + // 查询每条物流记录关联的产品 + const logisticsRecords = rows; + + if (logisticsRecords.length > 0) { + for (let i = 0; i < logisticsRecords.length; i++) { + const record = logisticsRecords[i]; + + // 查询关联产品 + const [productRows] = await connection.query(` + SELECT + lp.id, lp.logistics_id, lp.product_id, lp.quantity, lp.created_at, + p.name as product_name, p.code as product_code, p.image + FROM logistics_products lp + JOIN products p ON lp.product_id = p.id + WHERE lp.logistics_id = ? + `, [record.id]); + + record.products = productRows; + } + } + + return NextResponse.json({ + total, + records: logisticsRecords + }); + + } catch (error) { + console.error('获取物流记录列表失败:', error); + return NextResponse.json( + { error: '获取物流记录列表失败', details: (error as Error).message }, + { status: 500 } + ); + } +}; + +/** + * 获取单个物流记录详情 + */ +const getLogisticsRecord = async (req: RequestWithDB, params?: Record) => { + try { + // 正确处理动态路由参数 + const teamCode = params?.teamCode as string; + const logisticsId = params?.logisticsId as string; + + if (!teamCode || !logisticsId) { + return NextResponse.json( + { error: '缺少必要参数' }, + { status: 400 } + ); + } + + // 使用数据库连接 + const connection = req.db; + + // 查询物流记录 + const [logisticsRecords] = await connection.query( + `SELECT * FROM logistics_records WHERE id = ?`, + [logisticsId] + ); + + if (logisticsRecords.length === 0) { + return NextResponse.json( + { error: '物流记录不存在' }, + { status: 404 } + ); + } + + const logisticsRecord = logisticsRecords[0]; + + // 查询关联产品 + const [productRows] = await connection.query(` + SELECT + lp.id, lp.logistics_id, lp.product_id, lp.quantity, lp.created_at, + p.name as product_name, p.code as product_code, p.image + FROM logistics_products lp + JOIN products p ON lp.product_id = p.id + WHERE lp.logistics_id = ? + `, [logisticsId]); + + logisticsRecord.products = productRows; + + return NextResponse.json({ + record: logisticsRecord + }); + + } catch (error) { + console.error('获取物流记录详情失败:', error); + return NextResponse.json( + { error: '获取物流记录详情失败', details: (error as Error).message }, + { status: 500 } + ); + } +}; + +/** + * 处理GET请求 - 获取物流记录列表 + */ +export async function GET(request: NextRequest) { + try { + // 从URL路径中提取团队代码 + const pathname = request.nextUrl.pathname; + const teamCode = pathname.split('/')[3]; // /api/team/[teamCode]/... + + // 检查是否请求单个物流记录 + if (pathname.includes('/logistics/')) { + const pathParts = pathname.split('/'); + const logisticsId = pathParts[pathParts.length - 1]; + + if (logisticsId && /^\d+$/.test(logisticsId)) { + return connectTeamDB((dbReq: RequestWithDB) => { + return getLogisticsRecord(dbReq, { + teamCode, + logisticsId + }); + })(request); + } + } + + // 否则返回物流记录列表 + return connectTeamDB((dbReq: RequestWithDB) => { + return getLogisticsRecords(dbReq, { teamCode }); + })(request); + } catch (error) { + console.error('处理GET请求失败:', error); + return NextResponse.json( + { error: '处理请求失败', details: (error as Error).message }, + { status: 500 } + ); + } +} + +/** + * 处理POST请求 - 创建物流记录 + */ +export async function POST(request: NextRequest) { + try { + // 从URL路径中提取团队代码 + const pathname = request.nextUrl.pathname; + const teamCode = pathname.split('/')[3]; // /api/team/[teamCode]/... + + return connectTeamDB((dbReq: RequestWithDB) => { + return createLogisticsRecord(dbReq, { teamCode }); + })(request); + } catch (error) { + console.error('处理POST请求失败:', error); + return NextResponse.json( + { error: '处理请求失败', details: (error as Error).message }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/logistics/update-status/route.ts b/src/app/api/team/[teamCode]/logistics/update-status/route.ts new file mode 100644 index 0000000..b7b68c0 --- /dev/null +++ b/src/app/api/team/[teamCode]/logistics/update-status/route.ts @@ -0,0 +1,192 @@ +/** + * 物流状态批量更新API路由 + * 作者: 阿瑞 + * 功能: 批量更新物流记录状态 + * 版本: 1.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RowDataPacket } from 'mysql2/promise'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { querySFExpress } from '@/utils/querySFExpress'; + +/** + * 物流记录数据接口 + */ +interface LogisticsRecord extends RowDataPacket { + id: number; + tracking_number: string; + is_queryable: boolean; + customer_tail_number: string; + company: string; + status?: string; + details?: string; + record_id: number; + record_type: string; + created_at: string; + updated_at: string; +} + +/** + * 批量更新物流状态处理函数 + */ +const updateLogisticsStatus = async (req: RequestWithDB, params?: Record) => { + try { + // 获取团队代码 + const teamCode = params?.teamCode as string; + + if (!teamCode) { + return NextResponse.json( + { error: '缺少团队代码参数' }, + { status: 400 } + ); + } + + // 获取数据库连接 + const connection = req.db; + + // 查询需要更新的物流记录(只查询未签收且可查询的记录) + const [logisticsRecords] = await connection.query(` + SELECT id, tracking_number, customer_tail_number, status, is_queryable + FROM logistics_records + WHERE + is_queryable = 1 + AND customer_tail_number IS NOT NULL + AND tracking_number IS NOT NULL + AND (status IS NULL OR status != '已签收') + LIMIT 50 + `); + + if (logisticsRecords.length === 0) { + return NextResponse.json({ + success: true, + message: '没有需要更新的物流记录', + updatedCount: 0 + }); + } + + // 记录更新结果 + const results = { + success: 0, + failed: 0, + skipped: 0, + errors: [] as string[] + }; + + // 逐条更新物流记录 + for (const record of logisticsRecords) { + try { + // 跳过无法查询的记录 + if (!record.is_queryable || !record.tracking_number || !record.customer_tail_number) { + results.skipped++; + continue; + } + + // 调用顺丰查询API + const sfResult = await querySFExpress(record.tracking_number, record.customer_tail_number); + + // 记录API调用结果,用于调试 + console.log(`批量更新 - 物流查询结果 - ID: ${record.id}, 单号: ${record.tracking_number}, 状态码: ${sfResult.apiResultCode}`); + + if (sfResult.apiResultCode !== '0000' && sfResult.apiResultCode !== 'S0000') { + // 详细记录错误信息 + console.error(`批量更新 - 顺丰API查询失败: ${JSON.stringify({ + id: record.id, + trackingNumber: record.tracking_number, + phoneLast4Digits: record.customer_tail_number, + errorCode: sfResult.apiResultCode, + errorMsg: sfResult.apiErrorMsg, + originalError: sfResult.error + }, null, 2)}`); + + results.failed++; + results.errors.push(`记录ID ${record.id}: ${sfResult.apiErrorMsg} (错误码: ${sfResult.apiResultCode})`); + continue; + } + + // 解析API返回的物流信息 + const resultData = typeof sfResult.apiResultData === 'string' + ? JSON.parse(sfResult.apiResultData) + : sfResult.apiResultData; + + // 获取路由信息(兼容新旧格式) + const routeInfo = resultData?.msgData?.routeResps?.[0] || {}; + + // 提取状态信息 + let status = null; + if (routeInfo.routes && routeInfo.routes.length > 0) { + const latestRoute = routeInfo.routes[0]; + + // 根据内容识别状态 + const content = latestRoute.content || ''; + if (content.includes('已签收')) { + status = '已签收'; + } else if (content.includes('派送中') || content.includes('派件中')) { + status = '运输中'; + } else if (content.includes('已收取') || content.includes('已揽件')) { + status = '已揽件'; + } + } + + // 更新数据库中的物流记录 + const details = resultData ? JSON.stringify({ + routes: routeInfo.routes || [], + routeType: routeInfo.routeType, + mailNo: routeInfo.mailNo, + queryTime: new Date().toISOString() + }) : null; + + await connection.query( + `UPDATE logistics_records + SET status = ?, + details = ?, + updated_at = CURRENT_TIMESTAMP + WHERE id = ?`, + [status, details, record.id] + ); + + results.success++; + } catch (error) { + console.error(`更新物流记录 ${record.id} 失败:`, error); + results.failed++; + results.errors.push(`记录ID ${record.id}: ${error instanceof Error ? error.message : String(error)}`); + } + } + + // 返回更新结果 + return NextResponse.json({ + success: true, + message: '物流状态更新完成', + total: logisticsRecords.length, + results + }); + + } catch (error) { + console.error('批量更新物流状态失败:', error); + return NextResponse.json( + { error: error instanceof Error ? error.message : '批量更新物流状态失败' }, + { status: 500 } + ); + } +}; + +/** + * 处理POST请求 + */ +export async function POST(request: NextRequest) { + try { + // 从URL路径中提取团队代码 + const pathname = request.nextUrl.pathname; + const teamCode = pathname.split('/')[3]; // /api/team/[teamCode]/... + + return connectTeamDB((dbReq: RequestWithDB) => { + return updateLogisticsStatus(dbReq, { teamCode }); + })(request); + } catch (error) { + console.error('处理POST请求失败:', error); + return NextResponse.json( + { error: '处理请求失败', details: (error as Error).message }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/payment-platforms/[id]/route.ts b/src/app/api/team/[teamCode]/payment-platforms/[id]/route.ts new file mode 100644 index 0000000..6cf746f --- /dev/null +++ b/src/app/api/team/[teamCode]/payment-platforms/[id]/route.ts @@ -0,0 +1,387 @@ +/** + * 单个支付平台API路由 + * 作者: 阿瑞 + * 功能: 提供单个支付平台的查询、更新和删除接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + // 不复制body,因为它可能已被读取 + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/payment-platforms/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,支付平台ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * 获取单个支付平台 + */ +const getPaymentPlatform = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的支付平台ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始查询团队[${teamCode}]支付平台, ID:${id}`); + + const [rows] = await req.db.query( + `SELECT + id, \`order\`, name, description, status, + created_at as createdAt, updated_at as updatedAt + FROM payment_platforms WHERE id = ?`, + [id] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应支付平台' }, + { status: 404 } + ); + } + + console.log(`查询团队[${teamCode}]支付平台成功, ID:${id}`); + + return NextResponse.json({ + success: true, + paymentPlatform: rows[0] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]支付平台详情失败:`, error); + return NextResponse.json( + { success: false, error: '获取支付平台详情失败' }, + { status: 500 } + ); + } +}; + +/** + * 更新支付平台 + */ +interface PaymentPlatformRequestBody { + id?: number; + name?: string; + order?: number; + description?: string | null; + status?: number; +} + +const updatePaymentPlatform = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + const data = params?.requestBody as PaymentPlatformRequestBody; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的支付平台ID' }, + { status: 400 } + ); + } + + if (!data) { + return NextResponse.json( + { success: false, error: '请求数据为空' }, + { status: 400 } + ); + } + + // 验证是否有需要更新的字段 + if (!data.name && data.order === undefined && !data.description && data.status === undefined) { + return NextResponse.json( + { success: false, error: '缺少需要更新的字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始更新团队[${teamCode}]支付平台, ID:${id}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查支付平台是否存在 + const [existingPlatform] = await connection.query( + 'SELECT id FROM payment_platforms WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingPlatform) || existingPlatform.length === 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '未找到相应支付平台' }, + { status: 404 } + ); + } + + // 如果更新名称,检查名称是否已存在 + if (data.name) { + const [nameCheck] = await connection.query( + 'SELECT id FROM payment_platforms WHERE name = ? AND id != ?', + [data.name, id] + ); + + if (Array.isArray(nameCheck) && nameCheck.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该支付平台名称已存在' }, + { status: 409 } + ); + } + } + + // 构建更新SQL + const updateFields = []; + const updateParams = []; + + if (data.name) { + updateFields.push('name = ?'); + updateParams.push(data.name); + } + + if (data.order !== undefined) { + updateFields.push('`order` = ?'); + updateParams.push(data.order); + } + + if (data.description !== undefined) { + updateFields.push('description = ?'); + updateParams.push(data.description || null); + } + + if (data.status !== undefined) { + updateFields.push('status = ?'); + updateParams.push(data.status); + } + + updateFields.push('updated_at = NOW()'); + + // 添加ID作为WHERE条件参数 + updateParams.push(id); + + const updateSql = ` + UPDATE payment_platforms + SET ${updateFields.join(', ')} + WHERE id = ? + `; + + await connection.query(updateSql, updateParams); + + // 查询更新后的支付平台信息 + const [updatedPlatformRows] = await connection.query( + `SELECT + id, \`order\`, name, description, status, + created_at as createdAt, updated_at as updatedAt + FROM payment_platforms WHERE id = ?`, + [id] + ); + + // 提交事务 + await connection.commit(); + + console.log(`更新团队[${teamCode}]支付平台成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '支付平台更新成功', + paymentPlatform: Array.isArray(updatedPlatformRows) && updatedPlatformRows.length > 0 ? updatedPlatformRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`更新团队[${teamCode}]支付平台失败:`, error); + return NextResponse.json( + { success: false, error: '更新支付平台失败' }, + { status: 500 } + ); + } +}; + +/** + * 删除支付平台 + */ +const deletePaymentPlatform = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的支付平台ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始删除团队[${teamCode}]支付平台, ID:${id}`); + + // 检查支付平台是否存在 + const [existingPlatform] = await req.db.query( + 'SELECT id FROM payment_platforms WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingPlatform) || existingPlatform.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应支付平台' }, + { status: 404 } + ); + } + + // 直接删除支付平台记录 + await req.db.query('DELETE FROM payment_platforms WHERE id = ?', [id]); + + console.log(`删除团队[${teamCode}]支付平台成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '支付平台删除成功' + }); + } catch (error) { + console.error(`删除团队[${teamCode}]支付平台失败:`, error); + return NextResponse.json( + { success: false, error: '删除支付平台失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getPaymentPlatform)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理支付平台详情请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 先读取请求体 + let requestBody; + try { + // 克隆请求,确保body只被读取一次 + const clonedReq = req.clone(); + requestBody = await clonedReq.json(); + } catch (error) { + console.error('解析请求数据失败:', error); + return NextResponse.json( + { success: false, error: '无效的请求数据格式' }, + { status: 400 } + ); + } + + // 添加团队信息到请求(不传递body) + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(updatePaymentPlatform)(teamReq, { teamCode, id, requestBody }); + } catch (error) { + console.error('处理支付平台更新请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(deletePaymentPlatform)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理支付平台删除请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/payment-platforms/route.ts b/src/app/api/team/[teamCode]/payment-platforms/route.ts new file mode 100644 index 0000000..0677206 --- /dev/null +++ b/src/app/api/team/[teamCode]/payment-platforms/route.ts @@ -0,0 +1,152 @@ +/** + * 支付平台API接口 + * 作者: 阿瑞 + * 功能: 提供支付平台数据的查询接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/payment-platforms + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * GET 获取支付平台列表 + */ +const getPaymentPlatforms = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + const status = url.searchParams.get('status') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]支付平台列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(name LIKE ? OR description LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`); + } + + if (status) { + conditions.push('status = ?'); + queryParams.push(status); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = `SELECT COUNT(*) as total FROM payment_platforms ${whereClause}`; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = (totalRows as Array<{ total: number }>)[0].total; + + // 查询分页数据 + const querySql = ` + SELECT + id, \`order\`, name, description, status, + created_at as createdAt, updated_at as updatedAt + FROM payment_platforms + ${whereClause} + ORDER BY \`order\` ASC, id ASC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + console.log(`查询团队[${teamCode}]支付平台列表成功, 总数:${total}`); + + return NextResponse.json({ + success: true, + total, + paymentPlatforms: Array.isArray(rows) ? rows : [] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]支付平台列表失败:`, error); + return NextResponse.json( + { success: false, error: '获取支付平台列表失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getPaymentPlatforms)(teamReq, { teamCode }); + } catch (error) { + console.error('处理支付平台列表请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/products/[id]/route.ts b/src/app/api/team/[teamCode]/products/[id]/route.ts new file mode 100644 index 0000000..9993905 --- /dev/null +++ b/src/app/api/team/[teamCode]/products/[id]/route.ts @@ -0,0 +1,474 @@ +/** + * 单个产品API路由 + * 作者: 阿瑞 + * 功能: 提供单个产品的查询、更新和删除接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RowDataPacket } from 'mysql2'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/products/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,产品ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * 获取单个产品 + */ +const getProduct = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的产品ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始查询团队[${teamCode}]产品, ID:${id}`); + + // 查询产品信息,包括关联的供应商、品牌、品类名称 + const [rows] = await req.db.query( + `SELECT + p.id, p.supplier_id as supplierId, p.brand_id as brandId, p.category_id as categoryId, + p.name, p.description, p.code, p.image, p.sku, p.aliases, p.level, p.cost, + p.price, p.stock, p.logistics_status as logisticsStatus, + p.logistics_details as logisticsDetails, p.tracking_number as trackingNumber, + p.created_at as createdAt, p.updated_at as updatedAt, + s.name as supplierName, b.name as brandName, c.name as categoryName + FROM products p + LEFT JOIN suppliers s ON p.supplier_id = s.id + LEFT JOIN brands b ON p.brand_id = b.id + LEFT JOIN categories c ON p.category_id = c.id + WHERE p.id = ?`, + [id] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应产品' }, + { status: 404 } + ); + } + + // 处理产品数据 + const product: { + id: number; + aliases?: string | string[]; + cost?: string | Record; + [key: string]: string | number | boolean | null | string[] | Record | undefined; + } = rows[0] as RowDataPacket & { + id: number; + aliases?: string | string[]; + cost?: string | Record; + [key: string]: string | number | boolean | null | string[] | Record | undefined; + }; + + // 处理JSON字段 + if (product.aliases && typeof product.aliases === 'string') { + try { + product.aliases = JSON.parse(product.aliases); + } catch (error) { + console.error('解析别名JSON失败:', error); + product.aliases = []; + } + } + + if (product.cost && typeof product.cost === 'string') { + try { + product.cost = JSON.parse(product.cost); + } catch (error) { + console.error('解析成本JSON失败:', error); + product.cost = {}; + } + } + + console.log(`查询团队[${teamCode}]产品成功, ID:${id}`); + + return NextResponse.json({ + success: true, + product + }); + } catch (error) { + console.error(`获取团队[${teamCode}]产品详情失败:`, error); + return NextResponse.json( + { success: false, error: '获取产品详情失败' }, + { status: 500 } + ); + } +}; + +/** + * 更新产品 + */ +const updateProduct = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的产品ID' }, + { status: 400 } + ); + } + + // 获取请求数据或使用预解析的数据 + const data = req.parsedBody || await req.json(); + + // 验证是否有需要更新的字段 + if (Object.keys(data).length === 1 && data.id) { + return NextResponse.json( + { success: false, error: '缺少需要更新的字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始更新团队[${teamCode}]产品, ID:${id}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查产品是否存在 + const [existingProduct] = await connection.query( + 'SELECT id FROM products WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingProduct) || existingProduct.length === 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '未找到相应产品' }, + { status: 404 } + ); + } + + // 处理JSON字段 + let aliasesJson = undefined; + if (data.aliases !== undefined) { + aliasesJson = data.aliases ? JSON.stringify(data.aliases) : null; + } + + let costJson = undefined; + if (data.cost !== undefined) { + costJson = data.cost ? JSON.stringify(data.cost) : null; + } + + // 构建更新SQL + const updateFields = []; + const updateParams = []; + + // 映射字段名 + const fieldMappings: Record = { + supplierId: 'supplier_id', + brandId: 'brand_id', + categoryId: 'category_id', + logisticsStatus: 'logistics_status', + logisticsDetails: 'logistics_details', + trackingNumber: 'tracking_number', + }; + + // 特殊处理的字段 + const specialFields = ['aliases', 'cost', 'id']; + + // 添加常规字段 + for (const [key, value] of Object.entries(data)) { + if (key === 'id' || specialFields.includes(key)) continue; + + const dbField = fieldMappings[key] || key; + updateFields.push(`${dbField} = ?`); + updateParams.push(value === null ? null : value); + } + + // 添加JSON字段 + if (aliasesJson !== undefined) { + updateFields.push('aliases = ?'); + updateParams.push(aliasesJson); + } + + if (costJson !== undefined) { + updateFields.push('cost = ?'); + updateParams.push(costJson); + } + + // 添加更新时间 + updateFields.push('updated_at = NOW()'); + + // 如果没有需要更新的字段,返回错误 + if (updateFields.length === 1) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '缺少需要更新的字段' }, + { status: 400 } + ); + } + + // 添加ID作为WHERE条件参数 + updateParams.push(id); + + const updateSql = ` + UPDATE products + SET ${updateFields.join(', ')} + WHERE id = ? + `; + + await connection.query(updateSql, updateParams); + + // 查询更新后的产品信息,包括关联的供应商、品牌、品类名称 + const [updatedProductRows] = await connection.query( + `SELECT + p.id, p.supplier_id as supplierId, p.brand_id as brandId, p.category_id as categoryId, + p.name, p.description, p.code, p.image, p.sku, p.aliases, p.level, p.cost, + p.price, p.stock, p.logistics_status as logisticsStatus, + p.logistics_details as logisticsDetails, p.tracking_number as trackingNumber, + p.created_at as createdAt, p.updated_at as updatedAt, + s.name as supplierName, b.name as brandName, c.name as categoryName + FROM products p + LEFT JOIN suppliers s ON p.supplier_id = s.id + LEFT JOIN brands b ON p.brand_id = b.id + LEFT JOIN categories c ON p.category_id = c.id + WHERE p.id = ?`, + [id] + ); + + // 处理产品数据 + let updatedProduct = null; + if (Array.isArray(updatedProductRows) && updatedProductRows.length > 0) { + updatedProduct = updatedProductRows[0] as RowDataPacket & { + id: number; + aliases?: string | string[]; + cost?: string | Record; + [key: string]: string | number | boolean | null | string[] | Record | undefined; + }; + + // 处理JSON字段 + if (updatedProduct.aliases && typeof updatedProduct.aliases === 'string') { + try { + updatedProduct.aliases = JSON.parse(updatedProduct.aliases); + } catch (error) { + console.error('解析别名JSON失败:', error); + updatedProduct.aliases = []; + } + } + + if (updatedProduct.cost && typeof updatedProduct.cost === 'string') { + try { + updatedProduct.cost = JSON.parse(updatedProduct.cost); + } catch (error) { + console.error('解析成本JSON失败:', error); + updatedProduct.cost = {}; + } + } + } + + // 提交事务 + await connection.commit(); + + console.log(`更新团队[${teamCode}]产品成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '产品更新成功', + product: updatedProduct + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`更新团队[${teamCode}]产品失败:`, error); + return NextResponse.json( + { success: false, error: '更新产品失败' }, + { status: 500 } + ); + } +}; + +/** + * 删除产品 + */ +const deleteProduct = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的产品ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始删除团队[${teamCode}]产品, ID:${id}`); + + // 检查产品是否存在 + const [existingProduct] = await req.db.query( + 'SELECT id FROM products WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingProduct) || existingProduct.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应产品' }, + { status: 404 } + ); + } + + // 直接删除产品记录 + await req.db.query('DELETE FROM products WHERE id = ?', [id]); + + console.log(`删除团队[${teamCode}]产品成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '产品删除成功' + }); + } catch (error) { + console.error(`删除团队[${teamCode}]产品失败:`, error); + return NextResponse.json( + { success: false, error: '删除产品失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getProduct)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理产品详情请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 克隆请求,确保body只被读取一次 + const clonedReq = req.clone(); + + // 预先读取请求体 + let body; + try { + body = await clonedReq.json(); + } catch { + // 如果无法解析JSON,继续处理 + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 传递body数据到teamReq的headers中,避免重复读取body + if (body) { + teamReq.headers.set('x-request-body', JSON.stringify(body)); + } + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(updateProduct)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理产品更新请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(deleteProduct)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理产品删除请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/products/route.ts b/src/app/api/team/[teamCode]/products/route.ts new file mode 100644 index 0000000..29515c7 --- /dev/null +++ b/src/app/api/team/[teamCode]/products/route.ts @@ -0,0 +1,424 @@ +/** + * 产品API路由 + * 作者: 阿瑞 + * 功能: 提供产品数据的查询和创建接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RowDataPacket } from 'mysql2'; + +// 产品数据类型接口 +interface ProductRow extends RowDataPacket { + id: number; + supplierId?: number; + supplierName?: string; + brandId?: number; + brandName?: string; + categoryId?: number; + categoryName?: string; + name: string; + description?: string; + code?: string; + image?: string; + sku?: string; + aliases?: string | string[]; + level?: string; + cost?: string | Record; + price: number; + stock: number; + logisticsStatus?: string; + logisticsDetails?: string; + trackingNumber?: string; + createdAt: Date; + updatedAt: Date; + supplier?: { id: number; name: string }; + brand?: { id: number; name: string }; + category?: { id: number; name: string }; +} + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/products + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * GET 获取产品列表 + */ +const getProducts = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + const supplierId = url.searchParams.get('supplierId') || ''; + const brandId = url.searchParams.get('brandId') || ''; + const categoryId = url.searchParams.get('categoryId') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]产品列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(p.name LIKE ? OR p.code LIKE ? OR p.sku LIKE ? OR p.description LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`, `%${keyword}%`); + } + + if (supplierId) { + conditions.push('p.supplier_id = ?'); + queryParams.push(supplierId); + } + + if (brandId) { + conditions.push('p.brand_id = ?'); + queryParams.push(brandId); + } + + if (categoryId) { + conditions.push('p.category_id = ?'); + queryParams.push(categoryId); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = ` + SELECT COUNT(*) as total + FROM products p + ${whereClause} + `; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = totalRows[0].total as number; + + // 查询分页数据 + const querySql = ` + SELECT + p.id, p.supplier_id as supplierId, s.name as supplierName, + p.brand_id as brandId, b.name as brandName, + p.category_id as categoryId, c.name as categoryName, + p.name, p.description, p.code, p.image, p.sku, + p.aliases, p.level, p.cost, p.price, p.stock, + p.logistics_status as logisticsStatus, + p.logistics_details as logisticsDetails, + p.tracking_number as trackingNumber, + p.created_at as createdAt, p.updated_at as updatedAt + FROM products p + LEFT JOIN suppliers s ON p.supplier_id = s.id + LEFT JOIN brands b ON p.brand_id = b.id + LEFT JOIN categories c ON p.category_id = c.id + ${whereClause} + ORDER BY p.created_at DESC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + // 处理查询结果 + let products = rows; + + // 处理别名和成本字段的JSON解析 + products = products.map(product => { + // 处理别名字段 + if (product.aliases && typeof product.aliases === 'string') { + try { + product.aliases = JSON.parse(product.aliases) as string[]; + } catch (error) { + console.error('解析别名JSON失败:', error); + product.aliases = [] as string[]; + } + } + + // 处理成本字段 + if (product.cost && typeof product.cost === 'string') { + try { + product.cost = JSON.parse(product.cost) as Record; + } catch (error) { + console.error('解析成本JSON失败:', error); + product.cost = {} as Record; + } + } + + // 处理关联对象 + if (product.supplierId && product.supplierName) { + product.supplier = { + id: product.supplierId, + name: product.supplierName + }; + } + + if (product.brandId && product.brandName) { + product.brand = { + id: product.brandId, + name: product.brandName + }; + } + + if (product.categoryId && product.categoryName) { + product.category = { + id: product.categoryId, + name: product.categoryName + }; + } + + return product; + }); + + console.log(`查询团队[${teamCode}]产品列表成功, 总数:${total}`); + + return NextResponse.json({ + success: true, + total, + products + }); + } catch (error) { + console.error(`获取团队[${teamCode}]产品列表失败:`, error); + return NextResponse.json( + { success: false, error: '获取产品列表失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 创建产品 + */ +const createProduct = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取请求数据 + const data = await req.json(); + + // 验证必填字段 + if (!data.name) { + return NextResponse.json( + { success: false, error: '产品名称为必填字段' }, + { status: 400 } + ); + } + + if (data.price === undefined) { + return NextResponse.json( + { success: false, error: '产品价格为必填字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始创建团队[${teamCode}]产品, 名称:${data.name}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 处理JSON字段 + let aliasesJson = null; + if (data.aliases && Array.isArray(data.aliases)) { + aliasesJson = JSON.stringify(data.aliases); + } + + let costJson = null; + if (data.cost) { + costJson = JSON.stringify(data.cost); + } + + // 插入产品记录 + const insertSql = ` + INSERT INTO products ( + supplier_id, brand_id, category_id, name, description, code, image, sku, + aliases, level, cost, price, stock, logistics_status, logistics_details, + tracking_number, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `; + + const [result] = await connection.execute(insertSql, [ + data.supplierId || null, + data.brandId || null, + data.categoryId || null, + data.name, + data.description || null, + data.code || null, + data.image || null, + data.sku || null, + aliasesJson, + data.level || null, + costJson, + data.price, + data.stock || 0, + data.logisticsStatus || null, + data.logisticsDetails || null, + data.trackingNumber || null + ]); + + const insertId = (result as { insertId: number }).insertId; + + // 查询新插入的产品信息,包括关联的供应商、品牌、品类名称 + const [newProductRows] = await connection.query( + `SELECT + p.id, p.supplier_id as supplierId, p.brand_id as brandId, p.category_id as categoryId, + p.name, p.description, p.code, p.image, p.sku, p.aliases, p.level, p.cost, + p.price, p.stock, p.logistics_status as logisticsStatus, + p.logistics_details as logisticsDetails, p.tracking_number as trackingNumber, + p.created_at as createdAt, p.updated_at as updatedAt, + s.name as supplierName, b.name as brandName, c.name as categoryName + FROM products p + LEFT JOIN suppliers s ON p.supplier_id = s.id + LEFT JOIN brands b ON p.brand_id = b.id + LEFT JOIN categories c ON p.category_id = c.id + WHERE p.id = ?`, + [insertId] + ); + + // 处理查询结果 + let newProduct: ProductRow | null = null; + if (newProductRows.length > 0) { + newProduct = newProductRows[0]; + + // 处理JSON字段 + if (newProduct.aliases && typeof newProduct.aliases === 'string') { + try { + newProduct.aliases = JSON.parse(newProduct.aliases) as string[]; + } catch (error) { + console.error('解析别名JSON失败:', error); + newProduct.aliases = [] as string[]; + } + } + + if (newProduct.cost && typeof newProduct.cost === 'string') { + try { + newProduct.cost = JSON.parse(newProduct.cost) as Record; + } catch (error) { + console.error('解析成本JSON失败:', error); + newProduct.cost = {} as Record; + } + } + } + + // 提交事务 + await connection.commit(); + + console.log(`创建团队[${teamCode}]产品成功, ID:${insertId}`); + + return NextResponse.json({ + success: true, + message: '产品创建成功', + product: newProduct + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`创建团队[${teamCode}]产品失败:`, error); + return NextResponse.json( + { success: false, error: '创建产品失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getProducts)(teamReq, { teamCode }); + } catch (error) { + console.error('处理产品列表请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(createProduct)(teamReq, { teamCode }); + } catch (error) { + console.error('处理产品创建请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/sales-records/route.ts b/src/app/api/team/[teamCode]/sales-records/route.ts new file mode 100644 index 0000000..f6c9f73 --- /dev/null +++ b/src/app/api/team/[teamCode]/sales-records/route.ts @@ -0,0 +1,598 @@ +/** + * 销售记录API路由 + * 作者: 阿瑞 + * 功能: 提供销售记录的查询和创建接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RowDataPacket } from 'mysql2/promise'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RecordType } from '@/models/team/types/ILogisticsRecord'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/sales-records + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + + + +/** + * 销售记录数据接口 + */ +interface SalesRecordRow extends RowDataPacket { + id: number; + customerId: number; + customerName: string; + customerPhone: string; + //sourceId?: number; + sourceName?: string; + sourceWechat?: string; + guideId?: number; + guideUsername?: string; + guideName?: string; + guidePhone?: string; + guideWechat?: string; + paymentType: number; + dealDate: string; + receivable: number; + received: number; + pending: number; + platformId?: number; + platformName?: string; + dealShopId?: string; + dealShopName?: string; + dealShopWechat?: string; + orderStatus: string | string[]; + followupDate?: string; + remark?: string; + createdAt: string; + updatedAt: string; + products?: ProductRow[]; + logisticsStatus?: string; // 物流状态 + logisticsTrackingNumber?: string; // 物流单号 + logisticsCompany?: string; // 物流公司 + logisticsCustomerTailNumber?: string; // 客户尾号 + logisticsIsQueryable?: boolean; // 是否可查询 + logisticsDetails?: string; // 物流详情 +} + +/** + * 产品数据接口 + */ +interface ProductRow extends RowDataPacket { + id: number; + salesRecordId: number; + productId: number; + productName: string; + productCode?: string; + productSku?: string; + image?: string; + description?: string; + brandId?: number; + brandName?: string; + categoryId?: number; + categoryName?: string; + supplierId?: number; + supplierName?: string; + quantity: number; + price: number; + createdAt: string; +} + +/** + * 物流记录数据接口 + */ +interface LogisticsRecord extends RowDataPacket { + id: number; + tracking_number: string; + is_queryable: boolean; + customer_tail_number: string; + company: string; + details?: string; + status?: string; + record_id: number; + record_type: string; + created_at: string; + updated_at: string; +} + +/** + * 数据库查询结果类型 + */ +interface DbResult { + insertId: number; + affectedRows: number; +} + +/** + * GET 获取销售记录列表 + */ +const getSalesRecords = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + const customerId = url.searchParams.get('customerId') || ''; + //const sourceId = url.searchParams.get('sourceId') || ''; + const platformId = url.searchParams.get('platformId') || ''; + const startDate = url.searchParams.get('startDate') || ''; + const endDate = url.searchParams.get('endDate') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]销售记录列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(c.name LIKE ? OR sr.remark LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`); + } + + if (customerId) { + conditions.push('sr.customer_id = ?'); + queryParams.push(customerId); + } + + //if (sourceId) { + // conditions.push('sr.source_id = ?'); + // queryParams.push(sourceId); + //} + + if (platformId) { + conditions.push('sr.platform_id = ?'); + queryParams.push(platformId); + } + + if (startDate) { + conditions.push('sr.deal_date >= ?'); + queryParams.push(startDate); + } + + if (endDate) { + conditions.push('sr.deal_date <= ?'); + queryParams.push(endDate); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = ` + SELECT COUNT(*) as total + FROM sales_records sr + LEFT JOIN customers c ON sr.customer_id = c.id + ${whereClause} + `; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = totalRows[0].total as number; + + // 查询分页数据 + const querySql = ` + SELECT + sr.id, sr.customer_id as customerId, c.name as customerName, c.phone as customerPhone, + sr.source_id as sourceId, s1.nickname as sourceName, s1.wechat as sourceWechat, + sr.guide_id as guideId, + sr.payment_type as paymentType, sr.deal_date as dealDate, + sr.receivable, sr.received, sr.pending, + sr.platform_id as platformId, pp.name as platformName, + sr.deal_shop as dealShopId, s2.nickname as dealShopName, s2.wechat as dealShopWechat, + sr.order_status as orderStatus, + sr.followup_date as followupDate, sr.remark, + sr.created_at as createdAt, sr.updated_at as updatedAt + FROM sales_records sr + LEFT JOIN customers c ON sr.customer_id = c.id + LEFT JOIN shops s1 ON sr.source_id = s1.id + LEFT JOIN payment_platforms pp ON sr.platform_id = pp.id + LEFT JOIN shops s2 ON sr.deal_shop = s2.id + ${whereClause} + ORDER BY sr.deal_date DESC, sr.created_at DESC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + // 查询每条销售记录关联的产品 + const salesRecords = rows; + if (salesRecords.length > 0) { + for (let i = 0; i < salesRecords.length; i++) { + const record = salesRecords[i]; + + // 处理order_status字段,从JSON字符串转为数组 + if (record.orderStatus && typeof record.orderStatus === 'string') { + try { + record.orderStatus = JSON.parse(record.orderStatus as string); + } catch { + record.orderStatus = ['正常']; // 默认值 + } + } + + // 查询关联产品 + const [productRows] = await connection.query(` + SELECT + srp.id, srp.sales_record_id as salesRecordId, + srp.product_id as productId, p.name as productName, + p.code as productCode, p.sku as productSku, + p.image, p.description, + p.brand_id as brandId, b.name as brandName, + p.category_id as categoryId, c.name as categoryName, + p.supplier_id as supplierId, s.name as supplierName, + srp.quantity, srp.price, + srp.created_at as createdAt + FROM sales_record_products srp + LEFT JOIN products p ON srp.product_id = p.id + LEFT JOIN brands b ON p.brand_id = b.id + LEFT JOIN categories c ON p.category_id = c.id + LEFT JOIN suppliers s ON p.supplier_id = s.id + WHERE srp.sales_record_id = ? + `, [record.id]); + + record.products = productRows; + + // 查询关联的物流记录,并添加物流状态信息 + const [logisticsRows] = await connection.query(` + SELECT + id, tracking_number, company, status, details, + is_queryable, customer_tail_number + FROM logistics_records + WHERE record_id = ? AND record_type = ? + `, [record.id, RecordType.SALES_RECORD]); + + // 将物流信息添加到销售记录中 + if (logisticsRows.length > 0) { + const logistics = logisticsRows[0]; + record.logisticsStatus = logistics.status; + record.logisticsTrackingNumber = logistics.tracking_number; + record.logisticsCompany = logistics.company; + record.logisticsCustomerTailNumber = logistics.customer_tail_number; + record.logisticsIsQueryable = Boolean(logistics.is_queryable); + record.logisticsDetails = logistics.details; + } + } + } + + console.log(`查询团队[${teamCode}]销售记录列表成功, 总数:${total}`); + + return NextResponse.json({ + success: true, + total, + salesRecords + }); + } catch (error) { + console.error(`获取团队[${teamCode}]销售记录列表失败:`, error); + return NextResponse.json( + { success: false, error: '获取销售记录列表失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 创建销售记录 + */ +const createSalesRecord = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取请求数据 + const data = await req.json(); + + // 验证必填字段 + if (!data.customerId) { + return NextResponse.json( + { success: false, error: '客户ID为必填字段' }, + { status: 400 } + ); + } + + if (!data.dealDate) { + return NextResponse.json( + { success: false, error: '成交日期为必填字段' }, + { status: 400 } + ); + } + + if (typeof data.paymentType !== 'number') { + return NextResponse.json( + { success: false, error: '收款方式为必填字段' }, + { status: 400 } + ); + } + + if (typeof data.receivable !== 'number' || data.receivable < 0) { + return NextResponse.json( + { success: false, error: '应收金额必须是大于等于0的数字' }, + { status: 400 } + ); + } + + if (typeof data.received !== 'number' || data.received < 0) { + return NextResponse.json( + { success: false, error: '实收金额必须是大于等于0的数字' }, + { status: 400 } + ); + } + + if (!Array.isArray(data.products) || data.products.length === 0) { + return NextResponse.json( + { success: false, error: '至少需要添加一个产品' }, + { status: 400 } + ); + } + + // 验证每个产品的信息 + for (const product of data.products) { + if (!product.productId) { + return NextResponse.json( + { success: false, error: '产品ID为必填字段' }, + { status: 400 } + ); + } + + if (typeof product.quantity !== 'number' || product.quantity <= 0) { + return NextResponse.json( + { success: false, error: '产品数量必须大于0' }, + { status: 400 } + ); + } + + if (typeof product.price !== 'number' || product.price < 0) { + return NextResponse.json( + { success: false, error: '产品价格必须是大于等于0的数字' }, + { status: 400 } + ); + } + } + + try { + console.log(`开始创建团队[${teamCode}]销售记录, 客户ID:${data.customerId}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查客户是否存在 + const [existingCustomer] = await connection.query( + 'SELECT id FROM customers WHERE id = ? AND deleted_at IS NULL', + [data.customerId] + ); + + if (!Array.isArray(existingCustomer) || existingCustomer.length === 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '选择的客户不存在或已被删除' }, + { status: 404 } + ); + } + + // 处理订单状态,默认为["正常"] + const orderStatus = data.orderStatus && Array.isArray(data.orderStatus) && data.orderStatus.length > 0 + ? JSON.stringify(data.orderStatus) + : JSON.stringify(['正常']); + + // 插入销售记录 + const insertSql = ` + INSERT INTO sales_records ( + customer_id, source_id, guide_id, payment_type, deal_date, + receivable, received, pending, platform_id, deal_shop, + order_status, followup_date, remark, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `; + + const [result] = await connection.query(insertSql, [ + data.customerId, + data.sourceId || null, + data.guideId || null, // 使用请求中的导购ID + data.paymentType, + data.dealDate, + data.receivable, + data.received, + data.pending || 0, + data.platformId || null, + data.dealShop || null, // dealShop现在是店铺ID + orderStatus, + data.followupDate || null, + data.remark || null + ]); + + const salesRecordId = (result as DbResult).insertId; + + // 插入销售记录-产品关联 + if (data.products && data.products.length > 0) { + const productInsertSql = ` + INSERT INTO sales_record_products ( + sales_record_id, product_id, quantity, price, created_at + ) VALUES (?, ?, ?, ?, NOW()) + `; + + for (const product of data.products) { + await connection.query(productInsertSql, [ + salesRecordId, + product.productId, + product.quantity, + product.price + ]); + } + } + + // 查询新插入的销售记录信息及关联产品 + const [salesRecordRows] = await connection.query(` + SELECT + sr.id, sr.customer_id as customerId, c.name as customerName, c.phone as customerPhone, + sr.source_id as sourceId, s1.nickname as sourceName, s1.wechat as sourceWechat, + sr.guide_id as guideId, + sr.payment_type as paymentType, sr.deal_date as dealDate, + sr.receivable, sr.received, sr.pending, + sr.platform_id as platformId, pp.name as platformName, + sr.deal_shop as dealShopId, s2.nickname as dealShopName, s2.wechat as dealShopWechat, + sr.order_status as orderStatus, + sr.followup_date as followupDate, sr.remark, + sr.created_at as createdAt, sr.updated_at as updatedAt + FROM sales_records sr + LEFT JOIN customers c ON sr.customer_id = c.id + LEFT JOIN shops s1 ON sr.source_id = s1.id + LEFT JOIN payment_platforms pp ON sr.platform_id = pp.id + LEFT JOIN shops s2 ON sr.deal_shop = s2.id + WHERE sr.id = ? + `, [salesRecordId]); + + const salesRecord = salesRecordRows.length > 0 ? salesRecordRows[0] : null; + if (salesRecord) { + // 处理order_status字段,从JSON字符串转为数组 + if (salesRecord.orderStatus && typeof salesRecord.orderStatus === 'string') { + try { + salesRecord.orderStatus = JSON.parse(salesRecord.orderStatus as string); + } catch { + salesRecord.orderStatus = ['正常']; // 默认值 + } + } + + // 查询关联产品 + const [productRows] = await connection.query(` + SELECT + srp.id, srp.sales_record_id as salesRecordId, + srp.product_id as productId, p.name as productName, + p.code as productCode, p.sku as productSku, + p.image, p.description, + p.brand_id as brandId, b.name as brandName, + p.category_id as categoryId, c.name as categoryName, + p.supplier_id as supplierId, s.name as supplierName, + srp.quantity, srp.price, + srp.created_at as createdAt + FROM sales_record_products srp + LEFT JOIN products p ON srp.product_id = p.id + LEFT JOIN brands b ON p.brand_id = b.id + LEFT JOIN categories c ON p.category_id = c.id + LEFT JOIN suppliers s ON p.supplier_id = s.id + WHERE srp.sales_record_id = ? + `, [salesRecordId]); + + salesRecord.products = productRows; + } + + // 提交事务 + await connection.commit(); + + console.log(`创建团队[${teamCode}]销售记录成功, ID:${salesRecordId}`); + + return NextResponse.json({ + success: true, + message: '销售记录创建成功', + salesRecord + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`创建团队[${teamCode}]销售记录失败:`, error); + return NextResponse.json( + { success: false, error: '创建销售记录失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getSalesRecords)(teamReq, { teamCode }); + } catch (error) { + console.error('处理销售记录列表请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(createSalesRecord)(teamReq, { teamCode }); + } catch (error) { + console.error('处理销售记录创建请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/shop-follower-growth/[id]/route.ts b/src/app/api/team/[teamCode]/shop-follower-growth/[id]/route.ts new file mode 100644 index 0000000..227ca11 --- /dev/null +++ b/src/app/api/team/[teamCode]/shop-follower-growth/[id]/route.ts @@ -0,0 +1,404 @@ +/** + * 单个店铺粉丝增长记录API路由 + * 作者: 阿瑞 + * 功能: 提供单个店铺粉丝增长记录的获取、更新和删除接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RowDataPacket } from 'mysql2/promise'; + +// 定义数据类型接口 +interface ShopRecord extends RowDataPacket { + id: number; + shop_id: number; + date: Date; + total: number; + deducted: number; + daily_increase: number; + created_at: Date; + updated_at: Date; + wechat?: string; + nickname?: string; +} + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/shop-follower-growth/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,记录ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * 格式化MySQL日期为YYYY-MM-DD格式 + * 修复时区问题,保留原始日期 + */ +function formatMySQLDate(date: Date): string { + // MySQL中存储的是本地日期,直接获取日期部分即可 + // 注意:不使用toISOString(),因为它会转换为UTC时间并导致日期偏移 + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +/** + * 获取单个店铺粉丝增长记录 + */ +const getGrowthRecord = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + const id = params?.id as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的记录ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始查询团队[${teamCode}]店铺粉丝增长记录, ID:${id}`); + + // 查询记录详情 + const [rows] = await req.db.query( + `SELECT + g.id, g.shop_id, g.date, g.total, g.deducted, g.daily_increase, + g.created_at, g.updated_at, + s.wechat, s.nickname + FROM shop_follower_growth g + LEFT JOIN shops s ON g.shop_id = s.id + WHERE g.id = ?`, + [id] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应记录' }, + { status: 404 } + ); + } + + // 格式化返回数据 + const record = { + id: rows[0].id, + shop_id: rows[0].shop_id, + date: formatMySQLDate(rows[0].date), // 使用修复的日期格式化函数 + total: rows[0].total, + deducted: rows[0].deducted, + daily_increase: rows[0].daily_increase, + created_at: rows[0].created_at, + updated_at: rows[0].updated_at, + shop_name: rows[0].nickname || rows[0].wechat || `店铺ID: ${rows[0].shop_id}` + }; + + console.log(`查询团队[${teamCode}]店铺粉丝增长记录成功, ID:${id}`); + + return NextResponse.json({ + success: true, + record + }); + } catch (error) { + console.error(`获取团队[${teamCode}]店铺粉丝增长记录详情失败:`, error); + return NextResponse.json( + { success: false, error: '获取粉丝增长记录详情失败' }, + { status: 500 } + ); + } +}; + +/** + * 更新店铺粉丝增长记录 + */ +const updateGrowthRecord = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + const id = params?.id as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的记录ID' }, + { status: 400 } + ); + } + + try { + // 解析请求数据或使用预解析的数据 + const data = req.parsedBody || await req.json(); + const { shop_id, date, total, deducted } = data; + + // 验证必填字段 + if (!shop_id || !date || total === undefined) { + return NextResponse.json( + { success: false, error: '店铺ID、日期和粉丝总数为必填项' }, + { status: 400 } + ); + } + + // 验证数据类型 + if (typeof shop_id !== 'number' || typeof total !== 'number') { + return NextResponse.json( + { success: false, error: '数据类型错误' }, + { status: 400 } + ); + } + + // 使用数据库连接 + const connection = req.db; + + // 检查记录是否存在 + const [existingRecord] = await connection.query( + 'SELECT id FROM shop_follower_growth WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingRecord) || existingRecord.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应记录' }, + { status: 404 } + ); + } + + // 检查店铺是否存在 + const [shopExists] = await connection.query( + 'SELECT id FROM shops WHERE id = ?', + [shop_id] + ); + + if (!Array.isArray(shopExists) || shopExists.length === 0) { + return NextResponse.json( + { success: false, error: '店铺不存在' }, + { status: 400 } + ); + } + + // 检查日期是否与其他记录冲突 + const [dateConflict] = await connection.query( + 'SELECT id FROM shop_follower_growth WHERE shop_id = ? AND date = ? AND id != ?', + [shop_id, date, id] + ); + + if (Array.isArray(dateConflict) && dateConflict.length > 0) { + return NextResponse.json( + { success: false, error: '该店铺在选定日期已有其他记录,一个店铺每天只能有一条记录' }, + { status: 409 } + ); + } + + // 计算日增长 + const daily_increase = total - (deducted || 0); + + // 更新记录 + await connection.query( + `UPDATE shop_follower_growth + SET shop_id = ?, date = ?, total = ?, deducted = ?, daily_increase = ? + WHERE id = ?`, + [shop_id, date, total, deducted || 0, daily_increase, id] + ); + + console.log(`更新团队[${teamCode}]店铺粉丝增长记录成功, ID:${id}`); + + // 返回更新后的数据 + return NextResponse.json({ + success: true, + record: { + id: parseInt(id), + shop_id, + date, + total, + deducted: deducted || 0, + daily_increase + } + }); + } catch (error) { + console.error(`更新团队[${teamCode}]店铺粉丝增长记录失败:`, error); + return NextResponse.json( + { success: false, error: '更新粉丝增长记录失败' }, + { status: 500 } + ); + } +}; + +/** + * 删除店铺粉丝增长记录 + */ +const deleteGrowthRecord = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + const id = params?.id as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的记录ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始删除团队[${teamCode}]店铺粉丝增长记录, ID:${id}`); + + // 使用数据库连接 + const connection = req.db; + + // 检查记录是否存在 + const [existingRecord] = await connection.query( + 'SELECT id FROM shop_follower_growth WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingRecord) || existingRecord.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应记录' }, + { status: 404 } + ); + } + + // 删除记录 + await connection.query( + 'DELETE FROM shop_follower_growth WHERE id = ?', + [id] + ); + + console.log(`删除团队[${teamCode}]店铺粉丝增长记录成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '记录删除成功' + }); + } catch (error) { + console.error(`删除团队[${teamCode}]店铺粉丝增长记录失败:`, error); + return NextResponse.json( + { success: false, error: '删除粉丝增长记录失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getGrowthRecord)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理粉丝增长记录详情请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 克隆请求,确保body只被读取一次 + const clonedReq = req.clone(); + + // 预先读取请求体 + let body; + try { + body = await clonedReq.json(); + } catch { + // 如果无法解析JSON,继续处理 + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 传递body数据到teamReq的headers中,避免重复读取body + if (body) { + teamReq.headers.set('x-request-body', JSON.stringify(body)); + } + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(updateGrowthRecord)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理粉丝增长记录更新请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(deleteGrowthRecord)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理粉丝增长记录删除请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/shop-follower-growth/route.ts b/src/app/api/team/[teamCode]/shop-follower-growth/route.ts new file mode 100644 index 0000000..67fb9cf --- /dev/null +++ b/src/app/api/team/[teamCode]/shop-follower-growth/route.ts @@ -0,0 +1,324 @@ +/** + * 店铺粉丝增长API路由 + * 作者: 阿瑞 + * 功能: 提供店铺粉丝增长记录的查询和新增接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RowDataPacket } from 'mysql2/promise'; + +// 定义数据类型接口 +interface ShopRecord extends RowDataPacket { + id: number; + shop_id: number; + date: Date; + total: number; + deducted: number; + daily_increase: number; + created_at: Date; + updated_at: Date; + wechat?: string; + nickname?: string; +} + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/shop-follower-growth + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * 格式化MySQL日期为YYYY-MM-DD格式 + * 修复时区问题,保留原始日期 + */ +function formatMySQLDate(date: Date): string { + // MySQL中存储的是本地日期,直接获取日期部分即可 + // 注意:不使用toISOString(),因为它会转换为UTC时间并导致日期偏移 + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +/** + * GET 查询店铺粉丝增长记录列表 + */ +const getGrowthRecords = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + const { searchParams } = new URL(req.url); + + // 获取分页参数 + const page = parseInt(searchParams.get('page') || '1'); + const pageSize = parseInt(searchParams.get('pageSize') || '10'); + const offset = (page - 1) * pageSize; + + // 获取筛选参数 + const keyword = searchParams.get('keyword'); + const shopId = searchParams.get('shopId'); + const startDate = searchParams.get('startDate'); + const endDate = searchParams.get('endDate'); + + try { + console.log(`开始查询团队[${teamCode}]店铺粉丝增长记录`); + + // 构建查询条件 + const conditions = []; + const queryParams: (string | number)[] = []; + + if (shopId && parseInt(shopId) > 0) { + conditions.push('g.shop_id = ?'); + queryParams.push(parseInt(shopId)); + } + + if (startDate) { + conditions.push('g.date >= ?'); + queryParams.push(startDate); + } + + if (endDate) { + conditions.push('g.date <= ?'); + queryParams.push(endDate); + } + + if (keyword) { + conditions.push('(s.wechat LIKE ? OR s.nickname LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总记录数 + const [countResult] = await connection.query( + `SELECT COUNT(*) AS total FROM shop_follower_growth g + LEFT JOIN shops s ON g.shop_id = s.id + ${whereClause}`, + queryParams + ); + + const total = (countResult as Array<{ total: number }>)[0].total; + + // 查询分页数据 + const paginationParams = [...queryParams, offset, pageSize]; + const [rows] = await connection.query( + `SELECT + g.id, g.shop_id, g.date, g.total, g.deducted, g.daily_increase, + g.created_at, g.updated_at, + s.wechat, s.nickname + FROM shop_follower_growth g + LEFT JOIN shops s ON g.shop_id = s.id + ${whereClause} + ORDER BY g.date DESC, g.id DESC + LIMIT ?, ?`, + paginationParams + ); + + // 格式化返回数据 + const records = Array.isArray(rows) ? rows.map(row => ({ + id: row.id, + shop_id: row.shop_id, + // 使用日期格式化函数 + date: formatMySQLDate(row.date), + total: row.total, + deducted: row.deducted, + daily_increase: row.daily_increase, + created_at: row.created_at, + updated_at: row.updated_at, + shop_name: row.nickname || row.wechat || `店铺ID: ${row.shop_id}` + })) : []; + + console.log(`查询团队[${teamCode}]店铺粉丝增长记录成功,共 ${total} 条记录`); + + return NextResponse.json({ + success: true, + total, + records + }); + } catch (error) { + console.error(`获取团队[${teamCode}]店铺粉丝增长记录失败:`, error); + return NextResponse.json( + { success: false, error: '获取店铺粉丝增长记录失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 添加店铺粉丝增长记录 + */ +const addGrowthRecord = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + try { + // 解析请求数据 + const data = await req.json(); + const { shop_id, date, total, deducted } = data; + + // 验证必填字段 + if (!shop_id || !date || total === undefined) { + return NextResponse.json( + { success: false, error: '店铺ID、日期和粉丝总数为必填项' }, + { status: 400 } + ); + } + + // 验证数据类型 + if (typeof shop_id !== 'number' || typeof total !== 'number') { + return NextResponse.json( + { success: false, error: '数据类型错误' }, + { status: 400 } + ); + } + + // 使用数据库连接 + const connection = req.db; + + // 检查店铺是否存在 + const [shopExists] = await connection.query( + 'SELECT id FROM shops WHERE id = ?', + [shop_id] + ); + + if (!Array.isArray(shopExists) || shopExists.length === 0) { + return NextResponse.json( + { success: false, error: '店铺不存在' }, + { status: 400 } + ); + } + + // 检查记录是否已存在(同一店铺同一天只能有一条记录) + const [existingRecord] = await connection.query( + 'SELECT id FROM shop_follower_growth WHERE shop_id = ? AND date = ?', + [shop_id, date] + ); + + if (Array.isArray(existingRecord) && existingRecord.length > 0) { + return NextResponse.json( + { success: false, error: '该店铺在选定日期已有记录,请编辑现有记录或选择其他日期' }, + { status: 409 } + ); + } + + // 计算日增长 + const daily_increase = total - (deducted || 0); + + // 插入记录 + const [result] = await connection.query( + `INSERT INTO shop_follower_growth + (shop_id, date, total, deducted, daily_increase) + VALUES (?, ?, ?, ?, ?)`, + [shop_id, date, total, deducted || 0, daily_increase] + ); + + const insertId = (result as { insertId: number }).insertId; + + console.log(`添加团队[${teamCode}]店铺粉丝增长记录成功, ID: ${insertId}`); + + return NextResponse.json({ + success: true, + id: insertId, + shop_id, + date, + total, + deducted: deducted || 0, + daily_increase + }, { status: 201 }); + } catch (error) { + console.error(`添加团队[${teamCode}]店铺粉丝增长记录失败:`, error); + return NextResponse.json( + { success: false, error: '添加店铺粉丝增长记录失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getGrowthRecords)(teamReq, { teamCode }); + } catch (error) { + console.error('处理店铺粉丝增长记录请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(addGrowthRecord)(teamReq, { teamCode }); + } catch (error) { + console.error('处理添加店铺粉丝增长记录请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/shop-follower-growth/statistics/route.ts b/src/app/api/team/[teamCode]/shop-follower-growth/statistics/route.ts new file mode 100644 index 0000000..6ea3bb2 --- /dev/null +++ b/src/app/api/team/[teamCode]/shop-follower-growth/statistics/route.ts @@ -0,0 +1,186 @@ +/** + * 店铺粉丝增长统计API路由 + * 作者: 阿瑞 + * 功能: 提供店铺粉丝增长的统计分析数据 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RowDataPacket } from 'mysql2/promise'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/shop-follower-growth/statistics + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * 获取店铺粉丝增长统计数据 + */ +const getGrowthStatistics = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + const { searchParams } = new URL(req.url); + + // 获取查询参数 + const shopIds = searchParams.getAll('shopIds'); + const startDate = searchParams.get('startDate'); + const endDate = searchParams.get('endDate'); + + if (!shopIds || shopIds.length === 0) { + return NextResponse.json( + { success: false, error: '请提供至少一个店铺ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始查询团队[${teamCode}]店铺粉丝增长统计数据`); + + // 构建查询条件 + const conditions = ['1=1']; // 始终为真的条件,方便后续拼接 + const queryParams: (string | number)[] = []; + + // 添加店铺ID过滤 + if (shopIds.length > 0) { + const shopIdsInt = shopIds.map(id => parseInt(id)); + conditions.push(`g.shop_id IN (${shopIdsInt.map(() => '?').join(',')})`); + queryParams.push(...shopIdsInt); + } + + // 添加时间范围过滤 + if (startDate) { + conditions.push('g.date >= ?'); + queryParams.push(startDate); + } + + if (endDate) { + conditions.push('g.date <= ?'); + queryParams.push(endDate); + } + + // 拼接条件 + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询数据 + const [rows] = await connection.query( + `SELECT + g.shop_id, g.date, g.total, g.deducted, g.daily_increase, + s.wechat, s.nickname + FROM shop_follower_growth g + LEFT JOIN shops s ON g.shop_id = s.id + ${whereClause} + ORDER BY g.shop_id, g.date`, + queryParams + ); + + // 按店铺分组处理数据 + const shopGroups: Record = {}; + + if (Array.isArray(rows)) { + rows.forEach(row => { + const shopId = row.shop_id; + + if (!shopGroups[shopId]) { + shopGroups[shopId] = { + shop_id: shopId, + shop_name: row.nickname || row.wechat || `店铺ID: ${shopId}`, + dates: [], + total_counts: [], + daily_increases: [] + }; + } + + shopGroups[shopId].dates.push(row.date.toISOString().split('T')[0]); + shopGroups[shopId].total_counts.push(row.total); + shopGroups[shopId].daily_increases.push(row.daily_increase); + }); + } + + // 转换为数组 + const statistics = Object.values(shopGroups); + + console.log(`查询团队[${teamCode}]店铺粉丝增长统计数据成功,共 ${statistics.length} 个店铺的数据`); + + return NextResponse.json({ + success: true, + statistics + }); + } catch (error) { + console.error(`获取团队[${teamCode}]店铺粉丝增长统计数据失败:`, error); + return NextResponse.json( + { success: false, error: '获取店铺粉丝增长统计数据失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getGrowthStatistics)(teamReq, { teamCode }); + } catch (error) { + console.error('处理店铺粉丝增长统计请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/shop-sales-analysis/route.ts b/src/app/api/team/[teamCode]/shop-sales-analysis/route.ts new file mode 100644 index 0000000..db67427 --- /dev/null +++ b/src/app/api/team/[teamCode]/shop-sales-analysis/route.ts @@ -0,0 +1,139 @@ +/** + * 作者: 阿瑞 + * 功能: 店铺成交分析数据API + * 版本: 1.0.0 + * 文件有问题!!!!!api处理方式不对!!!!待修复 + */ +import { NextResponse, NextRequest } from "next/server"; +import { teamDbManager } from '@/lib/db'; +import { Shop } from "@/lib/db/types"; +import { RowDataPacket } from 'mysql2'; + +/** + * 类型定义 + */ +interface ShopRow extends Shop, RowDataPacket {} +interface FollowerResult extends RowDataPacket { total_followers: number } +interface ExpenseResult extends RowDataPacket { total_expenses: number } +interface SalesResult extends RowDataPacket { + order_count: number; + total_sales: number; +} +interface CostResult extends RowDataPacket { total_cost: number } + +/** + * 处理获取店铺成交分析数据的请求 + */ +export async function GET(request: NextRequest) { + try { + // 从URL路径中提取团队代码 + const pathname = request.nextUrl.pathname; + const teamCode = pathname.split('/')[3]; // /api/team/[teamCode]/shop-sales-analysis + + if (!teamCode) { + return NextResponse.json( + { error: "团队代码不能为空" }, + { status: 400 } + ); + } + + // 获取查询参数 + const searchParams = request.nextUrl.searchParams; + const startDate = searchParams.get("startDate"); + const endDate = searchParams.get("endDate"); + + if (!startDate || !endDate) { + return NextResponse.json( + { error: "开始日期和结束日期是必填参数" }, + { status: 400 } + ); + } + + // 获取团队数据库连接 + const teamDBInfo = await teamDbManager.getTeamDBInfoByCode(teamCode); + if (!teamDBInfo) { + return NextResponse.json( + { error: "无法获取团队信息,请确保团队代码有效" }, + { status: 400 } + ); + } + + // 使用团队数据库管理器获取连接池 + const teamPool = teamDbManager.getTeamDbPool(teamDBInfo); + + // 从连接池获取连接 + const connection = await teamPool.getConnection(); + console.log(`🟢 MySQL 团队数据库连接已获取: ${teamDBInfo.dbHost}/${teamDBInfo.dbName}`); + + try { + // 获取所有店铺数据 + const [shops] = await connection.query(` + SELECT id, nickname, wechat, account_no FROM shops WHERE status = 1 + `); + + // 获取分析数据 + const results = await Promise.all( + shops.map(async (shop: ShopRow) => { + // 获取进粉数量 (来自shop_follower_growth表,计算日增长人数总和) + const [followersResult] = await connection.query(` + SELECT COALESCE(SUM(daily_increase), 0) as total_followers + FROM shop_follower_growth + WHERE shop_id = ? AND date BETWEEN ? AND ? + `, [shop.id, startDate, endDate]); + + // 获取引流消费 (来自shop_traffic_expenses表) + const [expensesResult] = await connection.query(` + SELECT COALESCE(SUM(expense_amount), 0) as total_expenses + FROM shop_traffic_expenses + WHERE shop_id = ? AND DATE(record_time) BETWEEN ? AND ? + `, [shop.id, startDate, endDate]); + + // 获取出单数和销售额 (来自sales_records表) + const [salesResult] = await connection.query(` + SELECT + COUNT(*) as order_count, + COALESCE(SUM(receivable), 0) as total_sales + FROM sales_records + WHERE source_id = ? AND deal_date BETWEEN ? AND ? + `, [shop.id, startDate, endDate]); + + // 获取成本 (从sales_records关联到sales_record_products再到products表) + const [costResult] = await connection.query(` + SELECT COALESCE(SUM(p.cost->>'$.cost_price' * srp.quantity), 0) as total_cost + FROM sales_records sr + JOIN sales_record_products srp ON sr.id = srp.sales_record_id + JOIN products p ON srp.product_id = p.id + WHERE sr.source_id = ? AND sr.deal_date BETWEEN ? AND ? + `, [shop.id, startDate, endDate]); + + return { + shopId: shop.id, + shopName: shop.nickname || shop.wechat || shop.account_no || `店铺${shop.id}`, + followers: followersResult[0]?.total_followers || 0, + expenses: expensesResult[0]?.total_expenses || 0, + orderCount: salesResult[0]?.order_count || 0, + sales: salesResult[0]?.total_sales || 0, + cost: costResult[0]?.total_cost || 0 + }; + }) + ); + + // 释放连接 + connection.release(); + console.log(`🟢 MySQL 团队数据库连接已释放: ${teamDBInfo.dbHost}/${teamDBInfo.dbName}`); + + return NextResponse.json({ success: true, data: results }); + } catch (error) { + // 确保释放连接 + connection.release(); + console.log(`🟢 MySQL 团队数据库连接已释放(异常): ${teamDBInfo.dbHost}/${teamDBInfo.dbName}`); + throw error; + } + } catch (error) { + console.error("获取店铺成交分析数据失败:", error); + return NextResponse.json( + { error: "获取店铺成交分析数据失败" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/team/[teamCode]/shops/[id]/route.ts b/src/app/api/team/[teamCode]/shops/[id]/route.ts new file mode 100644 index 0000000..dadd0c2 --- /dev/null +++ b/src/app/api/team/[teamCode]/shops/[id]/route.ts @@ -0,0 +1,436 @@ +/** + * 单个店铺API路由 + * 作者: 阿瑞 + * 功能: 提供单个店铺的查询、更新和删除接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + // 不复制body,因为它可能已被读取 + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/shops/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,店铺ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * 获取单个店铺 + */ +const getShop = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的店铺ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始查询团队[${teamCode}]店铺, ID:${id}`); + + const [rows] = await req.db.query( + `SELECT + id, unionid, openid, account_no as accountNo, wechat, + avatar, nickname, phone, status, remark, + created_at as createdAt, updated_at as updatedAt + FROM shops WHERE id = ?`, + [id] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应店铺' }, + { status: 404 } + ); + } + + console.log(`查询团队[${teamCode}]店铺成功, ID:${id}`); + + return NextResponse.json({ + success: true, + shop: rows[0] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]店铺详情失败:`, error); + return NextResponse.json( + { success: false, error: '获取店铺详情失败' }, + { status: 500 } + ); + } +}; + +/** + * 更新店铺 + */ +interface ShopRequestBody { + id?: number; + unionid?: string | null; + openid?: string | null; + accountNo?: string | null; + wechat?: string | null; + avatar?: string | null; + nickname?: string | null; + phone?: string | null; + status?: number; + remark?: string | null; +} + +const updateShop = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + const data = params?.requestBody as ShopRequestBody; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的店铺ID' }, + { status: 400 } + ); + } + + if (!data) { + return NextResponse.json( + { success: false, error: '请求数据为空' }, + { status: 400 } + ); + } + + try { + console.log(`开始更新团队[${teamCode}]店铺, ID:${id}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查店铺是否存在 + const [existingShop] = await connection.query( + 'SELECT id FROM shops WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingShop) || existingShop.length === 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '未找到相应店铺' }, + { status: 404 } + ); + } + + // 如果更新unionid,检查是否已存在 + if (data.unionid) { + const [unionidCheck] = await connection.query( + 'SELECT id FROM shops WHERE unionid = ? AND id != ?', + [data.unionid, id] + ); + + if (Array.isArray(unionidCheck) && unionidCheck.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该unionid已存在' }, + { status: 409 } + ); + } + } + + // 如果更新wechat,检查是否已存在 + if (data.wechat) { + const [wechatCheck] = await connection.query( + 'SELECT id FROM shops WHERE wechat = ? AND id != ?', + [data.wechat, id] + ); + + if (Array.isArray(wechatCheck) && wechatCheck.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该微信号已存在' }, + { status: 409 } + ); + } + } + + // 构建更新SQL + const updateFields = []; + const updateParams = []; + + // 按字段顺序检查并添加到更新列表 + if (data.unionid !== undefined) { + updateFields.push('unionid = ?'); + updateParams.push(data.unionid || null); + } + + if (data.openid !== undefined) { + updateFields.push('openid = ?'); + updateParams.push(data.openid || null); + } + + if (data.accountNo !== undefined) { + updateFields.push('account_no = ?'); + updateParams.push(data.accountNo || null); + } + + if (data.wechat !== undefined) { + updateFields.push('wechat = ?'); + updateParams.push(data.wechat || null); + } + + if (data.avatar !== undefined) { + updateFields.push('avatar = ?'); + updateParams.push(data.avatar || null); + } + + if (data.nickname !== undefined) { + updateFields.push('nickname = ?'); + updateParams.push(data.nickname || null); + } + + if (data.phone !== undefined) { + updateFields.push('phone = ?'); + updateParams.push(data.phone || null); + } + + if (data.status !== undefined) { + updateFields.push('status = ?'); + updateParams.push(data.status); + } + + if (data.remark !== undefined) { + updateFields.push('remark = ?'); + updateParams.push(data.remark || null); + } + + if (updateFields.length === 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '没有提供需要更新的字段' }, + { status: 400 } + ); + } + + updateFields.push('updated_at = NOW()'); + + // 添加ID作为WHERE条件参数 + updateParams.push(id); + + const updateSql = ` + UPDATE shops + SET ${updateFields.join(', ')} + WHERE id = ? + `; + + await connection.query(updateSql, updateParams); + + // 查询更新后的店铺信息 + const [updatedShopRows] = await connection.query( + `SELECT + id, unionid, openid, account_no as accountNo, wechat, + avatar, nickname, phone, status, remark, + created_at as createdAt, updated_at as updatedAt + FROM shops WHERE id = ?`, + [id] + ); + + // 提交事务 + await connection.commit(); + + console.log(`更新团队[${teamCode}]店铺成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '店铺更新成功', + shop: Array.isArray(updatedShopRows) && updatedShopRows.length > 0 ? updatedShopRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`更新团队[${teamCode}]店铺失败:`, error); + return NextResponse.json( + { success: false, error: '更新店铺失败' }, + { status: 500 } + ); + } +}; + +/** + * 删除店铺 + */ +const deleteShop = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的店铺ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始删除团队[${teamCode}]店铺, ID:${id}`); + + // 检查店铺是否存在 + const [existingShop] = await req.db.query( + 'SELECT id FROM shops WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingShop) || existingShop.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应店铺' }, + { status: 404 } + ); + } + + // 直接删除店铺记录 + await req.db.query('DELETE FROM shops WHERE id = ?', [id]); + + console.log(`删除团队[${teamCode}]店铺成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '店铺删除成功' + }); + } catch (error) { + console.error(`删除团队[${teamCode}]店铺失败:`, error); + return NextResponse.json( + { success: false, error: '删除店铺失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getShop)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理店铺详情请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 先读取请求体 + let requestBody; + try { + // 克隆请求,确保body只被读取一次 + const clonedReq = req.clone(); + requestBody = await clonedReq.json(); + } catch (error) { + console.error('解析请求数据失败:', error); + return NextResponse.json( + { success: false, error: '无效的请求数据格式' }, + { status: 400 } + ); + } + + // 添加团队信息到请求(不传递body) + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(updateShop)(teamReq, { teamCode, id, requestBody }); + } catch (error) { + console.error('处理店铺更新请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(deleteShop)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理店铺删除请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/shops/route.ts b/src/app/api/team/[teamCode]/shops/route.ts new file mode 100644 index 0000000..19309b0 --- /dev/null +++ b/src/app/api/team/[teamCode]/shops/route.ts @@ -0,0 +1,385 @@ +/** + * 店铺API接口 + * 作者: 阿瑞 + * 功能: 提供店铺数据的查询和创建接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { RowDataPacket } from 'mysql2/promise'; + +/** + * 自定义类型定义 + */ +interface CountResult extends RowDataPacket { + total: number; +} + +interface ShopType extends RowDataPacket { + categoryId: number; +} + +interface Shop extends RowDataPacket { + id: number; + unionid?: string; + openid?: string; + accountNo?: string; + wechat?: string; + avatar?: string; + nickname?: string; + phone?: string; + status: number; + remark?: string; + createdAt: string; + updatedAt: string; + accountTypes?: number[]; +} + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/shops + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * GET 获取店铺列表 + */ +const getShops = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + const status = url.searchParams.get('status') || ''; + const accountType = url.searchParams.get('accountType') || ''; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]店铺列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(s.nickname LIKE ? OR s.wechat LIKE ? OR s.account_no LIKE ? OR s.remark LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`, `%${keyword}%`); + } + + if (status) { + conditions.push('s.status = ?'); + queryParams.push(status); + } + + // 需要连接账号类型表进行查询 + let joinAccountTypeTable = ''; + if (accountType) { + joinAccountTypeTable = 'LEFT JOIN shop_account_types sat ON s.id = sat.shop_id'; + conditions.push('sat.category_id = ?'); + queryParams.push(accountType); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = ` + SELECT COUNT(DISTINCT s.id) as total + FROM shops s + ${joinAccountTypeTable} + ${whereClause} + `; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = totalRows[0].total; + + // 查询分页数据 + const querySql = ` + SELECT DISTINCT + s.id, s.unionid, s.openid, s.account_no as accountNo, + s.wechat, s.avatar, s.nickname, s.phone, s.status, + s.remark, s.created_at as createdAt, s.updated_at as updatedAt + FROM shops s + ${joinAccountTypeTable} + ${whereClause} + ORDER BY s.created_at DESC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + // 获取每个店铺的账号类型 + const shops = Array.isArray(rows) ? rows : []; + if (shops.length > 0) { + for (const shop of shops) { + // 查询店铺关联的账号类型 + const [accountTypes] = await connection.query( + `SELECT category_id as categoryId FROM shop_account_types WHERE shop_id = ?`, + [shop.id] + ); + + shop.accountTypes = Array.isArray(accountTypes) + ? accountTypes.map((type) => type.categoryId) + : []; + } + } + + console.log(`查询团队[${teamCode}]店铺列表成功, 总数:${total}`); + + return NextResponse.json({ + success: true, + total, + shops + }); + } catch (error) { + console.error(`获取团队[${teamCode}]店铺列表失败:`, error); + return NextResponse.json( + { success: false, error: '获取店铺列表失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 创建店铺 + */ +const createShop = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + try { + console.log(`开始创建团队[${teamCode}]店铺`); + + // 解析请求数据 + const data = await req.json(); + + // 校验请求数据:至少需要nickname或wechat之一 + if (!data.nickname && !data.wechat) { + return NextResponse.json( + { success: false, error: '店铺名称或微信号至少需要填写一项' }, + { status: 400 } + ); + } + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查wechat是否已存在 + if (data.wechat) { + const [existingWechat] = await connection.query( + 'SELECT id FROM shops WHERE wechat = ?', + [data.wechat] + ); + + if (Array.isArray(existingWechat) && existingWechat.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该微信号已存在' }, + { status: 409 } + ); + } + } + + // 检查unionid是否已存在 + if (data.unionid) { + const [existingUnionid] = await connection.query( + 'SELECT id FROM shops WHERE unionid = ?', + [data.unionid] + ); + + if (Array.isArray(existingUnionid) && existingUnionid.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该unionid已存在' }, + { status: 409 } + ); + } + } + + // 准备插入数据 + const insertSql = ` + INSERT INTO shops ( + unionid, openid, account_no, wechat, + avatar, nickname, phone, status, remark + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `; + + const insertParams = [ + data.unionid || null, + data.openid || null, + data.accountNo || null, + data.wechat || null, + data.avatar || null, + data.nickname || null, + data.phone || null, + data.status || 1, // 默认状态为正常 + data.remark || null + ]; + + // 执行插入操作 + const [result] = await connection.query(insertSql, insertParams); + const shopId = (result as { insertId: number }).insertId; + + // 处理账号类型关联 + if (data.accountTypes && Array.isArray(data.accountTypes) && data.accountTypes.length > 0) { + // 创建账号类型关联 + for (const categoryId of data.accountTypes) { + await connection.query( + 'INSERT INTO shop_account_types (shop_id, category_id) VALUES (?, ?)', + [shopId, categoryId] + ); + } + } + + // 提交事务 + await connection.commit(); + + // 查询创建好的店铺完整信息 + const [shops] = await connection.query( + `SELECT + id, unionid, openid, account_no as accountNo, wechat, + avatar, nickname, phone, status, remark, + created_at as createdAt, updated_at as updatedAt + FROM shops WHERE id = ?`, + [shopId] + ); + + if (!Array.isArray(shops) || shops.length === 0) { + // 这种情况理论上不应该发生 + return NextResponse.json( + { success: false, error: '店铺创建失败,无法获取新建店铺信息' }, + { status: 500 } + ); + } + + const shop = shops[0]; + + // 获取关联的账号类型 + const [accountTypes] = await connection.query( + `SELECT category_id as categoryId FROM shop_account_types WHERE shop_id = ?`, + [shopId] + ); + + (shop as Shop).accountTypes = Array.isArray(accountTypes) + ? accountTypes.map((type) => (type as unknown as { categoryId: number }).categoryId) + : []; + + console.log(`创建团队[${teamCode}]店铺成功, ID:${shopId}`); + + return NextResponse.json({ + success: true, + message: '店铺创建成功', + shop + }); + } catch (error) { + // 捕获到错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`创建团队[${teamCode}]店铺失败:`, error); + return NextResponse.json( + { success: false, error: '创建店铺失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getShops)(teamReq, { teamCode }); + } catch (error) { + console.error('处理店铺列表请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(createShop)(teamReq, { teamCode }); + } catch (error) { + console.error('处理店铺创建请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/suppliers/[id]/route.ts b/src/app/api/team/[teamCode]/suppliers/[id]/route.ts new file mode 100644 index 0000000..352284d --- /dev/null +++ b/src/app/api/team/[teamCode]/suppliers/[id]/route.ts @@ -0,0 +1,396 @@ +/** + * 单个供应商API接口 + * 作者: 阿瑞 + * 功能: 提供单个供应商的查询、更新和删除接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从URL路径中提取参数 + * @param req 请求对象 + * @returns 提取的参数对象 + */ +const extractParamsFromPath = (req: NextRequest): { teamCode: string; id: string } => { + // 路径格式: /api/team/[teamCode]/suppliers/[id] + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3,供应商ID位于索引5 + return { + teamCode: pathParts[3] || '', + id: pathParts[5] || '' + }; +}; + +/** + * GET 获取单个供应商 + */ +const getSupplier = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的供应商ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始查询团队[${teamCode}]供应商, ID:${id}`); + + const [rows] = await req.db.query( + `SELECT + id, \`order\`, name, contact, status, level, type, remark, + created_at as createdAt, updated_at as updatedAt + FROM suppliers WHERE id = ?`, + [id] + ); + + if (!Array.isArray(rows) || rows.length === 0) { + return NextResponse.json( + { success: false, error: '未找到相应供应商' }, + { status: 404 } + ); + } + + console.log(`查询团队[${teamCode}]供应商成功, ID:${id}`); + + return NextResponse.json({ + success: true, + supplier: rows[0] + }); + } catch (error) { + console.error(`获取团队[${teamCode}]供应商详情失败:`, error); + return NextResponse.json( + { success: false, error: '获取供应商详情失败' }, + { status: 500 } + ); + } +}; + +/** + * PUT 更新供应商 + */ +const updateSupplier = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的供应商ID' }, + { status: 400 } + ); + } + + // 获取请求数据 + const data = await req.json(); + + // 验证是否有需要更新的字段 + if (!data.name && data.order === undefined && !data.contact && data.status === undefined && + !data.level && !data.type && data.remark === undefined) { + return NextResponse.json( + { success: false, error: '缺少需要更新的字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始更新团队[${teamCode}]供应商, ID:${id}`); + + // 使用数据库连接并开始事务 + try { + await req.db.beginTransaction(); + + // 检查供应商是否存在 + const [existingSupplier] = await req.db.query( + 'SELECT id FROM suppliers WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingSupplier) || existingSupplier.length === 0) { + await req.db.rollback(); + return NextResponse.json( + { success: false, error: '未找到相应供应商' }, + { status: 404 } + ); + } + + // 如果更新名称,检查名称是否已存在 + if (data.name) { + const [nameCheck] = await req.db.query( + 'SELECT id FROM suppliers WHERE name = ? AND id != ?', + [data.name, id] + ); + + if (Array.isArray(nameCheck) && nameCheck.length > 0) { + await req.db.rollback(); + return NextResponse.json( + { success: false, error: '该供应商名称已存在' }, + { status: 409 } + ); + } + } + + // 处理联系方式字段 + let contactJson = undefined; + if (data.contact !== undefined) { + contactJson = data.contact ? JSON.stringify(data.contact) : null; + } + + // 构建更新SQL + const updateFields = []; + const updateParams = []; + + if (data.name) { + updateFields.push('name = ?'); + updateParams.push(data.name); + } + + if (data.order !== undefined) { + updateFields.push('`order` = ?'); + updateParams.push(data.order); + } + + if (contactJson !== undefined) { + updateFields.push('contact = ?'); + updateParams.push(contactJson); + } + + if (data.status !== undefined) { + updateFields.push('status = ?'); + updateParams.push(data.status); + } + + if (data.level !== undefined) { + updateFields.push('level = ?'); + updateParams.push(data.level || null); + } + + if (data.type !== undefined) { + updateFields.push('type = ?'); + updateParams.push(data.type || null); + } + + if (data.remark !== undefined) { + updateFields.push('remark = ?'); + updateParams.push(data.remark || null); + } + + updateFields.push('updated_at = NOW()'); + + // 添加ID作为WHERE条件参数 + updateParams.push(id); + + const updateSql = ` + UPDATE suppliers + SET ${updateFields.join(', ')} + WHERE id = ? + `; + + await req.db.query(updateSql, updateParams); + + // 查询更新后的供应商信息 + const [updatedSupplierRows] = await req.db.query( + `SELECT + id, \`order\`, name, contact, status, level, type, remark, + created_at as createdAt, updated_at as updatedAt + FROM suppliers WHERE id = ?`, + [id] + ); + + // 提交事务 + await req.db.commit(); + + console.log(`更新团队[${teamCode}]供应商成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '供应商更新成功', + supplier: Array.isArray(updatedSupplierRows) && updatedSupplierRows.length > 0 ? updatedSupplierRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await req.db.rollback(); + throw error; + } + } catch (error) { + console.error(`更新团队[${teamCode}]供应商失败:`, error); + return NextResponse.json( + { success: false, error: '更新供应商失败' }, + { status: 500 } + ); + } +}; + +/** + * DELETE 删除供应商 + */ +const deleteSupplier = async (req: RequestWithDB, params?: Record) => { + const id = params?.id as string; + const teamCode = params?.teamCode as string; + + if (!id || isNaN(Number(id))) { + return NextResponse.json( + { success: false, error: '无效的供应商ID' }, + { status: 400 } + ); + } + + try { + console.log(`开始删除团队[${teamCode}]供应商, ID:${id}`); + + // 使用数据库连接执行删除操作 + try { + await req.db.beginTransaction(); + + // 检查供应商是否存在 + const [existingSupplier] = await req.db.query( + 'SELECT id FROM suppliers WHERE id = ?', + [id] + ); + + if (!Array.isArray(existingSupplier) || existingSupplier.length === 0) { + await req.db.rollback(); + return NextResponse.json( + { success: false, error: '未找到相应供应商' }, + { status: 404 } + ); + } + + // 删除供应商与品类的关联记录 + await req.db.query('DELETE FROM supplier_categories WHERE supplier_id = ?', [id]); + + // 删除供应商记录 + await req.db.query('DELETE FROM suppliers WHERE id = ?', [id]); + + // 提交事务 + await req.db.commit(); + + console.log(`删除团队[${teamCode}]供应商成功, ID:${id}`); + + return NextResponse.json({ + success: true, + message: '供应商删除成功' + }); + } catch (error) { + // 发生错误时回滚事务 + await req.db.rollback(); + throw error; + } + } catch (error) { + console.error(`删除团队[${teamCode}]供应商失败:`, error); + return NextResponse.json( + { success: false, error: '删除供应商失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getSupplier)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理供应商详情请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function PUT(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(updateSupplier)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理供应商更新请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function DELETE(req: NextRequest) { + try { + // 从URL路径中提取参数 + const { teamCode, id } = extractParamsFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(deleteSupplier)(teamReq, { teamCode, id }); + } catch (error) { + console.error('处理供应商删除请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/[teamCode]/suppliers/route.ts b/src/app/api/team/[teamCode]/suppliers/route.ts new file mode 100644 index 0000000..8872377 --- /dev/null +++ b/src/app/api/team/[teamCode]/suppliers/route.ts @@ -0,0 +1,300 @@ +/** + * 供应商API接口 + * 作者: 阿瑞 + * 功能: 提供供应商数据的查询和创建接口 + * 版本: 2.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { RequestWithDB, connectTeamDB } from '@/lib/db'; +import { SupplierStatus } from '@/models/team/types/ISupplier'; + +/** + * 为请求添加团队信息 + * @param req 原始请求对象 + * @param teamCode 团队代码 + * @returns 包含团队信息的新请求对象 + */ +const addTeamInfoToRequest = (req: NextRequest, teamCode: string): NextRequest => { + // 创建新的请求对象 + const teamReq = new NextRequest(req.url, { + method: req.method, + headers: new Headers(req.headers), + body: req.body, + cache: req.cache, + credentials: req.credentials, + integrity: req.integrity, + keepalive: req.keepalive, + mode: req.mode, + redirect: req.redirect, + referrer: req.referrer, + referrerPolicy: req.referrerPolicy + }); + + // 添加团队ID信息 + teamReq.headers.set('x-team-id', teamCode); + + return teamReq; +}; + +/** + * 从请求路径中提取团队代码 + * @param req 请求对象 + * @returns 团队代码 + */ +const extractTeamCodeFromPath = (req: NextRequest): string => { + // 路径格式: /api/team/[teamCode]/suppliers + const pathParts = req.nextUrl.pathname.split('/'); + // 团队代码位于索引3的位置 + return pathParts[3] || ''; +}; + +/** + * GET 获取供应商列表 + */ +const getSuppliers = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取查询参数 + const url = new URL(req.url); + const page = parseInt(url.searchParams.get('page') || '1'); + const pageSize = parseInt(url.searchParams.get('pageSize') || '10'); + const keyword = url.searchParams.get('keyword') || ''; + const status = url.searchParams.get('status') !== null ? parseInt(url.searchParams.get('status') as string) : null; + const level = url.searchParams.get('level') || null; + const type = url.searchParams.get('type') || null; + + // 计算偏移量 + const offset = (page - 1) * pageSize; + + try { + console.log(`开始查询团队[${teamCode}]供应商列表, 页码:${page}, 每页:${pageSize}`); + + // 构建查询条件 + const conditions = []; + const queryParams = []; + + if (keyword) { + conditions.push('(name LIKE ? OR JSON_EXTRACT(contact, "$.contactPerson") LIKE ? OR JSON_EXTRACT(contact, "$.phone") LIKE ?)'); + queryParams.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`); + } + + if (status !== null) { + conditions.push('status = ?'); + queryParams.push(status); + } + + if (level) { + conditions.push('level = ?'); + queryParams.push(level); + } + + if (type) { + conditions.push('type = ?'); + queryParams.push(type); + } + + const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; + + // 使用数据库连接 + const connection = req.db; + + // 查询总数 + const countSql = `SELECT COUNT(*) as total FROM suppliers ${whereClause}`; + const [totalRows] = await connection.query(countSql, queryParams); + + const total = (totalRows as Array<{ total: number }>)[0].total; + + // 查询分页数据 + const querySql = ` + SELECT + id, \`order\`, name, contact, status, level, type, remark, + created_at as createdAt, updated_at as updatedAt + FROM suppliers ${whereClause} + ORDER BY \`order\` ASC, id ASC + LIMIT ? OFFSET ? + `; + + // 添加分页参数 + const paginatedParams = [...queryParams, pageSize, offset]; + + const [rows] = await connection.query(querySql, paginatedParams); + + console.log(`查询团队[${teamCode}]供应商列表成功, 总数:${total}`); + + // 查询所有不同的供应商级别和类型,用于前端筛选 + const [levelRows] = await connection.query('SELECT DISTINCT level FROM suppliers WHERE level IS NOT NULL AND level != ""'); + const [typeRows] = await connection.query('SELECT DISTINCT type FROM suppliers WHERE type IS NOT NULL AND type != ""'); + + // 转换结果 + const levels = Array.isArray(levelRows) ? levelRows.map((row) => (row as unknown as { level: string }).level).filter(Boolean) : []; + const types = Array.isArray(typeRows) ? typeRows.map((row) => (row as unknown as { type: string }).type).filter(Boolean) : []; + + return NextResponse.json({ + success: true, + total, + suppliers: Array.isArray(rows) ? rows : [], + filters: { + levels, + types + } + }); + } catch (error) { + console.error(`获取团队[${teamCode}]供应商列表失败:`, error); + return NextResponse.json( + { success: false, error: '获取供应商列表失败' }, + { status: 500 } + ); + } +}; + +/** + * POST 创建供应商 + */ +const createSupplier = async (req: RequestWithDB, params?: Record) => { + const teamCode = params?.teamCode as string; + + // 获取请求数据 + const data = await req.json(); + + // 验证必填字段 + if (!data.name) { + return NextResponse.json( + { success: false, error: '供应商名称为必填字段' }, + { status: 400 } + ); + } + + try { + console.log(`开始创建团队[${teamCode}]供应商, 名称:${data.name}`); + + // 使用数据库连接 + const connection = req.db; + + try { + await connection.beginTransaction(); + + // 检查供应商名称是否已存在 + const [existingSuppliers] = await connection.query( + 'SELECT id FROM suppliers WHERE name = ?', + [data.name] + ); + + if (Array.isArray(existingSuppliers) && existingSuppliers.length > 0) { + await connection.rollback(); + return NextResponse.json( + { success: false, error: '该供应商名称已存在' }, + { status: 409 } + ); + } + + // 处理联系方式字段 + let contactJson = null; + if (data.contact) { + contactJson = JSON.stringify(data.contact); + } + + // 插入供应商记录 + const insertSql = ` + INSERT INTO suppliers ( + \`order\`, name, contact, status, level, type, remark, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW()) + `; + + const [result] = await connection.query(insertSql, [ + data.order || 0, + data.name, + contactJson, + data.status !== undefined ? data.status : SupplierStatus.ENABLED, + data.level || null, + data.type || null, + data.remark || null + ]); + + const insertId = (result as { insertId: number }).insertId; + + // 查询新插入的供应商信息 + const [newSupplierRows] = await connection.query( + `SELECT + id, \`order\`, name, contact, status, level, type, remark, + created_at as createdAt, updated_at as updatedAt + FROM suppliers WHERE id = ?`, + [insertId] + ); + + // 提交事务 + await connection.commit(); + + console.log(`创建团队[${teamCode}]供应商成功, ID:${insertId}`); + + return NextResponse.json({ + success: true, + message: '供应商创建成功', + supplier: Array.isArray(newSupplierRows) && newSupplierRows.length > 0 ? newSupplierRows[0] : null + }); + } catch (error) { + // 发生错误时回滚事务 + await connection.rollback(); + throw error; + } + } catch (error) { + console.error(`创建团队[${teamCode}]供应商失败:`, error); + return NextResponse.json( + { success: false, error: '创建供应商失败' }, + { status: 500 } + ); + } +}; + +// 导出处理函数 +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(getSuppliers)(teamReq, { teamCode }); + } catch (error) { + console.error('处理供应商列表请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} + +export async function POST(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const teamCode = extractTeamCodeFromPath(req); + + if (!teamCode) { + return NextResponse.json( + { success: false, error: '无效的团队代码' }, + { status: 400 } + ); + } + + // 添加团队信息到请求 + const teamReq = addTeamInfoToRequest(req, teamCode); + + // 使用新版的connectTeamDB中间件处理请求 + return connectTeamDB(createSupplier)(teamReq, { teamCode }); + } catch (error) { + console.error('处理供应商创建请求失败:', error); + return NextResponse.json( + { success: false, error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/team/users/route.ts b/src/app/api/team/users/route.ts new file mode 100644 index 0000000..30ab46c --- /dev/null +++ b/src/app/api/team/users/route.ts @@ -0,0 +1,39 @@ +/** + * 团队用户API路由 + * 作者: 阿瑞 + * 功能: 提供团队用户相关API,演示如何使用团队数据库连接 + * 版本: 2.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { connectTeamDB, RequestWithDB } from '@/lib/db'; + +/** + * 获取团队用户列表 + * 自动从请求中提取团队信息并连接对应的数据库 + */ +export async function GET(req: NextRequest) { + try { + // 使用connectTeamDB中间件处理请求 + return await connectTeamDB(async (dbReq: RequestWithDB) => { + try { + // 执行SQL查询 + const [users] = await dbReq.db.query('SELECT * FROM users LIMIT 100'); + + return NextResponse.json({ users }); + } catch (error) { + console.error('查询用户列表失败:', error); + return NextResponse.json( + { error: '查询用户列表失败' }, + { status: 500 } + ); + } + })(req); + } catch (error) { + console.error('处理请求失败:', error); + return NextResponse.json( + { error: '处理请求失败' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/teams/[teamCode]/init-database/route.ts b/src/app/api/teams/[teamCode]/init-database/route.ts new file mode 100644 index 0000000..3eaf71d --- /dev/null +++ b/src/app/api/teams/[teamCode]/init-database/route.ts @@ -0,0 +1,252 @@ +/** + * 团队数据库初始化API + * 作者: 阿瑞 + * 功能: 初始化团队专用数据库 + * 版本: 1.2.0 + */ + +import { NextRequest, NextResponse } from "next/server"; +import { TeamModel } from "@/models/team"; +import { AuthUtils } from "@/lib/auth"; +import mysql from 'mysql2/promise'; +import DbConfig from "@/lib/db/config"; +import { TeamDBInitializer } from "@/lib/db/init-team-db"; +import { ITeam } from "@/models/system/types"; + +/** + * 团队数据库处理类 + */ +class TeamDatabase { + private config: { + db_host: string; + db_name: string; + db_username: string; + db_password: string; + team_code: string; + }; + + constructor(config: { + db_host: string; + db_name: string; + db_username: string; + db_password: string; + team_code: string; + }) { + this.config = config; + } + + /** + * 初始化数据库 + */ + async initialize(): Promise { + // 连接到数据库 + console.log(`连接到团队数据库: ${this.config.db_name}`); + const connection = await mysql.createConnection({ + host: this.config.db_host, + user: this.config.db_username, + password: this.config.db_password, + database: this.config.db_name + }); + + try { + // 使用TeamDBInitializer初始化表结构 + await TeamDBInitializer.initialize(connection); + + // 初始化默认数据 + await TeamDBInitializer.initDefaultData(connection); + } catch (error) { + console.error(`团队 ${this.config.team_code} 数据库初始化失败:`, error); + throw error; + } finally { + // 确保关闭连接 + await connection.end(); + } + } +} + +/** + * POST 处理初始化团队数据库请求 + * 符合Next.js 15.3+的路由处理器签名 + */ +export async function POST(request: NextRequest) { + try { + // 从URL路径中提取团队代码 + const pathname = request.nextUrl.pathname; + const teamCode = pathname.split('/')[3]; // 路径格式: /api/teams/[teamCode]/init-database + + console.log(`开始初始化团队数据库, 团队代码: ${teamCode}`); + + // 从请求头获取访问令牌 + const authHeader = request.headers.get('Authorization'); + let token = ''; + + if (authHeader && authHeader.startsWith('Bearer ')) { + token = authHeader.substring(7); + } else { + // 尝试从Cookie中获取 + const tokenCookie = request.cookies.get('accessToken'); + if (tokenCookie) { + token = tokenCookie.value; + } else { + // 尝试读取localStorage中的令牌(从请求中) + const saasStorage = request.cookies.get('saas-user-storage'); + if (saasStorage) { + try { + const storageData = JSON.parse(decodeURIComponent(saasStorage.value)); + if (storageData && storageData.state && storageData.state.accessToken) { + token = storageData.state.accessToken; + } + } catch (e) { + console.error('解析存储的用户数据失败:', e); + } + } + } + } + + // 验证令牌并获取用户信息 + if (!token) { + console.error('初始化数据库失败: 用户未登录'); + return NextResponse.json( + { success: false, error: "用户未登录" }, + { status: 401 } + ); + } + + // 验证用户身份 + const user = await AuthUtils.verifyToken(token); + if (!user || !user.id) { + return NextResponse.json( + { success: false, error: "用户验证失败" }, + { status: 401 } + ); + } + + // 获取团队信息 + const team = await TeamModel.getByTeamCode(teamCode) as ITeam; + if (!team) { + console.error(`团队不存在, 团队代码: ${teamCode}`); + return NextResponse.json( + { success: false, error: "团队不存在" }, + { status: 404 } + ); + } + + console.log(`获取到团队信息: ${team.name}, ID: ${team.id}`); + + // 检查用户权限(必须是团队所有者) + const userId = Number(user.id); + if (team.owner_id !== userId) { + console.error(`权限错误: 用户(${userId})不是团队(${team.id})的所有者(${team.owner_id})`); + return NextResponse.json( + { success: false, error: "只有团队所有者可以初始化数据库" }, + { status: 403 } + ); + } + + try { + // 确保团队对象包含所需的数据库信息 + if (!team.db_name || !team.db_username || !team.db_password || !team.db_host) { + console.error('团队数据库信息不完整'); + return NextResponse.json( + { success: false, error: "团队数据库信息不完整" }, + { status: 500 } + ); + } + + // 首先使用管理员连接 + console.log(`使用管理员账号连接到数据库: ${DbConfig.host}`); + const rootConnection = await mysql.createConnection({ + host: DbConfig.host, + user: DbConfig.adminUser, + password: DbConfig.adminPassword + }); + + // 查询数据库是否存在 + const [existingDbs] = await rootConnection.query( + `SHOW DATABASES LIKE '${team.db_name}'` + ); + + // 如果数据库不存在,创建数据库 + if (Array.isArray(existingDbs) && existingDbs.length === 0) { + console.log(`创建团队数据库: ${team.db_name}`); + await rootConnection.query( + `CREATE DATABASE IF NOT EXISTS \`${team.db_name}\` + CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci` + ); + } else { + console.log(`团队数据库 ${team.db_name} 已存在`); + } + + // 创建或更新数据库用户 + console.log(`创建/更新团队数据库用户: ${team.db_username}`); + + // 删除可能存在的同名用户(确保用户不存在) + try { + await rootConnection.query(`DROP USER IF EXISTS '${team.db_username}'@'%'`); + await rootConnection.query(`DROP USER IF EXISTS '${team.db_username}'@'localhost'`); + } catch (e) { + console.log(`删除已存在用户失败,可能是用户不存在: ${e}`); + } + + // 创建新用户(同时为本地和远程连接创建) + await rootConnection.query( + `CREATE USER '${team.db_username}'@'localhost' IDENTIFIED BY '${team.db_password}'` + ); + await rootConnection.query( + `CREATE USER '${team.db_username}'@'%' IDENTIFIED BY '${team.db_password}'` + ); + + // 授予用户对该数据库的所有权限 + await rootConnection.query( + `GRANT ALL PRIVILEGES ON \`${team.db_name}\`.* TO '${team.db_username}'@'localhost'` + ); + await rootConnection.query( + `GRANT ALL PRIVILEGES ON \`${team.db_name}\`.* TO '${team.db_username}'@'%'` + ); + + // 刷新权限表 + await rootConnection.query('FLUSH PRIVILEGES'); + + console.log(`用户 ${team.db_username} 创建完成并授权成功`); + + // 关闭管理员连接 + await rootConnection.end(); + + // 初始化团队数据库表结构 + console.log(`使用团队专用账号连接并初始化数据库表结构: ${team.db_name}`); + const teamDatabase = new TeamDatabase({ + db_host: team.db_host, + db_name: team.db_name, + db_username: team.db_username, + db_password: team.db_password, + team_code: team.team_code + }); + + // 执行初始化 + await teamDatabase.initialize(); + console.log(`团队数据库 ${team.db_name} 初始化成功`); + + } catch (dbError: unknown) { + const err = dbError as Error; + console.error("数据库操作失败:", err); + return NextResponse.json( + { success: false, error: `数据库操作失败: ${err.message}` }, + { status: 500 } + ); + } + + // 返回成功响应 + return NextResponse.json({ + success: true, + message: `团队 ${team.name} 数据库初始化成功` + }); + + } catch (error: unknown) { + const err = error as Error; + console.error("初始化团队数据库出错:", err); + return NextResponse.json( + { success: false, error: err.message || "初始化团队数据库失败" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/teams/[teamCode]/route.ts b/src/app/api/teams/[teamCode]/route.ts new file mode 100644 index 0000000..e595b07 --- /dev/null +++ b/src/app/api/teams/[teamCode]/route.ts @@ -0,0 +1,114 @@ +/** + * 单个团队查询API路由 + * 作者: 阿瑞 + * 功能: 根据团队代码查询团队信息 + * 版本: 1.0 + */ + +import { NextRequest, NextResponse } from "next/server"; +import { connectSystemDB, RequestWithDB } from "@/lib/db"; + +/** + * 错误类型定义 + */ +interface ApiError extends Error { + message: string; + code?: string; + status?: number; +} + +/** + * GET 处理获取单个团队信息请求 + * 符合Next.js 15.3+的路由处理器签名 + */ +export async function GET(req: NextRequest) { + try { + // 从URL路径中提取团队代码 + const pathname = req.nextUrl.pathname; + const teamCode = pathname.split('/')[3]; // 路径格式: /api/teams/[teamCode] + + // 验证必要参数 + if (!teamCode) { + return NextResponse.json( + { + success: false, + error: "团队代码为必填项" + }, + { status: 400 } + ); + } + + // 使用数据库连接中间件处理请求 + const handler = async (dbReq: RequestWithDB) => { + // 执行查询 + const [rows] = await dbReq.db.query( + `SELECT * FROM teams WHERE team_code = ? AND status = 1`, + [teamCode] + ); + + // 转换结果为JS对象 + const teams = JSON.parse(JSON.stringify(rows)); + + // 检查是否找到团队 + if (teams.length === 0) { + return NextResponse.json( + { + success: false, + error: "未找到指定的团队" + }, + { status: 404 } + ); + } + + // 获取第一个结果并排除敏感信息 + const team = teams[0]; + const { ...safeTeamData } = team; + + // 查询团队拥有者信息 + const [ownerRows] = await dbReq.db.query( + `SELECT id, username, email FROM system_users WHERE id = ?`, + [team.owner_id] + ); + + const owners = JSON.parse(JSON.stringify(ownerRows)); + const ownerInfo = owners.length > 0 ? owners[0] : null; + + // 查询团队成员数量 + const [memberCountRows] = await dbReq.db.query( + `SELECT COUNT(*) as member_count FROM user_team_relations WHERE team_id = ?`, + [team.id] + ); + + const memberCounts = JSON.parse(JSON.stringify(memberCountRows)); + const memberCount = memberCounts.length > 0 ? memberCounts[0].member_count : 0; + + // 组装完整的团队信息 + const teamWithDetails = { + ...safeTeamData, + owner: ownerInfo, + member_count: memberCount + }; + + // 返回结果 + return NextResponse.json({ + success: true, + data: teamWithDetails + }); + }; + + // 执行处理函数 + const wrappedHandler = connectSystemDB(handler); + return await wrappedHandler(req); + + } catch (error: unknown) { + const err = error as ApiError; + console.error("获取团队信息出错:", err); + return NextResponse.json( + { + success: false, + error: err.message || "获取团队信息失败" + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/teams/route.ts b/src/app/api/teams/route.ts new file mode 100644 index 0000000..54a4eab --- /dev/null +++ b/src/app/api/teams/route.ts @@ -0,0 +1,389 @@ +/** + * 团队管理API路由 + * 作者: 阿瑞 + * 功能: 处理团队的创建和查询 + * 版本: 1.1.0 + */ + +import { NextRequest, NextResponse } from "next/server"; +import { connectSystemDB, RequestWithDB } from "@/lib/db"; +import { AuthUtils } from "@/lib/auth"; +import DbConfig from "@/lib/db/config"; +import { TeamMemberRole } from "@/models/system/types"; + +/** + * 扩展解码令牌接口添加workspaceId + */ +interface DecodedTokenWithWorkspace { + id: number; + username: string; + email: string; + role_type: string; + workspace_id: number; + sub: number; + iat: number; + exp: number; +} + +/** + * 生成安全的随机密码 + * @param length 密码长度,默认为12 + * @returns 生成的随机密码 + */ +function generateSecurePassword(length = 12): string { + // 定义字符集 + const lowercase = 'abcdefghijklmnopqrstuvwxyz'; + const uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const numbers = '0123456789'; + const special = '!@#$%^&*()_-+=<>?'; + + const allChars = lowercase + uppercase + numbers + special; + + // 确保密码至少包含每种字符各一个 + let password = ''; + password += lowercase.charAt(Math.floor(Math.random() * lowercase.length)); + password += uppercase.charAt(Math.floor(Math.random() * uppercase.length)); + password += numbers.charAt(Math.floor(Math.random() * numbers.length)); + password += special.charAt(Math.floor(Math.random() * special.length)); + + // 添加剩余的随机字符 + for (let i = 4; i < length; i++) { + password += allChars.charAt(Math.floor(Math.random() * allChars.length)); + } + + // 打乱字符顺序 + return password.split('').sort(() => 0.5 - Math.random()).join(''); +} + +/** + * 错误类型定义 + */ +interface ApiError extends Error { + message: string; + code?: string; + status?: number; +} + +/** + * GET 处理获取团队列表请求 + */ +export async function GET(req: NextRequest) { + try { + // 获取查询参数 + const searchParams = req.nextUrl.searchParams; + const workspaceId = searchParams.get('workspaceId'); + const userId = searchParams.get('userId'); + + // 验证必要参数 + if (!workspaceId) { + return NextResponse.json( + { + success: false, + error: "工作空间ID为必填项" + }, + { status: 400 } + ); + } + + // 使用数据库连接中间件处理请求 + const handler = async (dbReq: RequestWithDB) => { + let query = ''; + let params: (string | number)[] = []; + + // 根据不同的查询参数构建SQL + if (userId) { + // 查询用户所属的团队 + query = ` + SELECT t.* + FROM teams t + JOIN user_team_relations utr ON t.id = utr.team_id + WHERE t.workspace_id = ? + AND utr.user_id = ? + AND t.status = 1 + ORDER BY t.created_at DESC + `; + params = [workspaceId, userId]; + } else { + // 查询工作空间内的所有团队 + query = ` + SELECT * + FROM teams + WHERE workspace_id = ? + AND status = 1 + ORDER BY created_at DESC + `; + params = [workspaceId]; + } + + // 执行查询 + const [rows] = await dbReq.db.query(query, params); + + // 转换结果并排除敏感信息 + const teams = JSON.parse(JSON.stringify(rows)).map((team: Record) => { + // 排除敏感信息,使用解构赋值忽略db_password + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { db_password, ...safeTeamData } = team; + return safeTeamData; + }); + + // 返回结果 + return NextResponse.json({ + success: true, + data: teams + }); + }; + + // 执行处理函数 + const wrappedHandler = connectSystemDB(handler); + return await wrappedHandler(req); + + } catch (error: unknown) { + const err = error as ApiError; + console.error("获取团队列表出错:", err); + return NextResponse.json( + { + success: false, + error: err.message || "获取团队列表失败" + }, + { status: 500 } + ); + } +} + +/** + * POST 处理创建团队请求 + */ +export async function POST(req: NextRequest) { + try { + // 解析请求体,提取备份的用户ID和工作空间ID + const requestData = await req.json(); + const { teamName, teamCode, userId: backupUserId, workspaceId: backupWorkspaceId } = requestData; + + // 验证必要参数 + if (!teamName || !teamCode) { + return NextResponse.json( + { + success: false, + error: "团队名称和团队代码为必填项" + }, + { status: 400 } + ); + } + + // 从请求头获取访问令牌 + const authHeader = req.headers.get('Authorization'); + let token = ''; + + if (authHeader && authHeader.startsWith('Bearer ')) { + token = authHeader.substring(7); + } else { + // 尝试从Cookie中获取 + const tokenCookie = req.cookies.get('accessToken'); + if (tokenCookie) { + token = tokenCookie.value; + } else { + // 尝试读取localStorage中的令牌(从请求中) + const saasStorage = req.cookies.get('saas-user-storage'); + if (saasStorage) { + try { + const storageData = JSON.parse(decodeURIComponent(saasStorage.value)); + if (storageData && storageData.state && storageData.state.accessToken) { + token = storageData.state.accessToken; + } + } catch (e) { + console.error('解析存储的用户数据失败:', e); + } + } + } + } + + // 准备存储用户ID和工作空间ID + let userId: number | null = null; + let workspaceId: number | null = null; + + // 验证令牌并获取用户信息 + if (!token && !backupUserId && !backupWorkspaceId) { + return NextResponse.json( + { + success: false, + error: "用户未登录" + }, + { status: 401 } + ); + } + + // 尝试通过令牌获取用户信息 + if (token) { + try { + const user = await AuthUtils.verifyToken(token) as DecodedTokenWithWorkspace; + // 验证用户信息的完整性 + if (user && user.id && user.workspace_id) { + userId = Number(user.id); + workspaceId = Number(user.workspace_id); + + // 验证数字有效性 + if (isNaN(userId) || isNaN(workspaceId)) { + console.error('令牌中用户ID或工作空间ID无效:', { userId, workspaceId }); + userId = null; + workspaceId = null; + } + } else { + console.error('令牌中用户信息不完整:', user); + } + } catch (error) { + console.error('令牌验证失败:', error); + } + } + + // 如果令牌验证失败,尝试使用备份的用户信息 + if ((!userId || !workspaceId) && backupUserId && backupWorkspaceId) { + console.log('使用备份的用户信息:', { backupUserId, backupWorkspaceId }); + userId = Number(backupUserId); + workspaceId = Number(backupWorkspaceId); + + // 验证备份数据有效性 + if (isNaN(userId) || isNaN(workspaceId)) { + console.error('备份用户ID或工作空间ID无效:', { userId, workspaceId }); + return NextResponse.json( + { + success: false, + error: "用户信息无效,请重新登录" + }, + { status: 401 } + ); + } + } + + // 最终验证用户信息 + if (!userId || !workspaceId) { + return NextResponse.json( + { + success: false, + error: "无法验证用户身份,请重新登录" + }, + { status: 401 } + ); + } + + // 验证团队代码格式 + if (!/^[a-zA-Z0-9_]{3,20}$/.test(teamCode)) { + return NextResponse.json( + { + success: false, + error: "团队代码只能包含字母、数字和下划线,长度在3-20之间" + }, + { status: 400 } + ); + } + + // 使用连接数据库的模式 + const handler = async (dbReq: RequestWithDB) => { + // 检查团队代码是否已存在 + const [teamRows] = await dbReq.db.query( + `SELECT * FROM teams WHERE team_code = ?`, + [teamCode] + ); + + // 转换为纯JavaScript对象 + const existingTeams = JSON.parse(JSON.stringify(teamRows)); + + if (existingTeams.length > 0) { + return NextResponse.json( + { + success: false, + error: "该团队代码已存在,请使用其他代码" + }, + { status: 409 } + ); + } + + // 为团队生成数据库名称和用户名 + const teamDbName = `team_${teamCode}`; + const teamUsername = `user_${teamCode}`; + + // 生成安全的随机密码 + const teamDbPassword = generateSecurePassword(12); + + console.log('准备创建团队,用户ID:', userId, '工作空间ID:', workspaceId); + console.log('团队数据库信息:', { + db_name: teamDbName, + db_username: teamUsername, + // 不输出密码 + }); + + // 准备团队创建参数 + const teamParams = { + team_code: teamCode, + name: teamName, + db_host: DbConfig.host, + db_name: teamDbName, + db_username: teamUsername, + db_password: teamDbPassword, + workspace_id: workspaceId, + owner_id: userId + }; + + console.log('创建团队参数:', { + ...teamParams, + db_password: '******' // 不输出密码 + }); + + // 创建团队 + const [result] = await dbReq.db.query( + `INSERT INTO teams ( + team_code, name, db_host, db_name, db_username, db_password, + workspace_id, owner_id, status, created_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, NOW())`, + [ + teamParams.team_code, + teamParams.name, + teamParams.db_host, + teamParams.db_name, + teamParams.db_username, + teamParams.db_password, + teamParams.workspace_id, + teamParams.owner_id + ] + ); + + // 正确处理插入结果 + const insertResult = JSON.parse(JSON.stringify(result)); + const teamId = insertResult.insertId; + console.log('团队创建成功,ID:', teamId); + + // 建立用户与团队的关系 + await dbReq.db.query( + `INSERT INTO user_team_relations ( + user_id, team_id, role, created_at + ) VALUES (?, ?, ?, NOW())`, + [userId, teamId, TeamMemberRole.OWNER] + ); + console.log('用户团队关系创建成功'); + + // 返回成功响应 + return NextResponse.json({ + success: true, + team: { + id: teamId, + team_code: teamCode, + name: teamName + } + }, { status: 201 }); + }; + + // 执行处理函数 + const wrappedHandler = connectSystemDB(handler); + return await wrappedHandler(req); + + } catch (error: unknown) { + const err = error as ApiError; + console.error("创建团队出错:", err); + return NextResponse.json( + { + success: false, + error: err.message || "创建团队失败" + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/test/connection/route.ts b/src/app/api/test/connection/route.ts new file mode 100644 index 0000000..7b693f1 --- /dev/null +++ b/src/app/api/test/connection/route.ts @@ -0,0 +1,42 @@ +/** + * 数据库连接测试API + * 作者: 阿瑞 + * 功能: 测试系统数据库连接是否正常 + * 版本: 1.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; + +/** + * 测试数据库连接的处理器 + */ +async function handler(req: RequestWithDB) { + try { + // 尝试执行一个简单的查询 + const [result] = await req.db.query('SELECT 1 as connected'); + + return NextResponse.json({ + success: true, + message: '数据库连接成功', + data: result + }); + } catch (error) { + console.error('数据库连接测试失败:', error); + + return NextResponse.json({ + success: false, + error: error instanceof Error ? error.message : '未知错误' + }, { status: 500 }); + } +} + +// 使用数据库连接中间件包装处理器 +/** + * 处理GET请求 + * 符合Next.js 15.3+的路由处理器签名 + */ +export async function GET(request: NextRequest) { + const wrappedHandler = connectSystemDB(handler); + return wrappedHandler(request); +} \ No newline at end of file diff --git a/src/app/api/test/users/route.ts b/src/app/api/test/users/route.ts new file mode 100644 index 0000000..e5c9640 --- /dev/null +++ b/src/app/api/test/users/route.ts @@ -0,0 +1,61 @@ +/** + * 用户列表查询API + * 作者: 阿瑞 + * 功能: 查询系统用户列表数据 + * 版本: 1.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; + +/** + * 查询用户列表处理器 + */ +async function handler(req: RequestWithDB) { + try { + // 查询用户列表,排除密码和邀请令牌等敏感字段 + const [rows] = await req.db.query(` + SELECT + id, + username, + email, + phone, + role_name, + role_type, + workspace_id, + status, + last_login_at, + created_at, + updated_at + FROM + system_users + WHERE + status = 1 + ORDER BY + id DESC + LIMIT 20 + `); + + return NextResponse.json({ + success: true, + users: rows + }); + } catch (error) { + console.error('查询用户列表失败:', error); + + return NextResponse.json({ + success: false, + error: error instanceof Error ? error.message : '未知错误' + }, { status: 500 }); + } +} + +// 使用数据库连接中间件包装处理器 +/** + * 处理GET请求 + * 符合Next.js 15.3+的路由处理器签名 + */ +export async function GET(request: NextRequest) { + const wrappedHandler = connectSystemDB(handler); + return wrappedHandler(request); +} \ No newline at end of file diff --git a/src/app/api/tools/images/[...path]/route.ts b/src/app/api/tools/images/[...path]/route.ts new file mode 100644 index 0000000..2468038 --- /dev/null +++ b/src/app/api/tools/images/[...path]/route.ts @@ -0,0 +1,180 @@ +/** + * @作者: 阿瑞 + * @功能: 图片管理API,支持获取和删除图片 + * @版本: 1.1.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { access, constants, unlink, readFile } from 'fs/promises'; +import path from 'path'; + +// 设置基础上传目录 +const baseUploadsDir = path.join(process.cwd(), 'uploads'); + +// 获取图片的MIME类型 +function getMimeType(filePath: string): string { + const ext = path.extname(filePath).toLowerCase(); + + switch (ext) { + case '.jpg': + case '.jpeg': + return 'image/jpeg'; + case '.png': + return 'image/png'; + case '.gif': + return 'image/gif'; + case '.webp': + return 'image/webp'; + default: + return 'application/octet-stream'; + } +} + +// 检查文件是否存在 +async function fileExists(filePath: string) { + try { + await access(filePath, constants.F_OK); + return true; + } catch { + return false; + } +} + +// 提取路径参数 +function extractPathParams(request: NextRequest): string[] { + const pathname = request.nextUrl.pathname; + // 移除前面的 'api/tools/images' 部分 + const pathSegments = pathname.split('/').slice(4); + return pathSegments; +} + +/** + * 获取图片文件(GET请求) + */ +export async function GET(request: NextRequest) { + try { + // 从URL路径中提取动态参数 + const pathSegments = extractPathParams(request); + + // 合并path参数得到完整的相对路径 + const relativePath = pathSegments.join('/'); + + // 构建绝对路径,确保不会超出uploads目录范围 + const filePath = path.join(baseUploadsDir, relativePath); + + // 安全检查:确保文件路径在uploads目录下 + if (!filePath.startsWith(baseUploadsDir)) { + return NextResponse.json( + { + error: '无效的图片路径', + code: 'INVALID_PATH' + }, + { status: 400 } + ); + } + + // 检查文件是否存在 + if (!(await fileExists(filePath))) { + return NextResponse.json( + { + error: '图片不存在', + code: 'FILE_NOT_FOUND' + }, + { status: 404 } + ); + } + + // 使用fs.readFile读取文件,而不是fetch + const fileBuffer = await readFile(filePath); + + // 获取正确的content-type + const contentType = getMimeType(filePath); + + // 返回图片文件 + return new NextResponse(fileBuffer, { + status: 200, + headers: { + 'Content-Type': contentType, + 'Cache-Control': 'public, max-age=31536000' // 缓存1年 + } + }); + + } catch (error) { + console.error('获取图片时出错:', error); + const errorMessage = error instanceof Error ? error.message : '未知错误'; + + return NextResponse.json( + { + error: '获取图片时出错', + details: errorMessage + }, + { status: 500 } + ); + } +} + +/** + * 删除图片文件(DELETE请求) + */ +export async function DELETE(request: NextRequest) { + try { + // 从URL路径中提取动态参数 + const pathSegments = extractPathParams(request); + + // 合并path参数得到完整的相对路径 + const relativePath = pathSegments.join('/'); + + // 构建绝对路径 + const filePath = path.join(baseUploadsDir, relativePath); + + // 安全检查:确保文件路径在uploads目录下 + if (!filePath.startsWith(baseUploadsDir)) { + return NextResponse.json( + { + error: '无效的图片路径', + code: 'INVALID_PATH', + details: '请求的文件路径超出了允许的范围', + requestedPath: relativePath + }, + { status: 400 } + ); + } + + // 检查文件是否存在 + if (!(await fileExists(filePath))) { + return NextResponse.json( + { + error: '图片不存在', + code: 'FILE_NOT_FOUND', + details: `文件不存在或已被删除`, + requestedPath: relativePath + }, + { status: 404 } + ); + } + + // 删除文件 + await unlink(filePath); + + return NextResponse.json({ + message: '图片删除成功', + path: relativePath, + timestamp: new Date().toISOString() + }); + + } catch (error) { + console.error('删除图片时出错:', error); + + const errorMessage = error instanceof Error ? error.message : '未知错误'; + + return NextResponse.json( + { + error: '删除图片时出错', + code: 'DELETE_FAILED', + details: errorMessage, + timestamp: new Date().toISOString() + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/tools/parseAddress/route.ts b/src/app/api/tools/parseAddress/route.ts new file mode 100644 index 0000000..6a75f95 --- /dev/null +++ b/src/app/api/tools/parseAddress/route.ts @@ -0,0 +1,118 @@ +/** + * 地址解析API路由 + * 作者: 阿瑞 + * 功能: 解析文本中的姓名、电话和地址信息 + * 版本: 1.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; + +interface ExtractedInfoItem { + text: string; + start: number; + end: number; + probability: number; +} + +interface ExtractedInfo { + 姓名?: ExtractedInfoItem[]; + 电话?: ExtractedInfoItem[]; +} + +interface ExtractInfoResponse { + extracted_info: ExtractedInfo[]; +} + +interface ParseLocationResponse { + province: string | null; + city: string | null; + county: string | null; + detail: string | null; + full_location: string | null; + orig_location: string | null; + town: string | null; + village: string | null; +} + +interface CombinedResponse { + name: string | null; + phone: string | null; + address: ParseLocationResponse | null; +} + +/** + * POST处理函数 + * 接收文本内容,解析出姓名、电话和地址信息 + */ +export async function POST(req: NextRequest) { + try { + const body = await req.json(); + const { text } = body; + + if (!text || typeof text !== 'string') { + return NextResponse.json( + { error: '请求参数错误,缺少文本内容' }, + { status: 400 } + ); + } + + // 从环境变量获取 API 的 URL + const extractInfoUrl = process.env.EXTRACT_INFO_API_URL || 'http://192.168.1.22:8006/extract_info/'; + const parseLocationUrl = process.env.PARSE_LOCATION_API_URL || 'http://192.168.1.22:8100/parse_location/'; + + // 第一步:提取姓名和电话 + const extractInfoResponse = await fetch(extractInfoUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text }) + }); + + const extractedInfoData = await extractInfoResponse.json() as ExtractInfoResponse; + const extractedInfo = extractedInfoData.extracted_info[0]; + + let name: string | null = null; + let phone: string | null = null; + + if (extractedInfo['姓名'] && extractedInfo['姓名'].length > 0) { + name = extractedInfo['姓名'][0].text; + } + + if (extractedInfo['电话'] && extractedInfo['电话'].length > 0) { + phone = extractedInfo['电话'][0].text; + } + + // 从原始文本中移除姓名和电话,得到地址部分 + let addressText = text; + + if (name) { + addressText = addressText.replace(name, '').trim(); + } + + if (phone) { + addressText = addressText.replace(phone, '').trim(); + } + + // 第二步:解析地址 + const parseLocationResponse = await fetch(parseLocationUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: addressText }) + }); + + const locationData = await parseLocationResponse.json() as ParseLocationResponse; + + const combinedResponse: CombinedResponse = { + name, + phone, + address: locationData, + }; + + return NextResponse.json(combinedResponse); + } catch (error: Error | unknown) { + console.error('解析出错:', error); + return NextResponse.json( + { error: '服务器内部错误' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/tools/upload/route.ts b/src/app/api/tools/upload/route.ts new file mode 100644 index 0000000..d8cc7ba --- /dev/null +++ b/src/app/api/tools/upload/route.ts @@ -0,0 +1,157 @@ +/** + * @作者: 阿瑞 + * @功能: 测试用上传图片API,支持指定文件夹 + * @版本: 1.0.0 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { writeFile, mkdir, access } from 'fs/promises'; +import { constants } from 'fs'; +import path from 'path'; +import { existsSync } from 'fs'; +import { nanoid } from 'nanoid'; + +// 设置基础上传目录 +const baseUploadsDir = path.join(process.cwd(), 'uploads'); + +// 文件大小限制 (10MB) +const MAX_FILE_SIZE = 10 * 1024 * 1024; + +// 确保目录存在 +async function ensureDir(dirPath: string) { + if (!existsSync(dirPath)) { + await mkdir(dirPath, { recursive: true }); + } +} + +// 检查文件是否存在 +async function fileExists(filePath: string) { + try { + await access(filePath, constants.F_OK); + return true; + } catch { + return false; + } +} + +// 处理可能的文件重名 +async function generateUniqueFileName(dirPath: string, originalName: string): Promise { + // 分离文件名和扩展名 + const ext = path.extname(originalName); + const nameWithoutExt = path.basename(originalName, ext); + + // 检查文件是否已存在 + const filePath = path.join(dirPath, originalName); + if (!(await fileExists(filePath))) { + return originalName; // 文件不存在,可以直接使用原名 + } + + // 文件存在,生成唯一的文件名 + const uniqueName = `${nameWithoutExt}_${nanoid(6)}${ext}`; + return generateUniqueFileName(dirPath, uniqueName); // 递归检查新名称是否可用 +} + +// 处理图片上传请求 +export async function POST(request: NextRequest) { + try { + await ensureDir(baseUploadsDir); + + // 使用FormData API处理上传文件 + const formData = await request.formData(); + const file = formData.get('file') as File | null; + const folder = formData.get('folder') as string | null; + + // 确定上传目录 + let uploadsDir = baseUploadsDir; + if (folder) { + // 过滤文件夹名中的非法字符,防止目录遍历攻击 + const safeFolderName = folder.replace(/[^\w-]/g, '_'); + uploadsDir = path.join(baseUploadsDir, safeFolderName); + await ensureDir(uploadsDir); + } + + if (!file) { + return NextResponse.json( + { + error: '没有提供文件', + code: 'NO_FILE_PROVIDED', + details: '请确保在请求中包含一个有效的文件字段' + }, + { status: 400 } + ); + } + + // 验证文件类型 + const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + if (!validTypes.includes(file.type)) { + return NextResponse.json( + { + error: '不支持的文件类型', + code: 'INVALID_FILE_TYPE', + details: '请上传JPG、PNG、GIF或WEBP格式的图片', + supportedTypes: validTypes + }, + { status: 400 } + ); + } + + // 检查文件大小 + if (file.size > MAX_FILE_SIZE) { + return NextResponse.json( + { + error: '文件大小超出限制', + code: 'FILE_TOO_LARGE', + details: `文件大小超出限制,最大允许${MAX_FILE_SIZE / (1024 * 1024)}MB`, + maxSize: MAX_FILE_SIZE, + actualSize: file.size + }, + { status: 400 } + ); + } + + // 处理文件名(避免重名) + const originalFileName = file.name; + const fileName = await generateUniqueFileName(uploadsDir, originalFileName); + const filePath = path.join(uploadsDir, fileName); + + // 将文件内容转换为Buffer + const fileBuffer = Buffer.from(await file.arrayBuffer()); + + // 写入文件到服务器 + await writeFile(filePath, fileBuffer); + + // 构建相对于基础上传目录的URL路径 + let urlPath = `/api/tools/images/${fileName}`; + if (folder) { + const safeFolderName = folder.replace(/[^\w-]/g, '_'); + urlPath = `/api/tools/images/${safeFolderName}/${fileName}`; + } + + // 返回成功响应 + return NextResponse.json({ + message: '图片上传成功', + fileName, + folder: folder || null, + originalFileName: originalFileName !== fileName ? originalFileName : undefined, + url: urlPath, + size: file.size, + type: file.type + }); + + } catch (error) { + console.error('上传图片时出错:', error); + + // 提供更详细的错误信息 + const errorMessage = error instanceof Error ? error.message : '未知错误'; + + return NextResponse.json( + { + error: '上传图片时出错', + code: 'UPLOAD_FAILED', + details: errorMessage, + timestamp: new Date().toISOString() + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/workspace/invite/route.ts b/src/app/api/workspace/invite/route.ts new file mode 100644 index 0000000..1a4edd4 --- /dev/null +++ b/src/app/api/workspace/invite/route.ts @@ -0,0 +1,241 @@ +/** + * 邀请注册API路由 + * 作者: 阿瑞 + * 功能: 生成邀请链接和令牌 + * 版本: 2.2 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { AuthUtils } from '@/lib/auth'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; +import { v4 as uuidv4 } from 'uuid'; +import { SystemRoleType } from '@/models/system/types'; + +/** + * 生成邀请链接和令牌 + */ +export async function POST(req: NextRequest) { + try { + console.log('API: 接收到生成邀请链接请求'); + + // 解析请求体 + const body = await req.json(); + const { role_type, role_name, is_custom_role, workspaceId } = body; + + console.log('API: 请求参数:', { role_type, role_name, is_custom_role, workspaceId }); + + // 验证参数 + if (!workspaceId) { + console.log('API: 缺少工作空间ID参数'); + return NextResponse.json({ + success: false, + error: '缺少必要参数,需要工作空间ID' + }, { status: 400 }); + } + + // 验证授权 + const authHeader = req.headers.get('Authorization'); + console.log('API: 授权头:', authHeader ? '存在' : '不存在'); + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return NextResponse.json({ + success: false, + error: '未授权访问' + }, { status: 401 }); + } + + // 解析令牌 + const token = authHeader.split(' ')[1]; + let decoded; + + try { + decoded = await AuthUtils.verifyToken(token); + if (!decoded || !decoded.id) { + console.log('API: 令牌无效或缺少用户ID'); + return NextResponse.json({ + success: false, + error: '无效的访问令牌' + }, { status: 401 }); + } + console.log('API: 令牌解析成功, 用户ID:', decoded.id); + } catch (error) { + console.log('API: 令牌验证失败:', error); + return NextResponse.json({ + success: false, + error: '无效的访问令牌' + }, { status: 401 }); + } + + // 使用连接数据库的模式 + const handler = async (req: RequestWithDB) => { + // 查询当前用户 + const [userRows] = await req.db.query( + 'SELECT id, username, email, role_type, role_name, workspace_id FROM system_users WHERE id = ?', + [decoded.id] + ); + + // 转换为纯JavaScript对象 + const currentUserRows = JSON.parse(JSON.stringify(userRows)); + + if (currentUserRows.length === 0) { + console.log('API: 当前用户不存在'); + return NextResponse.json({ + success: false, + error: '当前用户不存在' + }, { status: 404 }); + } + + const currentUser = currentUserRows[0]; + console.log('API: 当前用户:', { id: currentUser.id, role_type: currentUser.role_type }); + + // 验证是否是管理员 + if (currentUser.role_type !== SystemRoleType.ADMIN) { + console.log('API: 用户不是管理员'); + return NextResponse.json({ + success: false, + error: '只有管理员可以生成邀请链接' + }, { status: 403 }); + } + + // 验证当前用户是否属于该工作空间 + if (currentUser.workspace_id.toString() !== workspaceId.toString()) { + console.log('API: 用户不属于该工作空间'); + return NextResponse.json({ + success: false, + error: '无权访问该工作空间' + }, { status: 403 }); + } + + // 查询工作空间 + const [wsRows] = await req.db.query( + 'SELECT id, name FROM workspaces WHERE id = ?', + [workspaceId] + ); + + // 转换为纯JavaScript对象 + const workspaceRows = JSON.parse(JSON.stringify(wsRows)); + + if (workspaceRows.length === 0) { + console.log('API: 工作空间不存在'); + return NextResponse.json({ + success: false, + error: '工作空间不存在' + }, { status: 404 }); + } + + const workspace = workspaceRows[0]; + console.log('API: 工作空间:', { id: workspace.id, name: workspace.name }); + + // 生成唯一邀请令牌 + const invitationToken = uuidv4(); + console.log('API: 生成邀请令牌:', invitationToken.substring(0, 8) + '...'); + + // 确定角色信息 + const finalRoleType = role_type || SystemRoleType.USER; + const finalRoleName = role_name || (finalRoleType === SystemRoleType.ADMIN ? '管理员' : '普通用户'); + const finalIsCustomRole = is_custom_role || false; + + // 检查表是否需要更新结构 + try { + await req.db.query('SELECT role_type FROM workspace_invitations LIMIT 1'); + } catch (err: unknown) { + // 如果字段不存在,添加新的字段 + if (err && typeof err === 'object' && 'code' in err && err.code === 'ER_BAD_FIELD_ERROR') { + console.log('API: 更新邀请表结构以支持新的角色系统'); + await req.db.query(` + ALTER TABLE workspace_invitations + ADD COLUMN role_type VARCHAR(30) NOT NULL DEFAULT 'user' AFTER role, + ADD COLUMN role_name VARCHAR(50) NOT NULL DEFAULT '普通用户' AFTER role_type, + ADD COLUMN is_custom_role TINYINT(1) NOT NULL DEFAULT 0 AFTER role_name + `); + } + } + + try { + // 将邀请数据存入数据库 + await req.db.query( + `INSERT INTO workspace_invitations + (token, workspace_id, role, role_type, role_name, is_custom_role, created_by, expires_at, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 7 DAY), NOW())`, + [ + invitationToken, + workspaceId, + finalRoleType, // 保持兼容 + finalRoleType, + finalRoleName, + finalIsCustomRole, + currentUser.id + ] + ); + } catch (err: unknown) { + // 如果表不存在,创建表并重试 + if (err && typeof err === 'object' && 'code' in err && err.code === 'ER_NO_SUCH_TABLE') { + console.log('API: 邀请表不存在,创建表'); + await req.db.query(` + CREATE TABLE IF NOT EXISTS workspace_invitations ( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + token VARCHAR(100) NOT NULL UNIQUE, + workspace_id INT NOT NULL, + role VARCHAR(30) NOT NULL DEFAULT 'user', + role_type VARCHAR(30) NOT NULL DEFAULT 'user', + role_name VARCHAR(50) NOT NULL DEFAULT '普通用户', + is_custom_role TINYINT(1) NOT NULL DEFAULT 0, + created_by INT NOT NULL, + used_by INT NULL DEFAULT NULL, + used_at DATETIME NULL DEFAULT NULL, + expires_at DATETIME NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_token (token), + INDEX idx_workspace (workspace_id), + FOREIGN KEY (workspace_id) REFERENCES workspaces(id), + FOREIGN KEY (created_by) REFERENCES system_users(id) + ) + `); + + await req.db.query( + `INSERT INTO workspace_invitations + (token, workspace_id, role, role_type, role_name, is_custom_role, created_by, expires_at, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 7 DAY), NOW())`, + [ + invitationToken, + workspaceId, + finalRoleType, // 保持兼容 + finalRoleType, + finalRoleName, + finalIsCustomRole, + currentUser.id + ] + ); + } else { + throw err; + } + } + + console.log('API: 邀请记录已保存'); + + // 返回成功响应 + return NextResponse.json({ + success: true, + message: '邀请链接已生成', + token: invitationToken, + expiresIn: '7天' + }); + }; + + // 执行处理函数 + const wrappedHandler = connectSystemDB(handler); + return await wrappedHandler(req); + + } catch (error: unknown) { + console.error('API: 生成邀请链接失败:', error); + + // 其他错误 + return NextResponse.json( + { + success: false, + error: '生成邀请链接失败: ' + (error instanceof Error ? error.message : String(error)) + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/workspace/members/route.ts b/src/app/api/workspace/members/route.ts new file mode 100644 index 0000000..6a8452d --- /dev/null +++ b/src/app/api/workspace/members/route.ts @@ -0,0 +1,88 @@ +/** + * 工作空间成员列表API路由 + * 作者: 阿瑞 + * 功能: 获取当前工作空间下的所有成员 + * 版本: 1.8 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { AuthUtils } from '@/lib/auth'; +import { SystemUserModel } from '@/models/system/UserModel'; + +/** + * 错误接口 + */ +interface ApiError extends Error { + message: string; +} + +/** + * 获取工作空间的成员列表 + */ +export async function GET(req: NextRequest) { + try { + console.log('API: 接收到获取成员列表请求'); + + // 获取查询参数 + const url = new URL(req.url); + const workspaceId = url.searchParams.get('workspaceId'); + + console.log('API: 获取工作空间ID:', workspaceId); + + // 验证参数 + if (!workspaceId) { + console.log('API: 缺少工作空间ID参数'); + return NextResponse.json({ + success: false, + error: '缺少工作空间ID参数' + }, { status: 400 }); + } + + // 从请求中获取授权信息 + const authHeader = req.headers.get('Authorization'); + console.log('API: 授权头信息:', authHeader ? '存在' : '不存在'); + + let userId = null; + + // 尝试验证授权 + if (authHeader && authHeader.startsWith('Bearer ')) { + try { + const token = authHeader.split(' ')[1]; + const decoded = await AuthUtils.verifyToken(token); + if (decoded) { + userId = decoded.id; + console.log('API: 令牌验证成功, 用户ID:', userId); + } + } catch { + // 空catch块,有意忽略错误 + console.log('API: 令牌验证失败, 跳过用户验证'); + // 令牌验证失败时,只是记录错误,但仍然允许请求继续 + } + } + + // 调用模型方法获取工作空间成员 + console.log('API: 查询工作空间ID:', workspaceId, '的成员'); + const members = await SystemUserModel.getWorkspaceMembers(workspaceId); + + console.log('API: 查询到成员数量:', members.length); + + // 返回成员列表 + return NextResponse.json({ + success: true, + members + }); + + } catch (error: unknown) { + const err = error as ApiError; + console.error('API: 获取工作空间成员列表失败:', err); + + // 其他错误 + return NextResponse.json( + { + success: false, + error: '获取工作空间成员列表失败: ' + err.message + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/workspace/teams/route.ts b/src/app/api/workspace/teams/route.ts new file mode 100644 index 0000000..46efccc --- /dev/null +++ b/src/app/api/workspace/teams/route.ts @@ -0,0 +1,73 @@ +/** + * 工作空间团队列表API路由 + * 作者: 阿瑞 + * 功能: 获取当前工作空间下的所有团队 + * 版本: 1.7 + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { connectSystemDB, RequestWithDB } from '@/lib/db'; + +/** + * 获取工作空间的团队列表 + */ +export async function GET(req: NextRequest) { + try { + console.log('API: 接收到获取团队列表请求'); + + // 获取查询参数 + const url = new URL(req.url); + const workspaceId = url.searchParams.get('workspaceId'); + + console.log('API: 获取工作空间ID:', workspaceId); + + // 验证参数 + if (!workspaceId) { + console.log('API: 缺少工作空间ID参数'); + return NextResponse.json({ + success: false, + error: '缺少工作空间ID参数' + }, { status: 400 }); + } + + // 使用数据库连接模式 + const handler = async (dbReq: RequestWithDB) => { + // 查询工作空间下的所有团队 + const [rows] = await dbReq.db.query( + `SELECT id, team_code as teamCode, name, status + FROM teams + WHERE workspace_id = ? + ORDER BY name ASC`, + [workspaceId] + ); + + // 转换为纯JavaScript对象 + const teams = JSON.parse(JSON.stringify(rows)); + + console.log('API: 查询到团队数量:', teams.length); + + // 返回团队列表 + return NextResponse.json({ + success: true, + teams: teams + }); + }; + + // 执行处理函数 + const wrappedHandler = connectSystemDB(handler); + return await wrappedHandler(req); + + } catch (error: unknown) { + const err = error as Error; + console.error('API: 获取团队列表失败:', err); + + // 其他错误 + return NextResponse.json( + { + success: false, + error: '获取团队列表失败: ' + err.message + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index a2dc41e..2a85bd4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,26 +1,581 @@ -@import "tailwindcss"; +/** + * @作者 阿瑞 + * @功能 全局样式定义,提供现代毛玻璃UI效果所需的基础样式 + * @版本 2.3.0 + */ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} + @import "tailwindcss"; + @tailwind base; + @tailwind components; + @tailwind utilities; + /* 全局变量定义 */ + :root { + /* 明亮现代主题配色 */ + --bg-primary: #f6f9fc; + --bg-secondary: #eef4ff; + --bg-dark: #0a1128; + --text-primary: #0a1128; + --text-secondary: rgba(10, 17, 40, 0.85); + --text-tertiary: rgba(10, 17, 40, 0.65); + --text-light: #ffffff; + + /* 鲜艳现代强调色 */ + --accent-blue: #2d7ff9; + --accent-purple: #8e6bff; + --accent-teal: #06d7b2; + --accent-pink: #ff66c2; + --accent-orange: #ff9640; + + /* 优化的毛玻璃效果参数 */ + --glass-bg-light: rgba(255, 255, 255, 0.25); + --glass-bg-dark: rgba(16, 22, 58, 0.25); + --glass-border-light: rgba(255, 255, 255, 0.25); + --glass-border-dark: rgba(255, 255, 255, 0.08); + --glass-highlight: rgba(255, 255, 255, 0.3); + --glass-shadow-light: rgba(31, 38, 135, 0.15); + --glass-shadow-dark: rgba(0, 0, 10, 0.2); + + /* 字体 */ + --font-primary: 'Geist', 'Inter', system-ui, sans-serif; + --font-mono: 'Geist Mono', ui-monospace, monospace; + } + + /* 基础样式 */ + body { + font-family: var(--font-primary); + transition: background-color 0.5s ease, color 0.3s ease; + min-height: 100vh; + } + + /* 明亮主题 */ + body.light-theme { + color: var(--text-primary); + background-color: var(--bg-primary); + background-image: + radial-gradient(circle at 80% 10%, rgba(142, 107, 255, 0.12) 0%, transparent 50%), + radial-gradient(circle at 20% 30%, rgba(6, 215, 178, 0.12) 0%, transparent 50%), + radial-gradient(circle at 90% 80%, rgba(45, 127, 249, 0.12) 0%, transparent 50%), + radial-gradient(circle at 10% 90%, rgba(255, 102, 194, 0.12) 0%, transparent 50%); + } + + /* 深色主题 */ + body.dark-theme { + color: var(--text-light); + background-color: var(--bg-dark); + background-image: + radial-gradient(circle at 80% 10%, rgba(142, 107, 255, 0.18) 0%, transparent 45%), + radial-gradient(circle at 20% 30%, rgba(6, 215, 178, 0.15) 0%, transparent 45%), + radial-gradient(circle at 90% 80%, rgba(45, 127, 249, 0.18) 0%, transparent 45%), + radial-gradient(circle at 10% 90%, rgba(255, 102, 194, 0.15) 0%, transparent 45%); + } + + /* ThemeProvider 组件包装的初始主题类 */ + .dark-theme { + color: var(--text-light); + background-color: var(--bg-dark); + --text-primary: #ffffff; + --text-secondary: rgba(255, 255, 255, 0.85); + --text-tertiary: rgba(255, 255, 255, 0.65); + } + + .light-theme { + color: var(--text-primary); + background-color: var(--bg-primary); + --text-primary: #0a1128; + --text-secondary: rgba(10, 17, 40, 0.85); + --text-tertiary: rgba(10, 17, 40, 0.65); + } + + /* 动画效果定义 */ + @keyframes float { + 0% { + transform: translate(0, 0) rotate(0); + } + 50% { + transform: translate(8px, -8px) rotate(2deg); + } + 100% { + transform: translate(0, 0) rotate(0); + } + } + + @keyframes blob { + 0%, 100% { + transform: translate(0, 0) scale(1); + opacity: 0.2; + } + 33% { + transform: translate(15px, -15px) scale(1.1); + opacity: 0.25; + } + 66% { + transform: translate(-10px, 10px) scale(0.95); + opacity: 0.18; + } + } + + @keyframes pulse { + 0%, 100% { + opacity: 0.95; + transform: scale(1); + } + 50% { + opacity: 1; + transform: scale(1.02); + } + } + + @keyframes gradientFlow { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } + } + + /* 进度条动画效果 */ + @keyframes progress { + 0% { + background-position: 1rem 0; + } + 100% { + background-position: 0 0; + } + } + + /* 动画应用类 */ + .animate-float { + animation: float 8s infinite ease-in-out; + } + + .animate-blob { + animation: blob 18s infinite ease-in-out alternate; + } + + .animate-pulse-slow { + animation: pulse 5s infinite ease-in-out; + } + + .animate-gradient { + animation: gradientFlow 8s ease infinite; + background-size: 200% auto; + } + + /* 进度条动画 */ + .animate-progress { + animation: progress 1s linear infinite; + } + + /* 动画延迟类 */ + .animation-delay-2000 { + animation-delay: 2s; + } + + .animation-delay-4000 { + animation-delay: 4s; + } + + .animation-delay-6000 { + animation-delay: 6s; + } + + /* 毛玻璃效果组件 - 明亮主题 */ + .glass-card { + @apply backdrop-blur-xl; + background: var(--glass-bg-light); + border: 1px solid var(--glass-border-light); + box-shadow: 0 8px 32px 0 var(--glass-shadow-light); + border-radius: 24px; + transition: all 0.3s ease, background 0.5s ease, border-color 0.5s ease, box-shadow 0.5s ease; + } + + .glass-card:hover { + box-shadow: 0 12px 48px 0 var(--glass-shadow-light); + border-color: var(--glass-highlight); + transform: translateY(-2px); + } + + /* 深色毛玻璃卡片 */ + .glass-card-dark { + @apply backdrop-blur-xl; + background: var(--glass-bg-dark); + border: 1px solid var(--glass-border-dark); + box-shadow: 0 8px 32px 0 var(--glass-shadow-dark); + border-radius: 24px; + transition: all 0.3s ease, background 0.5s ease, border-color 0.5s ease, box-shadow 0.5s ease; + color: var(--text-light); + } + + .glass-card-dark:hover { + box-shadow: 0 12px 48px 0 var(--glass-shadow-dark); + border-color: rgba(255, 255, 255, 0.15); + transform: translateY(-2px); + } + + /* 进度条条纹效果 */ + .bg-stripes { + background-image: linear-gradient( + 45deg, + rgba(255, 255, 255, 0.15) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, 0.15) 50%, + rgba(255, 255, 255, 0.15) 75%, + transparent 75%, + transparent + ); + background-size: 1rem 1rem; + } + + /* 深色主题下的条纹效果调整 */ + .dark-theme .bg-stripes { + background-image: linear-gradient( + 45deg, + rgba(255, 255, 255, 0.2) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, 0.2) 50%, + rgba(255, 255, 255, 0.2) 75%, + transparent 75%, + transparent + ); + } + + /* 导航栏毛玻璃效果 */ + .glass-nav { + @apply backdrop-blur-xl z-50 fixed top-0 left-0 right-0; + background: rgba(255, 255, 255, 0.2); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 4px 20px 0 rgba(31, 38, 135, 0.1); + transition: all 0.3s ease, background 0.5s ease, border-color 0.5s ease; + } + + .dark-theme .glass-nav { + background: rgba(10, 17, 40, 0.25); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + } + + .light-theme .glass-nav { + background: rgba(255, 255, 255, 0.2); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + /* 按钮样式 */ + .btn-primary { + @apply rounded-full font-medium text-white py-3 px-8 transition-all duration-300; + background: linear-gradient(135deg, var(--accent-blue) 0%, var(--accent-purple) 100%); + box-shadow: 0 4px 12px rgba(45, 127, 249, 0.25); + } + + .btn-primary:hover { + box-shadow: 0 8px 20px rgba(45, 127, 249, 0.35); + transform: translateY(-2px); + } + + .btn-primary:active { + transform: translateY(0); + } + + /* 导航栏紧凑按钮 */ + .nav-btn-primary { + @apply rounded-full font-medium text-white py-2 px-6 transition-all duration-300; + background: linear-gradient(135deg, var(--accent-blue) 0%, var(--accent-purple) 100%); + box-shadow: 0 3px 10px rgba(45, 127, 249, 0.2); + } + + .nav-btn-primary:hover { + box-shadow: 0 6px 15px rgba(45, 127, 249, 0.3); + transform: translateY(-2px); + } + + .nav-btn-primary:active { + transform: translateY(0); + } + + .btn-secondary { + @apply rounded-full font-medium py-3 px-8 transition-all duration-300; + background: rgba(255, 255, 255, 0.25); + border: 1px solid rgba(255, 255, 255, 0.5); + backdrop-filter: blur(8px); + color: var(--text-primary); + } + + /* 导航栏紧凑次要按钮 */ + .nav-btn-secondary { + @apply rounded-full font-medium py-2 px-6 transition-all duration-300; + background: rgba(255, 255, 255, 0.25); + border: 1px solid rgba(255, 255, 255, 0.5); + backdrop-filter: blur(8px); + color: var(--text-primary); + } + + .dark-theme .btn-secondary, + .dark-theme .nav-btn-secondary { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.15); + color: var(--text-light); + } + + .light-theme .btn-secondary, + .light-theme .nav-btn-secondary { + background: rgba(255, 255, 255, 0.25); + border: 1px solid rgba(255, 255, 255, 0.5); + color: var(--text-primary); + } + + .btn-secondary:hover, + .nav-btn-secondary:hover { + background: rgba(255, 255, 255, 0.35); + transform: translateY(-2px); + } + + .dark-theme .btn-secondary:hover, + .dark-theme .nav-btn-secondary:hover { + background: rgba(255, 255, 255, 0.15); + } + + /* 导航链接 */ + .nav-link { + @apply transition-all duration-300; + color: var(--text-secondary); + position: relative; + padding: 0.5rem 0.25rem; + } + + .dark-theme .nav-link { + color: rgba(255, 255, 255, 0.8); + } + + .light-theme .nav-link { + color: var(--text-secondary); + } + + .nav-link:hover { + color: var(--text-primary); + } + + .dark-theme .nav-link:hover { + color: var(--text-light); + } + + .light-theme .nav-link:hover { + color: var(--text-primary); + } + + .nav-link::after { + content: ""; + position: absolute; + width: 0; + height: 2px; + bottom: 0; + left: 0; + background: linear-gradient(90deg, var(--accent-teal), var(--accent-blue)); + transition: width 0.3s ease; + } + + .nav-link:hover::after { + width: 100%; + } + + /* 功能卡片样式 */ + .feature-card { + @apply transition-all duration-300 rounded-3xl p-8; + background: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(255, 255, 255, 0.5); + backdrop-filter: blur(12px); + box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1); + transition: all 0.3s ease, background 0.5s ease, border-color 0.5s ease; + } + + .dark-theme .feature-card { + background: rgba(22, 34, 73, 0.25); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .light-theme .feature-card { + background: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(255, 255, 255, 0.5); + } + + .feature-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 48px rgba(31, 38, 135, 0.15); + } + + /* 图标容器 */ + .icon-container { + @apply flex items-center justify-center rounded-full mb-6 w-16 h-16; + background: linear-gradient(135deg, var(--accent-blue) 0%, var(--accent-purple) 100%); + color: white; + transition: all 0.3s ease; + } + + .feature-card:hover .icon-container { + transform: scale(1.1); + } + + /* 统计卡片 */ + .stats-card { + @apply transition-all duration-300 rounded-3xl p-8 flex flex-col; + background: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(255, 255, 255, 0.5); + backdrop-filter: blur(12px); + box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1); + transition: all 0.3s ease, background 0.5s ease, border-color 0.5s ease; + } + + .dark-theme .stats-card { + background: rgba(22, 34, 73, 0.25); + border: 1px solid rgba(255, 255, 255, 0.1); + } + + .light-theme .stats-card { + background: rgba(255, 255, 255, 0.6); + border: 1px solid rgba(255, 255, 255, 0.5); + } + + .stats-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 48px rgba(31, 38, 135, 0.15); + } + + .stats-number { + @apply text-5xl font-bold mb-2; + background: linear-gradient(135deg, var(--accent-teal), var(--accent-blue)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + + /* 步骤样式 */ + .step-number { + @apply flex items-center justify-center text-white font-medium rounded-full w-10 h-10; + background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple)); + } + + /* 标签样式 */ + .tab-button { + @apply transition-all duration-300 px-6 py-3 rounded-xl text-base font-medium; + color: var(--text-tertiary); + } + + .dark-theme .tab-button { + color: rgba(255, 255, 255, 0.65); + } + + .light-theme .tab-button { + color: var(--text-tertiary); + } + + .tab-button.active { + background: rgba(255, 255, 255, 0.5); + color: var(--text-primary); + box-shadow: 0 4px 16px rgba(31, 38, 135, 0.1); + } + + .dark-theme .tab-button.active { + background: rgba(255, 255, 255, 0.1); + color: var(--text-light); + box-shadow: 0 4px 16px rgba(0, 0, 10, 0.15); + } + + .light-theme .tab-button.active { + background: rgba(255, 255, 255, 0.5); + color: var(--text-primary); + box-shadow: 0 4px 16px rgba(31, 38, 135, 0.1); + } + + .tab-button:not(.active):hover { + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.3); + } + + .dark-theme .tab-button:not(.active):hover { + color: rgba(255, 255, 255, 0.85); + background: rgba(255, 255, 255, 0.05); + } + + .light-theme .tab-button:not(.active):hover { + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.3); + } + + /* 主题切换器 */ + .theme-switch { + @apply relative inline-flex items-center cursor-pointer; + } + + .theme-switch input { + @apply sr-only; + } + + .theme-slider { + @apply relative w-14 h-7 bg-gray-200 rounded-full transition-colors duration-300 ease-in-out; + } + + .theme-slider:before { + @apply absolute content-[''] h-5 w-5 left-1 bottom-1 bg-white rounded-full transition-transform duration-300 ease-in-out; + } + + input:checked + .theme-slider { + @apply bg-blue-600; + } + + input:checked + .theme-slider:before { + @apply transform translate-x-7; + } + + /* 修改滚动条样式 */ + ::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + ::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.05); + border-radius: 10px; + } + + ::-webkit-scrollbar-thumb { + background: rgba(45, 127, 249, 0.5); + border-radius: 10px; + } + + ::-webkit-scrollbar-thumb:hover { + background: rgba(45, 127, 249, 0.7); + } + + .dark-theme ::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + } + + .dark-theme ::-webkit-scrollbar-thumb { + background: rgba(142, 107, 255, 0.5); + } + + .dark-theme ::-webkit-scrollbar-thumb:hover { + background: rgba(142, 107, 255, 0.7); + } + + /* 动画弹入效果 */ + @keyframes bounce-in { + 0% { + transform: scale(0.8); + opacity: 0; + } + 70% { + transform: scale(1.05); + opacity: 1; + } + 100% { + transform: scale(1); + } + } + + .animate-bounce-in { + animation: bounce-in 0.3s cubic-bezier(0.38, 1.6, 0.55, 0.9) forwards; + } + \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..0b5f741 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,22 @@ +/** + * @author: 阿瑞 + * @功能: 应用布局文件 + * @版本: 1.0.0 + */ + import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import ThemeProvider from "@/components/ThemeProvider"; +import { NotificationProvider, NotificationContainer } from '@/components/ui/Notification'; + +// 设置 Ant Design 兼容 React 19 +import '@ant-design/v5-patch-for-react-19'; + +// 引入 Ant Design 样式 +//import 'antd/dist/reset.css'; +// 引入自定义毛玻璃风格样式 +import '@/styles/antd-glass.css'; const geistSans = Geist({ variable: "--font-geist-sans", @@ -12,9 +28,16 @@ const geistMono = Geist_Mono({ subsets: ["latin"], }); +/* 站点元数据 */ export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "私域管理系统 - 现代团队协作平台", + description: "基于最新毛玻璃UI设计的SaaS管理平台,为多团队环境打造的高效协作工具", + keywords: ["SaaS", "团队协作", "项目管理", "毛玻璃UI", "Next.js"], + authors: [{ name: "阿瑞", url: "https://github.com/arei" }], + creator: "阿瑞", + icons: { + icon: "/favicon.ico", + } }; export default function RootLayout({ @@ -23,11 +46,22 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + - {children} + + + {children} + + + + + + + + ); diff --git a/src/app/page.tsx b/src/app/page.tsx index e68abe6..07901da 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,103 +1,350 @@ -import Image from "next/image"; +/** + * 私域管理系统首页 + * 作者: 阿瑞 + * 功能: 展示现代简约毛玻璃质感UI效果 + * 版本: 2.3.0 + */ +"use client"; + +import { useState } from "react"; +import { useIsAuthenticated } from "@/store/userStore"; +import Link from "next/link"; +import { ThemeToggle, useIsDarkMode } from "@/components/ThemeProvider"; + +/* 首页模块 - 展示毛玻璃UI效果和系统概览 */ export default function Home() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - src/app/page.tsx - - . -
  2. -
  3. - Save and see your changes instantly. -
  4. -
+ const [activeTab, setActiveTab] = useState<"features" | "stats" | "start">("features"); - -
- -
+ // 获取用户登录状态 + const isAuthenticated = useIsAuthenticated(); + + return ( + + ); +} + +// 分离内容组件,使用Context获取主题状态 +function HomeContent({ + activeTab, + setActiveTab, + isAuthenticated +}: { + activeTab: "features" | "stats" | "start"; + setActiveTab: (tab: "features" | "stats" | "start") => void; + isAuthenticated: boolean; +}) { + // 使用Context API获取主题状态,无需水合逻辑 + const { isDarkMode, isMounted } = useIsDarkMode(); + + // 使用CSS变量的类名 + const glassCard = "glass-card"; + const glassCardDark = "glass-card-dark"; + + return ( +
+ {/* 动态背景元素 - 使用大型柔和渐变气泡 */} +
+
+
+
+
+
+ + {/* 装饰小球元素 */} +
+
+
+
+
+
+ + {/* 页面内容 */} +
+ {/* 顶部导航栏 - 毛玻璃效果 */} +
+ +
+ + {/* 主内容区 */} +
+
+

+ + 简化团队协作的 + +
+ SaaS管理平台 +

+

+ 为多团队环境打造的高效管理系统,独立数据环境,统一管理界面 +

+
+ + + 查看UI组件 + +
+
+ + {/* 毛玻璃卡片区域 */} +
+ {/* 选项卡导航 */} +
+ + + +
+ + {/* 选项卡内容区域 */} +
+ {activeTab === "features" && ( +
+ {/* 功能卡片1 */} +
+
+ + + +
+

多团队隔离

+

数据库级别隔离架构,为每个团队提供独立安全的数据环境

+
+ + {/* 功能卡片2 */} +
+
+ + + +
+

实时数据分析

+

直观的数据可视化,帮助团队进行数据驱动决策

+
+ + {/* 功能卡片3 */} +
+
+ + + +
+

安全认证

+

JWT与bcryptjs加密确保系统安全,多层次访问控制

+
+
+ )} + + {activeTab === "stats" && ( +
+ {/* 数据统计卡片1 */} +
+

活跃用户

+
12,543
+
+ + + + ↑ 23.6% +
+
+ + {/* 数据统计卡片2 */} +
+

团队数量

+
542
+
+ + + + ↑ 18.2% +
+
+
+ )} + + {activeTab === "start" && ( +
+
+

快速开始使用

+

按照以下步骤开始使用私域管理系统

+
+
+
1
+
+

注册账户

+

创建您的管理员账户,并设置团队信息

+
+
+
+
2
+
+

邀请团队成员

+

通过邮件链接邀请团队成员加入

+
+
+
+
3
+
+

配置工作空间

+

根据需求设置工作流程和权限

+
+
+
+
+ +
+ )} +
+
+ + {/* 特色展示卡片 */} +
+

探索更多功能

+ +
+ {/* 特色卡片1 - 左侧 */} +
+
+
+
+ + + + +
+

数据可视化

+
+

+ 直观展现核心业务指标,支持自定义仪表盘和报表,帮助团队快速识别趋势和异常 +

+
+
+
月度增长
+
+24.8%
+
+
+
+
+
+ {[1, 2, 3, 4].map(i => ( +
+ ))} +
+
+
+
+ + {/* 特色卡片2 - 右侧 */} +
+
+
+
+ + + +
+

团队协作

+
+

+ 集成消息、任务和日历功能,让团队成员保持同步,提高协作效率 +

+
+
+ {[1, 2, 3].map(i => ( +
+
+
+
+
+
+
+ ))} +
+
+
+
+
+
+
+ + {/* 页脚 */} +
+
+ © 2024 私域管理系统 | 使用现代前端技术栈构建 | TypeScript + Next.js + React + Tailwind CSS +
+
+
+
); } diff --git a/src/app/team/[teamCode]/brands/brand-modal.tsx b/src/app/team/[teamCode]/brands/brand-modal.tsx new file mode 100644 index 0000000..a02ddc5 --- /dev/null +++ b/src/app/team/[teamCode]/brands/brand-modal.tsx @@ -0,0 +1,239 @@ +/** + * 品牌模态框组件 + * 作者: 阿瑞 + * 功能: 提供品牌信息的添加和编辑界面 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useParams } from 'next/navigation'; +import { IBrand } from '@/models/team/types/IBrand'; +import Modal from '@/components/ui/Modal'; + +interface BrandModalProps { + isOpen: boolean; + onClose: () => void; + brand?: IBrand | null; + onSuccess: () => void; +} + +/** + * 品牌模态框组件 + */ +export default function BrandModal({ isOpen, onClose, brand, onSuccess }: BrandModalProps) { + const params = useParams(); + const teamCode = params?.teamCode as string; + + const isEditing = !!brand; + + // 表单状态 + const [formData, setFormData] = useState>({ + name: '', + order: 0, + description: '' + }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + /** + * 初始化编辑表单数据 + */ + useEffect(() => { + if (brand) { + setFormData({ + id: brand.id, + name: brand.name, + order: brand.order, + description: brand.description || '' + }); + } else { + // 重置表单 + setFormData({ + name: '', + order: 0, + description: '' + }); + } + + setError(null); + }, [brand, isOpen]); + + /** + * 处理表单输入变化 + */ + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + // 处理数字类型字段 + if (name === 'order') { + setFormData(prev => ({ + ...prev, + [name]: value === '' ? 0 : parseInt(value, 10) + })); + return; + } + + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + /** + * 提交表单 + */ + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // 基本验证 + if (!formData.name) { + setError('品牌名称为必填项'); + return; + } + + setIsSubmitting(true); + setError(null); + + try { + let response; + + if (isEditing && brand) { + // 更新品牌 + response = await fetch(`/api/team/${teamCode}/brands/${brand.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + } else { + // 创建品牌 + response = await fetch(`/api/team/${teamCode}/brands`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + } + + // 先检查响应状态 + if (!response.ok) { + // 克隆响应以避免"body已被读取"的错误 + const clonedResponse = response.clone(); + const errorData = await clonedResponse.json(); + throw new Error(errorData.error || '操作失败'); + } + + // 响应成功,读取结果 + await response.json(); + + // 成功处理 + onSuccess(); + onClose(); + } catch (err) { + console.error('提交品牌数据失败:', err); + setError(err instanceof Error ? err.message : '未知错误'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + +
+ {/* 错误提示 */} + {error && ( +
+

{error}

+
+ )} + + {/* 品牌名称 */} +
+ + +
+ + {/* 显示顺序 */} +
+ + +

数字越小排序越靠前

+
+ + {/* 品牌描述 */} +
+ + +

+ 提示:可输入包含姓名、电话的完整地址,点击"智能解析"自动识别 +

+
+
+ +
+ ); +} diff --git a/src/app/team/[teamCode]/customers/page.tsx b/src/app/team/[teamCode]/customers/page.tsx new file mode 100644 index 0000000..197fc34 --- /dev/null +++ b/src/app/team/[teamCode]/customers/page.tsx @@ -0,0 +1,616 @@ +/** + * 客户管理页面 + * 作者: 阿瑞 + * 功能: 提供客户数据的展示和管理功能 + * 版本: 1.2.0 + */ + +'use client'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { useParams } from 'next/navigation'; +import { useTeam } from '@/hooks/useTeam'; +import { useThemeMode } from '@/store/settingStore'; +import { useAccessToken } from '@/store/userStore'; +import { ThemeMode } from '@/types/enum'; +import { MdAdd, MdSearch, MdEdit, MdDelete, MdRefresh, MdPerson, MdLocationOn, MdPhone, MdCalendarMonth } from 'react-icons/md'; +import { FaWeixin } from 'react-icons/fa'; +import CustomerModal from './customer-modal'; +import CustomerAnalytics from './components/CustomerAnalytics'; +import { Customer } from '@/models/team/types/old/customer'; +import { formatDate, isValidDate } from '@/utils'; + +/** + * 客户管理页面组件 + */ +export default function CustomersPage() { + const params = useParams(); + const teamCode = params?.teamCode as string; + const { currentTeam } = useTeam(); + const accessToken = useAccessToken(); + const themeMode = useThemeMode(); + const isDarkMode = themeMode === ThemeMode.Dark; + + // 客户数据状态 + const [customers, setCustomers] = useState([]); + const [totalCustomers, setTotalCustomers] = useState(0); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // 搜索状态 + const [searchKeyword, setSearchKeyword] = useState(''); + + // 分页状态 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize] = useState(10); + + // 模态框状态 + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedCustomer, setSelectedCustomer] = useState(null); + + // 删除确认状态 + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [customerToDelete, setCustomerToDelete] = useState(null); + + // 定义地址接口 + interface Address { + province?: string; + city?: string; + district?: string; + detail?: string; + postalCode?: string; + } + + /** + * 获取客户列表数据 + */ + const fetchCustomers = useCallback(async () => { + if (!currentTeam || !teamCode) return; + + setIsLoading(true); + setError(null); + + try { + // 构建查询参数 + const queryParams = new URLSearchParams({ + page: currentPage.toString(), + pageSize: pageSize.toString() + }); + + if (searchKeyword) queryParams.append('keyword', searchKeyword); + + const response = await fetch(`/api/team/${teamCode}/customers?${queryParams.toString()}`, { + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); + + if (!response.ok) { + throw new Error('获取客户列表失败'); + } + + const data = await response.json(); + setCustomers(data.customers || []); + setTotalCustomers(data.total || 0); + } catch (err) { + console.error('获取客户列表失败:', err); + setError(err instanceof Error ? err.message : '未知错误'); + } finally { + setIsLoading(false); + } + }, [currentTeam, teamCode, accessToken, currentPage, pageSize, searchKeyword]); + + /** + * 初始加载和依赖变化时获取数据 + */ + useEffect(() => { + if (accessToken && teamCode) { + fetchCustomers(); + } + }, [accessToken, teamCode, fetchCustomers]); + + /** + * 处理搜索 + */ + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setCurrentPage(1); // 重置到第一页 + fetchCustomers(); + }; + + /** + * 清空筛选条件 + */ + const handleClearFilters = () => { + setSearchKeyword(''); + setCurrentPage(1); + fetchCustomers(); + }; + + /** + * 处理添加客户按钮点击 + */ + const handleAddCustomer = () => { + setSelectedCustomer(null); + setIsModalOpen(true); + }; + + /** + * 处理编辑客户 + */ + const handleEditCustomer = (customer: Customer) => { + setSelectedCustomer(customer); + setIsModalOpen(true); + }; + + /** + * 处理删除客户确认 + */ + const handleDeleteClick = (customer: Customer) => { + setCustomerToDelete(customer); + setShowDeleteConfirm(true); + }; + + /** + * 执行删除客户操作 + */ + const confirmDelete = async () => { + if (!customerToDelete || !teamCode) return; + + try { + const response = await fetch(`/api/team/${teamCode}/customers/${customerToDelete.id}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); + + if (!response.ok) { + throw new Error('删除客户失败'); + } + + // 移除已删除的客户 + setCustomers(prev => prev.filter(c => c.id !== customerToDelete.id)); + setTotalCustomers(prev => prev - 1); + setShowDeleteConfirm(false); + setCustomerToDelete(null); + } catch (err) { + console.error('删除客户失败:', err); + } + }; + + /** + * 计算总页数 + */ + const totalPages = Math.ceil(totalCustomers / pageSize); + + /** + * 格式化地址信息 + */ + const formatAddress = (address: Address | undefined): string => { + if (!address) return '未设置地址'; + + const parts = [ + address.province, + address.city, + address.district, + address.detail + ].filter(Boolean); + + return parts.length > 0 ? parts.join(' ') : '未设置详细地址'; + }; + + /** + * 检查日期是否在本月 + */ + const isCurrentMonth = (dateStr: string | null | undefined): boolean => { + if (!dateStr || !isValidDate(dateStr)) return false; + + const date = new Date(dateStr); + const now = new Date(); + + return date.getMonth() === now.getMonth() && + date.getFullYear() === now.getFullYear(); + }; + + /** + * 检查日期是否即将到来(未来7天内) + */ + const isUpcoming = (dateStr: string | null | undefined): boolean => { + if (!dateStr || !isValidDate(dateStr)) return false; + + const date = new Date(dateStr); + const now = new Date(); + + // 重置年份为当前年份,只比较月和日 + date.setFullYear(now.getFullYear()); + + // 如果日期已经过去了当前日期,设置为下一年 + if (date < now) { + date.setFullYear(now.getFullYear() + 1); + } + + // 计算日期差 + const diffTime = date.getTime() - now.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + return diffDays >= 0 && diffDays < 7; + }; + + /** + * 格式化用户友好的日期 + */ + const formatFriendlyDate = (dateStr: string | undefined | null): string => { + if (!dateStr || !isValidDate(dateStr)) return '-'; + return formatDate(dateStr, 'YYYY-MM-DD'); + }; + + /** + * 格式化数值 + */ + const formatNumber = (value: number | undefined | null): string => { + if (value === undefined || value === null) return '0.00'; + // 确保value是数字类型 + const numValue = Number(value); + // 检查是否为有效数字 + if (isNaN(numValue)) return '0.00'; + return numValue.toFixed(2); + }; + + return ( +
+
+

+ 客户管理 +

+

+ 管理和查看团队的所有客户数据 +

+
+ + {/* 客户数据分析组件 */} + + + {/* 搜索区域 */} +
+
+ {/* 搜索表单 */} +
+
+
+ +
+ setSearchKeyword(e.target.value)} + className={`pl-10 pr-4 py-2 w-full rounded-lg ${ + isDarkMode + ? 'bg-gray-800/60 border-gray-700 text-white' + : 'bg-white border-gray-300 text-gray-900' + } border focus:outline-none focus:ring-2 focus:ring-blue-500`} + /> +
+ +
+ +
+ + +
+
+
+ + {/* 错误提示 */} + {error && ( +
+ {error} +
+ )} + + {/* 加载中提示 */} + {isLoading && ( +
+
+ 正在加载客户数据... +
+ )} + + {/* 无数据提示 */} + {!isLoading && customers.length === 0 && ( +
+ +

暂无客户数据

+

点击"添加客户"按钮创建新客户

+
+ )} + + {/* 客户列表 - 表格布局 */} + {!isLoading && customers.length > 0 && ( +
+
+ + + + + + + + + + + + + {customers.map(customer => ( + + {/* 客户信息 */} + + + {/* 联系方式 */} + + + {/* 地址 */} + + + {/* 重要日期 */} + + + {/* 余额 */} + + + {/* 操作按钮 */} + + + ))} + +
+ 客户信息 + + 联系方式 + + 地址 + + 重要日期 + + 余额 + + 操作 +
+
+
+ +
+
+
+ {customer.name} +
+
+ {customer.gender || '未设置性别'} +
+
+
+
+
+ + + {customer.phone} + +
+ {customer.wechat && ( +
+ + + {customer.wechat} + +
+ )} +
+
+
+ + + {formatAddress(customer.address)} + +
+
+
+
+
+
+ + 生日:{formatFriendlyDate(customer.birthday)} + + {customer.birthday && isCurrentMonth(customer.birthday) && ( + + 本月生日 + + )} + {customer.birthday && isUpcoming(customer.birthday) && ( + + 即将过生日 + + )} +
+
+ + {customer.followDate && ( +
+ + + 加粉:{formatFriendlyDate(customer.followDate)} + +
+ )} +
+
+
0 + ? isDarkMode ? 'bg-green-900/30 text-green-300' : 'bg-green-100 text-green-700' + : isDarkMode ? 'bg-gray-800 text-gray-400' : 'bg-gray-100 text-gray-600' + }`}> + ¥ {formatNumber(customer.balance)} +
+
+ + +
+
+ + {/* 分页控件 */} + {totalPages > 0 && ( +
+
+ 共 {totalCustomers} 位客户 +
+
+ + {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { + // 显示当前页附近的页码 + let pageNum = currentPage; + if (currentPage <= 3) { + pageNum = i + 1; + } else if (currentPage >= totalPages - 2) { + pageNum = totalPages - 4 + i; + } else { + pageNum = currentPage - 2 + i; + } + + // 确保页码在有效范围内 + if (pageNum > 0 && pageNum <= totalPages) { + return ( + + ); + } + return null; + })} + +
+
+ )} +
+ )} + + {/* 客户模态框 */} + setIsModalOpen(false)} + customer={selectedCustomer} + onSuccess={fetchCustomers} + /> + + {/* 删除确认框 */} + {showDeleteConfirm && ( +
+
setShowDeleteConfirm(false)}>
+
+

确认删除

+

+ 您确定要删除客户 "{customerToDelete?.name}" 吗?此操作不可撤销。 +

+
+ + +
+
+
+ )} +
+ ); +} diff --git a/src/app/team/[teamCode]/logistics/page.tsx b/src/app/team/[teamCode]/logistics/page.tsx new file mode 100644 index 0000000..dea7839 --- /dev/null +++ b/src/app/team/[teamCode]/logistics/page.tsx @@ -0,0 +1,748 @@ +/** + * 物流管理页面 + * 作者: 阿瑞 + * 功能: 显示和管理物流记录,提供物流查询和状态更新 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { useParams } from 'next/navigation'; +import { useTheme } from '@/hooks'; +import { useNotification } from '@/components/ui/Notification'; +import { MdLocalShipping, MdSearch, MdRefresh, MdOutlineCheckCircleOutline } from 'react-icons/md'; +import { ILogisticsRecord, ILogisticsRecordListItem, RecordType } from '@/models/team/types/ILogisticsRecord'; +import { Spin, Tag, Table, Button, Input, Select, DatePicker, Modal, Alert, Collapse, Space } from 'antd'; +import dayjs from 'dayjs'; +import type { RangePickerProps } from 'antd/es/date-picker'; + +const { Panel } = Collapse; + +type LogisticsStatus = '已填单' | '已揽件' | '运输中' | '已签收' | string | null | undefined; + +/** + * 格式化日期显示 + */ +const formatDate = (dateStr: string | undefined) => { + if (!dateStr) return '-'; + return dayjs(dateStr).format('YYYY-MM-DD HH:mm'); +}; + +/** + * 获取状态标签的类型和颜色 + */ +const getStatusTag = (status: LogisticsStatus) => { + if (!status) return { color: 'default', text: '未知' }; + + switch (status) { + case '已填单': + return { color: 'default', text: '已填单' }; + case '已揽件': + return { color: 'processing', text: '已揽件' }; + case '运输中': + return { color: 'processing', text: '运输中' }; + case '已签收': + return { color: 'success', text: '已签收' }; + default: + return { color: 'processing', text: status }; + } +}; + +/** + * 获取记录类型显示内容 + */ +const getRecordTypeDisplay = (type: RecordType) => { + switch (type) { + case RecordType.SALES_RECORD: + return { text: '销售单', color: 'blue' }; + case RecordType.AFTER_SALES_RECORD: + return { text: '售后单', color: 'orange' }; + default: + return { text: '未知', color: 'default' }; + } +}; + +/** + * 定义物流路由项的接口 + */ +interface LogisticsRoute { + acceptTime?: string; + time?: string; + remark?: string; + content?: string; + acceptAddress?: string; + location?: string; +} + +/** + * 定义物流详情的接口 + */ +interface LogisticsDetails { + routes?: LogisticsRoute[]; + msgData?: { + routeResps?: Array<{ + routes?: LogisticsRoute[]; + }>; + }; +} + +/** + * 物流详情查看弹窗组件 + */ +interface LogisticsDetailModalProps { + visible: boolean; + logisticsRecord: ILogisticsRecord | null; + onClose: () => void; + isDarkMode: boolean; +} + +const LogisticsDetailModal: React.FC = ({ + visible, + logisticsRecord, + onClose, + isDarkMode +}) => { + const [parsedDetails, setParsedDetails] = useState(null); + const [parseError, setParseError] = useState(null); + + useEffect(() => { + if (logisticsRecord?.details) { + try { + const details = JSON.parse(logisticsRecord.details) as LogisticsDetails; + console.log('解析的物流详情数据:', details); + setParsedDetails(details); + setParseError(null); + } catch (error) { + console.error('解析物流详情失败:', error, logisticsRecord.details); + setParsedDetails(null); + setParseError(`解析物流详情失败: ${error instanceof Error ? error.message : '未知错误'}`); + } + } else { + setParsedDetails(null); + setParseError(null); + } + }, [logisticsRecord]); + + // 判断是否有物流路由数据 + const hasRoutesData = parsedDetails?.routes && parsedDetails.routes.length > 0; + + return ( + + + 物流详情 + + } + open={visible} + onCancel={onClose} + footer={[ + + ]} + width={700} + > + {logisticsRecord ? ( +
+
+
+
+
物流单号:
+
{logisticsRecord.tracking_number || '-'}
+
+
+
物流公司:
+
{logisticsRecord.company || '-'}
+
+
+
状态:
+
+ + {getStatusTag(logisticsRecord.status).text} + +
+
+
+
发货时间:
+
{formatDate(logisticsRecord.created_at)}
+
+
+
客户手机尾号:
+
{logisticsRecord.customer_tail_number || '-'}
+
+
+
记录类型:
+
+ + {getRecordTypeDisplay(logisticsRecord.record_type).text} + +
+
+
+
+ + {/* 原始数据查看 */} + + +
+                {JSON.stringify(logisticsRecord, null, 2)}
+              
+
+
+ + {/* 解析错误提示 */} + {parseError && ( + + )} + + {/* 物流详情 */} +
+

+ + 物流跟踪信息 +

+ + {hasRoutesData ? ( +
+ {/* 时间轴线 */} +
+ +
+ {parsedDetails?.routes?.map((route, index) => { + // 提取路由节点显示数据 + const time = route.acceptTime || route.time || '-'; + const content = route.remark || route.content || '未知状态'; + const location = route.acceptAddress || route.location || null; + + return ( +
+ {/* 时间轴节点 */} +
+ +
+
+ {time} +
+
+ {content} +
+ {location && ( +
+ {location} +
+ )} +
+
+ ); + })} +
+
+ ) : ( +
+ {logisticsRecord.is_queryable ? '暂无物流跟踪信息' : '该物流单号不支持查询'} +
+ )} +
+
+ ) : ( +
+ +
+ )} +
+ ); +}; + +/** + * 修改调试信息类型 + */ +interface DebugInfo { + lastQuery: string | null; + lastResponse: Record | null; + error: string | null; + showDebug: boolean; +} + +/** + * 物流管理页面组件 + */ +export default function LogisticsPage() { + const params = useParams(); + const teamCode = params?.teamCode as string; + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [logisticsRecords, setLogisticsRecords] = useState([]); + const [detailModalVisible, setDetailModalVisible] = useState(false); + const [selectedRecord, setSelectedRecord] = useState(null); + const [queryLoading, setQueryLoading] = useState(null); + const [searchParams, setSearchParams] = useState({ + trackingNumber: '', + status: '', + dateRange: null as [dayjs.Dayjs, dayjs.Dayjs] | null, + customerTailNumber: '' + }); + + // 在组件内使用该类型 + const [debugInfo, setDebugInfo] = useState({ + lastQuery: null, + lastResponse: null, + error: null, + showDebug: false + }); + + const notification = useNotification(); + const { isDarkMode } = useTheme(); + + /** + * 加载物流记录列表 + */ + const loadLogisticsRecords = useCallback(async (showLoading = true) => { + if (showLoading) { + setLoading(true); + } else { + setRefreshing(true); + } + + try { + // 构建查询参数 + const queryParams = new URLSearchParams(); + + if (searchParams.trackingNumber) { + queryParams.append('trackingNumber', searchParams.trackingNumber); + } + + if (searchParams.status) { + queryParams.append('status', searchParams.status); + } + + if (searchParams.customerTailNumber) { + queryParams.append('customerTailNumber', searchParams.customerTailNumber); + } + + if (searchParams.dateRange && searchParams.dateRange[0] && searchParams.dateRange[1]) { + queryParams.append('startDate', searchParams.dateRange[0].format('YYYY-MM-DD')); + queryParams.append('endDate', searchParams.dateRange[1].format('YYYY-MM-DD')); + } + + const url = `/api/team/${teamCode}/logistics?${queryParams.toString()}`; + setDebugInfo(prev => ({ ...prev, lastQuery: url, error: null })); + + const response = await fetch(url); + const data = await response.json(); + + setDebugInfo(prev => ({ ...prev, lastResponse: data })); + + if (!response.ok) { + throw new Error(data.error || '获取物流记录失败'); + } + + setLogisticsRecords(data.records || []); + + } catch (error) { + console.error('加载物流记录失败:', error); + notification.error('加载物流记录失败,请稍后重试'); + setDebugInfo(prev => ({ + ...prev, + error: error instanceof Error ? error.message : String(error) + })); + } finally { + setLoading(false); + setRefreshing(false); + } + }, [teamCode, searchParams, notification]); + + /** + * 查询单个物流详情 + */ + const queryLogisticsDetails = async (record: ILogisticsRecordListItem) => { + if (!record.tracking_number || !record.customer_tail_number) { + notification.warning('物流单号或客户手机尾号缺失,无法查询物流信息'); + return; + } + + // 设置当前记录为查询中状态 + setQueryLoading(record.id); + + try { + const url = `/api/team/${teamCode}/logistics/query`; + setDebugInfo(prev => ({ ...prev, lastQuery: url, error: null })); + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + id: record.id, + trackingNumber: record.tracking_number, + phoneLast4Digits: record.customer_tail_number + }) + }); + + const data = await response.json(); + setDebugInfo(prev => ({ ...prev, lastResponse: data })); + + if (!response.ok) { + // 提取详细错误信息 + const errorDetails = data.details || {}; + const errorMessage = data.error || '查询物流信息失败'; + const apiErrorCode = errorDetails.apiResultCode || 'UNKNOWN'; + const apiErrorMsg = errorDetails.apiErrorMsg || '未知错误'; + + throw new Error(`${errorMessage}\n错误码: ${apiErrorCode}\n详情: ${apiErrorMsg}`); + } + + // 查询成功,显示结果弹窗 + setSelectedRecord(data.data); + setDetailModalVisible(true); + + // 刷新列表数据 + loadLogisticsRecords(false); + + notification.success('物流信息查询成功'); + + } catch (error) { + console.error('查询物流信息失败:', error); + notification.error( + error instanceof Error ? error.message : '查询物流信息失败' + ); + + setDebugInfo(prev => ({ + ...prev, + error: error instanceof Error ? error.message : String(error) + })); + } finally { + setQueryLoading(null); + } + }; + + /** + * 处理表单字段变更 + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleSearchChange = (field: string, value: any) => { + // 处理日期范围类型,确保格式一致 + if (field === 'dateRange' && Array.isArray(value) && value[0] && value[1]) { + // 只有当两个日期都有效时才设置日期范围 + setSearchParams(prev => ({ + ...prev, + dateRange: [value[0], value[1]] + })); + } else { + setSearchParams(prev => ({ + ...prev, + [field]: value + })); + } + }; + + /** + * 处理表单重置 + */ + const handleResetSearch = () => { + setSearchParams({ + trackingNumber: '', + status: '', + dateRange: null, + customerTailNumber: '' + }); + }; + + /** + * 处理表单提交 + */ + const handleSubmitSearch = () => { + loadLogisticsRecords(); + }; + + /** + * 更新所有物流状态 + */ + const updateAllLogisticsStatus = async () => { + setRefreshing(true); + try { + const url = `/api/team/${teamCode}/logistics/update-status`; + setDebugInfo(prev => ({ ...prev, lastQuery: url, error: null })); + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + + const data = await response.json(); + setDebugInfo(prev => ({ ...prev, lastResponse: data })); + + if (!response.ok) { + throw new Error(data.error || '更新物流状态失败'); + } + + notification.success('物流状态更新成功!'); + + // 重新加载物流记录 + loadLogisticsRecords(false); + + } catch (error) { + console.error('更新物流状态失败:', error); + notification.error('更新物流状态失败,请稍后重试'); + setDebugInfo(prev => ({ + ...prev, + error: error instanceof Error ? error.message : String(error) + })); + } finally { + setRefreshing(false); + } + }; + + /** + * 初始加载物流记录 + */ + useEffect(() => { + if (teamCode) { + loadLogisticsRecords(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [teamCode]); + + /** + * 表格列定义 + */ + const columns = [ + { + title: '物流单号', + dataIndex: 'tracking_number', + key: 'tracking_number', + render: (text: string) => text || '-' + }, + { + title: '物流公司', + dataIndex: 'company', + key: 'company', + render: (text: string) => text || '-' + }, + { + title: '客户手机尾号', + dataIndex: 'customer_tail_number', + key: 'customer_tail_number', + render: (text: string) => text || '-' + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: string | null | undefined) => ( + + {getStatusTag(status).text} + + ) + }, + { + title: '记录类型', + dataIndex: 'record_type', + key: 'record_type', + render: (type: RecordType) => ( + + {getRecordTypeDisplay(type).text} + + ) + }, + { + title: '物流详情', + dataIndex: 'details', + key: 'details', + render: (details: string) => { + if (!details) return '-'; + try { + const parsedDetails = JSON.parse(details); + // 检查是否有路由信息,适配不同格式 + const routes = parsedDetails?.routes || []; + + if (routes.length > 0) { + // 获取最新的物流节点信息 + const latestRoute = routes[0]; + // 适配不同的字段名 + const content = latestRoute.remark || latestRoute.content || ''; + + return ( +
+ {routes.length}条记录 +
+ {content} +
+
+ ); + } else { + return 无详情; + } + } catch (e) { + console.error('解析物流详情失败:', e, details); + return 格式错误; + } + } + }, + { + title: '可查询', + dataIndex: 'is_queryable', + key: 'is_queryable', + render: (isQueryable: boolean) => ( + isQueryable ? + 可查询 : + 不可查询 + ) + }, + { + title: '创建时间', + dataIndex: 'created_at', + key: 'created_at', + render: (date: string) => formatDate(date) + }, + { + title: '操作', + key: 'action', + render: (_: unknown, record: ILogisticsRecordListItem) => ( +
+ +
+ ) + } + ]; + + return ( +
+
+
+

+ + 物流管理 +

+

+ 管理和查询物流信息,跟踪物流状态 +

+
+ + + + +
+ + {/* 搜索面板 */} +
+
+
+
物流单号
+ handleSearchChange('trackingNumber', e.target.value)} + allowClear + /> +
+ +
+
物流状态
+ +
+ +
+
客户手机尾号
+ handleSearchChange('customerTailNumber', e.target.value)} + allowClear + /> +
+ +
+
日期范围
+ handleSearchChange('dateRange', dates as RangePickerProps['value'])} + style={{ width: '100%' }} + /> +
+ +
+ + +
+
+
+ + {/* 物流列表 */} +
+ `共 ${total} 条记录` + }} + locale={{ + emptyText:
+ + {debugInfo.error ? '加载物流记录出错' : '暂无物流记录'} + {logisticsRecords.length === 0 && !loading && !debugInfo.error && ( +
+ +
+ )} +
+ }} + /> + + + {/* 物流详情弹窗 */} + setDetailModalVisible(false)} + isDarkMode={isDarkMode} + /> + + ); +} diff --git a/src/app/team/[teamCode]/page.tsx b/src/app/team/[teamCode]/page.tsx new file mode 100644 index 0000000..e8edcc9 --- /dev/null +++ b/src/app/team/[teamCode]/page.tsx @@ -0,0 +1,232 @@ +/** + * 团队仪表盘页面 + * 作者: 阿瑞 + * 功能: 显示团队数据概览和统计信息 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useParams } from 'next/navigation'; +import { useTeam, useTheme, useAccessToken } from '@/hooks'; +import { MdPeople, MdStorefront, MdInventory, MdTrendingUp, MdLocalShipping, MdInfo } from 'react-icons/md'; + +/** + * 统计卡片组件 + */ +interface StatCardProps { + title: string; + value: string | number; + icon: React.ReactNode; + trend?: number; + loading?: boolean; + isDarkMode?: boolean; +} + +const StatCard: React.FC = ({ title, value, icon, trend, loading, isDarkMode }) => ( +
+
+

+ {title} +

+
+ {icon} +
+
+ {loading ? ( +
+ ) : ( +
+
{value}
+ {trend !== undefined && ( +
= 0 ? 'text-green-500' : 'text-red-500'}`}> + {trend >= 0 ? '↑' : '↓'} {Math.abs(trend)}% +
+ )} +
+ )} +
+); + +/** + * 团队仪表盘页面组件 + */ +export default function TeamDashboard() { + const { teamCode } = useParams(); + const { currentTeam } = useTeam(); + const { isDarkMode } = useTheme(); + const accessToken = useAccessToken(); + + const [stats, setStats] = useState({ + customers: 0, + shops: 0, + products: 0, + orders: 0, + suppliers: 0 + }); + + const [isLoading, setIsLoading] = useState(true); + + // 模拟获取统计数据 + useEffect(() => { + const fetchStats = async () => { + if (!teamCode || !accessToken || !currentTeam) return; + + setIsLoading(true); + + try { + // 这里应该是实际API调用,暂时模拟数据 + // const response = await fetch(`/api/team/${teamCode}/stats`, { + // headers: { Authorization: `Bearer ${accessToken}` } + // }); + // const data = await response.json(); + + // 模拟延迟和数据 + await new Promise(resolve => setTimeout(resolve, 1200)); + + // 模拟数据 + setStats({ + customers: 128, + shops: 5, + products: 76, + orders: 243, + suppliers: 12 + }); + } catch (error) { + console.error('获取统计数据失败:', error); + } finally { + setIsLoading(false); + } + }; + + fetchStats(); + }, [teamCode, accessToken, currentTeam]); + + return ( +
+
+

+ {currentTeam?.name || ''} 团队仪表盘 +

+

+ 查看团队整体运营状态和关键指标 +

+
+ + {/* 统计卡片 */} +
+ } + trend={8.2} + loading={isLoading} + isDarkMode={isDarkMode} + /> + } + loading={isLoading} + isDarkMode={isDarkMode} + /> + } + trend={4.5} + loading={isLoading} + isDarkMode={isDarkMode} + /> + } + trend={12.3} + loading={isLoading} + isDarkMode={isDarkMode} + /> + } + loading={isLoading} + isDarkMode={isDarkMode} + /> +
+ + {/* 系统消息和提示 */} +
+
+
+ +
+
+

+ 开始使用管理系统 +

+

+ 欢迎使用团队管理系统,您可以从左侧导航菜单访问各个功能模块,管理客户、产品、订单等数据。 +

+
+ + +
+
+
+
+ + {/* 最近活动 */} +
+

最近活动

+ + {isLoading ? ( +
+ {[...Array(3)].map((_, index) => ( +
+
+
+
+
+
+
+ ))} +
+ ) : ( +
+
+
+ +
+
+

新增客户 "张小姐" (1317620****)

+

10分钟前

+
+
+ +
+
+ +
+
+

新增产品 "高端美白套装"

+

30分钟前

+
+
+ +
+
+ +
+
+

新订单 #2023112 已生成

+

2小时前

+
+
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/team/[teamCode]/payment-platforms/page.tsx b/src/app/team/[teamCode]/payment-platforms/page.tsx new file mode 100644 index 0000000..481d86e --- /dev/null +++ b/src/app/team/[teamCode]/payment-platforms/page.tsx @@ -0,0 +1,503 @@ +/** + * 支付平台管理页面 + * 作者: 阿瑞 + * 功能: 提供支付平台数据的展示和管理功能 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState, useEffect, useCallback } from 'react'; +import { useParams } from 'next/navigation'; +import { useTeam } from '@/hooks/useTeam'; +import { useAccessToken } from '@/store/userStore'; +import { useThemeMode } from '@/store/settingStore'; +import { ThemeMode } from '@/types/enum'; +import { MdAdd, MdSearch, MdEdit, MdDelete, MdRefresh, MdFilterList, MdPayment } from 'react-icons/md'; +import { PaymentPlatform, PaymentPlatformStatus } from '@/models/team/types/old/payment-platform'; +import PaymentPlatformModal from './payment-platform-modal'; + +/** + * 获取支付平台状态标签 + */ +const getStatusLabel = (status: PaymentPlatformStatus): string => { + switch (status) { + case PaymentPlatformStatus.DISABLED: + return '停用'; + case PaymentPlatformStatus.NORMAL: + return '正常'; + case PaymentPlatformStatus.BACKUP: + return '备用'; + case PaymentPlatformStatus.OTHER: + return '其他'; + default: + return '未知'; + } +}; + +/** + * 获取支付平台状态类名 + */ +const getStatusClassName = (status: PaymentPlatformStatus): string => { + switch (status) { + case PaymentPlatformStatus.DISABLED: + return 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300'; + case PaymentPlatformStatus.NORMAL: + return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300'; + case PaymentPlatformStatus.BACKUP: + return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300'; + case PaymentPlatformStatus.OTHER: + return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'; + default: + return 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300'; + } +}; + +/** + * 支付平台管理页面组件 + */ +export default function PaymentPlatformsPage() { + const params = useParams(); + const teamCode = params?.teamCode as string; + const { currentTeam } = useTeam(); + const accessToken = useAccessToken(); + const themeMode = useThemeMode(); + const isDarkMode = themeMode === ThemeMode.Dark; + + // 支付平台数据状态 + const [platforms, setPlatforms] = useState([]); + const [totalPlatforms, setTotalPlatforms] = useState(0); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + // 搜索和筛选状态 + const [searchKeyword, setSearchKeyword] = useState(''); + const [statusFilter, setStatusFilter] = useState(''); + + // 分页状态 + const [currentPage, setCurrentPage] = useState(1); + const [pageSize] = useState(10); + + // 模态框状态 + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedPlatform, setSelectedPlatform] = useState(null); + + // 删除确认状态 + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [platformToDelete, setPlatformToDelete] = useState(null); + + /** + * 获取支付平台列表数据 + */ + const fetchPlatforms = useCallback(async () => { + if (!currentTeam || !teamCode) return; + + setIsLoading(true); + setError(null); + + try { + // 构建查询参数 + const queryParams = new URLSearchParams({ + page: currentPage.toString(), + pageSize: pageSize.toString() + }); + + if (searchKeyword) queryParams.append('keyword', searchKeyword); + if (statusFilter) queryParams.append('status', statusFilter); + + const response = await fetch(`/api/team/${teamCode}/payment-platforms?${queryParams.toString()}`, { + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); + + if (!response.ok) { + throw new Error('获取支付平台列表失败'); + } + + const data = await response.json(); + setPlatforms(data.paymentPlatforms || []); + setTotalPlatforms(data.total || 0); + } catch (err) { + console.error('获取支付平台列表失败:', err); + setError(err instanceof Error ? err.message : '未知错误'); + } finally { + setIsLoading(false); + } + }, [currentTeam, teamCode, accessToken, currentPage, pageSize, searchKeyword, statusFilter]); + + /** + * 初始加载和依赖变化时获取数据 + */ + useEffect(() => { + if (accessToken && teamCode) { + fetchPlatforms(); + } + }, [accessToken, teamCode, fetchPlatforms]); + + /** + * 处理搜索 + */ + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setCurrentPage(1); // 重置到第一页 + fetchPlatforms(); + }; + + /** + * 处理状态筛选变更 + */ + const handleStatusFilterChange = (e: React.ChangeEvent) => { + setStatusFilter(e.target.value); + setCurrentPage(1); // 重置到第一页 + }; + + /** + * 清空筛选条件 + */ + const handleClearFilters = () => { + setSearchKeyword(''); + setStatusFilter(''); + setCurrentPage(1); + fetchPlatforms(); + }; + + /** + * 处理添加支付平台按钮点击 + */ + const handleAddPlatform = () => { + setSelectedPlatform(null); + setIsModalOpen(true); + }; + + /** + * 处理编辑支付平台 + */ + const handleEditPlatform = (platform: PaymentPlatform) => { + setSelectedPlatform(platform); + setIsModalOpen(true); + }; + + /** + * 处理删除支付平台确认 + */ + const handleDeleteClick = (platform: PaymentPlatform) => { + setPlatformToDelete(platform); + setShowDeleteConfirm(true); + }; + + /** + * 执行删除支付平台操作 + */ + const confirmDelete = async () => { + if (!platformToDelete || !teamCode) return; + + try { + const response = await fetch(`/api/team/${teamCode}/payment-platforms/${platformToDelete.id}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${accessToken}` + } + }); + + if (!response.ok) { + throw new Error('删除支付平台失败'); + } + + // 移除已删除的支付平台 + setPlatforms(prev => prev.filter(p => p.id !== platformToDelete.id)); + setTotalPlatforms(prev => prev - 1); + setShowDeleteConfirm(false); + setPlatformToDelete(null); + } catch (err) { + console.error('删除支付平台失败:', err); + } + }; + + /** + * 计算总页数 + */ + const totalPages = Math.ceil(totalPlatforms / pageSize); + + return ( +
+ {/* 页面标题区域 */} +
+

+ 支付平台管理 +

+

+ 管理和查看支付平台信息 +

+
+ + {/* 搜索区域 */} +
+
+ {/* 搜索表单 */} +
+
+
+ +
+ setSearchKeyword(e.target.value)} + className={`pl-10 pr-4 py-2 w-full rounded-lg ${ + isDarkMode + ? 'bg-gray-800/60 border-gray-700 text-white' + : 'bg-white border-gray-300 text-gray-900' + } border focus:outline-none focus:ring-2 focus:ring-blue-500`} + /> +
+ +
+
+ +
+ +
+ + + +
+
+ + + + +
+ + +
+
+
+ + {/* 错误提示 */} + {error && ( +
+ {error} +
+ )} + + {/* 加载中提示 */} + {isLoading && ( +
+
+ 正在加载支付平台数据... +
+ )} + + {/* 无数据提示 */} + {!isLoading && platforms.length === 0 && ( +
+ +

暂无支付平台数据

+

点击"添加平台"按钮创建新支付平台

+
+ )} + + {/* 支付平台列表 */} + {!isLoading && platforms.length > 0 && ( +
+
+
+ + + + + + + + + + + {platforms.map((platform) => ( + + + + + + + + ))} + +
+ ID/排序 + + 平台名称 + + 描述 + + 状态 + + 操作 +
+
+ #{platform.id} +
+
+ 排序: {platform.order} +
+
+
+
+ +
+
+
+ {platform.name} +
+
+
+
+
+ {platform.description || '-'} +
+
+ + {getStatusLabel(platform.status)} + + +
+ + +
+
+
+ + {/* 分页控件 */} + {totalPages > 1 && ( +
+
+

+ 显示第 {(currentPage - 1) * pageSize + 1} 至{' '} + + {Math.min(currentPage * pageSize, totalPlatforms)} + {' '} + 条,共 {totalPlatforms} 条 +

+
+
+ + +
+
+ )} +
+ )} + + {/* 支付平台模态框 */} + setIsModalOpen(false)} + platform={selectedPlatform} + onSuccess={fetchPlatforms} + /> + + {/* 删除确认对话框 */} + {showDeleteConfirm && ( +
+
+

确认删除

+

+ 您确定要删除支付平台 "{platformToDelete?.name}" 吗?此操作不可撤销。 +

+
+ + +
+
+
+ )} + + ); +} \ No newline at end of file diff --git a/src/app/team/[teamCode]/payment-platforms/payment-platform-modal.tsx b/src/app/team/[teamCode]/payment-platforms/payment-platform-modal.tsx new file mode 100644 index 0000000..d4e8406 --- /dev/null +++ b/src/app/team/[teamCode]/payment-platforms/payment-platform-modal.tsx @@ -0,0 +1,272 @@ +/** + * 支付平台模态框组件 + * 作者: 阿瑞 + * 功能: 提供支付平台信息的添加和编辑界面 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useParams } from 'next/navigation'; +import { PaymentPlatform, PaymentPlatformStatus } from '@/models/team/types/old/payment-platform'; +import Modal from '@/components/ui/Modal'; + +interface PaymentPlatformModalProps { + isOpen: boolean; + onClose: () => void; + platform?: PaymentPlatform | null; + onSuccess: () => void; +} + +/** + * 支付平台模态框组件 + */ +export default function PaymentPlatformModal({ isOpen, onClose, platform, onSuccess }: PaymentPlatformModalProps) { + const params = useParams(); + const teamCode = params?.teamCode as string; + + const isEditing = !!platform; + + // 表单状态 + const [formData, setFormData] = useState>({ + name: '', + order: 0, + description: '', + status: PaymentPlatformStatus.NORMAL + }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + /** + * 初始化编辑表单数据 + */ + useEffect(() => { + if (platform) { + setFormData({ + id: platform.id, + name: platform.name, + order: platform.order, + description: platform.description || '', + status: platform.status + }); + } else { + // 重置表单 + setFormData({ + name: '', + order: 0, + description: '', + status: PaymentPlatformStatus.NORMAL + }); + } + + setError(null); + }, [platform, isOpen]); + + /** + * 处理表单输入变化 + */ + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + // 处理数字类型字段 + if (name === 'order') { + setFormData(prev => ({ + ...prev, + [name]: value === '' ? 0 : parseInt(value, 10) + })); + return; + } + + // 处理状态选择 + if (name === 'status') { + setFormData(prev => ({ + ...prev, + [name]: parseInt(value, 10) + })); + return; + } + + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + /** + * 提交表单 + */ + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // 基本验证 + if (!formData.name) { + setError('平台名称为必填项'); + return; + } + + if (formData.order === undefined || formData.order < 0) { + setError('显示顺序必须为非负整数'); + return; + } + + setIsSubmitting(true); + setError(null); + + try { + let response; + + if (isEditing && platform) { + // 更新支付平台 + response = await fetch(`/api/team/${teamCode}/payment-platforms/${platform.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + } else { + // 创建支付平台 + response = await fetch(`/api/team/${teamCode}/payment-platforms`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + } + + // 处理响应 + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error || '操作失败'); + } + + // 成功处理 + onSuccess(); + onClose(); + } catch (err) { + console.error('提交支付平台数据失败:', err); + setError(err instanceof Error ? err.message : '未知错误'); + } finally { + setIsSubmitting(false); + } + }; + + return ( + +
+ {/* 错误提示 */} + {error && ( +
+

{error}

+
+ )} + + {/* 平台名称 */} +
+ + +
+ + {/* 显示顺序 */} +
+ + +

数字越小排序越靠前

+
+ + {/* 平台状态 */} +
+ + +
+ + {/* 平台描述 */} +
+ + +

+ 提示:可输入包含姓名、电话的完整地址,点击"智能解析"自动识别 +

+
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/app/team/[teamCode]/sales2/components/CustomerSelector.tsx b/src/app/team/[teamCode]/sales2/components/CustomerSelector.tsx new file mode 100644 index 0000000..fc5221f --- /dev/null +++ b/src/app/team/[teamCode]/sales2/components/CustomerSelector.tsx @@ -0,0 +1,397 @@ +/** + * 客户选择器组件 + * 作者: 阿瑞 + * 功能: 提供客户搜索和选择功能 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState, useEffect, useCallback, useRef } from 'react'; +import { MdSearch, MdRefresh, MdPersonAdd, MdPerson, MdArrowDropDown, MdClose } from 'react-icons/md'; +import { useTheme } from '@/hooks'; + +// 地址接口类型 +interface CustomerAddress { + province?: string; + city?: string; + district?: string; + detail?: string; + postalCode?: string; +} + +// 客户接口类型 +export interface Customer { + id: number; + name: string; + phone?: string; + gender?: string; + wechat?: string; + address?: CustomerAddress; + birthday?: string; + followDate?: string; + balance?: number; + createdAt?: string; + updatedAt?: string; +} + +interface CustomerSelectorProps { + selectedCustomer: Customer | null; + onSelectCustomer: (customer: Customer) => void; + teamCode: string; + onCreateCustomer?: () => void; // 新增客户回调 +} + +/** + * 客户选择器组件 + * 用于搜索和选择客户 + */ +const CustomerSelector: React.FC = ({ + selectedCustomer, + onSelectCustomer, + teamCode, + onCreateCustomer +}) => { + const { isDarkMode } = useTheme(); + const [searchQuery, setSearchQuery] = useState(''); + const [customers, setCustomers] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + const [debouncedQuery, setDebouncedQuery] = useState(''); + const [isDropdownOpen, setIsDropdownOpen] = useState(false); + const dropdownRef = useRef(null); + const inputRef = useRef(null); + + // 节流查询,300ms后执行 + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedQuery(searchQuery); + }, 300); + + return () => clearTimeout(timer); + }, [searchQuery]); + + // 获取客户列表 + const fetchCustomers = useCallback(async (keyword: string = '') => { + if (!teamCode) return; + + setIsLoading(true); + setErrorMessage(null); + + try { + // 构建API URL + const url = `/api/team/${teamCode}/customers?pageSize=20${keyword ? `&keyword=${encodeURIComponent(keyword)}` : ''}`; + + const response = await fetch(url); + const data = await response.json(); + + if (data.success) { + setCustomers(data.customers || []); + } else { + setErrorMessage(data.error || '获取客户列表失败'); + setCustomers([]); + } + } catch { + setErrorMessage('网络请求失败,请稍后重试'); + setCustomers([]); + } finally { + setIsLoading(false); + } + }, [teamCode]); + + // 初始加载和查询变化时获取数据 + useEffect(() => { + fetchCustomers(debouncedQuery); + if (debouncedQuery.length > 0 && !isDropdownOpen) { + setIsDropdownOpen(true); + } + }, [fetchCustomers, debouncedQuery, isDropdownOpen]); + + // 点击外部关闭下拉框 + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { + setIsDropdownOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + // 手动刷新 + const handleRefresh = () => { + fetchCustomers(debouncedQuery); + if (!isDropdownOpen) { + setIsDropdownOpen(true); + } + }; + + // 处理搜索输入变化 + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + if (e.target.value.length > 0 && !isDropdownOpen) { + setIsDropdownOpen(true); + } + }; + + // 创建新客户 + const handleCreateCustomer = () => { + if (onCreateCustomer) { + onCreateCustomer(); + setIsDropdownOpen(false); + } + }; + + // 选择客户 + const handleSelectCustomer = (customer: Customer) => { + onSelectCustomer(customer); + setIsDropdownOpen(false); + setSearchQuery(''); + }; + + // 清除选择的客户 + const handleClearSelection = (e?: React.MouseEvent) => { + e?.stopPropagation(); + // 使用as null来解决类型问题,因为接口定义是Customer | null + onSelectCustomer(null as unknown as Customer); + setSearchQuery(''); + inputRef.current?.focus(); + }; + + // 处理下拉框切换 + const toggleDropdown = () => { + setIsDropdownOpen(!isDropdownOpen); + if (!isDropdownOpen) { + fetchCustomers(searchQuery); + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + } + }; + + // 渲染客户列表项 + const renderCustomerList = () => { + if (isLoading) { + return ( +
+ 加载中... +
+ ); + } + + if (customers.length > 0) { + return ( +
+ {customers.map(customer => ( +
handleSelectCustomer(customer)} + className={` + p-3 cursor-pointer + ${isDarkMode ? 'hover:bg-slate-700/50' : 'hover:bg-gray-50'} + border-b border-gray-100 dark:border-gray-800 + transition-colors duration-150 + `} + > +
+ {customer.name} + {customer.gender && ( + + {customer.gender} + + )} +
+
+ {customer.phone &&
{customer.phone}
} + {customer.wechat &&
微信:{customer.wechat}
} +
+
+ ))} +
+ ); + } + + if (customers.length === 0 && debouncedQuery) { + return ( +
+ 没有找到匹配的客户 + {onCreateCustomer && ( + + )} +
+ ); + } + + if (customers.length === 0) { + return ( +
+
+ +
+
+ 请选择一位客户 或 +
+ +
+ ); + } + + return null; + }; + + return ( +
+ {/* 搜索输入框 */} +
+ {selectedCustomer ? ( +
+
+
+ +
+
+
+ {selectedCustomer.name} + {selectedCustomer.gender && ( + + {selectedCustomer.gender} + + )} +
+ {selectedCustomer.phone && +
{selectedCustomer.phone}
+ } +
+
+
+ + +
+
+ ) : ( +
+
+ setIsDropdownOpen(true)} + className={` + w-full px-3 py-2.5 pl-10 pr-10 rounded-lg + ${isDarkMode ? 'bg-slate-800/50' : 'bg-white/50'} + backdrop-blur-sm border border-white/20 dark:border-white/10 + focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent + text-gray-800 dark:text-white + `} + /> + +
+ + +
+
+ + {onCreateCustomer && ( + + )} +
+ )} +
+ + {/* 错误信息 */} + {errorMessage && ( +
+ {errorMessage} +
+ )} + + {/* 下拉列表 */} + {isDropdownOpen && ( +
+ {renderCustomerList()} +
+ )} +
+ ); +}; + +export default CustomerSelector; \ No newline at end of file diff --git a/src/app/team/[teamCode]/sales2/components/ProductSelector.tsx b/src/app/team/[teamCode]/sales2/components/ProductSelector.tsx new file mode 100644 index 0000000..6041e30 --- /dev/null +++ b/src/app/team/[teamCode]/sales2/components/ProductSelector.tsx @@ -0,0 +1,157 @@ +/** + * 产品选择器组件 + * 作者: 阿瑞 + * 功能: 提供产品搜索和选择功能 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState } from 'react'; +import { MdSearch } from 'react-icons/md'; +import { useTheme } from '@/hooks'; + +// 产品接口类型 +export interface Product { + id: number; + name: string; + price: number; + image?: string; + description?: string; +} + +interface ProductSelectorProps { + selectedProduct: Product | null; + onSelectProduct: (product: Product) => void; + productPrice: number; + onPriceChange: (price: number) => void; + productQuantity: number; + onQuantityChange: (quantity: number) => void; +} + +/** + * 产品选择器组件 + * 用于搜索和选择产品 + */ +const ProductSelector: React.FC = ({ + selectedProduct, + onSelectProduct, + productPrice, + onPriceChange, + productQuantity, + onQuantityChange +}) => { + const { isDarkMode } = useTheme(); + const [searchQuery, setSearchQuery] = useState(''); + + // 模拟一些产品数据 + const [products] = useState([ + { id: 1, name: "产品A", price: 199.99, description: "这是产品A的描述" }, + { id: 2, name: "产品B", price: 299.99, description: "这是产品B的描述" }, + { id: 3, name: "产品C", price: 99.99, description: "这是产品C的描述" } + ]); + + // 过滤后的产品列表 + const filteredProducts = searchQuery.trim() === '' + ? products + : products.filter(product => + product.name.toLowerCase().includes(searchQuery.toLowerCase()) || + product.description?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + // 处理搜索输入变化 + const handleSearchChange = (e: React.ChangeEvent) => { + setSearchQuery(e.target.value); + }; + + return ( +
+
+ + +
+ +
+ {filteredProducts.length > 0 ? ( + filteredProducts.map(product => ( +
onSelectProduct(product)} + className={` + p-2 mb-2 rounded-lg cursor-pointer + ${selectedProduct?.id === product.id + ? (isDarkMode ? 'bg-blue-900/30 border-blue-500' : 'bg-blue-50 border-blue-200') + : (isDarkMode ? 'bg-slate-800/30 hover:bg-slate-700/30' : 'bg-white hover:bg-gray-50')} + border border-transparent + ${selectedProduct?.id === product.id ? 'border-opacity-100' : 'hover:border-gray-200 dark:hover:border-gray-700'} + transition-colors duration-150 + `} + > +
{product.name}
+
¥{product.price.toFixed(2)}
+ {product.description && ( +
{product.description}
+ )} +
+ )) + ) : ( +
+ 没有找到匹配的产品 +
+ )} +
+ +
+
+ onQuantityChange(parseInt(e.target.value) || 1)} + min="1" + className={` + w-full px-3 py-2 rounded-lg + ${isDarkMode ? 'bg-slate-800/50' : 'bg-white/50'} + backdrop-blur-sm border border-white/20 dark:border-white/10 + focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent + text-gray-800 dark:text-white + transition-all duration-200 + `} + /> +
+
+ onPriceChange(parseFloat(e.target.value) || 0)} + step="0.01" + min="0" + className={` + w-full px-3 py-2 rounded-lg + ${isDarkMode ? 'bg-slate-800/50' : 'bg-white/50'} + backdrop-blur-sm border border-white/20 dark:border-white/10 + focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent + text-gray-800 dark:text-white + transition-all duration-200 + `} + /> +
+
+
+ ); +}; + +export default ProductSelector; \ No newline at end of file diff --git a/src/app/team/[teamCode]/sales2/page.tsx b/src/app/team/[teamCode]/sales2/page.tsx new file mode 100644 index 0000000..df3f9d7 --- /dev/null +++ b/src/app/team/[teamCode]/sales2/page.tsx @@ -0,0 +1,712 @@ +/** + * 创建销售记录页面 + * 作者: 阿瑞 + * 功能: 提供创建销售记录数据的功能,目前仅做示例数据 + * 版本: 1.0.0 + */ + +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useTheme } from '@/hooks'; +import { useTeam } from '@/hooks/useTeam'; +import { MdShoppingCart, MdAdd, MdPerson, MdLocalShipping, MdAddCircleOutline, MdDelete } from 'react-icons/md'; +//从@/utils导入所有工具 +// 导入UI组件 +import Card from '@/components/ui/Card'; +// 导入自定义组件 +import CustomerSelector, { Customer } from './components/CustomerSelector'; +import ProductSelector, { Product } from './components/ProductSelector'; +import CustomerModal from './components/AddCustomer'; + +interface SelectedProductItem { + product: Product; + quantity: number; + price: number; +} + +enum PaymentType { + FULL_PAYMENT = 0, // 全款 + DEPOSIT = 1, // 定金 + UNPAID = 2, // 未付 + FREE = 3, // 赠送 + OTHER = 4 // 其他 +} + +interface SalesFormData { + customerId: number | null; + products: SelectedProductItem[]; + dealDate: string; + paymentType?: PaymentType; + platformId?: number; + receivable: number; + received: number; + pending: number; + dealShopId?: number; + remark?: string; +} + +/** + * 已选产品列表组件 + * 显示已添加到销售记录的产品 + */ +const SelectedProductList = ({ + products, + onRemove +}: { + products: SelectedProductItem[], + onRemove: (index: number) => void +}) => { + const { isDarkMode } = useTheme(); + + if (products.length === 0) { + return null; + } + + return ( +
+ + + + + + + + + + + + {products.map((item, index) => ( + + + + + + + + ))} + + + + + + + + +
产品名称单价数量金额操作
{item.product.name}¥{item.price.toFixed(2)}{item.quantity}¥{(item.price * item.quantity).toFixed(2)} + +
总计{products.reduce((sum, item) => sum + item.quantity, 0)}¥{products.reduce((sum, item) => sum + (item.price * item.quantity), 0).toFixed(2)}
+
+ ); +}; + +/** + * 销售记录页面组件 + * 展示销售记录列表和创建销售记录功能 + */ +export default function SalesPage() { + // 获取路由参数和团队信息 + const { isDarkMode } = useTheme(); + const { currentTeam } = useTeam(); + const teamCode = currentTeam?.team_code || ''; + + // 页面状态管理 + const [isPageLoading, setIsPageLoading] = useState(true); + const [error] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + // 客户和产品选择状态 + const [selectedCustomer, setSelectedCustomer] = useState(null); + const [selectedProduct, setSelectedProduct] = useState(null); + const [productQuantity, setProductQuantity] = useState(1); + const [productPrice, setProductPrice] = useState(0); + + // 客户模态框状态 + const [isCustomerModalOpen, setIsCustomerModalOpen] = useState(false); + + // 表单数据 + const [formData, setFormData] = useState({ + customerId: null, + products: [], + dealDate: new Date().toISOString().slice(0, 10), // 当前日期 + receivable: 0, + received: 0, + pending: 0 + }); + + // 模拟支付平台 + const [platforms] = useState([ + { id: 1, name: "微信支付" }, + { id: 2, name: "支付宝" }, + { id: 3, name: "银行转账" } + ]); + + // 模拟店铺 + const [shops] = useState([ + { id: 1, nickname: "官方旗舰店", wechat: "shop1" }, + { id: 2, nickname: "直营店", wechat: "shop2" } + ]); + + // 选择客户处理函数 + const handleSelectCustomer = (customer: Customer) => { + setSelectedCustomer(customer); + setFormData(prev => ({...prev, customerId: customer.id})); + }; + + // 处理创建新客户 + const handleCreateCustomer = () => { + // 打开新建客户模态框 + setIsCustomerModalOpen(true); + }; + + // 客户模态框成功回调 + const handleCustomerSuccess = () => { + // 刷新客户列表 + // TODO: 实现刷新客户列表的逻辑 + }; + + // 选择产品处理函数 + const handleSelectProduct = (product: Product) => { + setSelectedProduct(product); + setProductPrice(product.price); + }; + + // 添加产品到表单 + const handleAddProduct = () => { + if (!selectedProduct) return; + + const newProduct: SelectedProductItem = { + product: selectedProduct, + quantity: productQuantity, + price: productPrice + }; + + // 计算新的总额 + + setFormData(prev => { + const updatedProducts = [...prev.products, newProduct]; + const newReceivable = updatedProducts.reduce((total, item) => + total + (item.quantity * item.price), 0); + + return { + ...prev, + products: updatedProducts, + receivable: newReceivable, + received: newReceivable, // 默认实收等于应收 + pending: 0 // 默认待收为0 + }; + }); + + // 重置选择 + setSelectedProduct(null); + setProductQuantity(1); + }; + + // 移除已选产品 + const handleRemoveProduct = (index: number) => { + setFormData(prev => { + const updatedProducts = [...prev.products]; + updatedProducts.splice(index, 1); + + const newReceivable = updatedProducts.reduce((total, item) => + total + (item.quantity * item.price), 0); + + return { + ...prev, + products: updatedProducts, + receivable: newReceivable, + received: newReceivable, + pending: 0 + }; + }); + }; + + // 处理表单输入变化 + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + + setFormData(prev => { + const updated = {...prev, [name]: value}; + + // 如果修改了应收或实收,自动计算待收 + if (name === 'receivable' || name === 'received') { + const receivable = name === 'receivable' ? parseFloat(value) || 0 : prev.receivable; + const received = name === 'received' ? parseFloat(value) || 0 : prev.received; + updated.pending = Math.max(0, receivable - received); + } + + return updated; + }); + }; + + // 处理表单提交 + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + // 这里应该添加表单验证 + + setIsSubmitting(true); + + // 模拟API请求 + setTimeout(() => { + setSuccessMessage('销售记录创建成功!'); + setIsSubmitting(false); + + // 重置表单 + setFormData({ + customerId: null, + products: [], + dealDate: new Date().toISOString().slice(0, 10), + receivable: 0, + received: 0, + pending: 0 + }); + setSelectedCustomer(null); + }, 1500); + }; + + // 支付类型选项渲染 + const renderPaymentTypeOptions = () => { + return ( + <> + + + + + + + + ); + }; + + // 获取支付类型颜色 + + // 数据加载模拟 + useEffect(() => { + // 模拟数据加载 + const timer = setTimeout(() => { + setIsPageLoading(false); + }, 1000); + + return () => clearTimeout(timer); + }, []); + + return ( +
+ {/* 页面标题 */} +
+

+ + 销售记录 +

+
+ + {/* 客户模态框 */} + setIsCustomerModalOpen(false)} + onSuccess={handleCustomerSuccess} + teamCode={teamCode} + /> + + {/* 加载状态显示 */} + {isPageLoading ? ( + +
+
+ ) : ( + <> + {/* 成功消息 */} + {successMessage && ( +
+
+ + + +

{successMessage}

+
+
+ )} + + {/* 错误提示 */} + {error && ( +
+
+ + + +

{error}

+
+
+ )} + +
+ {/* 主要内容区域 */} +
+ {/* 上部分 - 客户与产品选择 */} +
+ {/* 左侧栏 - 客户选择 */} + + + 客户选择 +
+ } + > + + + + {/* 右侧栏 - 产品选择 */} + +
+ + 产品选择 +
+ +
+ } + > + + + + +
+ + {/* 产品列表部分 - 仅在有产品时显示 */} + {formData.products.length > 0 && ( +
+ + + +
+ )} + + {/* 订单详细信息 */} + +
+ {/* 第一列 */} +
+ {/* 成交店铺 */} +
+ + +
+ + {/* 成交日期 */} +
+ + +
+
+ + {/* 第二列 */} +
+ {/* 收款类型 */} +
+ +
+ +
+
+ + {/* 支付平台 */} +
+ + +
+
+ + {/* 第三列 */} +
+ {/* 金额信息 */} +
+ + +
+ +
+
+ + +
+ +
+ + +
+
+
+ + {/* 第四列 */} +
+ {/* 备注 */} +
+ +