From 9ca27af2bb5743c1486871faa4cfafbe8bddabd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20da=20Concei=C3=A7=C3=A3o?= Date: Sun, 8 Mar 2026 20:50:38 -0300 Subject: [PATCH] chore: change blazor to react/vite --- UIs/Merchants/.editorconfig | 9 + UIs/Merchants/.env.example | 2 + UIs/Merchants/.gitignore | 29 + UIs/Merchants/.prettierrc | 6 + UIs/Merchants/.vscode/extensions.json | 3 + UIs/Merchants/Comanda.Merchants.WebUI.sln | 42 - UIs/Merchants/README.md | 495 ++ UIs/Merchants/Source/App.razor | 80 - .../JwtAuthenticationStateProvider.cs | 34 - .../Authentication/PrincipalProvider.cs | 33 - .../Source/Authentication/SessionManager.cs | 28 - .../Source/Comanda.Merchants.WebUI.csproj | 22 - .../Source/Components/AnalyticsView.razor | 168 - .../Dialogs/ProductCreationDialog.razor | 20 - .../Dialogs/ProductUpdateDialog.razor | 28 - .../Source/Components/KitchenBoard.razor | 164 - .../Source/Components/PaginatedView.razor | 65 - .../Source/Components/ProductCard.razor | 75 - .../Source/Components/ProductGallery.razor | 68 - .../Source/Components/ProductSearch.razor | 107 - UIs/Merchants/Source/Components/Support.razor | 22 - .../Constants/AuthenticationDefaults.cs | 7 - UIs/Merchants/Source/Constants/Headers.cs | 6 - .../Source/Constants/SidebarLabels.cs | 10 - UIs/Merchants/Source/Constants/Storage.cs | 10 - .../Source/Contracts/ILocalStorageGateway.cs | 13 - .../Source/Contracts/IPrincipalProvider.cs | 6 - .../Source/Contracts/ISessionManager.cs | 7 - .../Extensions/AuthenticationExtension.cs | 14 - .../Source/Extensions/ClientExtension.cs | 33 - .../Source/Extensions/GatewaysExtension.cs | 30 - .../Source/Extensions/ServicesExtension.cs | 11 - .../Forms/EstablishmentUpdateForm.razor | 195 - .../Forms/IntegrationCredentialsForm.razor | 157 - .../Source/Forms/OnboardingForm.razor | 120 - .../Source/Forms/ProductCreationForm.razor | 172 - .../Source/Forms/ProductUpdateForm.razor | 110 - .../Forms/Schemes/EstablishmentFormScheme.cs | 12 - .../Schemes/EstablishmentUpdateFormScheme.cs | 19 - .../IntegrationCredentialsFormScheme.cs | 9 - .../Schemes/ProductCreationFormScheme.cs | 16 - .../Forms/Schemes/ProductUpdateFormScheme.cs | 16 - .../Source/Gateways/LocalStorageGateway.cs | 38 - .../Source/Http/Clients/IdentityClient.cs | 80 - .../Interceptors/AuthenticationInterceptor.cs | 16 - .../Http/Payloads/Identity/PrincipalScheme.cs | 13 - UIs/Merchants/Source/Layout/MainLayout.razor | 31 - .../Source/Layout/NavigationBar.razor | 7 - UIs/Merchants/Source/Layout/Sidebar.razor | 57 - UIs/Merchants/Source/Pages/Home.razor | 7 - UIs/Merchants/Source/Pages/Kitchen.razor | 33 - .../Source/Pages/NotAuthorized.razor | 28 - UIs/Merchants/Source/Pages/NotFound.razor | 28 - UIs/Merchants/Source/Pages/Onboarding.razor | 70 - UIs/Merchants/Source/Pages/Products.razor | 258 - UIs/Merchants/Source/Pages/Settings.razor | 64 - UIs/Merchants/Source/Program.cs | 20 - .../Source/Properties/launchSettings.json | 25 - UIs/Merchants/Source/_Imports.razor | 28 - UIs/Merchants/Source/_Usings.cs | 37 - UIs/Merchants/Source/wwwroot/appsettings.json | 8 - UIs/Merchants/Source/wwwroot/css/app.css | 77 - UIs/Merchants/Source/wwwroot/favicon.png | Bin 1148 -> 0 bytes UIs/Merchants/Source/wwwroot/icon-192.png | Bin 2626 -> 0 bytes UIs/Merchants/Source/wwwroot/index.html | 36 - UIs/Merchants/components.json | 21 + UIs/Merchants/eslint.config.js | 37 + UIs/Merchants/index.html | 21 + UIs/Merchants/package.json | 98 + UIs/Merchants/pnpm-lock.yaml | 6288 +++++++++++++++++ UIs/Merchants/pnpm-workspace.yaml | 5 + UIs/Merchants/pr-validation.yml | 30 + UIs/Merchants/production.yml | 36 + UIs/Merchants/public/_redirects | 1 + UIs/Merchants/src/app/app.tsx | 14 + .../src/app/config/react-query/index.tsx | 29 + .../react-query/types/tanstack-query.d.ts | 11 + .../src/app/layouts/public/index.tsx | 11 + .../system/components/button-logout/index.tsx | 50 + .../system/components/header-menu/index.tsx | 11 + .../system/components/header/index.tsx | 13 + .../system/components/sidebar/index.tsx | 21 + .../system/components/sidebar/nav-items.tsx | 52 + .../system/components/sidebar/nav-links.ts | 10 + .../system/components/user-menu/index.tsx | 51 + .../src/app/layouts/system/index.tsx | 31 + UIs/Merchants/src/app/providers/index.tsx | 12 + .../src/app/routers/guards/auth-guard.tsx | 23 + .../src/app/routers/guards/guest-guard.tsx | 16 + UIs/Merchants/src/app/routers/index.tsx | 62 + .../src/app/theme/theme-provider.tsx | 18 + UIs/Merchants/src/app/theme/toggle-theme.tsx | 23 + .../components/login-form/credentials.tsx | 70 + .../auth/components/login-form/header.tsx | 20 + .../auth/components/login-form/index.tsx | 53 + .../components/login-form/schema/index.ts | 8 + .../login-form/terms-and-services.tsx | 9 + .../src/features/auth/hooks/use-login.ts | 20 + .../src/features/auth/services/index.ts | 30 + .../src/features/auth/store/index.ts | 54 + .../src/features/auth/types/index.ts | 2 + .../src/features/auth/types/models.ts | 25 + .../dashboard/components/chart/index.tsx | 288 + .../dashboard/components/stats/index.tsx | 101 + .../src/features/dashboard/pages/index.tsx | 17 + UIs/Merchants/src/index.css | 179 + UIs/Merchants/src/main.tsx | 11 + .../global/datatable/hook/usetable.test.tsx | 37 + .../global/datatable/hook/usetable.tsx | 26 + .../global/datatable/index.test.tsx | 229 + .../components/global/datatable/index.tsx | 8 + .../pieces/data-table-tabs-filter/index.tsx | 52 + .../datatable/pieces/datatable-body/index.tsx | 47 + .../pieces/datatable-content/index.tsx | 33 + .../datatable-date-range-filter/index.tsx | 92 + .../index.tsx | 39 + .../index.tsx | 61 + .../pieces/datatable-header/index.tsx | 41 + .../pieces/datatable-nested-table/index.tsx | 67 + .../index.tsx | 91 + .../pieces/datatable-select-filter/index.tsx | 65 + .../datatable-text-filter/index.test.tsx | 129 + .../pieces/datatable-text-filter/index.tsx | 55 + .../datatable/pieces/datatable/index.tsx | 28 + .../global/datatable/types/table.d.ts | 15 + .../global/datatable/utils/index.ts | 21 + .../drag-datatable/hook/use-drag-table.tsx | 0 .../global/drag-datatable/index.tsx | 6 + .../pieces/drag-handle/index.tsx | 20 + .../pieces/drag-table-body/index.tsx | 36 + .../pieces/drag-table-content/index.tsx | 31 + .../pieces/drag-table-row/index.tsx | 44 + .../pieces/drag-table/index.tsx | 49 + .../global/drag-datatable/types/index.ts | 11 + .../global/drag-datatable/utils/index.tsx | 37 + .../global/file-picker/hooks/index.test.tsx | 40 + .../global/file-picker/hooks/index.tsx | 27 + .../file-picker/hooks/use-file-picker.test.ts | 251 + .../file-picker/hooks/use-file-picker.ts | 193 + .../global/file-picker/index.test.tsx | 265 + .../components/global/file-picker/index.tsx | 12 + .../pieces/buttons/add-more/index.tsx | 20 + .../pieces/buttons/import-files/index.tsx | 19 + .../pieces/buttons/remove-all/index.tsx | 14 + .../pieces/buttons/remove-file/index.tsx | 21 + .../file-picker/pieces/content/index.tsx | 61 + .../global/file-picker/pieces/count/index.tsx | 10 + .../global/file-picker/pieces/drag/index.tsx | 32 + .../global/file-picker/pieces/empty/index.tsx | 17 + .../global/file-picker/pieces/error/index.tsx | 24 + .../file-picker/pieces/file-picker/index.tsx | 47 + .../file-picker/pieces/header/index.tsx | 13 + .../global/file-picker/pieces/input/index.tsx | 19 + .../global/file-picker/types/index.ts | 49 + .../utils/generate-unique-id/index.test.ts | 59 + .../utils/generate-unique-id/index.tsx | 9 + .../utils/get-file-icon/index.test.tsx | 120 + .../file-picker/utils/get-file-icon/index.tsx | 63 + .../utils/get-file-preview/index.tsx | 42 + .../global/file-picker/utils/index.tsx | 5 + .../utils/process-new-files/index.test.tsx | 143 + .../utils/process-new-files/index.tsx | 49 + .../utils/validate-file/index.test.tsx | 101 + .../file-picker/utils/validate-file/index.tsx | 33 + .../global/floating-navigation/index.tsx | 64 + .../global/image-card/index.test.tsx | 120 + .../components/global/image-card/index.tsx | 73 + .../components/global/input-builder/index.tsx | 45 + .../inputs/input-installment.tsx | 27 + .../input-builder/inputs/input-money.tsx | 56 + .../input-builder/inputs/input-month.tsx | 20 + .../input-builder/inputs/input-percent.tsx | 38 + .../input-builder/inputs/input-year.tsx | 20 + .../shared/components/global/logo/index.tsx | 17 + .../components/global/page-header/index.tsx | 16 + .../global/placeholder-page/index.test.tsx | 40 + .../global/placeholder-page/index.tsx | 32 + .../global/restricted-link/index.tsx | 12 + .../global/simple-data-table/index.test.tsx | 75 + .../global/simple-data-table/index.tsx | 79 + .../global/submiting-button/index.tsx | 31 + .../components/global/wizard/hooks/index.tsx | 187 + .../components/global/wizard/index.test.tsx | 303 + .../shared/components/global/wizard/index.tsx | 6 + .../wizard/pieces/wizard-content/index.tsx | 17 + .../wizard/pieces/wizard-controls/index.tsx | 78 + .../wizard/pieces/wizard-header/index.tsx | 34 + .../global/wizard/pieces/wizard/index.tsx | 6 + .../components/global/wizard/types/index.ts | 38 + .../src/shared/components/ui/accordion.tsx | 64 + .../src/shared/components/ui/alert-dialog.tsx | 194 + .../src/shared/components/ui/alert.tsx | 66 + .../src/shared/components/ui/avatar.tsx | 51 + .../src/shared/components/ui/badge.tsx | 46 + .../src/shared/components/ui/breadcrumb.tsx | 109 + .../src/shared/components/ui/button-group.tsx | 83 + .../src/shared/components/ui/button.tsx | 60 + .../src/shared/components/ui/calendar.tsx | 214 + .../src/shared/components/ui/card.tsx | 92 + .../src/shared/components/ui/carousel.tsx | 239 + .../src/shared/components/ui/chart.tsx | 357 + .../src/shared/components/ui/checkbox.tsx | 30 + .../src/shared/components/ui/command.tsx | 182 + .../src/shared/components/ui/dialog.tsx | 143 + .../src/shared/components/ui/drawer.tsx | 133 + .../shared/components/ui/dropdown-menu.tsx | 255 + .../src/shared/components/ui/form.tsx | 165 + .../shared/components/ui/input-with-sufix.tsx | 34 + .../src/shared/components/ui/input.tsx | 21 + .../src/shared/components/ui/label.tsx | 24 + .../src/shared/components/ui/popover.tsx | 46 + .../src/shared/components/ui/progress.tsx | 29 + .../src/shared/components/ui/radio-group.tsx | 45 + .../src/shared/components/ui/scroll-area.tsx | 56 + .../src/shared/components/ui/select.tsx | 185 + .../src/shared/components/ui/separator.tsx | 26 + .../src/shared/components/ui/sheet.tsx | 137 + .../src/shared/components/ui/sidebar.tsx | 729 ++ .../src/shared/components/ui/skeleton.tsx | 13 + .../src/shared/components/ui/slider.tsx | 61 + .../src/shared/components/ui/sonner.tsx | 38 + .../src/shared/components/ui/switch.tsx | 29 + .../src/shared/components/ui/table.tsx | 114 + .../src/shared/components/ui/tabs.tsx | 64 + .../src/shared/components/ui/textarea.tsx | 18 + .../src/shared/components/ui/toggle-group.tsx | 81 + .../src/shared/components/ui/toggle.tsx | 45 + .../src/shared/components/ui/tooltip.tsx | 59 + .../shared/hooks/use-dropzone/index.test.ts | 151 + .../src/shared/hooks/use-dropzone/index.ts | 94 + .../hooks/use-form-draft/index.test.tsx | 191 + .../src/shared/hooks/use-form-draft/index.tsx | 68 + .../src/shared/hooks/use-mobile/index.ts | 20 + .../src/shared/services/form-draft/index.ts | 16 + .../services/local-storage/index.test.ts | 166 + .../shared/services/local-storage/index.ts | 55 + .../src/shared/types/paginated-response.tsx | 7 + UIs/Merchants/src/shared/utils/cn.ts | 6 + UIs/Merchants/src/shared/utils/debounce.ts | 10 + UIs/Merchants/src/shared/utils/decode-jwt.ts | 22 + .../src/shared/utils/format-bytes.ts | 12 + .../src/shared/utils/get-initials.ts | 10 + UIs/Merchants/src/shared/utils/index.ts | 11 + .../src/shared/utils/map-group-name.ts | 9 + .../src/shared/utils/masks/mask-decimal.ts | 21 + .../src/shared/utils/masks/mask-money.ts | 9 + .../shared/utils/masks/mask-pascal-case.ts | 5 + .../src/shared/utils/masks/mask-percentage.ts | 12 + .../src/shared/utils/masks/mask-phone.ts | 9 + UIs/Merchants/src/test/setup.ts | 15 + UIs/Merchants/tsconfig.app.json | 32 + UIs/Merchants/tsconfig.json | 13 + UIs/Merchants/tsconfig.node.json | 26 + UIs/Merchants/vercel.json | 13 + UIs/Merchants/vite.config.ts | 21 + 255 files changed, 18349 insertions(+), 2890 deletions(-) create mode 100644 UIs/Merchants/.editorconfig create mode 100644 UIs/Merchants/.env.example create mode 100644 UIs/Merchants/.gitignore create mode 100644 UIs/Merchants/.prettierrc create mode 100644 UIs/Merchants/.vscode/extensions.json delete mode 100644 UIs/Merchants/Comanda.Merchants.WebUI.sln create mode 100644 UIs/Merchants/README.md delete mode 100644 UIs/Merchants/Source/App.razor delete mode 100644 UIs/Merchants/Source/Authentication/JwtAuthenticationStateProvider.cs delete mode 100644 UIs/Merchants/Source/Authentication/PrincipalProvider.cs delete mode 100644 UIs/Merchants/Source/Authentication/SessionManager.cs delete mode 100644 UIs/Merchants/Source/Comanda.Merchants.WebUI.csproj delete mode 100644 UIs/Merchants/Source/Components/AnalyticsView.razor delete mode 100644 UIs/Merchants/Source/Components/Dialogs/ProductCreationDialog.razor delete mode 100644 UIs/Merchants/Source/Components/Dialogs/ProductUpdateDialog.razor delete mode 100644 UIs/Merchants/Source/Components/KitchenBoard.razor delete mode 100644 UIs/Merchants/Source/Components/PaginatedView.razor delete mode 100644 UIs/Merchants/Source/Components/ProductCard.razor delete mode 100644 UIs/Merchants/Source/Components/ProductGallery.razor delete mode 100644 UIs/Merchants/Source/Components/ProductSearch.razor delete mode 100644 UIs/Merchants/Source/Components/Support.razor delete mode 100644 UIs/Merchants/Source/Constants/AuthenticationDefaults.cs delete mode 100644 UIs/Merchants/Source/Constants/Headers.cs delete mode 100644 UIs/Merchants/Source/Constants/SidebarLabels.cs delete mode 100644 UIs/Merchants/Source/Constants/Storage.cs delete mode 100644 UIs/Merchants/Source/Contracts/ILocalStorageGateway.cs delete mode 100644 UIs/Merchants/Source/Contracts/IPrincipalProvider.cs delete mode 100644 UIs/Merchants/Source/Contracts/ISessionManager.cs delete mode 100644 UIs/Merchants/Source/Extensions/AuthenticationExtension.cs delete mode 100644 UIs/Merchants/Source/Extensions/ClientExtension.cs delete mode 100644 UIs/Merchants/Source/Extensions/GatewaysExtension.cs delete mode 100644 UIs/Merchants/Source/Extensions/ServicesExtension.cs delete mode 100644 UIs/Merchants/Source/Forms/EstablishmentUpdateForm.razor delete mode 100644 UIs/Merchants/Source/Forms/IntegrationCredentialsForm.razor delete mode 100644 UIs/Merchants/Source/Forms/OnboardingForm.razor delete mode 100644 UIs/Merchants/Source/Forms/ProductCreationForm.razor delete mode 100644 UIs/Merchants/Source/Forms/ProductUpdateForm.razor delete mode 100644 UIs/Merchants/Source/Forms/Schemes/EstablishmentFormScheme.cs delete mode 100644 UIs/Merchants/Source/Forms/Schemes/EstablishmentUpdateFormScheme.cs delete mode 100644 UIs/Merchants/Source/Forms/Schemes/IntegrationCredentialsFormScheme.cs delete mode 100644 UIs/Merchants/Source/Forms/Schemes/ProductCreationFormScheme.cs delete mode 100644 UIs/Merchants/Source/Forms/Schemes/ProductUpdateFormScheme.cs delete mode 100644 UIs/Merchants/Source/Gateways/LocalStorageGateway.cs delete mode 100644 UIs/Merchants/Source/Http/Clients/IdentityClient.cs delete mode 100644 UIs/Merchants/Source/Http/Interceptors/AuthenticationInterceptor.cs delete mode 100644 UIs/Merchants/Source/Http/Payloads/Identity/PrincipalScheme.cs delete mode 100644 UIs/Merchants/Source/Layout/MainLayout.razor delete mode 100644 UIs/Merchants/Source/Layout/NavigationBar.razor delete mode 100644 UIs/Merchants/Source/Layout/Sidebar.razor delete mode 100644 UIs/Merchants/Source/Pages/Home.razor delete mode 100644 UIs/Merchants/Source/Pages/Kitchen.razor delete mode 100644 UIs/Merchants/Source/Pages/NotAuthorized.razor delete mode 100644 UIs/Merchants/Source/Pages/NotFound.razor delete mode 100644 UIs/Merchants/Source/Pages/Onboarding.razor delete mode 100644 UIs/Merchants/Source/Pages/Products.razor delete mode 100644 UIs/Merchants/Source/Pages/Settings.razor delete mode 100644 UIs/Merchants/Source/Program.cs delete mode 100644 UIs/Merchants/Source/Properties/launchSettings.json delete mode 100644 UIs/Merchants/Source/_Imports.razor delete mode 100644 UIs/Merchants/Source/_Usings.cs delete mode 100644 UIs/Merchants/Source/wwwroot/appsettings.json delete mode 100644 UIs/Merchants/Source/wwwroot/css/app.css delete mode 100644 UIs/Merchants/Source/wwwroot/favicon.png delete mode 100644 UIs/Merchants/Source/wwwroot/icon-192.png delete mode 100644 UIs/Merchants/Source/wwwroot/index.html create mode 100644 UIs/Merchants/components.json create mode 100644 UIs/Merchants/eslint.config.js create mode 100644 UIs/Merchants/index.html create mode 100644 UIs/Merchants/package.json create mode 100644 UIs/Merchants/pnpm-lock.yaml create mode 100644 UIs/Merchants/pnpm-workspace.yaml create mode 100644 UIs/Merchants/pr-validation.yml create mode 100644 UIs/Merchants/production.yml create mode 100644 UIs/Merchants/public/_redirects create mode 100644 UIs/Merchants/src/app/app.tsx create mode 100644 UIs/Merchants/src/app/config/react-query/index.tsx create mode 100644 UIs/Merchants/src/app/config/react-query/types/tanstack-query.d.ts create mode 100644 UIs/Merchants/src/app/layouts/public/index.tsx create mode 100644 UIs/Merchants/src/app/layouts/system/components/button-logout/index.tsx create mode 100644 UIs/Merchants/src/app/layouts/system/components/header-menu/index.tsx create mode 100644 UIs/Merchants/src/app/layouts/system/components/header/index.tsx create mode 100644 UIs/Merchants/src/app/layouts/system/components/sidebar/index.tsx create mode 100644 UIs/Merchants/src/app/layouts/system/components/sidebar/nav-items.tsx create mode 100644 UIs/Merchants/src/app/layouts/system/components/sidebar/nav-links.ts create mode 100644 UIs/Merchants/src/app/layouts/system/components/user-menu/index.tsx create mode 100644 UIs/Merchants/src/app/layouts/system/index.tsx create mode 100644 UIs/Merchants/src/app/providers/index.tsx create mode 100644 UIs/Merchants/src/app/routers/guards/auth-guard.tsx create mode 100644 UIs/Merchants/src/app/routers/guards/guest-guard.tsx create mode 100644 UIs/Merchants/src/app/routers/index.tsx create mode 100644 UIs/Merchants/src/app/theme/theme-provider.tsx create mode 100644 UIs/Merchants/src/app/theme/toggle-theme.tsx create mode 100644 UIs/Merchants/src/features/auth/components/login-form/credentials.tsx create mode 100644 UIs/Merchants/src/features/auth/components/login-form/header.tsx create mode 100644 UIs/Merchants/src/features/auth/components/login-form/index.tsx create mode 100644 UIs/Merchants/src/features/auth/components/login-form/schema/index.ts create mode 100644 UIs/Merchants/src/features/auth/components/login-form/terms-and-services.tsx create mode 100644 UIs/Merchants/src/features/auth/hooks/use-login.ts create mode 100644 UIs/Merchants/src/features/auth/services/index.ts create mode 100644 UIs/Merchants/src/features/auth/store/index.ts create mode 100644 UIs/Merchants/src/features/auth/types/index.ts create mode 100644 UIs/Merchants/src/features/auth/types/models.ts create mode 100644 UIs/Merchants/src/features/dashboard/components/chart/index.tsx create mode 100644 UIs/Merchants/src/features/dashboard/components/stats/index.tsx create mode 100644 UIs/Merchants/src/features/dashboard/pages/index.tsx create mode 100644 UIs/Merchants/src/index.css create mode 100644 UIs/Merchants/src/main.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/hook/usetable.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/hook/usetable.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/data-table-tabs-filter/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-body/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-content/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-date-range-filter/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-dropdown-column-visibility/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header-sortable-column/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-nested-table/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-pagination-controllers/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-select-filter/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/pieces/datatable/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/datatable/types/table.d.ts create mode 100644 UIs/Merchants/src/shared/components/global/datatable/utils/index.ts create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/hook/use-drag-table.tsx create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-handle/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-body/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-content/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-row/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/types/index.ts create mode 100644 UIs/Merchants/src/shared/components/global/drag-datatable/utils/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/hooks/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/hooks/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.test.ts create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.ts create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/add-more/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/import-files/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-all/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-file/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/content/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/count/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/drag/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/empty/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/error/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/file-picker/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/header/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/pieces/input/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/types/index.ts create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.test.ts create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-preview/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/floating-navigation/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/image-card/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/image-card/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/input-builder/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/input-builder/inputs/input-installment.tsx create mode 100644 UIs/Merchants/src/shared/components/global/input-builder/inputs/input-money.tsx create mode 100644 UIs/Merchants/src/shared/components/global/input-builder/inputs/input-month.tsx create mode 100644 UIs/Merchants/src/shared/components/global/input-builder/inputs/input-percent.tsx create mode 100644 UIs/Merchants/src/shared/components/global/input-builder/inputs/input-year.tsx create mode 100644 UIs/Merchants/src/shared/components/global/logo/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/page-header/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/placeholder-page/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/placeholder-page/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/restricted-link/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/simple-data-table/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/simple-data-table/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/submiting-button/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/hooks/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/index.test.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-content/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-controls/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-header/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/pieces/wizard/index.tsx create mode 100644 UIs/Merchants/src/shared/components/global/wizard/types/index.ts create mode 100644 UIs/Merchants/src/shared/components/ui/accordion.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/alert-dialog.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/alert.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/avatar.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/badge.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/breadcrumb.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/button-group.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/button.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/calendar.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/card.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/carousel.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/chart.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/checkbox.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/command.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/dialog.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/drawer.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/dropdown-menu.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/form.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/input-with-sufix.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/input.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/label.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/popover.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/progress.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/radio-group.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/scroll-area.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/select.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/separator.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/sheet.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/sidebar.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/skeleton.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/slider.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/sonner.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/switch.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/table.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/tabs.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/textarea.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/toggle-group.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/toggle.tsx create mode 100644 UIs/Merchants/src/shared/components/ui/tooltip.tsx create mode 100644 UIs/Merchants/src/shared/hooks/use-dropzone/index.test.ts create mode 100644 UIs/Merchants/src/shared/hooks/use-dropzone/index.ts create mode 100644 UIs/Merchants/src/shared/hooks/use-form-draft/index.test.tsx create mode 100644 UIs/Merchants/src/shared/hooks/use-form-draft/index.tsx create mode 100644 UIs/Merchants/src/shared/hooks/use-mobile/index.ts create mode 100644 UIs/Merchants/src/shared/services/form-draft/index.ts create mode 100644 UIs/Merchants/src/shared/services/local-storage/index.test.ts create mode 100644 UIs/Merchants/src/shared/services/local-storage/index.ts create mode 100644 UIs/Merchants/src/shared/types/paginated-response.tsx create mode 100644 UIs/Merchants/src/shared/utils/cn.ts create mode 100644 UIs/Merchants/src/shared/utils/debounce.ts create mode 100644 UIs/Merchants/src/shared/utils/decode-jwt.ts create mode 100644 UIs/Merchants/src/shared/utils/format-bytes.ts create mode 100644 UIs/Merchants/src/shared/utils/get-initials.ts create mode 100644 UIs/Merchants/src/shared/utils/index.ts create mode 100644 UIs/Merchants/src/shared/utils/map-group-name.ts create mode 100644 UIs/Merchants/src/shared/utils/masks/mask-decimal.ts create mode 100644 UIs/Merchants/src/shared/utils/masks/mask-money.ts create mode 100644 UIs/Merchants/src/shared/utils/masks/mask-pascal-case.ts create mode 100644 UIs/Merchants/src/shared/utils/masks/mask-percentage.ts create mode 100644 UIs/Merchants/src/shared/utils/masks/mask-phone.ts create mode 100644 UIs/Merchants/src/test/setup.ts create mode 100644 UIs/Merchants/tsconfig.app.json create mode 100644 UIs/Merchants/tsconfig.json create mode 100644 UIs/Merchants/tsconfig.node.json create mode 100644 UIs/Merchants/vercel.json create mode 100644 UIs/Merchants/vite.config.ts diff --git a/UIs/Merchants/.editorconfig b/UIs/Merchants/.editorconfig new file mode 100644 index 0000000..a3ad0dc --- /dev/null +++ b/UIs/Merchants/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false \ No newline at end of file diff --git a/UIs/Merchants/.env.example b/UIs/Merchants/.env.example new file mode 100644 index 0000000..3999104 --- /dev/null +++ b/UIs/Merchants/.env.example @@ -0,0 +1,2 @@ +VITE_AUTH_API +VITE_GATEWAY_API \ No newline at end of file diff --git a/UIs/Merchants/.gitignore b/UIs/Merchants/.gitignore new file mode 100644 index 0000000..cd568f3 --- /dev/null +++ b/UIs/Merchants/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env + +coverage/ +scripts/ \ No newline at end of file diff --git a/UIs/Merchants/.prettierrc b/UIs/Merchants/.prettierrc new file mode 100644 index 0000000..098ec71 --- /dev/null +++ b/UIs/Merchants/.prettierrc @@ -0,0 +1,6 @@ +{ + "trailingComma": "none", + "semi": true, + "singleQuote": true, + "bracketSpacing": true +} \ No newline at end of file diff --git a/UIs/Merchants/.vscode/extensions.json b/UIs/Merchants/.vscode/extensions.json new file mode 100644 index 0000000..74baffc --- /dev/null +++ b/UIs/Merchants/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["denoland.vscode-deno"] +} diff --git a/UIs/Merchants/Comanda.Merchants.WebUI.sln b/UIs/Merchants/Comanda.Merchants.WebUI.sln deleted file mode 100644 index b1d536c..0000000 --- a/UIs/Merchants/Comanda.Merchants.WebUI.sln +++ /dev/null @@ -1,42 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.3.11312.210 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{B8EFCA5F-814F-285C-A8CB-F00F14650265}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Comanda.Merchants.WebUI", "Source\Comanda.Merchants.WebUI.csproj", "{C835E518-C45B-4575-93EC-78103FD20F9B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {C835E518-C45B-4575-93EC-78103FD20F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Debug|x64.ActiveCfg = Debug|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Debug|x64.Build.0 = Debug|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Debug|x86.ActiveCfg = Debug|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Debug|x86.Build.0 = Debug|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Release|Any CPU.Build.0 = Release|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Release|x64.ActiveCfg = Release|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Release|x64.Build.0 = Release|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Release|x86.ActiveCfg = Release|Any CPU - {C835E518-C45B-4575-93EC-78103FD20F9B}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C835E518-C45B-4575-93EC-78103FD20F9B} = {B8EFCA5F-814F-285C-A8CB-F00F14650265} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B38CFDDE-933A-4367-B105-C867F91B05BD} - EndGlobalSection -EndGlobal diff --git a/UIs/Merchants/README.md b/UIs/Merchants/README.md new file mode 100644 index 0000000..37fa08e --- /dev/null +++ b/UIs/Merchants/README.md @@ -0,0 +1,495 @@ +# Nome da aplicação UI + +**Propósito da aplicação** + +O projeto segue princípios de **Feature-Sliced Design** e **Clean Architecture**, com forte tipagem e isolamento entre UI, domínio e infraestrutura. + +--- + +# 📌 Visão Geral da Arquitetura + +A aplicação separa responsabilidades em camadas bem definidas. + +``` +React Components + ↓ +Feature Hooks + ↓ +Feature Services + ↓ +Infra Layer (API / SSE / WebSocket) + ↓ +Backend +``` + +### Responsabilidades + +| Camada | Responsabilidade | +| ---------- | ----------------------------------- | +| Components | UI e interação com o utilizador | +| Hooks | Orquestração de dados e lógica | +| Services | Comunicação com backend | +| Mappers | Conversão DTO ⇄ Model | +| Infra | Clientes HTTP e comunicação externa | + +Essa separação garante: + +* isolamento de regras de negócio +* proteção da UI contra mudanças da API +* código previsível e escalável + +--- + +# 🚀 Stack Tecnológica + +### Core + +* React 19 +* React Router 7 +* Vite (SWC) + +### Estado + +| Tipo de estado | Tecnologia | +| --------------- | -------------- | +| Server State | TanStack Query | +| Global UI State | Zustand | +| Local State | React Hooks | + +### UI + +* Tailwind CSS +* Radix UI +* Lucide Icons + +### Formulários + +* React Hook Form +* Zod + +### Tempo real + +* SignalR +* Server Sent Events (SSE) + +### Testes + +* Vitest +* React Testing Library + +--- + +# 📂 Estrutura do Projeto + +``` +src +├── app +│ ├── layouts +│ ├── routers +│ └── theme +│ +├── assets +│ ├── images +│ ├── icons +│ └── styles +│ +├── features +│ ├── operators +│ ├── partners +│ └── ... +│ +├── shared +│ ├── components +│ │ ├── ui +│ │ └── common +│ ├── hooks +│ ├── services +│ └── utils +│ +├── infra +│ ├── api +│ └── sse +``` + +### Descrição + +| Diretório | Função | +| ------------------------ | ---------------------------------------------- | +| app/layouts | estruturas de página | +| app/routers | rotas e guards | +| app/theme | configuração de tema | +| assets | recursos estáticos (imagens, ícones e estilos) | +| features | módulos de domínio da aplicação | +| shared/components/ui | componentes visuais primitivos | +| shared/components/common | componentes reutilizáveis com lógica de UI | +| shared/hooks | hooks reutilizáveis | +| shared/services | serviços compartilhados | +| shared/utils | utilitários globais | +| infra/api | clientes HTTP | +| infra/sse | comunicação em tempo real | + +--- + +# 🧩 Estrutura de uma Feature + +Cada feature encapsula completamente o seu domínio. + +Exemplo: + +``` +features/operators +│ +├── components +│ +├── hooks +│ ├── use-operators.ts +│ └── use-create-operator.ts +│ +├── services +│ ├── index.ts +│ ├── mapper.ts +│ └── error-handler.ts +│ +├── types +│ ├── operator.dto.ts +│ └── operator.model.ts +``` + +### Responsabilidades + +| Arquivo | Função | +| ---------------------- | -------------------------- | +| services/index | chamadas HTTP | +| services/mapper | conversão DTO ⇄ Model | +| services/error-handler | tratamento de erros | +| hooks | integração com React Query | +| components | UI da feature | + +--- + +# 🔄 DTO vs Model + +A UI **nunca consome diretamente os DTOs da API**. + +### DTO + +Representa o formato da API. + +``` +OperatorDTO +``` + +### Model + +Representa o modelo usado pela UI. + +``` +Operator +``` + +### Conversão + +``` +DTO → mapper → Model +Model → mapper → DTO +``` + +Isso protege a aplicação contra mudanças no backend. + +--- + +# 🔁 Fluxo de Dados + +## Leitura (Queries) + +``` +API + ↓ +Service + ↓ +Mapper + ↓ +React Query + ↓ +Hook + ↓ +Component +``` + +## Escrita (Mutations) + +``` +Component + ↓ +Hook + ↓ +Service + ↓ +Mapper + ↓ +API +``` + +--- + +# 🧠 Gestão de Estado + +A aplicação segue regras claras: + +### 1️⃣ Server State + +TanStack Query + +Usado para: + +* listas +* detalhes +* dados vindos da API + +### 2️⃣ Estado Global + +Zustand + +Usado para: + +* token de autenticação +* tema +* estado do menu + +### 3️⃣ Estado Local + +React Hooks + +Usado para: + +* UI +* dropdowns +* inputs +* interações locais + +--- + +# 📡 Comunicação em Tempo Real + +Algumas áreas da aplicação utilizam atualizações passivas. + +Tecnologias usadas: + +* SignalR +* Server Sent Events (SSE) + +Esses clientes atualizam diretamente o **cache do React Query**, evitando polling. + +--- + +# 🧩 Componentes + +Os componentes são divididos em três níveis. + +## UI Components + +``` +shared/components/ui +``` + +Componentes visuais baseados em Radix. + +Regras: + +* sem lógica de negócio +* apenas props e eventos + +--- + +## Shared Components + +``` +shared/components/common +``` + +Componentes reutilizáveis com lógica interna de UI. + +Exemplos: + +* Datatable +* File Picker +* Wizard + +--- + +## Feature Components + +``` +features/.../components +``` + +Componentes específicos do domínio. + +Regras: + +* não acessam API diretamente +* recebem dados via hooks + +--- + +# 🔐 Roteamento e Guards + +O projeto possui dois tipos de proteção. + +### Auth Guard + +Protege rotas privadas. + +Se o utilizador não estiver autenticado: + +``` +redirect → /login +``` + +--- + +### Guest Guard + +Impede utilizadores autenticados de acessar páginas públicas. + +``` +/login → redirect → /dashboard +``` + +--- + +# 🚨 Tratamento de Erros + +Erros são tratados em três níveis. + +### Validação + +Zod + +### Domínio + +`error-handler.ts` da feature. + +### Infraestrutura + +Interceptores HTTP. + +O feedback ao utilizador é exibido via **toasts**. + +--- + +# 🧪 Testes + +Ferramentas usadas: + +* Vitest +* React Testing Library + +Testamos principalmente: + +* utilitários +* hooks +* interações de UI + +--- + +# 🚀 Começando + +### Pré-requisitos + +* Node 20+ +* pnpm + +### Instalação + +``` +pnpm install +``` + +### Configuração + +``` +cp .env .env.local +``` + +Preencher: + +``` +VITE_API_GATEWAY_URL +VITE_AUTH_API_URL +``` + +--- + +# ▶ Executar o projeto + +``` +pnpm dev +``` + +Servidor de desenvolvimento: + +``` +http://localhost:5173 +``` + +--- + +# 🛠 Scripts + +| Script | Função | +| ------------------ | --------------------------- | +| pnpm dev | ambiente de desenvolvimento | +| pnpm build | build de produção | +| pnpm preview | preview da build | +| pnpm lint | lint do projeto | +| pnpm test | executar testes | +| pnpm test:coverage | relatório de coverage | + +--- + +# 📏 Convenções do Projeto + +### Arquivos + +``` +kebab-case +``` + +### Componentes + +``` +PascalCase +``` + +### Sufixos obrigatórios + +``` +*.dto.ts +*.model.ts +*.test.ts +*.mapper.ts +``` + +--- + +# 📚 Regras Arquiteturais + +1️⃣ UI nunca consome DTO diretamente +2️⃣ chamadas HTTP apenas em services +3️⃣ hooks são responsáveis por React Query +4️⃣ componentes não fazem side-effects +5️⃣ lógica de domínio nunca fica em componentes + +--- + +# 🤝 Contribuindo + +Ao criar uma nova feature: + +1. criar pasta em `features/` +2. definir DTOs e Models +3. criar `services` +4. criar `hooks` +5. criar `components` +6. registrar rotas diff --git a/UIs/Merchants/Source/App.razor b/UIs/Merchants/Source/App.razor deleted file mode 100644 index 1e57ce8..0000000 --- a/UIs/Merchants/Source/App.razor +++ /dev/null @@ -1,80 +0,0 @@ -@inject IProfilesClient profilesClient -@inject IPrincipalProvider principalProvider -@inject IStoreClient storeClient -@inject ISessionManager sessionManager -@inject IIdentityClient identityClient -@inject ILocalStorageGateway localStorage -@inject AuthenticationStateProvider authenticationStateProvider -@inject NavigationManager navigationManager - - - - - - - - - - - - - - -@code { - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - await InitializeMockSessionAsync(); - await SetPrincipalAsync(); - - var session = await authenticationStateProvider.GetAuthenticationStateAsync(); - var principal = session.User; - - if (principal?.Identity?.IsAuthenticated is true) - { - var identifier = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; - var owners = await profilesClient.GetOwnersAsync(new() { UserId = identifier! }); - - var owner = owners?.Data?.Items.FirstOrDefault(); - if (owner is null) - return; - - var establishments = await storeClient.GetEstablishmentsAsync(new() { OwnerId = owner.Identifier }); - var establishment = establishments?.Data?.Items.FirstOrDefault(); - - if (establishment is null) - { - navigationManager.NavigateTo("/onboarding"); - return; - } - - await localStorage.SetAsync(Storage.Establishment, establishment); - await localStorage.SetAsync(Storage.Merchant, owner); - } - - } - - private async Task SetPrincipalAsync() - { - var principal = await principalProvider.GetPrincipalAsync(); - if (principal.IsFailure || principal.Data is null) - return; - - await localStorage.SetAsync(Storage.Principal, principal.Data); - } - - private async Task InitializeMockSessionAsync() - { - var authentication = await identityClient.AuthenticateAsync(new() - { - Username = "jane.doe@email.com", - Password = "password" - }); - - if (authentication.IsFailure || authentication.Data is null) - return; - - await sessionManager.SignInAsync(authentication.Data.AccessToken, authentication.Data.RefreshToken); - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Authentication/JwtAuthenticationStateProvider.cs b/UIs/Merchants/Source/Authentication/JwtAuthenticationStateProvider.cs deleted file mode 100644 index 1998d91..0000000 --- a/UIs/Merchants/Source/Authentication/JwtAuthenticationStateProvider.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace Comanda.Merchants.WebUI.Authentication; - -public sealed class JwtAuthenticationStateProvider(ILocalStorageGateway localStorage) : - AuthenticationStateProvider -{ - private readonly JwtSecurityTokenHandler tokenHandler = new(); - private readonly ClaimsPrincipal anonymous = - new(new ClaimsIdentity()); - - public override async Task GetAuthenticationStateAsync() - { - var tokenString = await localStorage.GetAsStringAsync(Storage.SecurityToken); - if (string.IsNullOrWhiteSpace(tokenString)) - { - return new AuthenticationState(anonymous); - } - - var token = tokenHandler.ReadJwtToken(tokenString); - if (token.ValidTo < DateTime.UtcNow) - { - return new AuthenticationState(anonymous); - } - - var identity = new ClaimsIdentity(token.Claims, "https://www.rfc-editor.org/rfc/rfc7519", nameType: "preferred_username", roleType: "role"); - var principal = new ClaimsPrincipal(identity); - - return new AuthenticationState(principal); - } - - public void NotifyAuthenticationStateChanged() - { - NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); - } -} diff --git a/UIs/Merchants/Source/Authentication/PrincipalProvider.cs b/UIs/Merchants/Source/Authentication/PrincipalProvider.cs deleted file mode 100644 index febe92d..0000000 --- a/UIs/Merchants/Source/Authentication/PrincipalProvider.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Comanda.Merchants.WebUI.Authentication; - -public sealed class PrincipalProvider(HttpClient httpClient) : IPrincipalProvider -{ - private readonly JsonSerializerOptions serializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true - }; - - public async Task> GetPrincipalAsync(CancellationToken cancellation = default) - { - var response = await httpClient.GetAsync("/api/v1/identity/principal", cancellation); - var content = await response.Content.ReadAsStringAsync(cancellation); - - // we prefer explicit boolean comparisons for readability - // https://rules.sonarsource.com/csharp/RSPEC-1125/ - - #pragma warning disable S1125 - if (response.IsSuccessStatusCode is false) - { - return Result.Failure(UserErrors.UserDoesNotExist); - } - - var result = JsonSerializer.Deserialize(content, serializerOptions); - if (result is null) - { - return Result.Failure(UserErrors.UserDoesNotExist); - } - - return Result.Success(result); - } -} diff --git a/UIs/Merchants/Source/Authentication/SessionManager.cs b/UIs/Merchants/Source/Authentication/SessionManager.cs deleted file mode 100644 index 531d4a5..0000000 --- a/UIs/Merchants/Source/Authentication/SessionManager.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Comanda.Merchants.WebUI.Authentication; - -public sealed class SessionManager(ILocalStorageGateway localStorage, IIdentityClient identityClient, AuthenticationStateProvider provider) : ISessionManager -{ - public async Task SignInAsync(string token, string refreshToken) - { - await localStorage.SetAsStringAsync(Storage.SecurityToken, token); - await localStorage.SetAsStringAsync(Storage.RefreshToken, refreshToken); - - // inform provider of signin because blazor does not automatically detect localstorage updates - if (provider is JwtAuthenticationStateProvider authenticationState) - authenticationState.NotifyAuthenticationStateChanged(); - } - - public async Task SignOutAsync() - { - var refreshToken = await localStorage.GetAsStringAsync(Storage.RefreshToken); - if (string.IsNullOrWhiteSpace(refreshToken)) - return; - - await localStorage.RemoveAsync(Storage.SecurityToken); - await identityClient.InvalidateSessionAsync(new() { RefreshToken = refreshToken }); - - // notify provider of logout because blazor does not detect localstorage changes automatically - if (provider is JwtAuthenticationStateProvider authenticationState) - authenticationState.NotifyAuthenticationStateChanged(); - } -} diff --git a/UIs/Merchants/Source/Comanda.Merchants.WebUI.csproj b/UIs/Merchants/Source/Comanda.Merchants.WebUI.csproj deleted file mode 100644 index 7e3d477..0000000 --- a/UIs/Merchants/Source/Comanda.Merchants.WebUI.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net10.0 - enable - enable - true - - - - - - - - - - - - - - - diff --git a/UIs/Merchants/Source/Components/AnalyticsView.razor b/UIs/Merchants/Source/Components/AnalyticsView.razor deleted file mode 100644 index c988663..0000000 --- a/UIs/Merchants/Source/Components/AnalyticsView.razor +++ /dev/null @@ -1,168 +0,0 @@ - - - - -
- - - -
- - Vendas Hoje - - - R$ 2.847,50 - -
-
- - - -
- - - -
- - Pedidos Hoje - - - 42 - -
-
- - - -
- - - -
- - Pedidos Pendentes - - - 7 - -
- - - Aguardando - -
-
-
- - - -
- - - -
- - Ticket Médio - - - R$ 67,80 - -
-
-
- - -
-
- - Resumo dos Últimos 7 Dias - - - Atualizado @DateTime.Now.ToString("HH:mm") - -
- - Atualizar - -
- - - -
- -
- - Total Pedidos - - - 287 - -
-
-
- - -
- -
- - Receita Total - - - R$ 19.458,90 - -
-
-
- - -
- -
- - Média Diária - - - R$ 2.779,84 - -
-
-
- - -
- -
- - Crescimento - - - +15.8% - -
-
-
-
-
-
\ No newline at end of file diff --git a/UIs/Merchants/Source/Components/Dialogs/ProductCreationDialog.razor b/UIs/Merchants/Source/Components/Dialogs/ProductCreationDialog.razor deleted file mode 100644 index a2be2e4..0000000 --- a/UIs/Merchants/Source/Components/Dialogs/ProductCreationDialog.razor +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - -@code { - [CascadingParameter] - IMudDialogInstance MudDialog { get; set; } = null!; - - private void HandleSuccess() - { - MudDialog.Close(DialogResult.Ok(true)); - } - - private void HandleCancel() - { - MudDialog.Cancel(); - } -} diff --git a/UIs/Merchants/Source/Components/Dialogs/ProductUpdateDialog.razor b/UIs/Merchants/Source/Components/Dialogs/ProductUpdateDialog.razor deleted file mode 100644 index 05f5fcd..0000000 --- a/UIs/Merchants/Source/Components/Dialogs/ProductUpdateDialog.razor +++ /dev/null @@ -1,28 +0,0 @@ -@inject IDialogService DialogService - - - - - - - -@code { - [CascadingParameter] - IMudDialogInstance MudDialog { get; set; } = default!; - - [Parameter] - public ProductScheme? Product { get; set; } - - [Parameter] - public string ProductId { get; set; } = string.Empty; - - private void HandleSuccess() - { - MudDialog.Close(DialogResult.Ok(true)); - } - - private void HandleCancel() - { - MudDialog.Cancel(); - } -} diff --git a/UIs/Merchants/Source/Components/KitchenBoard.razor b/UIs/Merchants/Source/Components/KitchenBoard.razor deleted file mode 100644 index 0c5ba20..0000000 --- a/UIs/Merchants/Source/Components/KitchenBoard.razor +++ /dev/null @@ -1,164 +0,0 @@ - - - - - -
-
- - - - - Confirmado - -
- - @_allOrders.Count(x => x.Status == "Confirmed") - -
- -
-
- - - -
-
- - - - - Em Preparação - -
- - @_allOrders.Count(x => x.Status == "InPreparation") - -
- -
-
- - - -
-
- - - - - Pronto - -
- - @_allOrders.Count(x => x.Status == "Ready") - -
- -
-
- - - -
-
- - - - - Finalizado - -
- - @_allOrders.Count(x => x.Status == "Finalized") - -
- -
-
-
-
- - -
- -
-
- - Pedido #@context.Id - - - @context.Time - -
- - @context.Items - -
- - Mesa @context.Table - - @if (context.Status == "Finalized") - { - - Entregue - - } -
-
-
-
-
-
- -@code { - private List _allOrders = new() -{ -new KitchenOrder { Id = "001", Time = "10:15", Items = "2x Hambúrguer, 1x Batata Frita", Table = "5", Status = -"Confirmed" }, -new KitchenOrder { Id = "002", Time = "10:18", Items = "1x Pizza Margherita", Table = "12", Status = "Confirmed" }, -new KitchenOrder { Id = "003", Time = "10:22", Items = "3x Refrigerante, 2x Pastel", Table = "7", Status = "Confirmed" }, -new KitchenOrder { Id = "004", Time = "10:05", Items = "1x X-Bacon, 1x Suco", Table = "3", Status = "InPreparation" }, -new KitchenOrder { Id = "005", Time = "10:08", Items = "2x Porção de Fritas", Table = "9", Status = "InPreparation" }, -new KitchenOrder { Id = "006", Time = "09:55", Items = "1x Misto Quente, 1x Café", Table = "2", Status = "Ready" }, -new KitchenOrder { Id = "007", Time = "09:45", Items = "2x Açaí, 1x Tapioca", Table = "8", Status = "Finalized" } -}; - - private void OnItemDropped(MudItemDropInfo dropItem) - { - if (dropItem.Item == null) - return; - - dropItem.Item.Status = dropItem.DropzoneIdentifier; - StateHasChanged(); - } - - private class KitchenOrder - { - public string Id { get; set; } = string.Empty; - public string Time { get; set; } = string.Empty; - public string Items { get; set; } = string.Empty; - public string Table { get; set; } = string.Empty; - public string Status { get; set; } = string.Empty; - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Components/PaginatedView.razor b/UIs/Merchants/Source/Components/PaginatedView.razor deleted file mode 100644 index 300793d..0000000 --- a/UIs/Merchants/Source/Components/PaginatedView.razor +++ /dev/null @@ -1,65 +0,0 @@ -@typeparam TItem - -
- @if (Data?.Items is not null && Data.Items.Any()) - { - @Content(Data.Items) - - @if (Data.TotalPages > 1) - { -
- -
- -
- - Mostrando @GetStartItem() até @GetEndItem() de @Data.Total itens - -
- } - } - else - { - @EmptyContent - } -
- -@code { - [Parameter] - public PaginationScheme? Data { get; set; } - - [Parameter] - public RenderFragment> Content { get; set; } = null!; - - [Parameter] - public RenderFragment EmptyContent { get; set; } = null!; - - [Parameter] - public EventCallback OnPageChanged { get; set; } - - private async Task HandlePageChanged(int pageNumber) - { - await OnPageChanged.InvokeAsync(pageNumber - 1); - } - - private int GetStartItem() - { - if (Data is null || Data.PageNumber < 1) return 0; - return ((Data.PageNumber - 1) * Data.PageSize) + 1; - } - - private int GetEndItem() - { - if (Data is null) return 0; - return Math.Min(Data.PageNumber * Data.PageSize, Data.Total); - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Components/ProductCard.razor b/UIs/Merchants/Source/Components/ProductCard.razor deleted file mode 100644 index 2957610..0000000 --- a/UIs/Merchants/Source/Components/ProductCard.razor +++ /dev/null @@ -1,75 +0,0 @@ - -
-
- -
- -
-
-
- - @Name - -
-
- - Preço - - - @Price.ToString("C2") - -
-
- - - @Description - - - - -
- - @ProductId - - -
- - Editar - - - Excluir - -
-
-
-
-
- - -@code { - [Parameter] - public string ProductId { get; set; } = string.Empty; - - [Parameter] - public string Name { get; set; } = string.Empty; - - [Parameter] - public string Description { get; set; } = string.Empty; - - [Parameter] - public decimal Price { get; set; } - - [Parameter] - public string ImageUrl { get; set; } = string.Empty; - - [Parameter] - public EventCallback OnEdit { get; set; } - - [Parameter] - public EventCallback OnDelete { get; set; } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Components/ProductGallery.razor b/UIs/Merchants/Source/Components/ProductGallery.razor deleted file mode 100644 index fc709a2..0000000 --- a/UIs/Merchants/Source/Components/ProductGallery.razor +++ /dev/null @@ -1,68 +0,0 @@ -@inject IConfiguration configuration - - - -@code { - [Parameter] - public List Products { get; set; } = new(); - - [Parameter] - public EventCallback OnEdit { get; set; } - - [Parameter] - public EventCallback OnDelete { get; set; } - - private async Task HandleEdit(string productId) - { - await OnEdit.InvokeAsync(productId); - } - - private async Task HandleDelete(string productId) - { - await OnDelete.InvokeAsync(productId); - } - - private string GetFullImageUrl(string imageUrl) - { - if (string.IsNullOrEmpty(imageUrl)) - return string.Empty; - - var blobUrl = configuration["Settings:Blob"]; - if (string.IsNullOrEmpty(blobUrl)) - return imageUrl; - - blobUrl = blobUrl.TrimEnd('/'); - imageUrl = imageUrl.TrimStart('/'); - - return $"{blobUrl}/{imageUrl}"; - } - - public class ProductModel - { - public string Id { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public decimal Price { get; set; } - public string ImageUrl { get; set; } = string.Empty; - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Components/ProductSearch.razor b/UIs/Merchants/Source/Components/ProductSearch.razor deleted file mode 100644 index 9619c43..0000000 --- a/UIs/Merchants/Source/Components/ProductSearch.razor +++ /dev/null @@ -1,107 +0,0 @@ -
-
- - - - - - - - - - - - - - -
- - Limpar Filtros - - - Buscar - -
- - @if (HasActiveFilters) - { -
- - - @GetActiveFiltersText() - -
- } -
-
- -@code { - private string? _searchTitle; - private decimal? _minPrice; - private decimal? _maxPrice; - - [Parameter, EditorRequired] - public required ProductsFetchParameters Filters { get; set; } - - [Parameter] - public EventCallback OnFiltersChanged { get; set; } - - private bool HasActiveFilters => !string.IsNullOrWhiteSpace(_searchTitle) || - _minPrice.HasValue || - _maxPrice.HasValue; - - protected override void OnParametersSet() - { - _searchTitle = Filters.Title; - _minPrice = Filters.MinPrice; - _maxPrice = Filters.MaxPrice; - } - - private async Task OnSearchChanged() => await ApplyFilters(); - private async Task ApplyFilters() - { - var updatedFilters = Filters with - { - Title = string.IsNullOrWhiteSpace(_searchTitle) ? null : _searchTitle.Trim(), - MinPrice = _minPrice, - MaxPrice = _maxPrice - }; - - await OnFiltersChanged.InvokeAsync(updatedFilters); - } - - private async Task HandleClearAll() - { - _searchTitle = null; - _minPrice = null; - _maxPrice = null; - - await ApplyFilters(); - } - - private string GetActiveFiltersText() - { - var filters = new List(); - - if (!string.IsNullOrWhiteSpace(_searchTitle)) - filters.Add($"Título: \"{_searchTitle}\""); - - if (_minPrice.HasValue) - filters.Add($"Min: {_minPrice:C2}"); - - if (_maxPrice.HasValue) - filters.Add($"Max: {_maxPrice:C2}"); - - return $"Filtros ativos: {string.Join(" | ", filters)}"; - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Components/Support.razor b/UIs/Merchants/Source/Components/Support.razor deleted file mode 100644 index 57e4043..0000000 --- a/UIs/Merchants/Source/Components/Support.razor +++ /dev/null @@ -1,22 +0,0 @@ -@inject IJSRuntime javascript - -
- - - - Falar com suporte - -
- -@code { - private async Task OpenWhatsApp() - { - var message = @"Olá, preciso de suporte com o comanda"; - var encodedMessage = Uri.EscapeDataString(message); - var whatsappUrl = $"https://wa.me/5521988547794?text={encodedMessage}"; - - await javascript.InvokeVoidAsync("open", whatsappUrl, "_blank"); - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Constants/AuthenticationDefaults.cs b/UIs/Merchants/Source/Constants/AuthenticationDefaults.cs deleted file mode 100644 index b6dc2a0..0000000 --- a/UIs/Merchants/Source/Constants/AuthenticationDefaults.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Comanda.Merchants.WebUI.Constants; - -public static class AuthenticationDefaults -{ - public const string Type = "Jwt"; - public const string Scheme = "Bearer"; -} diff --git a/UIs/Merchants/Source/Constants/Headers.cs b/UIs/Merchants/Source/Constants/Headers.cs deleted file mode 100644 index 1fd16d0..0000000 --- a/UIs/Merchants/Source/Constants/Headers.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Comanda.Merchants.WebUI.Constants; - -public static class Headers -{ - public const string Realm = "Realm"; -} diff --git a/UIs/Merchants/Source/Constants/SidebarLabels.cs b/UIs/Merchants/Source/Constants/SidebarLabels.cs deleted file mode 100644 index 494f2b7..0000000 --- a/UIs/Merchants/Source/Constants/SidebarLabels.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Comanda.Merchants.WebUI.Constants; - -public static class SidebarLabels -{ - public const string Orders = "Pedidos"; - public const string Payments = "Pagamentos"; - public const string Products = "Produtos"; - public const string Settings = "Configurações"; - public const string Kitchen = "Cozinha"; -} diff --git a/UIs/Merchants/Source/Constants/Storage.cs b/UIs/Merchants/Source/Constants/Storage.cs deleted file mode 100644 index 9f6cfd3..0000000 --- a/UIs/Merchants/Source/Constants/Storage.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Comanda.Merchants.WebUI.Constants; - -public static class Storage -{ - public const string SecurityToken = "comanda:security-token"; - public const string RefreshToken = "comanda:refresh-token"; - public const string Merchant = "comanda:merchant"; - public const string Establishment = "comanda:establishment"; - public const string Principal = "comanda:principal"; -} diff --git a/UIs/Merchants/Source/Contracts/ILocalStorageGateway.cs b/UIs/Merchants/Source/Contracts/ILocalStorageGateway.cs deleted file mode 100644 index ba59d71..0000000 --- a/UIs/Merchants/Source/Contracts/ILocalStorageGateway.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Comanda.Merchants.WebUI.Contracts; - -public interface ILocalStorageGateway -{ - public Task GetAsync(string key, CancellationToken cancellation = default); - public Task GetAsStringAsync(string key, CancellationToken cancellation = default); - - public Task SetAsync(string key, T value, CancellationToken cancellation = default); - public Task SetAsStringAsync(string key, string value, CancellationToken cancellation = default); - - public Task RemoveAsync(string key, CancellationToken cancellation = default); - public Task ClearAsync(CancellationToken cancellation = default); -} diff --git a/UIs/Merchants/Source/Contracts/IPrincipalProvider.cs b/UIs/Merchants/Source/Contracts/IPrincipalProvider.cs deleted file mode 100644 index 643c5e9..0000000 --- a/UIs/Merchants/Source/Contracts/IPrincipalProvider.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Comanda.Merchants.WebUI.Contracts; - -public interface IPrincipalProvider -{ - public Task> GetPrincipalAsync(CancellationToken cancellation = default); -} diff --git a/UIs/Merchants/Source/Contracts/ISessionManager.cs b/UIs/Merchants/Source/Contracts/ISessionManager.cs deleted file mode 100644 index a4adc67..0000000 --- a/UIs/Merchants/Source/Contracts/ISessionManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Comanda.Merchants.WebUI.Contracts; - -public interface ISessionManager -{ - Task SignInAsync(string token, string refreshToken); - Task SignOutAsync(); -} diff --git a/UIs/Merchants/Source/Extensions/AuthenticationExtension.cs b/UIs/Merchants/Source/Extensions/AuthenticationExtension.cs deleted file mode 100644 index 59cda1b..0000000 --- a/UIs/Merchants/Source/Extensions/AuthenticationExtension.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Comanda.Merchants.WebUI.Extensions; - -public static class AuthenticationExtension -{ - public static void AddAuthentication(this IServiceCollection services) - { - services.AddAuthorizationCore(); - services.AddCascadingAuthenticationState(); - - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - } -} diff --git a/UIs/Merchants/Source/Extensions/ClientExtension.cs b/UIs/Merchants/Source/Extensions/ClientExtension.cs deleted file mode 100644 index 410ceb0..0000000 --- a/UIs/Merchants/Source/Extensions/ClientExtension.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace Comanda.Merchants.WebUI.Extensions; - -public static class ClientExtension -{ - public static void AddClients(this IServiceCollection services, IConfiguration configuration) - { - var address = configuration.GetValue("Settings:Gateway")!; - var storeClient = services.AddHttpClient(client => - { - client.BaseAddress = new Uri(address); - client.Timeout = TimeSpan.FromMinutes(minutes: 1, seconds: 30); - }); - - var orderClient = services.AddHttpClient(client => - { - client.BaseAddress = new Uri(address); - client.Timeout = TimeSpan.FromMinutes(minutes: 1, seconds: 30); - }); - - var profilesClient = services.AddHttpClient(client => - { - client.BaseAddress = new Uri(address); - client.Timeout = TimeSpan.FromMinutes(minutes: 1, seconds: 30); - }); - - // ensure an authentication interceptor is always registered for http clients - // every http client must include a message handler responsible for authentication - - storeClient.AddHttpMessageHandler(); - orderClient.AddHttpMessageHandler(); - profilesClient.AddHttpMessageHandler(); - } -} diff --git a/UIs/Merchants/Source/Extensions/GatewaysExtension.cs b/UIs/Merchants/Source/Extensions/GatewaysExtension.cs deleted file mode 100644 index cdb8a11..0000000 --- a/UIs/Merchants/Source/Extensions/GatewaysExtension.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Comanda.Merchants.WebUI.Extensions; - -public static class GatewaysExtension -{ - public static void AddGateways(this IServiceCollection services, IConfiguration configuration) - { - var address = configuration.GetValue("Settings:IdentityProvider")!; - var realm = configuration.GetValue("Settings:Realm")!; - - services.AddScoped(); - services.AddHttpClient(client => - { - client.BaseAddress = new Uri(address); - client.DefaultRequestHeaders.Add(Headers.Realm, realm); - client.Timeout = TimeSpan.FromMinutes(minutes: 1, seconds: 30); - }); - - var principalGateway = services.AddHttpClient(client => - { - client.BaseAddress = new Uri(address); - client.DefaultRequestHeaders.Add(Headers.Realm, realm); - client.Timeout = TimeSpan.FromMinutes(minutes: 1, seconds: 30); - }); - - // the authentication interceptor will add the access token to each request - // register always after the http client registration - - principalGateway.AddHttpMessageHandler(); - } -} diff --git a/UIs/Merchants/Source/Extensions/ServicesExtension.cs b/UIs/Merchants/Source/Extensions/ServicesExtension.cs deleted file mode 100644 index a782fea..0000000 --- a/UIs/Merchants/Source/Extensions/ServicesExtension.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Comanda.Merchants.WebUI.Extensions; - -public static class ServicesExtension -{ - public static void AddInfrastructure(this IServiceCollection services, IConfiguration configuration) - { - services.AddClients(configuration); - services.AddGateways(configuration); - services.AddAuthentication(); - } -} diff --git a/UIs/Merchants/Source/Forms/EstablishmentUpdateForm.razor b/UIs/Merchants/Source/Forms/EstablishmentUpdateForm.razor deleted file mode 100644 index 7b0bdd0..0000000 --- a/UIs/Merchants/Source/Forms/EstablishmentUpdateForm.razor +++ /dev/null @@ -1,195 +0,0 @@ -@inject IStoreClient storeClient -@inject ILocalStorageGateway localStorage -@inject ISnackbar snackbar - - - - - - - Informações do Estabelecimento - - - Configure o nome e descrição que aparecerão para seus clientes - - - - - - - - - - - - - - - - Identidade Visual - - - Personalize as cores do seu estabelecimento - - - - -
- - - @if (_showPrimaryColorPicker) - { - - } - - @if (!string.IsNullOrEmpty(_establishment.PrimaryColor)) - { - - - Preview da Cor Primária - - - } -
-
- - -
- - - @if (_showSecondaryColorPicker) - { - - } - - @if (!string.IsNullOrEmpty(_establishment.SecondaryColor)) - { - - - Preview da Cor Secundária - - - } -
-
-
- - - -
- - Cancelar - - - @if (_isSubmitting) - { - - Salvando... - } - else - { - Salvar Alterações - } - -
-
-
- -@code { - private EstablishmentUpdateFormScheme _establishment = new(); - private EstablishmentScheme? _currentEstablishment; - - private bool _isSubmitting = false; - private bool _showPrimaryColorPicker = false; - private bool _showSecondaryColorPicker = false; - - protected override async Task OnInitializedAsync() - { - await LoadEstablishmentDataAsync(); - } - - private async Task LoadEstablishmentDataAsync() - { - _currentEstablishment = await localStorage.GetAsync(Storage.Establishment); - if (_currentEstablishment is not null) - { - _establishment.Title = _currentEstablishment.Title ?? string.Empty; - _establishment.Description = _currentEstablishment.Description ?? string.Empty; - _establishment.PrimaryColor = _currentEstablishment.Branding?.PrimaryColor ?? "#5B21B6"; - _establishment.SecondaryColor = _currentEstablishment.Branding?.SecondaryColor ?? "#EC4899"; - } - } - - private async Task HandleSubmitAsync() - { - if (_currentEstablishment is null) - { - snackbar.Add("Não foi possível identificar o estabelecimento", Severity.Error); - return; - } - - _isSubmitting = true; - - var primaryColor = _establishment.PrimaryColor?.Length == 9 - ? _establishment.PrimaryColor.Substring(0, 7) - : _establishment.PrimaryColor; - - var secondaryColor = _establishment.SecondaryColor?.Length == 9 - ? _establishment.SecondaryColor.Substring(0, 7) - : _establishment.SecondaryColor; - - var modificationScheme = new EstablishmentModificationScheme - { - EstablishmentId = _currentEstablishment.Identifier, - Title = _establishment.Title, - Description = _establishment.Description, - Branding = new Branding(primaryColor ?? "#5B21B6", secondaryColor ?? "#EC4899", "https://placehold.co/600x400.png") - }; - - var result = await storeClient.UpdateEstablishmentAsync(modificationScheme); - if (result.IsFailure || result.Data is null) - { - snackbar.Add($"Erro ao atualizar estabelecimento: {result.Error.Code}", Severity.Error); - _isSubmitting = false; - return; - } - - await localStorage.SetAsync(Storage.Establishment, result.Data); - - _currentEstablishment = result.Data; - _isSubmitting = false; - - snackbar.Add("Estabelecimento atualizado com sucesso!", Severity.Success); - } - - private async Task HandleCancelAsync() - { - await LoadEstablishmentDataAsync(); - - _showPrimaryColorPicker = false; - _showSecondaryColorPicker = false; - - snackbar.Add("Alterações canceladas", Severity.Info); - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Forms/IntegrationCredentialsForm.razor b/UIs/Merchants/Source/Forms/IntegrationCredentialsForm.razor deleted file mode 100644 index 2717e85..0000000 --- a/UIs/Merchants/Source/Forms/IntegrationCredentialsForm.razor +++ /dev/null @@ -1,157 +0,0 @@ -@inject IStoreClient storeClient -@inject ILocalStorageGateway localStorage -@inject ISnackbar snackbar - - - - - - - Integrações - - - Configure as credenciais dos serviços integrados - - - - -
- - - Gateway de Pagamentos - Processamento de Pagamentos - -
- -
- - -
-
- - - Huggy - Mensagens WhatsApp - -
- - Em Breve - -
- -
-
- - - -
- - Cancelar - - - @if (_isSubmitting) - { - - Salvando... - } - else - { - Salvar Alterações - } - -
-
-
- -@code { - private IntegrationCredentialsFormScheme _model = new(); - - private bool _isSubmitting = false; - private string? _establishmentId; - private string? _paymentGatewayCredentialId; - private string? _whatsappCredentialId; - - protected override async Task OnInitializedAsync() - { - await LoadCredentialsAsync(); - } - - private async Task LoadCredentialsAsync() - { - var establishment = await localStorage.GetAsync(Storage.Establishment); - if (establishment is null) - { - snackbar.Add("Estabelecimento não encontrado", Severity.Warning); - return; - } - - _establishmentId = establishment.Identifier; - - var result = await storeClient.GetCredentialsAsync(new() { EstablishmentId = _establishmentId }); - if (result.IsFailure || result.Data is null) - { - return; - } - - foreach (var credential in result.Data) - { - if (credential.Provider == "PaymentGateway" || credential.Provider == IntegrationTarget.PaymentGateway.ToString()) - { - _model.PaymentGatewaySecretKey = credential.SecretKey ?? string.Empty; - _paymentGatewayCredentialId = credential.Identifier; - } - else if (credential.Provider == "Whatsapp" || credential.Provider == IntegrationTarget.Whatsapp.ToString()) - { - _model.WhatsappToken = credential.SecretKey ?? string.Empty; - _whatsappCredentialId = credential.Identifier; - } - } - } - private async Task HandleSubmitAsync() - { - if (string.IsNullOrEmpty(_establishmentId)) - { - snackbar.Add("Estabelecimento não identificado", Severity.Error); - return; - } - - _isSubmitting = true; - - if (!string.IsNullOrEmpty(_model.PaymentGatewaySecretKey)) - { - var paymentCredential = new CredentialCreationScheme - { - EstablishmentId = _establishmentId, - SecretKey = _model.PaymentGatewaySecretKey, - Provider = IntegrationTarget.PaymentGateway - }; - - var paymentResult = await storeClient.AssignIntegrationCredentialAsync(paymentCredential); - if (paymentResult.IsFailure) - { - snackbar.Add($"Erro ao salvar credencial de pagamento: {paymentResult.Error.Code}", Severity.Error); - _isSubmitting = false; - - return; - } - } - - snackbar.Add("Credenciais salvas com sucesso!", Severity.Success); - _isSubmitting = false; - - } - - private async Task HandleCancelAsync() - { - await LoadCredentialsAsync(); - snackbar.Add("Alterações canceladas", Severity.Info); - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Forms/OnboardingForm.razor b/UIs/Merchants/Source/Forms/OnboardingForm.razor deleted file mode 100644 index 36e9631..0000000 --- a/UIs/Merchants/Source/Forms/OnboardingForm.razor +++ /dev/null @@ -1,120 +0,0 @@ -@inject IStoreClient storeClient -@inject IProfilesClient profilesClient -@inject ILocalStorageGateway localStorage -@inject AuthenticationStateProvider authenticationStateProvider -@inject NavigationManager navigationManager -@inject ISnackbar snackbar - - - - - - - Informaes do Estabelecimento - - - Cadastre seu estabelecimento para comear a usar o sistema - - - - - - - - - - - - - - -
- - - @if (_isSubmitting) - { - - Criando... - } - else - { - Criar Estabelecimento - } - -
-
-
- -@code { - private EstablishmentFormScheme _model = new(); - private bool _isSubmitting = false; - - private async Task HandleSubmitAsync() - { - var authentication = await authenticationStateProvider.GetAuthenticationStateAsync(); - var principal = authentication.User; - - if (principal?.Identity?.IsAuthenticated is not true) - { - snackbar.Add("Voc precisa estar autenticado para criar um estabelecimento", Severity.Error); - return; - } - - var identifier = principal.FindFirst("sub")?.Value; - if (string.IsNullOrEmpty(identifier)) - { - snackbar.Add("Erro ao identificar o usurio", Severity.Error); - return; - } - - var ownersResponse = await profilesClient.GetOwnersAsync(new() { UserId = identifier }); - if (ownersResponse?.Data?.Items is null || !ownersResponse.Data.Items.Any()) - { - snackbar.Add("Erro ao buscar informaes do proprietrio", Severity.Error); - return; - } - - var owner = ownersResponse.Data.Items.First(); - var scheme = new EstablishmentCreationScheme - { - Title = _model.Title, - Description = _model.Description, - Owner = new(owner.Identifier, owner.Email) - }; - - var result = await storeClient.CreateEstablishmentAsync(scheme); - if (result.IsFailure || result.Data is null) - { - snackbar.Add($"Erro ao criar estabelecimento ({result.Error.Code})", Severity.Error); - return; - } - - await localStorage.SetAsync(Storage.Establishment, result.Data); - await localStorage.SetAsync(Storage.Merchant, owner); - - snackbar.Add("Estabelecimento criado com sucesso!", Severity.Success); - navigationManager.NavigateTo("/"); - - _isSubmitting = false; - } -} diff --git a/UIs/Merchants/Source/Forms/ProductCreationForm.razor b/UIs/Merchants/Source/Forms/ProductCreationForm.razor deleted file mode 100644 index 45cd6a3..0000000 --- a/UIs/Merchants/Source/Forms/ProductCreationForm.razor +++ /dev/null @@ -1,172 +0,0 @@ -@inject IStoreClient storeClient -@inject ILocalStorageGateway localStorage -@inject ISnackbar snackbar - - - - - - - - - - - - @if (_imagePreview is not null) - { - -
- - - -
- - Imagem selecionada - - - @_selectedFileName - -
-
- -
- } - else - { - - - - - Adicionar imagem - - - - - } - - - -
- - Cancelar - - - @if (_isSubmitting) - { - - Salvando... - } - else - { - - Salvar - } - -
-
-
- -@code { - [Parameter] - public EventCallback OnSuccess { get; set; } - - [Parameter] - public EventCallback OnCancel { get; set; } - - private ProductCreationFormScheme _model = new(); - private bool _isSubmitting = false; - private byte[]? _imageBytes; - private string? _imagePreview; - private string? _selectedFileName; - private string? _imageContentType; - - private async Task HandleFileSelected(IBrowserFile? file) - { - if (file is null) - return; - - _selectedFileName = file.Name; - _imageContentType = file.ContentType; - - var buffer = new byte[file.Size]; - using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024); - - await stream.ReadAsync(buffer); - - _imageBytes = buffer; - _imagePreview = $"data:{file.ContentType};base64,{Convert.ToBase64String(buffer)}"; - } - - private void ClearImage() - { - _imageBytes = null; - _imagePreview = null; - _selectedFileName = null; - _imageContentType = null; - } - - private async Task HandleSubmitAsync() - { - _isSubmitting = true; - - var establishment = await localStorage.GetAsync(Storage.Establishment); - if (establishment is null) - { - snackbar.Add("Erro ao identificar o estabelecimento", Severity.Error); - return; - } - - var creationScheme = new ProductCreationScheme - { - EstablishmentId = establishment.Identifier, - Title = _model.Title, - Description = _model.Description, - Price = _model.Price - }; - - var result = await storeClient.CreateProductAsync(creationScheme); - if (!result.IsSuccess || result.Data is null) - { - snackbar.Add($"Erro ao criar produto ({result.Error.Code})", Severity.Error); - return; - } - - if (_imageBytes is not null) - { - using var memoryStream = new MemoryStream(_imageBytes); - var imageScheme = new ProductImageStreamScheme - { - EstablishmentId = establishment.Identifier, - ProductId = result.Data.Identifier, - Stream = memoryStream - }; - - var imageResult = await storeClient.UploadProductImage(imageScheme); - if (!imageResult.IsSuccess) - { - snackbar.Add($"Produto criado, mas houve erro ao fazer upload da imagem ({imageResult.Error.Code})", Severity.Warning); - } - } - - snackbar.Add("Produto criado com sucesso!", Severity.Success); - - _model = new(); - ClearImage(); - await OnSuccess.InvokeAsync(); - - _isSubmitting = false; - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Forms/ProductUpdateForm.razor b/UIs/Merchants/Source/Forms/ProductUpdateForm.razor deleted file mode 100644 index fd1d2de..0000000 --- a/UIs/Merchants/Source/Forms/ProductUpdateForm.razor +++ /dev/null @@ -1,110 +0,0 @@ -@inject IStoreClient storeClient -@inject ILocalStorageGateway localStorage -@inject ISnackbar snackbar - - - - - - - - - - - - - -
- - Cancelar - - - @if (_isSubmitting) - { - - Salvando... - } - else - { - - Salvar - } - -
-
-
- -@code { - [Parameter] - public string ProductId { get; set; } = string.Empty; - - [Parameter] - public EventCallback OnSuccess { get; set; } - - [Parameter] - public EventCallback OnCancel { get; set; } - - private ProductUpdateFormScheme _model = new(); - private bool _isSubmitting = false; - - protected override void OnParametersSet() - { - if (Product is not null) - { - _model = new ProductUpdateFormScheme - { - Title = Product.Title, - Description = Product.Description, - Price = Product.Price - }; - } - } - - [Parameter] - public ProductScheme? Product { get; set; } - - private async Task HandleSubmitAsync() - { - _isSubmitting = true; - - var establishment = await localStorage.GetAsync(Storage.Establishment); - if (establishment is null) - { - snackbar.Add("Erro ao identificar o estabelecimento", Severity.Error); - _isSubmitting = false; - return; - } - - var modificationScheme = new ProductModificationScheme - { - EstablishmentId = establishment.Identifier, - ProductId = ProductId, - Title = _model.Title, - Description = _model.Description, - Price = _model.Price - }; - - var result = await storeClient.UpdateProductAsync(modificationScheme); - if (!result.IsSuccess || result.Data is null) - { - snackbar.Add($"Erro ao atualizar produto ({result.Error.Code})", Severity.Error); - _isSubmitting = false; - return; - } - - snackbar.Add("Produto atualizado com sucesso!", Severity.Success); - - await OnSuccess.InvokeAsync(); - - _isSubmitting = false; - } -} diff --git a/UIs/Merchants/Source/Forms/Schemes/EstablishmentFormScheme.cs b/UIs/Merchants/Source/Forms/Schemes/EstablishmentFormScheme.cs deleted file mode 100644 index 3082950..0000000 --- a/UIs/Merchants/Source/Forms/Schemes/EstablishmentFormScheme.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Comanda.Merchants.WebUI.Forms.Schemes; - -public sealed record EstablishmentFormScheme -{ - [Required(ErrorMessage = "O nome do estabelecimento é obrigatório")] - [StringLength(100, MinimumLength = 3, ErrorMessage = "O nome deve ter entre 3 e 100 caracteres")] - public string Title { get; set; } = string.Empty; - - [Required(ErrorMessage = "A descrição é obrigatória")] - [StringLength(500, MinimumLength = 10, ErrorMessage = "A descrição deve ter entre 10 e 500 caracteres")] - public string Description { get; set; } = string.Empty; -} diff --git a/UIs/Merchants/Source/Forms/Schemes/EstablishmentUpdateFormScheme.cs b/UIs/Merchants/Source/Forms/Schemes/EstablishmentUpdateFormScheme.cs deleted file mode 100644 index 7a7d05c..0000000 --- a/UIs/Merchants/Source/Forms/Schemes/EstablishmentUpdateFormScheme.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Comanda.Merchants.WebUI.Forms.Schemes; - -public sealed record EstablishmentUpdateFormScheme -{ - [Required(ErrorMessage = "O nome do estabelecimento é obrigatório")] - [StringLength(100, MinimumLength = 3, ErrorMessage = "O nome deve ter entre 3 e 100 caracteres")] - public string Title { get; set; } = string.Empty; - - [Required(ErrorMessage = "A descrição é obrigatória")] - [StringLength(500, MinimumLength = 10, ErrorMessage = "A descrição deve ter entre 10 e 500 caracteres")] - public string Description { get; set; } = string.Empty; - - [RegularExpression(@"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8})$", ErrorMessage = "Cor primária deve ser um código hexadecimal válido (ex: #4a1998)")] - public string PrimaryColor { get; set; } = "#5B21B6"; - - [RegularExpression(@"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{8})$", ErrorMessage = "Cor secundária deve ser um código hexadecimal válido (ex: #ec4899)")] - public string SecondaryColor { get; set; } = "#EC4899"; - public string Logo { get; set; } = string.Empty; -} diff --git a/UIs/Merchants/Source/Forms/Schemes/IntegrationCredentialsFormScheme.cs b/UIs/Merchants/Source/Forms/Schemes/IntegrationCredentialsFormScheme.cs deleted file mode 100644 index c03b2cb..0000000 --- a/UIs/Merchants/Source/Forms/Schemes/IntegrationCredentialsFormScheme.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Comanda.Merchants.WebUI.Forms.Schemes; - -public sealed record IntegrationCredentialsFormScheme -{ - [Required(ErrorMessage = "A chave secreta do gateway de pagamentos é obrigatória")] - [StringLength(500, MinimumLength = 10, ErrorMessage = "A chave secreta deve ter entre 10 e 500 caracteres")] - public string PaymentGatewaySecretKey { get; set; } = string.Empty; - public string WhatsappToken { get; set; } = string.Empty; -} diff --git a/UIs/Merchants/Source/Forms/Schemes/ProductCreationFormScheme.cs b/UIs/Merchants/Source/Forms/Schemes/ProductCreationFormScheme.cs deleted file mode 100644 index fb6b13b..0000000 --- a/UIs/Merchants/Source/Forms/Schemes/ProductCreationFormScheme.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Comanda.Merchants.WebUI.Forms.Schemes; - -public sealed record ProductCreationFormScheme -{ - [Required(ErrorMessage = "O título do produto é obrigatório")] - [StringLength(100, MinimumLength = 3, ErrorMessage = "O título deve ter entre 3 e 100 caracteres")] - public string Title { get; set; } = string.Empty; - - [Required(ErrorMessage = "A descrição é obrigatória")] - [StringLength(500, MinimumLength = 10, ErrorMessage = "A descrição deve ter entre 10 e 500 caracteres")] - public string Description { get; set; } = string.Empty; - - [Required(ErrorMessage = "O preço é obrigatório")] - [Range(0.01, 999999.99, ErrorMessage = "O preço deve ser maior que zero")] - public decimal Price { get; set; } -} diff --git a/UIs/Merchants/Source/Forms/Schemes/ProductUpdateFormScheme.cs b/UIs/Merchants/Source/Forms/Schemes/ProductUpdateFormScheme.cs deleted file mode 100644 index b5e31a5..0000000 --- a/UIs/Merchants/Source/Forms/Schemes/ProductUpdateFormScheme.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Comanda.Merchants.WebUI.Forms.Schemes; - -public sealed record ProductUpdateFormScheme -{ - [Required(ErrorMessage = "O título do produto é obrigatório")] - [StringLength(100, MinimumLength = 3, ErrorMessage = "O título deve ter entre 3 e 100 caracteres")] - public string Title { get; set; } = string.Empty; - - [Required(ErrorMessage = "A descrição é obrigatória")] - [StringLength(500, MinimumLength = 10, ErrorMessage = "A descrição deve ter entre 10 e 500 caracteres")] - public string Description { get; set; } = string.Empty; - - [Required(ErrorMessage = "O preço é obrigatório")] - [Range(0.01, 999999.99, ErrorMessage = "O preço deve ser maior que zero")] - public decimal Price { get; set; } -} diff --git a/UIs/Merchants/Source/Gateways/LocalStorageGateway.cs b/UIs/Merchants/Source/Gateways/LocalStorageGateway.cs deleted file mode 100644 index 6da19e2..0000000 --- a/UIs/Merchants/Source/Gateways/LocalStorageGateway.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Comanda.Merchants.WebUI.Gateways; - -public sealed class LocalStorageGateway(IJSRuntime javascript) : ILocalStorageGateway -{ - public async Task GetAsStringAsync(string key, CancellationToken cancellation = default) - { - return await javascript.InvokeAsync("localStorage.getItem", key); - } - - public async Task GetAsync(string key, CancellationToken cancellation = default) - { - var value = await javascript.InvokeAsync("localStorage.getItem", key); - if (string.IsNullOrEmpty(value)) - return default; - - return JsonSerializer.Deserialize(value); - } - - public async Task SetAsStringAsync(string key, string value, CancellationToken cancellation = default) - { - await javascript.InvokeVoidAsync("localStorage.setItem", key, value); - } - - public async Task SetAsync(string key, T value, CancellationToken cancellation = default) - { - await javascript.InvokeVoidAsync("localStorage.setItem", key, JsonSerializer.Serialize(value)); - } - - public async Task RemoveAsync(string key, CancellationToken cancellation = default) - { - await javascript.InvokeVoidAsync("localStorage.removeItem", key); - } - - public async Task ClearAsync(CancellationToken cancellation = default) - { - await javascript.InvokeVoidAsync("localStorage.clear"); - } -} diff --git a/UIs/Merchants/Source/Http/Clients/IdentityClient.cs b/UIs/Merchants/Source/Http/Clients/IdentityClient.cs deleted file mode 100644 index 202186c..0000000 --- a/UIs/Merchants/Source/Http/Clients/IdentityClient.cs +++ /dev/null @@ -1,80 +0,0 @@ -#pragma warning disable S1125 // we prefer explicit boolean comparisons for readability - -namespace Comanda.Merchants.WebUI.Http.Clients; - -public sealed class IdentityClient(HttpClient httpClient) : IIdentityClient -{ - private readonly JsonSerializerOptions serializerOptions = new() - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - - public async Task> AuthenticateAsync(AuthenticationCredentials credentials, CancellationToken cancellation = default) - { - var response = await httpClient.PostAsJsonAsync("api/v1/identity/authenticate", credentials, cancellation); - if (response.IsSuccessStatusCode is false) - { - var error = await response.Content.ReadFromJsonAsync( - options: serializerOptions, - cancellationToken: cancellation - ); - - return error is not null - ? Result.Failure(error) - : Result.Failure(Error.Unknown); - } - - var result = await response.Content.ReadFromJsonAsync( - options: serializerOptions, - cancellationToken: cancellation - ); - - return result is not null - ? Result.Success(result) - : Result.Failure(Error.Unknown); - } - - public async Task> CreateIdentityAsync(IdentityEnrollmentCredentials credentials, CancellationToken cancellation = default) - { - var response = await httpClient.PostAsJsonAsync("api/v1/identity", credentials, cancellation); - if (response.IsSuccessStatusCode is false) - { - var error = await response.Content.ReadFromJsonAsync( - options: serializerOptions, - cancellationToken: cancellation - ); - - return error is not null - ? Result.Failure(error) - : Result.Failure(Error.Unknown); - } - - var result = await response.Content.ReadFromJsonAsync( - options: serializerOptions, - cancellationToken: cancellation - ); - - return result is not null - ? Result.Success(result) - : Result.Failure(Error.Unknown); - } - - public async Task InvalidateSessionAsync(SessionInvalidation session, CancellationToken cancellation = default) - { - var response = await httpClient.PostAsJsonAsync("api/v1/identity/invalidate-session", session, cancellation); - if (response.IsSuccessStatusCode is false) - { - var error = await response.Content.ReadFromJsonAsync( - options: serializerOptions, - cancellationToken: cancellation - ); - - return error is not null - ? Result.Failure(error) - : Result.Failure(Error.Unknown); - } - - return Result.Success(); - } -} diff --git a/UIs/Merchants/Source/Http/Interceptors/AuthenticationInterceptor.cs b/UIs/Merchants/Source/Http/Interceptors/AuthenticationInterceptor.cs deleted file mode 100644 index 5a2dea4..0000000 --- a/UIs/Merchants/Source/Http/Interceptors/AuthenticationInterceptor.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Comanda.Merchants.WebUI.Http.Interceptors; - -public sealed class AuthenticationInterceptor(ILocalStorageGateway localStorage) : DelegatingHandler -{ - protected override async Task SendAsync( - HttpRequestMessage request, CancellationToken cancellationToken) - { - var token = await localStorage.GetAsStringAsync(Storage.SecurityToken, cancellationToken); - - /* https://www.rfc-editor.org/rfc/rfc6750 */ - if (!string.IsNullOrWhiteSpace(token)) - request.Headers.Authorization = new(AuthenticationDefaults.Scheme, token); - - return await base.SendAsync(request, cancellationToken); - } -} diff --git a/UIs/Merchants/Source/Http/Payloads/Identity/PrincipalScheme.cs b/UIs/Merchants/Source/Http/Payloads/Identity/PrincipalScheme.cs deleted file mode 100644 index b35ebe5..0000000 --- a/UIs/Merchants/Source/Http/Payloads/Identity/PrincipalScheme.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Comanda.Merchants.WebUI.Http.Payloads.Identity; - -public sealed record PrincipalScheme -{ - public string Id { get; init; } = default!; - public string Username { get; init; } = default!; - - public DateTime CreatedAt { get; init; } - public DateTime? UpdatedAt { get; init; } - - public IReadOnlyCollection Permissions { get; init; } = []; - public IReadOnlyCollection Groups { get; init; } = []; -} diff --git a/UIs/Merchants/Source/Layout/MainLayout.razor b/UIs/Merchants/Source/Layout/MainLayout.razor deleted file mode 100644 index 2065178..0000000 --- a/UIs/Merchants/Source/Layout/MainLayout.razor +++ /dev/null @@ -1,31 +0,0 @@ -@inherits LayoutComponentBase - - - - - - - - - - - - - - - @Body - - - -@code { - private readonly MudTheme _theme = new() - { - PaletteLight = new PaletteLight - { - Primary = "#EE254B", - PrimaryDarken = "#D11F3F", - PrimaryLighten = "#F24D6F", - Secondary = "#EC4899", - } - }; -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Layout/NavigationBar.razor b/UIs/Merchants/Source/Layout/NavigationBar.razor deleted file mode 100644 index f3aba3a..0000000 --- a/UIs/Merchants/Source/Layout/NavigationBar.razor +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/UIs/Merchants/Source/Layout/Sidebar.razor b/UIs/Merchants/Source/Layout/Sidebar.razor deleted file mode 100644 index 26edb47..0000000 --- a/UIs/Merchants/Source/Layout/Sidebar.razor +++ /dev/null @@ -1,57 +0,0 @@ - - - - - Dashboard - - - - - - @SidebarLabels.Orders - - - - - - @SidebarLabels.Products - - - - - - @SidebarLabels.Kitchen - - - - - - @SidebarLabels.Payments - - - - - - @SidebarLabels.Settings - - - - - - - - -@code { - [Parameter] - public bool IsOpen { get; set; } - - [Parameter] - public EventCallback IsOpenChanged { get; set; } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Pages/Home.razor b/UIs/Merchants/Source/Pages/Home.razor deleted file mode 100644 index 47920b1..0000000 --- a/UIs/Merchants/Source/Pages/Home.razor +++ /dev/null @@ -1,7 +0,0 @@ -@page "/" - -Dashboard - -
- -
\ No newline at end of file diff --git a/UIs/Merchants/Source/Pages/Kitchen.razor b/UIs/Merchants/Source/Pages/Kitchen.razor deleted file mode 100644 index 35c8d8d..0000000 --- a/UIs/Merchants/Source/Pages/Kitchen.razor +++ /dev/null @@ -1,33 +0,0 @@ -@page "/cozinha" - -Cozinha - - - -
-
- - - -
- - Cozinha - - - Gerencie o fluxo de preparação dos pedidos - -
-
-
- -
-
-
- -
- diff --git a/UIs/Merchants/Source/Pages/NotAuthorized.razor b/UIs/Merchants/Source/Pages/NotAuthorized.razor deleted file mode 100644 index 896383b..0000000 --- a/UIs/Merchants/Source/Pages/NotAuthorized.razor +++ /dev/null @@ -1,28 +0,0 @@ -@page "/not-authorized" -@layout MainLayout - -Não Autorizado - - - - 403 - - - - Você não está autorizado - - - - Você não possui permissão para acessar esta página. Entre em contato com o administrador se acredita que isso é um erro. - - - - Voltar para o início - - diff --git a/UIs/Merchants/Source/Pages/NotFound.razor b/UIs/Merchants/Source/Pages/NotFound.razor deleted file mode 100644 index 8822e79..0000000 --- a/UIs/Merchants/Source/Pages/NotFound.razor +++ /dev/null @@ -1,28 +0,0 @@ -@page "/not-found" -@layout MainLayout - -Não Encontrado - - - - 404 - - - - Está página não existe - - - - A página que você está procurando pode ter sido removida, teve seu nome alterado ou está temporariamente indisponível. - - - - Voltar para o início - - \ No newline at end of file diff --git a/UIs/Merchants/Source/Pages/Onboarding.razor b/UIs/Merchants/Source/Pages/Onboarding.razor deleted file mode 100644 index 9e6e6eb..0000000 --- a/UIs/Merchants/Source/Pages/Onboarding.razor +++ /dev/null @@ -1,70 +0,0 @@ -@page "/onboarding" -@layout MainLayout - -@inject ILocalStorageGateway localStorage -@inject IStoreClient storeClient -@inject NavigationManager navigationManager - -Onboarding - -@if (isLoading) -{ - - - -} -else if (!hasEstablishment) -{ - - -
- - - -
- - Bem-vindo! - - - Vamos começar criando seu estabelecimento - -
-
-
- -
-} - -@code { - private bool hasEstablishment = true; - private bool isLoading = true; - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - isLoading = true; - - var owner = await localStorage.GetAsync(Storage.Merchant); - if (owner is null) - { - isLoading = false; - return; - } - - var establishments = await storeClient.GetEstablishmentsAsync(new() { OwnerId = owner.Identifier }); - var establishment = establishments.Data?.Items.FirstOrDefault(); - - hasEstablishment = establishment is not null; - - if (hasEstablishment) - { - navigationManager.NavigateTo("/"); - return; - } - - isLoading = false; - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Pages/Products.razor b/UIs/Merchants/Source/Pages/Products.razor deleted file mode 100644 index 78350b2..0000000 --- a/UIs/Merchants/Source/Pages/Products.razor +++ /dev/null @@ -1,258 +0,0 @@ -@page "/produtos" - -@inject IStoreClient storeClient -@inject ILocalStorageGateway localStorage -@inject IDialogService dialogService -@inject ISnackbar snackbar -@inject IConfiguration configuration - -Produtos - - - -
-
- - - -
- - Produtos - - - Gerencie o cardápio do seu estabelecimento - -
-
-
- - - Adicionar Produto - -
-
-
- - - - @if (_isLoading) - { -
- -
- } - else if (_paginatedProducts is null) - { - Dados de paginação nulos - } - else if (_paginatedProducts.Items is null || !_paginatedProducts.Items.Any()) - { - - - - Nenhum produto cadastrado - - - Adicione produtos para começar a vender - - - } - else - { - - -
- -
- -
- - Mostrando @GetStartItem() até @GetEndItem() de @_paginatedProducts.Total itens - -
- } -
- -@code { - private PaginationScheme? _paginatedProducts; - private bool _isLoading = true; - private int _currentPage = 0; - private const int PageSize = 20; - private ProductsFetchParameters _currentFilters = new(); - - protected override async Task OnInitializedAsync() - { - await LoadProductsAsync(); - } - - private async Task LoadProductsAsync() - { - _isLoading = true; - StateHasChanged(); - - var establishment = await localStorage.GetAsync(Storage.Establishment); - if (establishment is null) - { - snackbar.Add("Erro ao identificar o estabelecimento", Severity.Error); - return; - } - - var parameters = _currentFilters with - { - EstablishmentId = establishment.Identifier, - Pagination = PaginationFilters.From(_currentPage + 1, PageSize) - }; - - var result = await storeClient.GetProductsAsync(parameters); - if (result.IsSuccess && result.Data is not null) - { - _paginatedProducts = result.Data; - } - else - { - snackbar.Add($"Erro ao carregar produtos ({result.Error.Code})", Severity.Error); - } - - _isLoading = false; - StateHasChanged(); - } - - private async Task HandleFiltersChanged(ProductsFetchParameters filters) - { - _currentFilters = filters; - _currentPage = 0; - - await LoadProductsAsync(); - } - - private async Task HandlePageChanged(int pageNumber) - { - _currentPage = pageNumber - 1; - await LoadProductsAsync(); - } - - private int GetStartItem() - { - if (_paginatedProducts is null || _paginatedProducts.PageNumber < 1) return 0; - return ((_paginatedProducts.PageNumber - 1) * _paginatedProducts.PageSize) + 1; - } - - private int GetEndItem() - { - if (_paginatedProducts is null) return 0; - return Math.Min(_paginatedProducts.PageNumber * _paginatedProducts.PageSize, _paginatedProducts.Total); - } - - private async Task HandleAddProduct() - { - var options = new DialogOptions - { - CloseButton = true, - CloseOnEscapeKey = true, - MaxWidth = MaxWidth.Small, - FullWidth = true, - Position = DialogPosition.Center - }; - - var dialog = await dialogService.ShowAsync("", options); - var result = await dialog.Result; - - if (result is not null && !result.Canceled) - { - await LoadProductsAsync(); - } - } - - private async Task HandleEditProduct(string productId) - { - var product = _paginatedProducts?.Items?.FirstOrDefault(product => product.Identifier == productId); - if (product is null) - { - snackbar.Add("Produto não encontrado", Severity.Error); - return; - } - - var parameters = new DialogParameters - { - { "ProductId", productId }, - { "Product", product } - }; - - var options = new DialogOptions - { - CloseButton = true, - CloseOnEscapeKey = true, - MaxWidth = MaxWidth.Small, - FullWidth = true, - Position = DialogPosition.Center - }; - - var dialog = await dialogService.ShowAsync("", parameters, options); - var result = await dialog.Result; - - if (result is not null && !result.Canceled) - { - await LoadProductsAsync(); - } - } - - private async Task HandleDeleteProduct(string productId) - { - var confirm = await dialogService.ShowMessageBox( - "Confirmar exclusão", - "Tem certeza que deseja excluir este produto?", - yesText: "Excluir", - cancelText: "Cancelar"); - - var establishment = await localStorage.GetAsync(Storage.Establishment); - if (establishment is null) - { - snackbar.Add("Erro ao identificar o estabelecimento", Severity.Error); - return; - } - - if (confirm == true) - { - var result = await storeClient.DeleteProductAsync(new() { EstablishmentId = establishment.Identifier, ProductId = productId }); - if (result.IsSuccess) - { - snackbar.Add("Produto excluído com sucesso!", Severity.Success); - - await LoadProductsAsync(); - return; - } - - snackbar.Add($"Erro ao excluir produto ({result.Error.Code})", Severity.Error); - } - } - - private string GetFullImageUrl(string imageUrl) - { - if (string.IsNullOrEmpty(imageUrl)) - return string.Empty; - - var blobUrl = configuration["Settings:Blob"]; - if (string.IsNullOrEmpty(blobUrl)) - return imageUrl; - - blobUrl = blobUrl.TrimEnd('/'); - imageUrl = imageUrl.TrimStart('/'); - - return $"{blobUrl}/{imageUrl}"; - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Pages/Settings.razor b/UIs/Merchants/Source/Pages/Settings.razor deleted file mode 100644 index 1b73a69..0000000 --- a/UIs/Merchants/Source/Pages/Settings.razor +++ /dev/null @@ -1,64 +0,0 @@ -@page "/configuracoes" - -Configurações - - - -
- - - -
- - Configurações - - - Gerencie as informações e preferências do seu estabelecimento - -
-
-
- - - - - -
- - - -
- - Zona de Perigo - - - Ações irreversíveis que afetam sua conta - -
-
- - - -
-
- - Cancelar Assinatura - - - Ao cancelar, você perderá acesso a todos os recursos do sistema - -
- - Cancelar Assinatura - -
-
-
- -@code { -} \ No newline at end of file diff --git a/UIs/Merchants/Source/Program.cs b/UIs/Merchants/Source/Program.cs deleted file mode 100644 index 93a0bf1..0000000 --- a/UIs/Merchants/Source/Program.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Comanda.Merchants.WebUI; - -internal static class Program -{ - private static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - var configuration = builder.Configuration; - - builder.RootComponents.Add("#app"); - builder.RootComponents.Add("head::after"); - - builder.Services.AddMudServices(); - builder.Services.AddInfrastructure(configuration); - - var host = builder.Build(); - - await host.RunAsync(); - } -} diff --git a/UIs/Merchants/Source/Properties/launchSettings.json b/UIs/Merchants/Source/Properties/launchSettings.json deleted file mode 100644 index 081cf7f..0000000 --- a/UIs/Merchants/Source/Properties/launchSettings.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5136", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7112;http://localhost:5136", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/UIs/Merchants/Source/_Imports.razor b/UIs/Merchants/Source/_Imports.razor deleted file mode 100644 index 2842e0a..0000000 --- a/UIs/Merchants/Source/_Imports.razor +++ /dev/null @@ -1,28 +0,0 @@ -@using System.Net.Http -@using System.Net.Http.Json - -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.AspNetCore.Components.WebAssembly.Http -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.JSInterop - -@using Comanda.Merchants.WebUI -@using Comanda.Merchants.WebUI.Pages -@using Comanda.Merchants.WebUI.Layout -@using Comanda.Merchants.WebUI.Components -@using Comanda.Merchants.WebUI.Components.Dialogs -@using Comanda.Merchants.WebUI.Forms -@using Comanda.Merchants.WebUI.Forms.Schemes -@using Comanda.Merchants.WebUI.Constants -@using Comanda.Merchants.WebUI.Contracts - -@using Comanda.Internal.Contracts.Transport.Internal.Stores -@using Comanda.Internal.Contracts.Transport.Internal.Profiles -@using Comanda.Internal.Contracts.Transport.Internal.Products -@using Comanda.Internal.Contracts.Transport.Internal - -@using HttpsRichardy.Internal.Essentials.Filtering -@using MudBlazor diff --git a/UIs/Merchants/Source/_Usings.cs b/UIs/Merchants/Source/_Usings.cs deleted file mode 100644 index 81e5d86..0000000 --- a/UIs/Merchants/Source/_Usings.cs +++ /dev/null @@ -1,37 +0,0 @@ -global using System.Text.Json; -global using System.Net.Http.Json; -global using System.ComponentModel.DataAnnotations; - -global using System.Security.Claims; -global using System.IdentityModel.Tokens.Jwt; - -global using Microsoft.JSInterop; -global using Microsoft.AspNetCore.Components.Web; -global using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -global using Microsoft.AspNetCore.Components.Authorization; - -global using Comanda.Merchants.WebUI.Extensions; -global using Comanda.Merchants.WebUI.Contracts; -global using Comanda.Merchants.WebUI.Constants; -global using Comanda.Merchants.WebUI.Gateways; -global using Comanda.Merchants.WebUI.Authentication; - -global using Comanda.Merchants.WebUI.Http.Clients; -global using Comanda.Merchants.WebUI.Http.Payloads.Identity; -global using Comanda.Merchants.WebUI.Http.Interceptors; - -global using Comanda.Internal.Contracts.Clients; -global using Comanda.Internal.Contracts.Clients.Interfaces; -global using Comanda.Internal.Contracts.Transport.Internal.Stores; -global using Comanda.Internal.Contracts.Transport.Internal.Profiles; - -global using HttpsRichardy.Federation.Sdk.Contracts.Clients; -global using HttpsRichardy.Federation.Sdk.Contracts.Errors; -global using HttpsRichardy.Federation.Sdk.Contracts.Payloads.Identity; -global using HttpsRichardy.Federation.Sdk.Contracts.Payloads.User; -global using HttpsRichardy.Federation.Sdk.Contracts.Payloads.Group; -global using HttpsRichardy.Federation.Sdk.Contracts.Payloads.Permission; - -global using HttpsRichardy.Internal.Essentials.Patterns; -global using HttpsRichardy.Internal.Essentials.Filtering; -global using MudBlazor.Services; diff --git a/UIs/Merchants/Source/wwwroot/appsettings.json b/UIs/Merchants/Source/wwwroot/appsettings.json deleted file mode 100644 index ad239d4..0000000 --- a/UIs/Merchants/Source/wwwroot/appsettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Settings": { - "Gateway": "https://sandbox-comanda-orchestrator.o1iq6t.easypanel.host/api/v1/", - "Blob": "https://sandbox-comanda-stores.o1iq6t.easypanel.host/", - "IdentityProvider": "https://sandbox-federation.o1iq6t.easypanel.host/", - "Realm": "comanda" - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/wwwroot/css/app.css b/UIs/Merchants/Source/wwwroot/css/app.css deleted file mode 100644 index 2787f51..0000000 --- a/UIs/Merchants/Source/wwwroot/css/app.css +++ /dev/null @@ -1,77 +0,0 @@ -#blazor-error-ui { - color-scheme: light only; - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - box-sizing: border-box; - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - -#blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; -} - -.blazor-error-boundary { - background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; - padding: 1rem 1rem 1rem 3.7rem; - color: white; -} - -.blazor-error-boundary::after { - content: "An error has occurred." -} - -/* Modern Loading Screen - Simple */ -.loading-container { - position: fixed; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); - z-index: 9999; -} - -.loading-dots { - display: flex; - gap: 0.75rem; -} - -.loading-dots span { - width: 1rem; - height: 1rem; - border-radius: 50%; - background-color: #EE254B; - animation: pulse 1.4s ease-in-out infinite; -} - -.loading-dots span:nth-child(1) { - animation-delay: 0s; -} - -.loading-dots span:nth-child(2) { - animation-delay: 0.2s; -} - -.loading-dots span:nth-child(3) { - animation-delay: 0.4s; -} - -@keyframes pulse { - 0%, 100% { - transform: scale(1); - opacity: 1; - } - 50% { - transform: scale(1.5); - opacity: 0.5; - } -} \ No newline at end of file diff --git a/UIs/Merchants/Source/wwwroot/favicon.png b/UIs/Merchants/Source/wwwroot/favicon.png deleted file mode 100644 index 8422b59695935d180d11d5dbe99653e711097819..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1148 zcmV-?1cUpDP)9h26h2-Cs%i*@Moc3?#6qJID|D#|3|2Hn7gTIYEkr|%Xjp);YgvFmB&0#2E2b=| zkVr)lMv9=KqwN&%obTp-$<51T%rx*NCwceh-E+=&e(oLO`@Z~7gybJ#U|^tB2Pai} zRN@5%1qsZ1e@R(XC8n~)nU1S0QdzEYlWPdUpH{wJ2Pd4V8kI3BM=)sG^IkUXF2-j{ zrPTYA6sxpQ`Q1c6mtar~gG~#;lt=s^6_OccmRd>o{*=>)KS=lM zZ!)iG|8G0-9s3VLm`bsa6e ze*TlRxAjXtm^F8V`M1%s5d@tYS>&+_ga#xKGb|!oUBx3uc@mj1%=MaH4GR0tPBG_& z9OZE;->dO@`Q)nr<%dHAsEZRKl zedN6+3+uGHejJp;Q==pskSAcRcyh@6mjm2z-uG;s%dM-u0*u##7OxI7wwyCGpS?4U zBFAr(%GBv5j$jS@@t@iI8?ZqE36I^4t+P^J9D^ELbS5KMtZ z{Qn#JnSd$15nJ$ggkF%I4yUQC+BjDF^}AtB7w348EL>7#sAsLWs}ndp8^DsAcOIL9 zTOO!!0!k2`9BLk25)NeZp7ev>I1Mn={cWI3Yhx2Q#DnAo4IphoV~R^c0x&nw*MoIV zPthX?{6{u}sMS(MxD*dmd5rU(YazQE59b|TsB5Tm)I4a!VaN@HYOR)DwH1U5y(E)z zQqQU*B%MwtRQ$%x&;1p%ANmc|PkoFJZ%<-uq%PX&C!c-7ypis=eP+FCeuv+B@h#{4 zGx1m0PjS~FJt}3mdt4c!lel`1;4W|03kcZRG+DzkTy|7-F~eDsV2Tx!73dM0H0CTh zl)F-YUkE1zEzEW(;JXc|KR5{ox%YTh{$%F$a36JP6Nb<0%#NbSh$dMYF-{ z1_x(Vx)}fs?5_|!5xBTWiiIQHG<%)*e=45Fhjw_tlnmlixq;mUdC$R8v#j( zhQ$9YR-o%i5Uc`S?6EC51!bTRK=Xkyb<18FkCKnS2;o*qlij1YA@-nRpq#OMTX&RbL<^2q@0qja!uIvI;j$6>~k@IMwD42=8$$!+R^@5o6HX(*n~v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- diff --git a/UIs/Merchants/Source/wwwroot/index.html b/UIs/Merchants/Source/wwwroot/index.html deleted file mode 100644 index c1c16b4..0000000 --- a/UIs/Merchants/Source/wwwroot/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - Comanda.Merchants.WebUI - - - - - - - - - - -
-
-
- - - -
-
-
- -
- An unhandled error has occurred. - Reload - 🗙 -
- - - - diff --git a/UIs/Merchants/components.json b/UIs/Merchants/components.json new file mode 100644 index 0000000..4cc9fbd --- /dev/null +++ b/UIs/Merchants/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/shared/components", + "utils": "@/shared/utils", + "ui": "@/shared/components/ui", + "hooks": "@/shared/hooks" + }, + "registries": {} +} diff --git a/UIs/Merchants/eslint.config.js b/UIs/Merchants/eslint.config.js new file mode 100644 index 0000000..f3664f8 --- /dev/null +++ b/UIs/Merchants/eslint.config.js @@ -0,0 +1,37 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import eslintPluginPrettier from 'eslint-plugin-prettier/recommended' +import { defineConfig, globalIgnores } from 'eslint/config' +import eslintConfigPrettier from 'eslint-config-prettier/prettier' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + eslintPluginPrettier + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + rules:{ + "react-refresh/only-export-components":"off", + "@typescript-eslint/no-explicit-any": "off", + "prettier/prettier": [ + 'error', + { + 'endOfLine': 'auto', + } + ] + } + }, + eslintConfigPrettier +]) diff --git a/UIs/Merchants/index.html b/UIs/Merchants/index.html new file mode 100644 index 0000000..8f5e756 --- /dev/null +++ b/UIs/Merchants/index.html @@ -0,0 +1,21 @@ + + + + + + + + + Administração | SmartTechInnovation + + + + +
+ + + + + diff --git a/UIs/Merchants/package.json b/UIs/Merchants/package.json new file mode 100644 index 0000000..cb43141 --- /dev/null +++ b/UIs/Merchants/package.json @@ -0,0 +1,98 @@ +{ + "name": "react-apps-template", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "test": "vitest --coverage", + "lint": "eslint .", + "preview": "vite preview", + "prepare": "husky" + }, + "lint-staged": { + "src/**/*": [ + "pnpm lint --fix", + "pnpm vitest related --run --passWithNoTests" + ] + }, + "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@hookform/resolvers": "^5.2.2", + "@microsoft/signalr": "^10.0.0", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.1.14", + "@tanstack/react-query": "^5.90.6", + "@tanstack/react-query-devtools": "^5.90.2", + "@tanstack/react-table": "^8.21.3", + "axios": "^1.13.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "embla-carousel-react": "^8.6.0", + "jwt-decode": "^4.0.0", + "lucide-react": "^0.546.0", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19.1.1", + "react-day-picker": "^9.11.1", + "react-dom": "^19.1.1", + "react-easy-crop": "^5.5.6", + "react-hook-form": "^7.65.0", + "react-router": "^7.9.4", + "recharts": "2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.14", + "vaul": "^1.1.2", + "zod": "^4.1.12", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.6.0", + "@types/react": "^19.1.16", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react-swc": "^4.1.0", + "@vitest/coverage-v8": "4.0.6", + "eslint": "^9.36.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.22", + "globals": "^16.4.0", + "husky": "^9.1.7", + "jsdom": "^27.2.0", + "lint-staged": "^16.2.6", + "prettier": "3.6.2", + "tw-animate-css": "^1.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.45.0", + "vite": "^7.1.7", + "vitest": "^4.0.6", + "vitest-localstorage-mock": "^0.1.2" + } +} diff --git a/UIs/Merchants/pnpm-lock.yaml b/UIs/Merchants/pnpm-lock.yaml new file mode 100644 index 0000000..0d195b5 --- /dev/null +++ b/UIs/Merchants/pnpm-lock.yaml @@ -0,0 +1,6288 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@dnd-kit/core': + specifier: ^6.3.1 + version: 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@dnd-kit/modifiers': + specifier: ^9.0.0 + version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) + '@dnd-kit/sortable': + specifier: ^10.0.0 + version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0) + '@dnd-kit/utilities': + specifier: ^3.2.2 + version: 3.2.2(react@19.2.0) + '@hookform/resolvers': + specifier: ^5.2.2 + version: 5.2.2(react-hook-form@7.65.0(react@19.2.0)) + '@microsoft/signalr': + specifier: ^10.0.0 + version: 10.0.0 + '@radix-ui/react-avatar': + specifier: ^1.1.10 + version: 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-label': + specifier: ^2.1.7 + version: 2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-progress': + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-radio-group': + specifier: ^1.3.8 + version: 1.3.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slider': + specifier: ^1.3.6 + version: 1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@tailwindcss/vite': + specifier: ^4.1.14 + version: 4.1.14(vite@7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1)) + '@tanstack/react-query': + specifier: ^5.90.6 + version: 5.90.6(react@19.2.0) + '@tanstack/react-query-devtools': + specifier: ^5.90.2 + version: 5.90.2(@tanstack/react-query@5.90.6(react@19.2.0))(react@19.2.0) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + axios: + specifier: ^1.13.2 + version: 1.13.2 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + embla-carousel-react: + specifier: ^8.6.0 + version: 8.6.0(react@19.2.0) + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 + lucide-react: + specifier: ^0.546.0 + version: 0.546.0(react@19.2.0) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + radix-ui: + specifier: ^1.4.3 + version: 1.4.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: + specifier: ^19.1.1 + version: 19.2.0 + react-day-picker: + specifier: ^9.11.1 + version: 9.11.1(react@19.2.0) + react-dom: + specifier: ^19.1.1 + version: 19.2.0(react@19.2.0) + react-easy-crop: + specifier: ^5.5.6 + version: 5.5.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react-hook-form: + specifier: ^7.65.0 + version: 7.65.0(react@19.2.0) + react-router: + specifier: ^7.9.4 + version: 7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + recharts: + specifier: 2.15.4 + version: 2.15.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + sonner: + specifier: ^2.0.7 + version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + tailwindcss: + specifier: ^4.1.14 + version: 4.1.14 + vaul: + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + zod: + specifier: ^4.1.12 + version: 4.1.12 + zustand: + specifier: ^5.0.11 + version: 5.0.11(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + devDependencies: + '@eslint/js': + specifier: ^9.36.0 + version: 9.37.0 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: ^24.6.0 + version: 24.8.1 + '@types/react': + specifier: ^19.1.16 + version: 19.2.2 + '@types/react-dom': + specifier: ^19.1.9 + version: 19.2.2(@types/react@19.2.2) + '@vitejs/plugin-react-swc': + specifier: ^4.1.0 + version: 4.1.0(vite@7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1)) + '@vitest/coverage-v8': + specifier: 4.0.6 + version: 4.0.6(vitest@4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1)) + eslint: + specifier: ^9.36.0 + version: 9.37.0(jiti@2.6.1) + eslint-config-prettier: + specifier: ^10.1.8 + version: 10.1.8(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-prettier: + specifier: ^5.5.4 + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))(prettier@3.6.2) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.37.0(jiti@2.6.1)) + eslint-plugin-react-refresh: + specifier: ^0.4.22 + version: 0.4.24(eslint@9.37.0(jiti@2.6.1)) + globals: + specifier: ^16.4.0 + version: 16.4.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + jsdom: + specifier: ^27.2.0 + version: 27.2.0 + lint-staged: + specifier: ^16.2.6 + version: 16.2.6 + prettier: + specifier: 3.6.2 + version: 3.6.2 + tw-animate-css: + specifier: ^1.4.0 + version: 1.4.0 + typescript: + specifier: ~5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.45.0 + version: 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + vite: + specifier: ^7.1.7 + version: 7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + vitest: + specifier: ^4.0.6 + version: 4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1) + vitest-localstorage-mock: + specifier: ^0.1.2 + version: 0.1.2(vitest@4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1)) + +packages: + + '@acemir/cssom@0.9.23': + resolution: {integrity: sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==} + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@asamuzakjp/css-color@4.0.5': + resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} + + '@asamuzakjp/dom-selector@6.7.4': + resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.16': + resolution: {integrity: sha512-2SpS4/UaWQaGpBINyG5ZuCHnUDeVByOhvbkARwfmnfxDvTaj80yOI1cD8Tw93ICV5Fx4fnyDKWQZI1CDtcWyUg==} + engines: {node: '>=18'} + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + + '@dnd-kit/accessibility@3.1.1': + resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/modifiers@9.0.0': + resolution: {integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} + peerDependencies: + react: '>=16.8.0' + + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + 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.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.0': + resolution: {integrity: sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} + 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.37.0': + resolution: {integrity: sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==} + 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.4.0': + resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@hookform/resolvers@5.2.2': + resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + peerDependencies: + react-hook-form: ^7.55.0 + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@microsoft/signalr@10.0.0': + resolution: {integrity: sha512-0BRqz/uCx3JdrOqiqgFhih/+hfTERaUfCZXFB52uMaZJrKaPRzHzMuqVsJC/V3pt7NozcNXGspjKiQEK+X7P2w==} + + '@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'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accessible-icon@1.1.7': + resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-aspect-ratio@1.1.7': + resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-avatar@1.1.10': + resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-form@0.1.8': + resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menubar@1.1.16': + resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-one-time-password-field@0.1.8': + resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-password-toggle-field@0.1.3': + resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.7': + resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-radio-group@1.3.8': + resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.3.6': + resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toast@1.2.15': + resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle-group@1.1.11': + resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toggle@1.1.10': + resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-toolbar@1.1.11': + resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.0': + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/pluginutils@1.0.0-beta.35': + resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==} + + '@rollup/rollup-android-arm-eabi@4.52.4': + resolution: {integrity: sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.52.4': + resolution: {integrity: sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.52.4': + resolution: {integrity: sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.52.4': + resolution: {integrity: sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.52.4': + resolution: {integrity: sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.52.4': + resolution: {integrity: sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + resolution: {integrity: sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.52.4': + resolution: {integrity: sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.52.4': + resolution: {integrity: sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.52.4': + resolution: {integrity: sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.52.4': + resolution: {integrity: sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.52.4': + resolution: {integrity: sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.52.4': + resolution: {integrity: sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.52.4': + resolution: {integrity: sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.52.4': + resolution: {integrity: sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.52.4': + resolution: {integrity: sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.52.4': + resolution: {integrity: sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.52.4': + resolution: {integrity: sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.52.4': + resolution: {integrity: sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.52.4': + resolution: {integrity: sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.52.4': + resolution: {integrity: sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.52.4': + resolution: {integrity: sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==} + cpu: [x64] + os: [win32] + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@swc/core-darwin-arm64@1.13.5': + resolution: {integrity: sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.13.5': + resolution: {integrity: sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.13.5': + resolution: {integrity: sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.13.5': + resolution: {integrity: sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.13.5': + resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.13.5': + resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.13.5': + resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.13.5': + resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.13.5': + resolution: {integrity: sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.13.5': + resolution: {integrity: sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.13.5': + resolution: {integrity: sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.25': + resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} + + '@tailwindcss/node@4.1.14': + resolution: {integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==} + + '@tailwindcss/oxide-android-arm64@4.1.14': + resolution: {integrity: sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.14': + resolution: {integrity: sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.14': + resolution: {integrity: sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.14': + resolution: {integrity: sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + resolution: {integrity: sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + resolution: {integrity: sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + resolution: {integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + resolution: {integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.14': + resolution: {integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.14': + resolution: {integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==} + 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.14': + resolution: {integrity: sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + resolution: {integrity: sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.14': + resolution: {integrity: sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.14': + resolution: {integrity: sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tanstack/query-core@5.90.6': + resolution: {integrity: sha512-AnZSLF26R8uX+tqb/ivdrwbVdGemdEDm1Q19qM6pry6eOZ6bEYiY7mWhzXT1YDIPTNEVcZ5kYP9nWjoxDLiIVw==} + + '@tanstack/query-devtools@5.90.1': + resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} + + '@tanstack/react-query-devtools@5.90.2': + resolution: {integrity: sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==} + peerDependencies: + '@tanstack/react-query': ^5.90.2 + react: ^18 || ^19 + + '@tanstack/react-query@5.90.6': + resolution: {integrity: sha512-gB1sljYjcobZKxjPbKSa31FUTyr+ROaBdoH+wSSs9Dk+yDCmMs+TkTV3PybRRVLC7ax7q0erJ9LvRWnMktnRAw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.8.1': + resolution: {integrity: sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q==} + + '@types/react-dom@19.2.2': + resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + + '@typescript-eslint/eslint-plugin@8.46.1': + resolution: {integrity: sha512-rUsLh8PXmBjdiPY+Emjz9NX2yHvhS11v0SR6xNJkm5GM1MO9ea/1GoDKlHHZGrOJclL/cZ2i/vRUYVtjRhrHVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.46.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.46.1': + resolution: {integrity: sha512-6JSSaBZmsKvEkbRUkf7Zj7dru/8ZCrJxAqArcLaVMee5907JdtEbKGsZ7zNiIm/UAkpGUkaSMZEXShnN2D1HZA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.46.1': + resolution: {integrity: sha512-FOIaFVMHzRskXr5J4Jp8lFVV0gz5ngv3RHmn+E4HYxSJ3DgDzU7fVI1/M7Ijh1zf6S7HIoaIOtln1H5y8V+9Zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.46.1': + resolution: {integrity: sha512-weL9Gg3/5F0pVQKiF8eOXFZp8emqWzZsOJuWRUNtHT+UNV2xSJegmpCNQHy37aEQIbToTq7RHKhWvOsmbM680A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.46.1': + resolution: {integrity: sha512-X88+J/CwFvlJB+mK09VFqx5FE4H5cXD+H/Bdza2aEWkSb8hnWIQorNcscRl4IEo1Cz9VI/+/r/jnGWkbWPx54g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.46.1': + resolution: {integrity: sha512-+BlmiHIiqufBxkVnOtFwjah/vrkF4MtKKvpXrKSPLCkCtAp8H01/VV43sfqA98Od7nJpDcFnkwgyfQbOG0AMvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.46.1': + resolution: {integrity: sha512-C+soprGBHwWBdkDpbaRC4paGBrkIXxVlNohadL5o0kfhsXqOC6GYH2S/Obmig+I0HTDl8wMaRySwrfrXVP8/pQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.46.1': + resolution: {integrity: sha512-uIifjT4s8cQKFQ8ZBXXyoUODtRoAd7F7+G8MKmtzj17+1UbdzFl52AzRyZRyKqPHhgzvXunnSckVu36flGy8cg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.46.1': + resolution: {integrity: sha512-vkYUy6LdZS7q1v/Gxb2Zs7zziuXN0wxqsetJdeZdRe/f5dwJFglmuvZBfTUivCtjH725C1jWCDfpadadD95EDQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.46.1': + resolution: {integrity: sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react-swc@4.1.0': + resolution: {integrity: sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4 || ^5 || ^6 || ^7 + + '@vitest/coverage-v8@4.0.6': + resolution: {integrity: sha512-cv6pFXj9/Otk7q1Ocoj8k3BUVVwnFr3jqcqpwYrU5LkKClU9DpaMEdX+zptx/RyIJS+/VpoxMWmfISXchmVDPQ==} + peerDependencies: + '@vitest/browser': 4.0.6 + vitest: 4.0.6 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.0.6': + resolution: {integrity: sha512-5j8UUlBVhOjhj4lR2Nt9sEV8b4WtbcYh8vnfhTNA2Kn5+smtevzjNq+xlBuVhnFGXiyPPNzGrOVvmyHWkS5QGg==} + + '@vitest/mocker@4.0.6': + resolution: {integrity: sha512-3COEIew5HqdzBFEYN9+u0dT3i/NCwppLnO1HkjGfAP1Vs3vti1Hxm/MvcbC4DAn3Szo1M7M3otiAaT83jvqIjA==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.6': + resolution: {integrity: sha512-4vptgNkLIA1W1Nn5X4x8rLJBzPiJwnPc+awKtfBE5hNMVsoAl/JCCPPzNrbf+L4NKgklsis5Yp2gYa+XAS442g==} + + '@vitest/runner@4.0.6': + resolution: {integrity: sha512-trPk5qpd7Jj+AiLZbV/e+KiiaGXZ8ECsRxtnPnCrJr9OW2mLB72Cb824IXgxVz/mVU3Aj4VebY+tDTPn++j1Og==} + + '@vitest/snapshot@4.0.6': + resolution: {integrity: sha512-PaYLt7n2YzuvxhulDDu6c9EosiRuIE+FI2ECKs6yvHyhoga+2TBWI8dwBjs+IeuQaMtZTfioa9tj3uZb7nev1g==} + + '@vitest/spy@4.0.6': + resolution: {integrity: sha512-g9jTUYPV1LtRPRCQfhbMintW7BTQz1n6WXYQYRQ25qkyffA4bjVXjkROokZnv7t07OqfaFKw1lPzqKGk1hmNuQ==} + + '@vitest/utils@4.0.6': + resolution: {integrity: sha512-bG43VS3iYKrMIZXBo+y8Pti0O7uNju3KvNn6DrQWhQQKcLavMB+0NZfO1/QBAEbq0MaQ3QjNsnnXlGQvsh0Z6A==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@7.2.0: + resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@0.3.8: + resolution: {integrity: sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@6.2.0: + resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==} + engines: {node: '>=18'} + + 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'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.1.1: + resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==} + engines: {node: '>=20'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@14.0.2: + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + engines: {node: '>=20'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@5.3.3: + resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==} + engines: {node: '>=20'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + data-urls@6.0.0: + resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + engines: {node: '>=20'} + + date-fns-jalali@4.1.0-0: + resolution: {integrity: sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==} + + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + embla-carousel-react@8.6.0: + resolution: {integrity: sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==} + peerDependencies: + react: ^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + embla-carousel-reactive-utils@8.6.0: + resolution: {integrity: sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==} + peerDependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: + resolution: {integrity: sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + 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-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + 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'} + + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@10.1.8: + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.5.4: + resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + 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-refresh@0.4.24: + resolution: {integrity: sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + 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.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.37.0: + resolution: {integrity: sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + 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'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + eventsource@2.0.2: + resolution: {integrity: sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==} + engines: {node: '>=12.0.0'} + + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.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.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-cookie@2.2.0: + resolution: {integrity: sha512-h9AgfjURuCgA2+2ISl8GbavpUdR+WGAM2McW/ovn4tVccegp8ZqCKWSBR8uRdM8dDNlx5WdKRWxBYUwteLDCNQ==} + + 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'} + + 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==} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + 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'} + + globals@16.4.0: + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} + engines: {node: '>=18'} + + 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-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + 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'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + 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'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + 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'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsdom@27.2.0: + resolution: {integrity: sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: 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==} + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + lint-staged@16.2.6: + resolution: {integrity: sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + + lucide-react@0.546.0: + resolution: {integrity: sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.1.0: + resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} + engines: {node: '>= 18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nano-spawn@2.0.0: + resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==} + engines: {node: '>=20.17'} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + normalize-wheel@1.0.1: + resolution: {integrity: sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + + 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'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + 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.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + radix-ui@1.4.3: + resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + react-day-picker@9.11.1: + resolution: {integrity: sha512-l3ub6o8NlchqIjPKrRFUCkTUEq6KwemQlfv3XZzzwpUeGwmDJ+0u0Upmt38hJyd7D/vn2dQoOoLV/qAp0o3uUw==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + peerDependencies: + react: ^19.2.0 + + react-easy-crop@5.5.6: + resolution: {integrity: sha512-Jw3/ozs8uXj3NpL511Suc4AHY+mLRO23rUgipXvNYKqezcFSYHxe4QXibBymkOoY6oOtLVMPO2HNPRHYvMPyTw==} + peerDependencies: + react: '>=16.4.0' + react-dom: '>=16.4.0' + + react-hook-form@7.65.0: + resolution: {integrity: sha512-xtOzDz063WcXvGWaHgLNrNzlsdFgtUWcb32E6WFaGTd7kPZG3EeDusjdZfUsPwKCKVXy1ZlntifaHZ4l8pAsmw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router@7.9.4: + resolution: {integrity: sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + engines: {node: '>=0.10.0'} + + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rollup@4.52.4: + resolution: {integrity: sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + 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'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + sonner@2.0.7: + resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.1.0: + resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} + engines: {node: '>=20'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + synckit@0.11.11: + resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwindcss@4.1.14: + resolution: {integrity: sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tar@7.5.1: + resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} + engines: {node: '>=18'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.17: + resolution: {integrity: sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==} + + tldts@7.0.17: + resolution: {integrity: sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tw-animate-css@1.4.0: + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.46.1: + resolution: {integrity: sha512-VHgijW803JafdSsDO8I761r3SHrgk4T00IdyQ+/UsthtgPRsBWQLqoSxOolxTpxRKi1kGXK0bSz4CoAc9ObqJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.14.0: + resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vaul@1.1.2: + resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + + vite@7.1.10: + resolution: {integrity: sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest-localstorage-mock@0.1.2: + resolution: {integrity: sha512-1oee6iDWhhquzVogssbpwQi6a2F3L+nCKF2+qqyCs5tH0sOYRyTqnsfj2dtmEQiL4xtJkHLn42hEjHGESlsJHw==} + peerDependencies: + vitest: '*' + + vitest@4.0.6: + resolution: {integrity: sha512-gR7INfiVRwnEOkCk47faros/9McCZMp5LM+OMNWGLaDBSvJxIzwjgNFufkuePBNaesGRnLmNfW+ddbUJRZn0nQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.6 + '@vitest/browser-preview': 4.0.6 + '@vitest/browser-webdriverio': 4.0.6 + '@vitest/ui': 4.0.6 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@8.0.0: + resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} + engines: {node: '>=20'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@15.1.0: + resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} + engines: {node: '>=20'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + 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: + + '@acemir/cssom@0.9.23': {} + + '@adobe/css-tools@4.4.4': {} + + '@asamuzakjp/css-color@4.0.5': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 11.2.2 + + '@asamuzakjp/dom-selector@6.7.4': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.2 + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/runtime@7.28.4': {} + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-syntax-patches-for-csstree@1.0.16': {} + + '@csstools/css-tokenizer@3.0.4': {} + + '@date-fns/tz@1.4.1': {} + + '@dnd-kit/accessibility@3.1.1(react@19.2.0)': + dependencies: + react: 19.2.0 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@19.2.0) + '@dnd-kit/utilities': 3.2.2(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + tslib: 2.8.1 + + '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@dnd-kit/utilities': 3.2.2(react@19.2.0) + react: 19.2.0 + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react@19.2.0)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@dnd-kit/utilities': 3.2.2(react@19.2.0) + react: 19.2.0 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@19.2.0)': + dependencies: + react: 19.2.0 + tslib: 2.8.1 + + '@esbuild/aix-ppc64@0.25.11': + optional: true + + '@esbuild/android-arm64@0.25.11': + optional: true + + '@esbuild/android-arm@0.25.11': + optional: true + + '@esbuild/android-x64@0.25.11': + optional: true + + '@esbuild/darwin-arm64@0.25.11': + optional: true + + '@esbuild/darwin-x64@0.25.11': + optional: true + + '@esbuild/freebsd-arm64@0.25.11': + optional: true + + '@esbuild/freebsd-x64@0.25.11': + optional: true + + '@esbuild/linux-arm64@0.25.11': + optional: true + + '@esbuild/linux-arm@0.25.11': + optional: true + + '@esbuild/linux-ia32@0.25.11': + optional: true + + '@esbuild/linux-loong64@0.25.11': + optional: true + + '@esbuild/linux-mips64el@0.25.11': + optional: true + + '@esbuild/linux-ppc64@0.25.11': + optional: true + + '@esbuild/linux-riscv64@0.25.11': + optional: true + + '@esbuild/linux-s390x@0.25.11': + optional: true + + '@esbuild/linux-x64@0.25.11': + optional: true + + '@esbuild/netbsd-arm64@0.25.11': + optional: true + + '@esbuild/netbsd-x64@0.25.11': + optional: true + + '@esbuild/openbsd-arm64@0.25.11': + optional: true + + '@esbuild/openbsd-x64@0.25.11': + optional: true + + '@esbuild/openharmony-arm64@0.25.11': + optional: true + + '@esbuild/sunos-x64@0.25.11': + optional: true + + '@esbuild/win32-arm64@0.25.11': + optional: true + + '@esbuild/win32-ia32@0.25.11': + optional: true + + '@esbuild/win32-x64@0.25.11': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0(jiti@2.6.1))': + dependencies: + eslint: 9.37.0(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.0': + dependencies: + '@eslint/core': 0.16.0 + + '@eslint/core@0.16.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.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.37.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.4.0': + dependencies: + '@eslint/core': 0.16.0 + levn: 0.4.1 + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@floating-ui/utils@0.2.10': {} + + '@hookform/resolvers@5.2.2(react-hook-form@7.65.0(react@19.2.0))': + dependencies: + '@standard-schema/utils': 0.3.0 + react-hook-form: 7.65.0(react@19.2.0) + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@microsoft/signalr@10.0.0': + dependencies: + abort-controller: 3.0.0 + eventsource: 2.0.2 + fetch-cookie: 2.2.0 + node-fetch: 2.7.0 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + + '@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 + + '@pkgr/core@0.2.9': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-context@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-id@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + aria-hidden: 1.2.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/pluginutils@1.0.0-beta.35': {} + + '@rollup/rollup-android-arm-eabi@4.52.4': + optional: true + + '@rollup/rollup-android-arm64@4.52.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.52.4': + optional: true + + '@rollup/rollup-darwin-x64@4.52.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.52.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.52.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.52.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.52.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.52.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.52.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.52.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.52.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.52.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.52.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.52.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.52.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.52.4': + optional: true + + '@standard-schema/spec@1.0.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@swc/core-darwin-arm64@1.13.5': + optional: true + + '@swc/core-darwin-x64@1.13.5': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.13.5': + optional: true + + '@swc/core-linux-arm64-gnu@1.13.5': + optional: true + + '@swc/core-linux-arm64-musl@1.13.5': + optional: true + + '@swc/core-linux-x64-gnu@1.13.5': + optional: true + + '@swc/core-linux-x64-musl@1.13.5': + optional: true + + '@swc/core-win32-arm64-msvc@1.13.5': + optional: true + + '@swc/core-win32-ia32-msvc@1.13.5': + optional: true + + '@swc/core-win32-x64-msvc@1.13.5': + optional: true + + '@swc/core@1.13.5': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.13.5 + '@swc/core-darwin-x64': 1.13.5 + '@swc/core-linux-arm-gnueabihf': 1.13.5 + '@swc/core-linux-arm64-gnu': 1.13.5 + '@swc/core-linux-arm64-musl': 1.13.5 + '@swc/core-linux-x64-gnu': 1.13.5 + '@swc/core-linux-x64-musl': 1.13.5 + '@swc/core-win32-arm64-msvc': 1.13.5 + '@swc/core-win32-ia32-msvc': 1.13.5 + '@swc/core-win32-x64-msvc': 1.13.5 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + + '@tailwindcss/node@4.1.14': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.1 + magic-string: 0.30.19 + source-map-js: 1.2.1 + tailwindcss: 4.1.14 + + '@tailwindcss/oxide-android-arm64@4.1.14': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.14': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.14': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.14': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.14': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + optional: true + + '@tailwindcss/oxide@4.1.14': + dependencies: + detect-libc: 2.1.2 + tar: 7.5.1 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-arm64': 4.1.14 + '@tailwindcss/oxide-darwin-x64': 4.1.14 + '@tailwindcss/oxide-freebsd-x64': 4.1.14 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.14 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.14 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.14 + '@tailwindcss/oxide-linux-x64-musl': 4.1.14 + '@tailwindcss/oxide-wasm32-wasi': 4.1.14 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 + + '@tailwindcss/vite@4.1.14(vite@7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1))': + dependencies: + '@tailwindcss/node': 4.1.14 + '@tailwindcss/oxide': 4.1.14 + tailwindcss: 4.1.14 + vite: 7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + + '@tanstack/query-core@5.90.6': {} + + '@tanstack/query-devtools@5.90.1': {} + + '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.6(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/query-devtools': 5.90.1 + '@tanstack/react-query': 5.90.6(react@19.2.0) + react: 19.2.0 + + '@tanstack/react-query@5.90.6(react@19.2.0)': + dependencies: + '@tanstack/query-core': 5.90.6 + react: 19.2.0 + + '@tanstack/react-table@8.21.3(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@tanstack/table-core@8.21.3': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.4 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.4 + '@testing-library/dom': 10.4.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.8.1': + dependencies: + undici-types: 7.14.0 + + '@types/react-dom@19.2.2(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@types/react@19.2.2': + dependencies: + csstype: 3.1.3 + + '@typescript-eslint/eslint-plugin@8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/type-utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + eslint: 9.37.0(jiti@2.6.1) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.1 + debug: 4.4.3 + eslint: 9.37.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.46.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) + '@typescript-eslint/types': 8.46.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.46.1': + dependencies: + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/visitor-keys': 8.46.1 + + '@typescript-eslint/tsconfig-utils@8.46.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.37.0(jiti@2.6.1) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.46.1': {} + + '@typescript-eslint/typescript-estree@8.46.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.46.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.1(typescript@5.9.3) + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/visitor-keys': 8.46.1 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.46.1 + '@typescript-eslint/types': 8.46.1 + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.46.1': + dependencies: + '@typescript-eslint/types': 8.46.1 + eslint-visitor-keys: 4.2.1 + + '@vitejs/plugin-react-swc@4.1.0(vite@7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1))': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.35 + '@swc/core': 1.13.5 + vite: 7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + transitivePeerDependencies: + - '@swc/helpers' + + '@vitest/coverage-v8@4.0.6(vitest@4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.6 + ast-v8-to-istanbul: 0.3.8 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.3.5 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.0.6': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.6 + '@vitest/utils': 4.0.6 + chai: 6.2.0 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.6(vite@7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1))': + dependencies: + '@vitest/spy': 4.0.6 + estree-walker: 3.0.3 + magic-string: 0.30.19 + optionalDependencies: + vite: 7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + + '@vitest/pretty-format@4.0.6': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.6': + dependencies: + '@vitest/utils': 4.0.6 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.6': + dependencies: + '@vitest/pretty-format': 4.0.6 + magic-string: 0.30.19 + pathe: 2.0.3 + + '@vitest/spy@4.0.6': {} + + '@vitest/utils@4.0.6': + dependencies: + '@vitest/pretty-format': 4.0.6 + tinyrainbow: 3.0.3 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + 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-escapes@7.2.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@0.3.8: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + + asynckit@0.4.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + balanced-match@1.0.2: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + chai@6.2.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chownr@3.0.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.1.1: + dependencies: + slice-ansi: 7.1.2 + string-width: 8.1.0 + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@14.0.2: {} + + concat-map@0.0.1: {} + + cookie@1.0.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + cssstyle@5.3.3: + dependencies: + '@asamuzakjp/css-color': 4.0.5 + '@csstools/css-syntax-patches-for-csstree': 1.0.16 + css-tree: 3.1.0 + + csstype@3.1.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + data-urls@6.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + + date-fns-jalali@4.1.0-0: {} + + date-fns@4.1.0: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + + deep-is@0.1.4: {} + + delayed-stream@1.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.28.4 + csstype: 3.1.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + embla-carousel-react@8.6.0(react@19.2.0): + dependencies: + embla-carousel: 8.6.0 + embla-carousel-reactive-utils: 8.6.0(embla-carousel@8.6.0) + react: 19.2.0 + + embla-carousel-reactive-utils@8.6.0(embla-carousel@8.6.0): + dependencies: + embla-carousel: 8.6.0 + + embla-carousel@8.6.0: {} + + emoji-regex@10.6.0: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@6.0.1: {} + + environment@1.1.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + 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 + + esbuild@0.25.11: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.6.1)): + dependencies: + eslint: 9.37.0(jiti@2.6.1) + + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.37.0(jiti@2.6.1)))(eslint@9.37.0(jiti@2.6.1))(prettier@3.6.2): + dependencies: + eslint: 9.37.0(jiti@2.6.1) + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + eslint-config-prettier: 10.1.8(eslint@9.37.0(jiti@2.6.1)) + + eslint-plugin-react-hooks@5.2.0(eslint@9.37.0(jiti@2.6.1)): + dependencies: + eslint: 9.37.0(jiti@2.6.1) + + eslint-plugin-react-refresh@0.4.24(eslint@9.37.0(jiti@2.6.1)): + dependencies: + eslint: 9.37.0(jiti@2.6.1) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.37.0(jiti@2.6.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.4.0 + '@eslint/core': 0.16.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.37.0 + '@eslint/plugin-kit': 0.4.0 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + event-target-shim@5.0.1: {} + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.1: {} + + eventsource@2.0.2: {} + + expect-type@1.2.2: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-equals@5.4.0: {} + + 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.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fetch-cookie@2.2.0: + dependencies: + set-cookie-parser: 2.7.1 + tough-cookie: 4.1.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-east-asian-width@1.4.0: {} + + 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-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + 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: {} + + globals@16.4.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + 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 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + husky@9.1.7: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + internmap@2.0.3: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.4.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdom@27.2.0: + dependencies: + '@acemir/cssom': 0.9.23 + '@asamuzakjp/dom-selector': 6.7.4 + cssstyle: 5.3.3 + data-urls: 6.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 15.1.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jwt-decode@4.0.0: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + lint-staged@16.2.6: + dependencies: + commander: 14.0.2 + listr2: 9.0.5 + micromatch: 4.0.8 + nano-spawn: 2.0.0 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.1 + + listr2@9.0.5: + dependencies: + cli-truncate: 5.1.1 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.2.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@11.2.2: {} + + lucide-react@0.546.0(react@19.2.0): + dependencies: + react: 19.2.0 + + lz-string@1.5.0: {} + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + math-intrinsics@1.1.0: {} + + mdn-data@2.12.2: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-function@5.0.1: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + minizlib@3.1.0: + dependencies: + minipass: 7.1.2 + + ms@2.1.3: {} + + nano-spawn@2.0.0: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + normalize-wheel@1.0.1: {} + + object-assign@4.1.1: {} + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5@8.0.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.2: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@1.1.0: {} + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + radix-ui@1.4.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + '@types/react-dom': 19.2.2(@types/react@19.2.2) + + react-day-picker@9.11.1(react@19.2.0): + dependencies: + '@date-fns/tz': 1.4.1 + date-fns: 4.1.0 + date-fns-jalali: 4.1.0-0 + react: 19.2.0 + + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react-easy-crop@5.5.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + normalize-wheel: 1.0.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + tslib: 2.8.1 + + react-hook-form@7.65.0(react@19.2.0): + dependencies: + react: 19.2.0 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + react-remove-scroll@2.7.1(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.2)(react@19.2.0) + react-style-singleton: 2.2.3(@types/react@19.2.2)(react@19.2.0) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.2)(react@19.2.0) + use-sidecar: 1.1.3(@types/react@19.2.2)(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + + react-router@7.9.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + cookie: 1.0.2 + react: 19.2.0 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.2.0(react@19.2.0) + + react-smooth@4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + fast-equals: 5.4.0 + prop-types: 15.8.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-transition-group: 4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + + react-style-singleton@2.2.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + get-nonce: 1.0.1 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + react-transition-group@4.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@babel/runtime': 7.28.4 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + react@19.2.0: {} + + recharts-scale@0.4.5: + dependencies: + decimal.js-light: 2.5.1 + + recharts@2.15.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.17.21 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.3 + victory-vendor: 36.9.2 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-from-string@2.0.2: {} + + requires-port@1.0.0: {} + + resolve-from@4.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rollup@4.52.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.52.4 + '@rollup/rollup-android-arm64': 4.52.4 + '@rollup/rollup-darwin-arm64': 4.52.4 + '@rollup/rollup-darwin-x64': 4.52.4 + '@rollup/rollup-freebsd-arm64': 4.52.4 + '@rollup/rollup-freebsd-x64': 4.52.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.52.4 + '@rollup/rollup-linux-arm-musleabihf': 4.52.4 + '@rollup/rollup-linux-arm64-gnu': 4.52.4 + '@rollup/rollup-linux-arm64-musl': 4.52.4 + '@rollup/rollup-linux-loong64-gnu': 4.52.4 + '@rollup/rollup-linux-ppc64-gnu': 4.52.4 + '@rollup/rollup-linux-riscv64-gnu': 4.52.4 + '@rollup/rollup-linux-riscv64-musl': 4.52.4 + '@rollup/rollup-linux-s390x-gnu': 4.52.4 + '@rollup/rollup-linux-x64-gnu': 4.52.4 + '@rollup/rollup-linux-x64-musl': 4.52.4 + '@rollup/rollup-openharmony-arm64': 4.52.4 + '@rollup/rollup-win32-arm64-msvc': 4.52.4 + '@rollup/rollup-win32-ia32-msvc': 4.52.4 + '@rollup/rollup-win32-x64-gnu': 4.52.4 + '@rollup/rollup-win32-x64-msvc': 4.52.4 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@7.7.3: {} + + set-cookie-parser@2.7.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + sonner@2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-argv@0.3.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + string-width@8.1.0: + dependencies: + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + + synckit@0.11.11: + dependencies: + '@pkgr/core': 0.2.9 + + tailwind-merge@3.3.1: {} + + tailwindcss@4.1.14: {} + + tapable@2.3.0: {} + + tar@7.5.1: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.1.0 + yallist: 5.0.0 + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + tldts-core@7.0.17: {} + + tldts@7.0.17: + dependencies: + tldts-core: 7.0.17 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.17 + + tr46@0.0.3: {} + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + tw-animate-css@1.4.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.46.1(@typescript-eslint/parser@8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.1(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + undici-types@7.14.0: {} + + universalify@0.2.0: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + use-callback-ref@1.3.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + use-sidecar@1.1.3(@types/react@19.2.2)(react@19.2.0): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.0 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.2 + + use-sync-external-store@1.6.0(react@19.2.0): + dependencies: + react: 19.2.0 + + vaul@1.1.2(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + victory-vendor@36.9.2: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite@7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1): + dependencies: + esbuild: 0.25.11 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.4 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.8.1 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.1 + yaml: 2.8.1 + + vitest-localstorage-mock@0.1.2(vitest@4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1)): + dependencies: + vitest: 4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1) + + vitest@4.0.6(@types/node@24.8.1)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.1)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.6 + '@vitest/mocker': 4.0.6(vite@7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.6 + '@vitest/runner': 4.0.6 + '@vitest/snapshot': 4.0.6 + '@vitest/spy': 4.0.6 + '@vitest/utils': 4.0.6 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.10(@types/node@24.8.1)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.8.1 + jsdom: 27.2.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@3.0.1: {} + + webidl-conversions@8.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@15.1.0: + dependencies: + tr46: 6.0.0 + webidl-conversions: 8.0.0 + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + ws@7.5.10: {} + + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@5.0.0: {} + + yaml@2.8.1: {} + + yocto-queue@0.1.0: {} + + zod@4.1.12: {} + + zustand@5.0.11(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + optionalDependencies: + '@types/react': 19.2.2 + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) diff --git a/UIs/Merchants/pnpm-workspace.yaml b/UIs/Merchants/pnpm-workspace.yaml new file mode 100644 index 0000000..d24d030 --- /dev/null +++ b/UIs/Merchants/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +onlyBuiltDependencies: + - '@swc/core' + - '@tailwindcss/oxide' + - esbuild + - supabase diff --git a/UIs/Merchants/pr-validation.yml b/UIs/Merchants/pr-validation.yml new file mode 100644 index 0000000..3677ad5 --- /dev/null +++ b/UIs/Merchants/pr-validation.yml @@ -0,0 +1,30 @@ +name: smarttechinnovate.administration.ui - pr validation + +trigger: + branches: + exclude: + - main + +pr: none + +pool: + vmImage: 'ubuntu-latest' + +steps: +- task: NodeTool@0 + inputs: + versionSpec: '20.x' + displayName: 'install Node.js 20.x' + +- script: npm install -g pnpm + displayName: 'install pnpm' + +- script: pnpm install + displayName: 'install project dependencies' + +- script: pnpm build + displayName: 'build Vite application' + env: + VITE_GATEWAY_API: https://smartconsig-gateway-live.victoriousocean-22a8a528.brazilsouth.azurecontainerapps.io/api/v1 + VITE_AUTH_API: https://feijuca-auth-api.victoriousocean-22a8a528.brazilsouth.azurecontainerapps.io/api/v1 + CI: true diff --git a/UIs/Merchants/production.yml b/UIs/Merchants/production.yml new file mode 100644 index 0000000..24507e6 --- /dev/null +++ b/UIs/Merchants/production.yml @@ -0,0 +1,36 @@ +name: smarttechinnovate.admin.ui - production deploy + +trigger: + branches: + include: + - main + +pool: + vmImage: 'ubuntu-latest' + +steps: +- task: NodeTool@0 + inputs: + versionSpec: '20.x' + displayName: 'install Node.js 20.x' + +- script: npm install -g pnpm + displayName: 'install pnpm' + +- script: pnpm install + displayName: 'install project dependencies' + +- script: pnpm build + displayName: 'build Vite application' + env: + VITE_GATEWAY_API: https://smartconsig-gateway-live.victoriousocean-22a8a528.brazilsouth.azurecontainerapps.io/api/v1 + VITE_AUTH_API: https://feijuca-auth-api.victoriousocean-22a8a528.brazilsouth.azurecontainerapps.io/api/v1 + CI: true + +- script: | + npm install -g netlify-cli + netlify deploy --site a7857b39-8740-4b09-b1ac-7f2a8927bc5e --auth $(NETLIFY_AUTH_TOKEN) --dir=dist --prod --message "deploy from azure pipeline $(Build.BuildNumber)" + displayName: 'Deploy to Netlify Production' + env: + NETLIFY_SITE_ID: a7857b39-8740-4b09-b1ac-7f2a8927bc5e + NETLIFY_AUTH_TOKEN: $(NETLIFY_AUTH_TOKEN) diff --git a/UIs/Merchants/public/_redirects b/UIs/Merchants/public/_redirects new file mode 100644 index 0000000..abe2ab3 --- /dev/null +++ b/UIs/Merchants/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/UIs/Merchants/src/app/app.tsx b/UIs/Merchants/src/app/app.tsx new file mode 100644 index 0000000..1748834 --- /dev/null +++ b/UIs/Merchants/src/app/app.tsx @@ -0,0 +1,14 @@ +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { AppRouters } from './routers'; +import { Toaster } from '../shared/components/ui/sonner'; +import { AppProviders } from './providers'; + +export default function App() { + return ( + + + + + + ); +} diff --git a/UIs/Merchants/src/app/config/react-query/index.tsx b/UIs/Merchants/src/app/config/react-query/index.tsx new file mode 100644 index 0000000..15cc68b --- /dev/null +++ b/UIs/Merchants/src/app/config/react-query/index.tsx @@ -0,0 +1,29 @@ +import { MutationCache, QueryClient } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; +import { toast } from 'sonner'; + +export const queryClient = new QueryClient({ + mutationCache: new MutationCache({ + onError: (error, _variables, _context, mutation) => { + if (mutation.meta?.suppressErrorToast) return; + + const customMessage = mutation.meta?.errorMessage; + const backendMessage = + error instanceof AxiosError ? error.message : 'Erro desconhecido'; + + toast.error(customMessage || `Erro na operação: ${backendMessage}`); + }, + onSuccess: (_data, _variables, _context, mutation) => { + if (mutation.meta?.successMessage) { + toast.success(mutation.meta.successMessage); + } + } + }), + + defaultOptions: { + queries: { + retry: false, + refetchOnWindowFocus: false + } + } +}); diff --git a/UIs/Merchants/src/app/config/react-query/types/tanstack-query.d.ts b/UIs/Merchants/src/app/config/react-query/types/tanstack-query.d.ts new file mode 100644 index 0000000..517d244 --- /dev/null +++ b/UIs/Merchants/src/app/config/react-query/types/tanstack-query.d.ts @@ -0,0 +1,11 @@ +import '@tanstack/react-query'; + +declare module '@tanstack/react-query' { + interface Register { + mutationMeta: { + errorMessage?: string; + successMessage?: string; + suppressErrorToast?: boolean; + }; + } +} diff --git a/UIs/Merchants/src/app/layouts/public/index.tsx b/UIs/Merchants/src/app/layouts/public/index.tsx new file mode 100644 index 0000000..adee0f9 --- /dev/null +++ b/UIs/Merchants/src/app/layouts/public/index.tsx @@ -0,0 +1,11 @@ +import { Outlet } from 'react-router'; + +export function PublicLayout() { + return ( +
+
+ +
+
+ ); +} diff --git a/UIs/Merchants/src/app/layouts/system/components/button-logout/index.tsx b/UIs/Merchants/src/app/layouts/system/components/button-logout/index.tsx new file mode 100644 index 0000000..dccc6c7 --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/components/button-logout/index.tsx @@ -0,0 +1,50 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger +} from '@/shared/components/ui/alert-dialog'; +import { LogOut } from 'lucide-react'; +import { useAuthStore } from '@/features/auth/store'; +import { DropdownMenuItem } from '@/shared/components/ui/dropdown-menu'; + +export const ButtonLogout = () => { + const logout = useAuthStore((state) => state.logout); + + return ( + + + e.preventDefault()} + className="cursor-pointer text-destructive focus:text-destructive" + > + + Encerrar sessão + + + + + + Deseja encerrar a sessão atual? + + Após esta ação, você será deslogado do sistema e redirecionado para + se autenticar novamente. + + + + + Cancelar + + + Encerrar sessão + + + + + ); +}; diff --git a/UIs/Merchants/src/app/layouts/system/components/header-menu/index.tsx b/UIs/Merchants/src/app/layouts/system/components/header-menu/index.tsx new file mode 100644 index 0000000..49cadb7 --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/components/header-menu/index.tsx @@ -0,0 +1,11 @@ +import { ToggleTheme } from '@/app/theme/toggle-theme'; +import { UserMenu } from '../user-menu'; + +export function HeaderMenu() { + return ( +
+ + +
+ ); +} diff --git a/UIs/Merchants/src/app/layouts/system/components/header/index.tsx b/UIs/Merchants/src/app/layouts/system/components/header/index.tsx new file mode 100644 index 0000000..b999eb0 --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/components/header/index.tsx @@ -0,0 +1,13 @@ + +import { HeaderMenu } from '../header-menu'; + +export const HeaderLayout = () => { + return ( +
+ + + + +
+ ); +}; diff --git a/UIs/Merchants/src/app/layouts/system/components/sidebar/index.tsx b/UIs/Merchants/src/app/layouts/system/components/sidebar/index.tsx new file mode 100644 index 0000000..585fd8b --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/components/sidebar/index.tsx @@ -0,0 +1,21 @@ +import { + Sidebar, + SidebarContent, + SidebarRail +} from '@/shared/components/ui/sidebar'; + +import { NavItems } from './nav-items'; +import { Navlinks } from './nav-links'; + +export function SystemSidebar({ + ...props +}: React.ComponentProps) { + return ( + + + + + + + ); +} diff --git a/UIs/Merchants/src/app/layouts/system/components/sidebar/nav-items.tsx b/UIs/Merchants/src/app/layouts/system/components/sidebar/nav-items.tsx new file mode 100644 index 0000000..910230c --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/components/sidebar/nav-items.tsx @@ -0,0 +1,52 @@ +import { type LucideIcon } from 'lucide-react'; +import { + SidebarGroup, + SidebarMenu, + SidebarMenuButton +} from '@/shared/components/ui/sidebar'; +import { NavLink } from 'react-router'; +import { RestrictedLink } from '@/shared/components/global/restricted-link'; + +export type NavItem = { + title: string; + url: string; + icon?: LucideIcon; + isRestricted?: boolean; +}; + +export function NavItems({ items }: { items: NavItem[] }) { + return ( + + + {items.map((item) => { + const LinkWrapper = item.isRestricted ? RestrictedLink : NavLink; + + return ( + + `relative h-full py-1.5 cursor-pointer transition-colors duration-20 flex items-center overflow-hidden outline-none select-none text-foreground hover:text-red-500 ${isActive ? ' text-red-500' : ''}` + } + > + + {item.icon && ( +
+ +
+ )} + + {item.title} + + {item.title} +
+
+ ); + })} +
+
+ ); +} diff --git a/UIs/Merchants/src/app/layouts/system/components/sidebar/nav-links.ts b/UIs/Merchants/src/app/layouts/system/components/sidebar/nav-links.ts new file mode 100644 index 0000000..b795cdf --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/components/sidebar/nav-links.ts @@ -0,0 +1,10 @@ +import { Home } from 'lucide-react'; +import type { NavItem } from './nav-items'; + +export const Navlinks: NavItem[] = [ + { + title: 'Dashboard', + url: '/dashboard', + icon: Home + }, +]; diff --git a/UIs/Merchants/src/app/layouts/system/components/user-menu/index.tsx b/UIs/Merchants/src/app/layouts/system/components/user-menu/index.tsx new file mode 100644 index 0000000..1d9d7b7 --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/components/user-menu/index.tsx @@ -0,0 +1,51 @@ +import { UsersIcon } from 'lucide-react'; +import { useAuthStore } from '@/features/auth/store'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '@/shared/components/ui/dropdown-menu'; +import { Avatar, AvatarFallback } from '@/shared/components/ui/avatar'; +import { Button } from '@/shared/components/ui/button'; +import { getInitials, mapGroupName } from '@/shared/utils'; +import { ButtonLogout } from '../button-logout'; + +export const UserMenu = () => { + const user = useAuthStore((state) => state.user); + + if (!user) return null; + + return ( + + + + + + + +
+

{user.name}

+ +

+ {user.email} +

+
+
+ + +
+
+ ); +}; diff --git a/UIs/Merchants/src/app/layouts/system/index.tsx b/UIs/Merchants/src/app/layouts/system/index.tsx new file mode 100644 index 0000000..9a22c15 --- /dev/null +++ b/UIs/Merchants/src/app/layouts/system/index.tsx @@ -0,0 +1,31 @@ +import { SidebarInset, SidebarProvider } from '@/shared/components/ui/sidebar'; +import { HeaderLayout } from './components/header'; +import { Outlet } from 'react-router'; +import { SystemSidebar } from './components/sidebar'; + +export const SystemLayout = () => { + return ( +
+ +
+ + + +
+
+ +
+
+
+
+
+
+ ); +}; diff --git a/UIs/Merchants/src/app/providers/index.tsx b/UIs/Merchants/src/app/providers/index.tsx new file mode 100644 index 0000000..d9ddfe3 --- /dev/null +++ b/UIs/Merchants/src/app/providers/index.tsx @@ -0,0 +1,12 @@ +import type { ReactNode } from 'react'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { queryClient } from '../config/react-query'; +import { ThemeProvider } from '../theme/theme-provider'; + +export const AppProviders = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ); +}; diff --git a/UIs/Merchants/src/app/routers/guards/auth-guard.tsx b/UIs/Merchants/src/app/routers/guards/auth-guard.tsx new file mode 100644 index 0000000..a756da6 --- /dev/null +++ b/UIs/Merchants/src/app/routers/guards/auth-guard.tsx @@ -0,0 +1,23 @@ +import { useEffect } from 'react'; +import { Outlet, useLocation, Navigate } from 'react-router'; +import { useAuthStore } from '@/features/auth/store'; + +export const AuthGuard = () => { + const location = useLocation(); + const status = useAuthStore((state) => state.status); + const checkSession = useAuthStore((state) => state.checkSession); + + useEffect(() => { + checkSession(); + }, [checkSession]); + + if (status === 'loading' || status === 'idle') { + return null; + } + + if (status !== 'authenticated') { + return ; + } + + return ; +}; diff --git a/UIs/Merchants/src/app/routers/guards/guest-guard.tsx b/UIs/Merchants/src/app/routers/guards/guest-guard.tsx new file mode 100644 index 0000000..eb9ce35 --- /dev/null +++ b/UIs/Merchants/src/app/routers/guards/guest-guard.tsx @@ -0,0 +1,16 @@ +import { Outlet, Navigate } from 'react-router'; +import { useAuthStore } from '@/features/auth/store'; + +export const GuestGuard = () => { + const status = useAuthStore((state) => state.status); + + if (status === 'loading' || status === 'idle') { + return null; + } + + if (status === 'authenticated') { + return ; + } + + return ; +}; diff --git a/UIs/Merchants/src/app/routers/index.tsx b/UIs/Merchants/src/app/routers/index.tsx new file mode 100644 index 0000000..3315eef --- /dev/null +++ b/UIs/Merchants/src/app/routers/index.tsx @@ -0,0 +1,62 @@ +import { createBrowserRouter, RouterProvider } from 'react-router'; +import { AuthGuard } from './guards/auth-guard'; +import { GuestGuard } from './guards/guest-guard'; +import { PublicLayout } from '../layouts/public'; + +export const routers = createBrowserRouter([ + { + element: , + children: [ + { + element: , + children: [ + { + path: '/login', + lazy: async () => { + const { LoginForm } = await import( + '@/features/auth/components/login-form' + ); + return { Component: LoginForm }; + } + } + ] + } + ] + }, + { + element: , + children: [ + { + path: '/', + lazy: async () => { + const { SystemLayout } = await import('@/app/layouts/system'); + return { Component: SystemLayout }; + }, + children: [ + { + index: true, + lazy: async () => { + const { DashboardPage } = await import( + '@/features/dashboard/pages' + ); + return { Component: DashboardPage }; + } + }, + { + path: 'dashboard', + lazy: async () => { + const { DashboardPage } = await import( + '@/features/dashboard/pages' + ); + return { Component: DashboardPage }; + } + }, + ] + } + ] + } +]); + +export function AppRouters() { + return ; +} diff --git a/UIs/Merchants/src/app/theme/theme-provider.tsx b/UIs/Merchants/src/app/theme/theme-provider.tsx new file mode 100644 index 0000000..83f3008 --- /dev/null +++ b/UIs/Merchants/src/app/theme/theme-provider.tsx @@ -0,0 +1,18 @@ +import type { ComponentProps } from 'react'; +import { ThemeProvider as NextThemesProvider } from 'next-themes'; + +export function ThemeProvider({ + children, + ...props +}: ComponentProps) { + return ( + + {children} + + ); +} diff --git a/UIs/Merchants/src/app/theme/toggle-theme.tsx b/UIs/Merchants/src/app/theme/toggle-theme.tsx new file mode 100644 index 0000000..d8d60da --- /dev/null +++ b/UIs/Merchants/src/app/theme/toggle-theme.tsx @@ -0,0 +1,23 @@ +import { useTheme } from 'next-themes'; +import { MoonIcon, SunIcon } from 'lucide-react'; +import { Button } from '../../shared/components/ui/button'; + +export function ToggleTheme() { + const { setTheme, resolvedTheme } = useTheme(); + const toggleTheme = () => + setTheme(resolvedTheme === 'dark' ? 'light' : 'dark'); + + return ( + + ); +} diff --git a/UIs/Merchants/src/features/auth/components/login-form/credentials.tsx b/UIs/Merchants/src/features/auth/components/login-form/credentials.tsx new file mode 100644 index 0000000..e12a059 --- /dev/null +++ b/UIs/Merchants/src/features/auth/components/login-form/credentials.tsx @@ -0,0 +1,70 @@ +import { + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage +} from '@/shared/components/ui/form'; + +import { Input } from '@/shared/components/ui/input'; +import {LockIcon, MailIcon } from 'lucide-react'; +import type { UseFormReturn } from 'react-hook-form'; +import type { LoginFormSchema } from './schema'; + +interface LoginFormCredentialsProps { + form: UseFormReturn; +} + +export function CredentialsFields({ + form +}: Readonly) { + return ( + <> + ( + + E-mail + +
+ + +
+
+ +
+ )} + /> + + ( + + Senha + +
+ + +
+
+ +
+ )} + /> + + ); +} diff --git a/UIs/Merchants/src/features/auth/components/login-form/header.tsx b/UIs/Merchants/src/features/auth/components/login-form/header.tsx new file mode 100644 index 0000000..b64853b --- /dev/null +++ b/UIs/Merchants/src/features/auth/components/login-form/header.tsx @@ -0,0 +1,20 @@ +import { AppLogo } from '@/shared/components/global/logo'; + +export function LoginHeader() { + return ( +
+
+ +
+ +

Faça login na sua conta.

+ +
+ Não possui uma conta?{' '} + + Contate o adminstrador + +
+
+ ); +} diff --git a/UIs/Merchants/src/features/auth/components/login-form/index.tsx b/UIs/Merchants/src/features/auth/components/login-form/index.tsx new file mode 100644 index 0000000..65799d9 --- /dev/null +++ b/UIs/Merchants/src/features/auth/components/login-form/index.tsx @@ -0,0 +1,53 @@ +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Form } from '@/shared/components/ui/form'; +import { useLogin } from '../../hooks/use-login'; +import { loginFormSchema, type LoginFormSchema } from './schema'; +import { LoginHeader } from './header'; +import { CredentialsFields } from './credentials'; +import { LoginTermsAndServices } from './terms-and-services'; +import { formDraftService } from '@/shared/services/form-draft'; +import { SubmitingButton } from '@/shared/components/global/submiting-button'; + +export function LoginForm() { + const { mutateAsync: login, isPending } = useLogin(); + + const form = useForm({ + resolver: zodResolver(loginFormSchema), + defaultValues: { + username: '', + password: '' + } + }); + + const onSubmit = (data: LoginFormSchema) => { + login({ + username: data.username, + password: data.password, + }); + + data.password = '********'; + formDraftService.write('login', data); + }; + + return ( +
+
+ +
+ +
+ + +
+
+ + +
+ + ); +} diff --git a/UIs/Merchants/src/features/auth/components/login-form/schema/index.ts b/UIs/Merchants/src/features/auth/components/login-form/schema/index.ts new file mode 100644 index 0000000..c8a083e --- /dev/null +++ b/UIs/Merchants/src/features/auth/components/login-form/schema/index.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const loginFormSchema = z.object({ + username: z.string().email('O formato do e-mail informado é inválido'), + password: z.string().min(4, 'A senha deve ter no mínimo 3 caracteres.') +}); + +export type LoginFormSchema = z.infer; diff --git a/UIs/Merchants/src/features/auth/components/login-form/terms-and-services.tsx b/UIs/Merchants/src/features/auth/components/login-form/terms-and-services.tsx new file mode 100644 index 0000000..602cce7 --- /dev/null +++ b/UIs/Merchants/src/features/auth/components/login-form/terms-and-services.tsx @@ -0,0 +1,9 @@ +export function LoginTermsAndServices() { + return ( +
+ Ao clicar em continuar, você concorda com nossos{' '} + Termos de Serviço e{' '} + Política de Privacidade. +
+ ); +} diff --git a/UIs/Merchants/src/features/auth/hooks/use-login.ts b/UIs/Merchants/src/features/auth/hooks/use-login.ts new file mode 100644 index 0000000..c81f44e --- /dev/null +++ b/UIs/Merchants/src/features/auth/hooks/use-login.ts @@ -0,0 +1,20 @@ +import { useMutation } from '@tanstack/react-query'; +import { authService } from '../services'; +import { useAuthStore } from '../store'; +import type { LoginCredentials } from '../types'; + +export const useLogin = () => { + const setSession = useAuthStore((state) => state.setSession); + + return useMutation({ + mutationFn: (credentials: LoginCredentials) => + authService.login(credentials), + meta: { + successMessage: 'Login realizado com sucesso! Redirecionando...' + }, + onSuccess: (session) => { + console.log(session) + setSession(session); + } + }); +}; diff --git a/UIs/Merchants/src/features/auth/services/index.ts b/UIs/Merchants/src/features/auth/services/index.ts new file mode 100644 index 0000000..3b5c092 --- /dev/null +++ b/UIs/Merchants/src/features/auth/services/index.ts @@ -0,0 +1,30 @@ + +import type { + AuthSession, + LoginCredentials, +} from '../types'; + + +export const authService = { + login: async (credentials: LoginCredentials): Promise => { + const now = new Date() + console.log(now) + const expiresAt = new Date(now.getFullYear(), now.getMonth(), now.getDay(), now.getHours() + 2, now.getMinutes(), now.getSeconds()) + console.log(expiresAt) + + return { + token: "TOKENT.EMPLATE.EXAMPLE", + status: "authenticated", + expiresAt: expiresAt.getTime(), + user: { + name: "User Template Example", + email: credentials.username, + id: "123456", + } + }; + }, + + logout: async () => { + return Promise.resolve(); + } +}; diff --git a/UIs/Merchants/src/features/auth/store/index.ts b/UIs/Merchants/src/features/auth/store/index.ts new file mode 100644 index 0000000..aa90179 --- /dev/null +++ b/UIs/Merchants/src/features/auth/store/index.ts @@ -0,0 +1,54 @@ +import { create } from 'zustand'; +import { persist, createJSONStorage } from 'zustand/middleware'; +import type { AuthSession } from '../types'; +import { authService } from '../services'; + +interface AuthState extends AuthSession { + setSession: (session: AuthSession) => void; + logout: () => void; + checkSession: () => void; +} + +const initialState: AuthSession = { + token: null, + user: null, + status: 'unauthenticated', + expiresAt: null +}; + +export const useAuthStore = create()( + persist( + (set, get) => ({ + ...initialState, + + setSession: (session) => { + console.log(session) + set(session); + }, + + logout: () => { + authService.logout(); + set(initialState); + }, + + checkSession: () => { + const { expiresAt, token } = get(); + console.log(expiresAt) + console.log(Date.now()) + + if (!token || !expiresAt) return; + + } + }), + { + name: 'template-app', + storage: createJSONStorage(() => localStorage), + partialize: (state) => ({ + token: state.token, + user: state.user, + expiresAt: state.expiresAt, + status: state.token ? 'authenticated' : 'unauthenticated' + }) + } + ) +); diff --git a/UIs/Merchants/src/features/auth/types/index.ts b/UIs/Merchants/src/features/auth/types/index.ts new file mode 100644 index 0000000..28793d8 --- /dev/null +++ b/UIs/Merchants/src/features/auth/types/index.ts @@ -0,0 +1,2 @@ +export * from './dtos'; +export * from './models'; diff --git a/UIs/Merchants/src/features/auth/types/models.ts b/UIs/Merchants/src/features/auth/types/models.ts new file mode 100644 index 0000000..835207c --- /dev/null +++ b/UIs/Merchants/src/features/auth/types/models.ts @@ -0,0 +1,25 @@ +export type SessionStatus = + | 'loading' + | 'authenticated' + | 'unauthenticated' + | 'expired' + | 'idle'; + +export interface LoginCredentials { + username: string; + password: string; +} + +export interface User { + id: string; + email: string; + name: string; + +} + +export interface AuthSession { + token: string | null; + expiresAt: number | null; + user: User | null; + status: SessionStatus; +} diff --git a/UIs/Merchants/src/features/dashboard/components/chart/index.tsx b/UIs/Merchants/src/features/dashboard/components/chart/index.tsx new file mode 100644 index 0000000..6e4cfb9 --- /dev/null +++ b/UIs/Merchants/src/features/dashboard/components/chart/index.tsx @@ -0,0 +1,288 @@ +'use client'; + +import * as React from 'react'; +import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts'; + +import { useIsMobile } from '@/shared/hooks/use-mobile'; +import { + Card, + CardAction, + CardContent, + CardDescription, + CardHeader, + CardTitle +} from '@/shared/components/ui/card'; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + type ChartConfig +} from '@/shared/components/ui/chart'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/shared/components/ui/select'; +import { ToggleGroup, ToggleGroupItem } from '@/shared/components/ui/toggle-group'; + +export const description = 'An interactive area chart'; + +const chartData = [ + { date: '2024-04-01', desktop: 222, mobile: 150 }, + { date: '2024-04-02', desktop: 97, mobile: 180 }, + { date: '2024-04-03', desktop: 167, mobile: 120 }, + { date: '2024-04-04', desktop: 242, mobile: 260 }, + { date: '2024-04-05', desktop: 373, mobile: 290 }, + { date: '2024-04-06', desktop: 301, mobile: 340 }, + { date: '2024-04-07', desktop: 245, mobile: 180 }, + { date: '2024-04-08', desktop: 409, mobile: 320 }, + { date: '2024-04-09', desktop: 59, mobile: 110 }, + { date: '2024-04-10', desktop: 261, mobile: 190 }, + { date: '2024-04-11', desktop: 327, mobile: 350 }, + { date: '2024-04-12', desktop: 292, mobile: 210 }, + { date: '2024-04-13', desktop: 342, mobile: 380 }, + { date: '2024-04-14', desktop: 137, mobile: 220 }, + { date: '2024-04-15', desktop: 120, mobile: 170 }, + { date: '2024-04-16', desktop: 138, mobile: 190 }, + { date: '2024-04-17', desktop: 446, mobile: 360 }, + { date: '2024-04-18', desktop: 364, mobile: 410 }, + { date: '2024-04-19', desktop: 243, mobile: 180 }, + { date: '2024-04-20', desktop: 89, mobile: 150 }, + { date: '2024-04-21', desktop: 137, mobile: 200 }, + { date: '2024-04-22', desktop: 224, mobile: 170 }, + { date: '2024-04-23', desktop: 138, mobile: 230 }, + { date: '2024-04-24', desktop: 387, mobile: 290 }, + { date: '2024-04-25', desktop: 215, mobile: 250 }, + { date: '2024-04-26', desktop: 75, mobile: 130 }, + { date: '2024-04-27', desktop: 383, mobile: 420 }, + { date: '2024-04-28', desktop: 122, mobile: 180 }, + { date: '2024-04-29', desktop: 315, mobile: 240 }, + { date: '2024-04-30', desktop: 454, mobile: 380 }, + { date: '2024-05-01', desktop: 165, mobile: 220 }, + { date: '2024-05-02', desktop: 293, mobile: 310 }, + { date: '2024-05-03', desktop: 247, mobile: 190 }, + { date: '2024-05-04', desktop: 385, mobile: 420 }, + { date: '2024-05-05', desktop: 481, mobile: 390 }, + { date: '2024-05-06', desktop: 498, mobile: 520 }, + { date: '2024-05-07', desktop: 388, mobile: 300 }, + { date: '2024-05-08', desktop: 149, mobile: 210 }, + { date: '2024-05-09', desktop: 227, mobile: 180 }, + { date: '2024-05-10', desktop: 293, mobile: 330 }, + { date: '2024-05-11', desktop: 335, mobile: 270 }, + { date: '2024-05-12', desktop: 197, mobile: 240 }, + { date: '2024-05-13', desktop: 197, mobile: 160 }, + { date: '2024-05-14', desktop: 448, mobile: 490 }, + { date: '2024-05-15', desktop: 473, mobile: 380 }, + { date: '2024-05-16', desktop: 338, mobile: 400 }, + { date: '2024-05-17', desktop: 499, mobile: 420 }, + { date: '2024-05-18', desktop: 315, mobile: 350 }, + { date: '2024-05-19', desktop: 235, mobile: 180 }, + { date: '2024-05-20', desktop: 177, mobile: 230 }, + { date: '2024-05-21', desktop: 82, mobile: 140 }, + { date: '2024-05-22', desktop: 81, mobile: 120 }, + { date: '2024-05-23', desktop: 252, mobile: 290 }, + { date: '2024-05-24', desktop: 294, mobile: 220 }, + { date: '2024-05-25', desktop: 201, mobile: 250 }, + { date: '2024-05-26', desktop: 213, mobile: 170 }, + { date: '2024-05-27', desktop: 420, mobile: 460 }, + { date: '2024-05-28', desktop: 233, mobile: 190 }, + { date: '2024-05-29', desktop: 78, mobile: 130 }, + { date: '2024-05-30', desktop: 340, mobile: 280 }, + { date: '2024-05-31', desktop: 178, mobile: 230 }, + { date: '2024-06-01', desktop: 178, mobile: 200 }, + { date: '2024-06-02', desktop: 470, mobile: 410 }, + { date: '2024-06-03', desktop: 103, mobile: 160 }, + { date: '2024-06-04', desktop: 439, mobile: 380 }, + { date: '2024-06-05', desktop: 88, mobile: 140 }, + { date: '2024-06-06', desktop: 294, mobile: 250 }, + { date: '2024-06-07', desktop: 323, mobile: 370 }, + { date: '2024-06-08', desktop: 385, mobile: 320 }, + { date: '2024-06-09', desktop: 438, mobile: 480 }, + { date: '2024-06-10', desktop: 155, mobile: 200 }, + { date: '2024-06-11', desktop: 92, mobile: 150 }, + { date: '2024-06-12', desktop: 492, mobile: 420 }, + { date: '2024-06-13', desktop: 81, mobile: 130 }, + { date: '2024-06-14', desktop: 426, mobile: 380 }, + { date: '2024-06-15', desktop: 307, mobile: 350 }, + { date: '2024-06-16', desktop: 371, mobile: 310 }, + { date: '2024-06-17', desktop: 475, mobile: 520 }, + { date: '2024-06-18', desktop: 107, mobile: 170 }, + { date: '2024-06-19', desktop: 341, mobile: 290 }, + { date: '2024-06-20', desktop: 408, mobile: 450 }, + { date: '2024-06-21', desktop: 169, mobile: 210 }, + { date: '2024-06-22', desktop: 317, mobile: 270 }, + { date: '2024-06-23', desktop: 480, mobile: 530 }, + { date: '2024-06-24', desktop: 132, mobile: 180 }, + { date: '2024-06-25', desktop: 141, mobile: 190 }, + { date: '2024-06-26', desktop: 434, mobile: 380 }, + { date: '2024-06-27', desktop: 448, mobile: 490 }, + { date: '2024-06-28', desktop: 149, mobile: 200 }, + { date: '2024-06-29', desktop: 103, mobile: 160 }, + { date: '2024-06-30', desktop: 446, mobile: 400 } +]; + +const chartConfig = { + visitors: { + label: 'Visitors' + }, + desktop: { + label: 'Desktop', + color: 'var(--chart-1)' + }, + mobile: { + label: 'Mobile', + color: 'var(--chart-2)' + } +} satisfies ChartConfig; + +export function ChartAreaInteractive() { + const isMobile = useIsMobile(); + const [timeRange, setTimeRange] = React.useState('90d'); + + React.useEffect(() => { + if (isMobile) { + setTimeRange('7d'); + } + }, [isMobile]); + + const filteredData = chartData.filter((item) => { + const date = new Date(item.date); + const referenceDate = new Date('2024-06-30'); + let daysToSubtract = 90; + if (timeRange === '30d') { + daysToSubtract = 30; + } else if (timeRange === '7d') { + daysToSubtract = 7; + } + const startDate = new Date(referenceDate); + startDate.setDate(startDate.getDate() - daysToSubtract); + return date >= startDate; + }); + + return ( + + + Total Visitors + + + Total for the last 3 months + + Last 3 months + + + + Last 3 months + Last 30 days + Last 7 days + + + + + + + + + + + + + + + + + + + { + const date = new Date(value); + return date.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric' + }); + }} + /> + { + return new Date(value).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric' + }); + }} + indicator="dot" + /> + } + /> + + + + + + + ); +} diff --git a/UIs/Merchants/src/features/dashboard/components/stats/index.tsx b/UIs/Merchants/src/features/dashboard/components/stats/index.tsx new file mode 100644 index 0000000..4ca66e5 --- /dev/null +++ b/UIs/Merchants/src/features/dashboard/components/stats/index.tsx @@ -0,0 +1,101 @@ +import { Badge } from '@/shared/components/ui/badge'; +import { + Card, + CardAction, + CardDescription, + CardFooter, + CardHeader, + CardTitle +} from '@/shared/components/ui/card'; +import { TrendingDown, TrendingUp } from 'lucide-react'; + +export function StatsCards() { + return ( +
+ + + Total Revenue + + $1,250.00 + + + + + +12.5% + + + + +
+ Trending up this month +
+
+ Visitors for the last 6 months +
+
+
+ + + New Customers + + 1,234 + + + + + -20% + + + + +
+ Down 20% this period +
+
+ Acquisition needs attention +
+
+
+ + + Active Accounts + + 45,678 + + + + + +12.5% + + + + +
+ Strong user retention +
+
Engagement exceed targets
+
+
+ + + Growth Rate + + 4.5% + + + + + +4.5% + + + + +
+ Steady performance increase +
+
Meets growth projections
+
+
+
+ ); +} diff --git a/UIs/Merchants/src/features/dashboard/pages/index.tsx b/UIs/Merchants/src/features/dashboard/pages/index.tsx new file mode 100644 index 0000000..af53c3d --- /dev/null +++ b/UIs/Merchants/src/features/dashboard/pages/index.tsx @@ -0,0 +1,17 @@ +import { PageHeader } from '@/shared/components/global/page-header'; +import { ChartAreaInteractive } from '../components/chart'; +import { StatsCards } from '../components/stats'; + +export const DashboardPage = () => { + return ( + <> + +
+ +
+ +
+
+ + ); +}; diff --git a/UIs/Merchants/src/index.css b/UIs/Merchants/src/index.css new file mode 100644 index 0000000..f668f48 --- /dev/null +++ b/UIs/Merchants/src/index.css @@ -0,0 +1,179 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + font-family: 'Poppins', sans-serif; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.13 0.028 261.692); + --card: oklch(1 0 0); + --card-foreground: oklch(0.13 0.028 261.692); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.13 0.028 261.692); + --primary: oklch(0.21 0.034 264.665); + --primary-foreground: oklch(0.985 0.002 247.839); + --secondary: oklch(0.967 0.003 264.542); + --secondary-foreground: oklch(0.21 0.034 264.665); + --muted: oklch(0.967 0.003 264.542); + --muted-foreground: oklch(0.551 0.027 264.364); + --accent: oklch(0.967 0.003 264.542); + --accent-foreground: oklch(0.21 0.034 264.665); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.928 0.006 264.531); + --input: oklch(0.928 0.006 264.531); + --ring: oklch(0.707 0.022 261.325); + --chart-1: var(--color-red-900); + --chart-2: var(--color-red-500); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0.002 247.839); + --sidebar-foreground: oklch(0.13 0.028 261.692); + --sidebar-primary: oklch(0.21 0.034 264.665); + --sidebar-primary-foreground: oklch(0.985 0.002 247.839); + --sidebar-accent: oklch(0.967 0.003 264.542); + --sidebar-accent-foreground: oklch(0.21 0.034 264.665); + --sidebar-border: oklch(0.928 0.006 264.531); + --sidebar-ring: oklch(0.707 0.022 261.325); +} + +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.92 0.004 286.32); + --primary-foreground: oklch(0.21 0.006 285.885); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.552 0.016 285.938); + --chart-1: var(--color-red-700); + --chart-2: var(--color-red-300); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.552 0.016 285.938); +} + + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + margin: 0; + font-family: 'Poppins', sans-serif; + } + :-webkit-scrollbar { + width: 10px; + height: 10px; + } + + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + background-color: #b91c1c; + border-radius: 10px; + border: 2px solid var(--background); + } + + ::-webkit-scrollbar-thumb:hover { + background-color: #991b1b; + } + + * { + scrollbar-width: thin; + scrollbar-color: #b91c1c transparent; + } + + .font-extralight, + .font-light, + .font-medium, + .font-normal { + font-family: 'Poppins', sans-serif; + } + + .font-semibold, + .font-bold, + .font-extrabold, + .font-black { + font-family: 'Montserrat', sans-serif; + } + + .font-weight-200, + .font-weight-300, + .font-weight-400 { + font-family: 'Poppins', sans-serif; + } + + .font-weight-500, + .font-weight-600, + .font-weight-700, + .font-weight-800, + .font-weight-900 { + font-family: 'Montserrat', sans-serif; + } +} \ No newline at end of file diff --git a/UIs/Merchants/src/main.tsx b/UIs/Merchants/src/main.tsx new file mode 100644 index 0000000..b5bfb2c --- /dev/null +++ b/UIs/Merchants/src/main.tsx @@ -0,0 +1,11 @@ +import './index.css'; +import App from './app/app'; + +import { createRoot } from 'react-dom/client'; +import { StrictMode } from 'react'; + +createRoot(document.getElementById('root')!).render( + + + +); diff --git a/UIs/Merchants/src/shared/components/global/datatable/hook/usetable.test.tsx b/UIs/Merchants/src/shared/components/global/datatable/hook/usetable.test.tsx new file mode 100644 index 0000000..7103830 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/hook/usetable.test.tsx @@ -0,0 +1,37 @@ +import { renderHook } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; +import { useDataTable, DataTableContext } from './usetable'; +import { type Table, type Row } from '@tanstack/react-table'; + +const MockTableInstance = { + getCoreRowModel: vi.fn(), + setPageSize: vi.fn() +} as unknown as Table; + +const MockProvider = ({ children }: { children: React.ReactNode }) => ( + , index: number) => React.ReactNode + }} + > + {children} + +); + +describe('useDataTable', () => { + it('should return the context value when used inside a DataTable provider', () => { + const { result } = renderHook(() => useDataTable<{ id: string }>(), { + wrapper: MockProvider + }); + + expect(result.current.table).toBe(MockTableInstance); + expect(result.current.renderSubRow).toBeInstanceOf(Function); + }); + + it('should throw an error when used outside of a DataTable provider (Guard Clause)', () => { + expect(() => renderHook(() => useDataTable())).toThrow( + 'useDataTable must be used within a DataTable' + ); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/datatable/hook/usetable.tsx b/UIs/Merchants/src/shared/components/global/datatable/hook/usetable.tsx new file mode 100644 index 0000000..ce3a0f5 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/hook/usetable.tsx @@ -0,0 +1,26 @@ +'use client'; + +import type { Row, Table } from '@tanstack/react-table'; +import { createContext, useContext, type ReactNode } from 'react'; + +type DataTableContextType = { + table: Table; + emptyMessage?: string; + renderSubRow?: (row: Row, index: number) => ReactNode; +}; + +export const DataTableContext = createContext< + DataTableContextType | undefined +>(undefined); + +export const useDataTable = () => { + const context = useContext( + DataTableContext as React.Context | undefined> + ); + + if (!context) { + throw new Error('useDataTable must be used within a DataTable'); + } + + return context; +}; diff --git a/UIs/Merchants/src/shared/components/global/datatable/index.test.tsx b/UIs/Merchants/src/shared/components/global/datatable/index.test.tsx new file mode 100644 index 0000000..bc3e2b2 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/index.test.tsx @@ -0,0 +1,229 @@ +import { render, screen, act, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { describe, it, expect, vi, afterEach, beforeAll } from 'vitest'; +import { + getCoreRowModel, + getPaginationRowModel, + getFilteredRowModel, + getSortedRowModel, + getExpandedRowModel, + type ColumnDef, + type TableState +} from '@tanstack/react-table'; + +import { DataTable } from './pieces/datatable'; +import { DataTableBody } from './pieces/datatable-body'; +import { PaginationControllers } from './pieces/datatable-pagination-controllers'; +import { DataTableHeader } from './pieces/datatable-header'; +import { DataTableTextFilter } from './pieces/datatable-text-filter'; +import { DataTableHeaderSortableColumn } from './pieces/datatable-header-sortable-column'; +import { DataTableDropdownColumnsVisibility } from './pieces/datatable-dropdown-column-visibility'; + +beforeAll(() => { + globalThis.ResizeObserver = class ResizeObserver { + observe() {} + unobserve() {} + disconnect() {} + }; + window.HTMLElement.prototype.scrollIntoView = vi.fn(); + window.HTMLElement.prototype.hasPointerCapture = vi.fn(); + window.HTMLElement.prototype.setPointerCapture = vi.fn(); + window.HTMLElement.prototype.releasePointerCapture = vi.fn(); +}); + +type TestData = { + id: string; + name: string; + price: number; +}; + +const mockData: TestData[] = Array.from({ length: 25 }, (_, i) => ({ + id: `id-${i}`, + name: `Produto ${i < 10 ? '0' + i : i}`, + price: (i + 1) * 10 +})); + +const columns: ColumnDef[] = [ + { + accessorKey: 'name', + header: ({ column }) => ( + + ), + meta: { nameInFilters: 'Nome do Produto' } + }, + { + accessorKey: 'price', + header: 'Preço', + cell: ({ getValue }) => `R$ ${getValue()}` + } +]; + +const IntegrationTable = ({ + data = mockData, + customEmptyMessage, + showSubRow = false, + initialState = {} +}: { + data?: TestData[]; + customEmptyMessage?: string; + showSubRow?: boolean; + initialState?: Partial; +}) => ( + ( +
+ Detalhes: {row.original.name} +
+ ) + : undefined + } + tableOptions={{ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + getExpandedRowModel: getExpandedRowModel(), + getRowCanExpand: () => true, + initialState: { + pagination: { pageSize: 10 }, + ...initialState + } + }} + > + + + + + + +
+ +
+); + +describe('DataTable Integration & Coverage', () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it('should render custom empty message', () => { + render( + + ); + expect(screen.getByText('Nada encontrado!')).toBeInTheDocument(); + }); + + it('should navigate using ALL pagination buttons (First, Prev, Next, Last)', async () => { + const user = userEvent.setup(); + render(); + + const firstBtn = screen.getByRole('button', { name: 'Primeira página' }); + const prevBtn = screen.getByRole('button', { name: 'Página anterior' }); + const nextBtn = screen.getByRole('button', { name: 'Próxima página' }); + const lastBtn = screen.getByRole('button', { name: 'Última página' }); + + expect(prevBtn).toBeDisabled(); + expect(nextBtn).toBeEnabled(); + expect(screen.getByText('Produto 00')).toBeInTheDocument(); + + await user.click(nextBtn); + expect(screen.getByText('Produto 10')).toBeInTheDocument(); + expect(prevBtn).toBeEnabled(); + + await user.click(lastBtn); + expect(screen.getByText('Produto 24')).toBeInTheDocument(); + expect(nextBtn).toBeDisabled(); + + await user.click(prevBtn); + expect(screen.getByText('Produto 10')).toBeInTheDocument(); + + await user.click(firstBtn); + expect(screen.getByText('Produto 00')).toBeInTheDocument(); + }); + + it('should filter by specific column AND global filter', async () => { + vi.useFakeTimers(); + render(); + + const globalInput = screen.getByPlaceholderText('Buscar Geral...'); + fireEvent.change(globalInput, { target: { value: 'Produto 2' } }); + + act(() => { + vi.advanceTimersByTime(500); + }); + expect(screen.getByText('Produto 20')).toBeInTheDocument(); + + const nameInput = screen.getByPlaceholderText('Filtrar Nome...'); + fireEvent.change(nameInput, { target: { value: '23' } }); + + act(() => { + vi.advanceTimersByTime(500); + }); + + expect(screen.getByText('Produto 23')).toBeInTheDocument(); + expect(screen.queryByText('Produto 20')).not.toBeInTheDocument(); + }); + + it('should sort Ascending and Descending', async () => { + const user = userEvent.setup(); + render(); + + const headerBtn = screen.getByRole('button', { name: 'Nome do Produto' }); + + await user.click(headerBtn); + await user.click(screen.getByRole('menuitem', { name: 'Desc' })); + let rows = screen.getAllByRole('row'); + expect(rows[1]).toHaveTextContent('Produto 24'); + + await user.click(headerBtn); + await user.click(screen.getByRole('menuitem', { name: 'Asc' })); + + rows = screen.getAllByRole('row'); + expect(rows[1]).toHaveTextContent('Produto 00'); + }); + + it('should render sub-row content when expanded', () => { + render( + + ); + + const subRows = screen.getAllByTestId('sub-row-content'); + + expect(subRows.length).toBeGreaterThan(0); + expect(subRows[0]).toHaveTextContent('Detalhes: Produto 00'); + }); + + it('should change page size via select', async () => { + const user = userEvent.setup(); + render(); + + const selectTrigger = screen.getByRole('combobox'); + await user.click(selectTrigger); + + const option20 = await screen.findByRole('option', { name: '20' }); + await user.click(option20); + + expect(screen.getByText('Produto 19')).toBeInTheDocument(); + expect(screen.getByText(/Página 1 de 2/i)).toBeInTheDocument(); + }); + + it('should toggle column visibility', async () => { + const user = userEvent.setup(); + render(); + + expect(screen.getByText('Preço')).toBeInTheDocument(); + + const toggleBtn = screen.getByRole('button', { name: /Exibir Colunas/i }); + await user.click(toggleBtn); + + const priceOption = screen.getByRole('menuitemcheckbox', { name: 'Preço' }); + await user.click(priceOption); + + expect(screen.queryByText('Preço')).not.toBeInTheDocument(); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/datatable/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/index.tsx new file mode 100644 index 0000000..f28973b --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/index.tsx @@ -0,0 +1,8 @@ +export { DataTable, type IDataTable } from './pieces/datatable'; +export { DataTableContent } from './pieces/datatable-content'; +export { DataTableDropdownColumnsVisibility } from './pieces/datatable-dropdown-column-visibility'; +export { PaginationControllers } from './pieces/datatable-pagination-controllers'; +export { DataTableTextFilter } from './pieces/datatable-text-filter'; +export { NestedDataTable } from './pieces/datatable-nested-table'; +export { DataTableSelectFilter } from './pieces/datatable-select-filter'; +export { DataTableDateRangeFilter } from './pieces/datatable-date-range-filter'; diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/data-table-tabs-filter/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/data-table-tabs-filter/index.tsx new file mode 100644 index 0000000..0f06501 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/data-table-tabs-filter/index.tsx @@ -0,0 +1,52 @@ +import { Tabs, TabsList, TabsTrigger } from '@/shared/components/ui/tabs'; +import { useDataTable } from '../../hook/usetable'; +import { useState } from 'react'; + +export interface IDataTableTabFilter { + column: string; + options: { + value: T; + label: string; + }[]; + initialValue: T; + onValueChange?: (value: T) => void; +} + +export function DataTableTabFilter({ + column, + options, + initialValue, + onValueChange +}: IDataTableTabFilter) { + const { table } = useDataTable(); + const [currentValue, setCurrentValue] = useState(initialValue); + const tableColumn = table.getColumn(column); + + const handleValueChange = (newValue: T) => { + tableColumn?.setFilterValue(newValue); + setCurrentValue(newValue); + onValueChange?.(newValue); + }; + + return ( + void} + className="w-auto" + > + + {options.map(({ value, label }) => { + return ( + + {label} + + ); + })} + + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-body/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-body/index.tsx new file mode 100644 index 0000000..051cdf5 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-body/index.tsx @@ -0,0 +1,47 @@ +import { Fragment, memo } from 'react'; +import { flexRender } from '@tanstack/react-table'; +import { TableBody, TableCell, TableRow } from '@/shared/components/ui/table'; +import { useDataTable } from '../../hook/usetable'; + +export function DataTableBody() { + const { table, emptyMessage, renderSubRow } = useDataTable(); + + return ( + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map((row, index) => ( + + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + {renderSubRow && row.getIsExpanded() && ( + + + {renderSubRow(row, index)} + + + )} + + )) + ) : ( + + + {emptyMessage || 'Nenhum registro disponível.'} + + + )} + + ); +} + +export const MemoizedDataTableBody = memo(DataTableBody); diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-content/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-content/index.tsx new file mode 100644 index 0000000..81387eb --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-content/index.tsx @@ -0,0 +1,33 @@ +import { useMemo } from 'react'; +import { useDataTable } from '../../hook/usetable'; +import { Table } from '@/shared/components/ui/table'; +import { DataTableHeader } from '../datatable-header'; +import { DataTableBody, MemoizedDataTableBody } from '../datatable-body'; + +export function DataTableContent() { + const { table } = useDataTable(); + const { columnSizingInfo, columnSizing } = table.getState(); + + const colSizeVariables = useMemo( + () => + table.getFlatHeaders().reduce>( + (acc, header) => ({ + ...acc, + [`--th-${header.id}-size`]: header.getSize(), + [`--col-${header.column.id}-size`]: header.column.getSize() + }), + {} + ), + + // eslint-disable-next-line react-hooks/exhaustive-deps + [columnSizing, columnSizingInfo, table.getFlatHeaders] + ); + + return ( + + + {columnSizingInfo.isResizingColumn && } + {!columnSizingInfo.isResizingColumn && } +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-date-range-filter/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-date-range-filter/index.tsx new file mode 100644 index 0000000..291541a --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-date-range-filter/index.tsx @@ -0,0 +1,92 @@ +import * as React from 'react'; +import { format } from 'date-fns'; +import { ptBR } from 'date-fns/locale'; +import { Calendar as CalendarIcon, X } from 'lucide-react'; +import { type DateRange } from 'react-day-picker'; + +import { cn } from '@/shared/utils'; +import { Button } from '@/shared/components/ui/button'; +import { Calendar } from '@/shared/components/ui/calendar'; +import { useDataTable } from '../../hook/usetable'; + +import { + Popover, + PopoverContent, + PopoverTrigger +} from '@/shared/components/ui/popover'; + +interface DataTableDateRangeFilterProps { + column: string; + title?: string; +} + +export function DataTableDateRangeFilter({ + column, + title = 'Selecione uma data' +}: DataTableDateRangeFilterProps) { + const { table } = useDataTable(); + const tableColumn = table.getColumn(column); + + const date = tableColumn?.getFilterValue() as DateRange | undefined; + + const setDate = (newDate: DateRange | undefined) => { + tableColumn?.setFilterValue(newDate); + }; + + const clearFilter = (e: React.MouseEvent) => { + e.stopPropagation(); + tableColumn?.setFilterValue(undefined); + }; + + return ( +
+ + + + + + + + +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-dropdown-column-visibility/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-dropdown-column-visibility/index.tsx new file mode 100644 index 0000000..ead64b2 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-dropdown-column-visibility/index.tsx @@ -0,0 +1,39 @@ +import { useDataTable } from '../../hook/usetable'; +import { Button } from '@/shared/components/ui/button'; +import { Eye } from 'lucide-react'; +import { + DropdownMenuCheckboxItem, + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger +} from '@/shared/components/ui/dropdown-menu'; + +export function DataTableDropdownColumnsVisibility() { + const { table } = useDataTable(); + + return ( + + + + + + {table.getAllColumns().map( + (column) => + column.getCanHide() && ( + + {column.columnDef.meta?.nameInFilters || + column.columnDef.header?.toString()} + + ) + )} + + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header-sortable-column/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header-sortable-column/index.tsx new file mode 100644 index 0000000..7a7edc4 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header-sortable-column/index.tsx @@ -0,0 +1,61 @@ +import type { Column } from '@tanstack/react-table'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger +} from '@/shared/components/ui/dropdown-menu'; +import { Button } from '@/shared/components/ui/button'; +import type { ReactNode } from 'react'; +import { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'; + +type THeaderSortableColumn = { + column: Column; + title: ReactNode | string; +}; + +export function DataTableHeaderSortableColumn({ + column, + title +}: THeaderSortableColumn) { + return ( + + + + + + {column.getCanSort() && ( + <> + column.toggleSorting(false)} + > + + Asc + + column.toggleSorting(true)} + > + + Desc + + + )} + + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header/index.tsx new file mode 100644 index 0000000..1c13269 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-header/index.tsx @@ -0,0 +1,41 @@ +import { TableHead, TableHeader, TableRow } from '@/shared/components/ui/table'; +import { useDataTable } from '../../hook/usetable'; +import { flexRender } from '@tanstack/react-table'; +import { cn } from '@/shared/utils'; + +export function DataTableHeader() { + const { table } = useDataTable(); + + return ( + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {!header.isPlaceholder && + flexRender(header.column.columnDef.header, header.getContext())} + + {header.column.getCanResize() && ( +
+ )} +
+ ))} +
+ ))} +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-nested-table/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-nested-table/index.tsx new file mode 100644 index 0000000..23368ee --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-nested-table/index.tsx @@ -0,0 +1,67 @@ +import { + useReactTable, + getCoreRowModel, + flexRender, + type ColumnDef +} from '@tanstack/react-table'; + +import { + Table, + TableBody, + TableRow, + TableCell +} from '@/shared/components/ui/table'; +import { useDataTable } from '../../hook/usetable'; + +interface TNestedDataTable { + data: TData[]; + + columns: ColumnDef[]; + + parentData?: any; +} + +export function NestedDataTable({ + data, + columns, + parentData +}: TNestedDataTable) { + const { table: parentTable } = useDataTable(); + const parentColumns = parentTable.getVisibleFlatColumns(); + + const nestedTable = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + meta: { + parentData: parentData + } + }); + + return ( + + + {nestedTable.getRowModel().rows.map((row, index) => ( + + {row.getVisibleCells().map((cell, idx) => { + const parentCol = parentColumns[idx]; + + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ); + })} + + ))} + +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-pagination-controllers/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-pagination-controllers/index.tsx new file mode 100644 index 0000000..73e38af --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-pagination-controllers/index.tsx @@ -0,0 +1,91 @@ +import { Button } from '@/shared/components/ui/button'; +import { ButtonGroup } from '@/shared/components/ui/button-group'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/shared/components/ui/select'; +import { + ChevronLeft, + ChevronRight, + ChevronsLeft, + ChevronsRight +} from 'lucide-react'; +import { useDataTable } from '../../hook/usetable'; + +export function PaginationControllers() { + const { table } = useDataTable(); + + return ( +
+
+ Resultados por página: + +
+
+ + Página {table.getState().pagination.pageIndex + 1} de{' '} + {table.getPageCount()} + +
+ + + + + + +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-select-filter/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-select-filter/index.tsx new file mode 100644 index 0000000..4a34346 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-select-filter/index.tsx @@ -0,0 +1,65 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/shared/components/ui/select'; +import { useDataTable } from '../../hook/usetable'; +import { cn } from '@/shared/utils'; + +export interface DataTableSelectFilter { + placeholder: string; + column: string; + className?: string; + options: { + value: string | number | boolean; + label: string; + }[]; +} + +export function DataTableSelectFilter({ + placeholder, + column, + className, + options +}: DataTableSelectFilter) { + const { table } = useDataTable(); + const tableColumn = table.getColumn(column); + const filterValue = tableColumn?.getFilterValue() as string; + const currentValue = filterValue ?? 'all'; + + const handleOnChangeValue = (newValue: string) => { + if (newValue === 'all') { + tableColumn?.setFilterValue(undefined); + + return; + } + + tableColumn?.setFilterValue(newValue); + }; + + return ( + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.test.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.test.tsx new file mode 100644 index 0000000..498c552 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.test.tsx @@ -0,0 +1,129 @@ +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { + render, + screen, + fireEvent, + cleanup, + act +} from '@testing-library/react'; +import { DataTableTextFilter } from '.'; + +const { mockUseDataTable, MockInput, mockSetGlobalFilter, mockSetFilterValue } = + vi.hoisted(() => { + const mockSetGlobalFilter = vi.fn(); + const mockSetFilterValue = vi.fn(); + const mockGetFilterValue = vi.fn(() => 'valor inicial'); + + const mockColumn = { + getFilterValue: mockGetFilterValue, + setFilterValue: mockSetFilterValue + }; + + const mockTable = { + getState: vi.fn(() => ({ globalFilter: '' })), + setGlobalFilter: mockSetGlobalFilter, + getColumn: vi.fn((id) => (id === 'name' ? mockColumn : undefined)) + }; + + const mockUseDataTable = vi.fn(() => ({ table: mockTable })); + + const MockInput = vi.fn((props) => ( + props.onChange && props.onChange(e)} + value={props.value ?? ''} + /> + )); + + return { + mockUseDataTable, + MockInput, + mockSetGlobalFilter, + mockSetFilterValue, + mockGetFilterValue + }; + }); + +vi.mock('../../hook/usetable', () => ({ useDataTable: mockUseDataTable })); +vi.mock('@/app/components/ui/input', () => ({ Input: MockInput })); + +describe('DataTableTextFilter', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.useFakeTimers(); + }); + + afterEach(() => { + cleanup(); + vi.useRealTimers(); + }); + + it('must call `table.setGlobalFilter` after debounce delay', async () => { + const placeholder = 'Buscar em tudo...'; + render(); + + expect(MockInput).toHaveBeenCalledWith( + expect.objectContaining({ placeholder: placeholder }), + undefined + ); + + const typedValue = 'teste global'; + + fireEvent.change(screen.getByTestId('text-filter-input'), { + target: { value: typedValue } + }); + + expect(mockSetGlobalFilter).not.toHaveBeenCalled(); + + act(() => { + vi.advanceTimersByTime(500); + }); + + expect(mockSetGlobalFilter).toHaveBeenCalledWith(typedValue); + expect(mockSetGlobalFilter).toHaveBeenCalledTimes(1); + }); + + it('must call setFilterValue for specific column after debounce delay', () => { + const placeholder = 'Buscar por nome...'; + render(); + + expect(MockInput).toHaveBeenCalledWith( + expect.objectContaining({ value: 'valor inicial' }), + undefined + ); + + const newValue = 'novo filtro'; + + fireEvent.change(screen.getByTestId('text-filter-input'), { + target: { value: newValue } + }); + + expect(mockSetFilterValue).not.toHaveBeenCalled(); + + act(() => { + vi.advanceTimersByTime(500); + }); + + expect(mockSetFilterValue).toHaveBeenCalledWith(newValue); + expect(mockSetGlobalFilter).not.toHaveBeenCalled(); + }); + + it('should fail silently if the column is not found', () => { + const placeholder = 'Coluna Inexistente'; + + render( + + ); + + fireEvent.change(screen.getByTestId('text-filter-input'), { + target: { value: 'teste' } + }); + + act(() => { + vi.advanceTimersByTime(500); + }); + + expect(mockSetFilterValue).not.toHaveBeenCalled(); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.tsx new file mode 100644 index 0000000..8ac0aad --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable-text-filter/index.tsx @@ -0,0 +1,55 @@ +import { Input } from '@/shared/components/ui/input'; +import { useDataTable } from '../../hook/usetable'; +import { Search } from 'lucide-react'; +import { useMemo, useState } from 'react'; +import { cn, debounce } from '@/shared/utils'; + +export interface IDataTableTextFilter { + placeholder: string; + column?: string; + className?: string; +} + +export function DataTableTextFilter({ + placeholder, + column, + className +}: IDataTableTextFilter) { + const { table } = useDataTable(); + const [localValue, setLocalValue] = useState( + () => + ((column + ? table.getColumn(column)?.getFilterValue() + : table.getState().globalFilter) as string) ?? '' + ); + + const updateTableFilter = useMemo( + () => + debounce((value: string) => { + if (column) { + table.getColumn(column)?.setFilterValue(value); + } else { + table.setGlobalFilter(value); + } + }, 1000), + [table, column] + ); + + const onChange = (event: React.ChangeEvent) => { + const newValue = event.target.value; + setLocalValue(newValue); + updateTableFilter(newValue); + }; + + return ( +
+ + +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable/index.tsx b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable/index.tsx new file mode 100644 index 0000000..0b31b7b --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/pieces/datatable/index.tsx @@ -0,0 +1,28 @@ +import type { ReactNode } from 'react'; +import { + useReactTable, + type Row, + type TableOptions +} from '@tanstack/react-table'; +import { DataTableContext } from '../../hook/usetable'; + +export interface IDataTable { + tableOptions: TableOptions; + emptyMessage?: string; + renderSubRow?: (row: Row, index: number) => ReactNode; + children?: ReactNode; +} +export function DataTable({ + tableOptions, + emptyMessage, + renderSubRow, + children +}: IDataTable) { + const table = useReactTable(tableOptions); + + return ( + + {children} + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/types/table.d.ts b/UIs/Merchants/src/shared/components/global/datatable/types/table.d.ts new file mode 100644 index 0000000..2e4cbc6 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/types/table.d.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-object-type */ +import '@tanstack/react-table'; + +interface InventtoTableMeta { + nameInFilters?: string; + parentData?: TParentData; +} + +declare module '@tanstack/react-table' { + interface ColumnMeta + extends InventtoTableMeta {} + + interface TableMeta extends InventtoTableMeta {} +} diff --git a/UIs/Merchants/src/shared/components/global/datatable/utils/index.ts b/UIs/Merchants/src/shared/components/global/datatable/utils/index.ts new file mode 100644 index 0000000..40541ff --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/datatable/utils/index.ts @@ -0,0 +1,21 @@ +import type { Row } from '@tanstack/react-table'; + +export const dateRangeFilter = ( + row: Row, + columnId: string, + value: any +) => { + const dateValue = row.getValue(columnId); + const { from, to } = value || {}; + + if (!from && !to) return true; + if (!dateValue) return false; + + const rowDate = new Date(dateValue as string | number); + + if (to) { + return rowDate >= from && rowDate <= to; + } + + return rowDate >= from; +}; diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/hook/use-drag-table.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/hook/use-drag-table.tsx new file mode 100644 index 0000000..e69de29 diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/index.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/index.tsx new file mode 100644 index 0000000..56fd8de --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/index.tsx @@ -0,0 +1,6 @@ +export * from './pieces/drag-handle'; +export * from './pieces/drag-table'; +export * from './pieces/drag-table-body'; +export * from './pieces/drag-table-content'; +export * from './pieces/drag-table-row'; +export * from './utils'; diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-handle/index.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-handle/index.tsx new file mode 100644 index 0000000..a23f4ac --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-handle/index.tsx @@ -0,0 +1,20 @@ +import { useSortable } from '@dnd-kit/sortable'; +import { Button } from '@/shared/components/ui/button'; +import { GripVertical } from 'lucide-react'; + +export function DragHandle({ id }: { id: string | number }) { + const { attributes, listeners } = useSortable({ id }); + + return ( + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-body/index.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-body/index.tsx new file mode 100644 index 0000000..1b9cb93 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-body/index.tsx @@ -0,0 +1,36 @@ +import { TableBody, TableCell, TableRow } from '@/shared/components/ui/table'; +import { + SortableContext, + verticalListSortingStrategy +} from '@dnd-kit/sortable'; + +import { useDataTable } from '../../../datatable/hook/usetable'; +import { DragTableRow } from '../drag-table-row'; + +export function DragTableBody() { + const { table, emptyMessage } = useDataTable(); + const { rows } = table.getRowModel(); + + const dataIds = rows.map((row) => row.id); + + return ( + + {rows.length > 0 ? ( + + {rows.map((row) => ( + + ))} + + ) : ( + + + {emptyMessage || 'Nenhum registro disponível.'} + + + )} + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-content/index.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-content/index.tsx new file mode 100644 index 0000000..9153be1 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-content/index.tsx @@ -0,0 +1,31 @@ +import { useMemo } from 'react'; +import { Table } from '@/shared/components/ui/table'; +import { useDataTable } from '../../../datatable/hook/usetable'; +import { DataTableHeader } from '../../../datatable/pieces/datatable-header'; +import { DragTableBody } from '../drag-table-body'; + +export function DragTableContent() { + const { table } = useDataTable(); + const { columnSizingInfo, columnSizing } = table.getState(); + + const colSizeVariables = useMemo( + () => + table.getFlatHeaders().reduce>( + (acc, header) => ({ + ...acc, + [`--th-${header.id}-size`]: header.getSize(), + [`--col-${header.column.id}-size`]: header.column.getSize() + }), + {} + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [columnSizing, columnSizingInfo, table.getFlatHeaders] + ); + + return ( + + + +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-row/index.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-row/index.tsx new file mode 100644 index 0000000..a03666d --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table-row/index.tsx @@ -0,0 +1,44 @@ +import { TableCell, TableRow } from '@/shared/components/ui/table'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { flexRender, type Row } from '@tanstack/react-table'; +import { type CSSProperties } from 'react'; + +interface DragTableRowProps { + row: Row; +} + +export function DragTableRow({ row }: DragTableRowProps) { + const { transform, transition, setNodeRef, isDragging } = useSortable({ + id: row.id + }); + + const style: CSSProperties = { + transform: CSS.Transform.toString(transform), + transition: transition, + position: 'relative', + zIndex: isDragging ? 10 : 0, + opacity: isDragging ? 0.6 : 1 + }; + + return ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table/index.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table/index.tsx new file mode 100644 index 0000000..b8eb205 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/pieces/drag-table/index.tsx @@ -0,0 +1,49 @@ +import { type ReactNode } from 'react'; +import { useReactTable, type TableOptions } from '@tanstack/react-table'; +import { + closestCenter, + DndContext, + type DragEndEvent, + KeyboardSensor, + MouseSensor, + TouchSensor, + useSensor, + useSensors +} from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { DataTableContext } from '../../../datatable/hook/usetable'; + +export interface IDragTable { + tableOptions: TableOptions; + onDragEnd: (event: DragEndEvent) => void; + emptyMessage?: string; + children?: ReactNode; +} + +export function DragTable({ + tableOptions, + onDragEnd, + emptyMessage, + children +}: IDragTable) { + const table = useReactTable(tableOptions); + + const sensors = useSensors( + useSensor(MouseSensor, {}), + useSensor(TouchSensor, {}), + useSensor(KeyboardSensor, {}) + ); + + return ( + + + {children} + + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/types/index.ts b/UIs/Merchants/src/shared/components/global/drag-datatable/types/index.ts new file mode 100644 index 0000000..1bb0d12 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/types/index.ts @@ -0,0 +1,11 @@ +import type { ColumnDef, Row } from '@tanstack/react-table'; +import type { ReactNode } from 'react'; + +export interface DragTableProps { + data: TData[]; + columns: ColumnDef[]; + onReorder: (newData: TData[]) => void; + renderDragHandle?: (row: Row) => ReactNode; + emptyMessage?: string; + children?: ReactNode; +} diff --git a/UIs/Merchants/src/shared/components/global/drag-datatable/utils/index.tsx b/UIs/Merchants/src/shared/components/global/drag-datatable/utils/index.tsx new file mode 100644 index 0000000..5ca0074 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/drag-datatable/utils/index.tsx @@ -0,0 +1,37 @@ +import { type ColumnDef } from '@tanstack/react-table'; +import { DragHandle } from '../pieces/drag-handle'; +import { + Tooltip, + TooltipContent, + TooltipTrigger +} from '@/shared/components/ui/tooltip'; + +/** + * Prepend the drag handle column to the existing columns definition. + * @param columns The original columns definition. + * @returns A new array of columns with the drag handle as the first column. + */ +export function getDragTableColumns( + columns: ColumnDef[] +): ColumnDef[] { + const dragColumn: ColumnDef = { + id: 'drag', + header: () => null, + cell: ({ row }) => ( + + + + + +

Arraste para alterar a prioridade do banco

+
+
+ ), + size: 50, + enableSorting: false, + enableHiding: false, + enableResizing: false + }; + + return [dragColumn, ...columns]; +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/hooks/index.test.tsx b/UIs/Merchants/src/shared/components/global/file-picker/hooks/index.test.tsx new file mode 100644 index 0000000..ddfe4fa --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/hooks/index.test.tsx @@ -0,0 +1,40 @@ +import { renderHook } from '@testing-library/react'; +import { vi, describe, it, expect } from 'vitest'; +import { useFilePickerContext, FilePickerContext } from '.'; + +const mockContextValue = [ + { errors: ['erro teste'], files: [{ id: 'a', name: 'a.jpg' }] }, + { + addFiles: vi.fn(), + removeFile: vi.fn(), + clearFiles: vi.fn(), + setPrimaryFile: vi.fn(), + clearErrors: vi.fn(), + handleFileChange: vi.fn(), + openFileDialog: vi.fn(), + getInputProps: vi.fn() + } +]; + +const MockProvider = ({ children }: { children: React.ReactNode }) => ( + + {children} + +); + +describe('useFilePickerContext', () => { + it('should return the context value when used inside the provider (Happy Path)', () => { + const { result } = renderHook(() => useFilePickerContext(), { + wrapper: MockProvider + }); + + expect(result.current[0].errors).toEqual(['erro teste']); + expect(result.current[1].addFiles).toBe(mockContextValue[1].addFiles); + }); + + it('should throw an error when used outside of the provider (Guard Clause)', () => { + expect(() => renderHook(() => useFilePickerContext())).toThrow( + 'useFilePickerContext deve ser usado dentro de ' + ); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/file-picker/hooks/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/hooks/index.tsx new file mode 100644 index 0000000..975dbba --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/hooks/index.tsx @@ -0,0 +1,27 @@ +import { createContext, useContext } from 'react'; +import type { + FilePickerActions, + FilePickerState, + FileWithPreview +} from '../types'; + +type FilePickerContextType = [ + state: FilePickerState & { files: FileWithPreview[] }, + actions: FilePickerActions +]; + +export const FilePickerContext = createContext( + null +); + +export function useFilePickerContext() { + const context = useContext(FilePickerContext); + + if (!context) { + throw new Error( + 'useFilePickerContext deve ser usado dentro de ' + ); + } + + return context; +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.test.ts b/UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.test.ts new file mode 100644 index 0000000..d5264a6 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.test.ts @@ -0,0 +1,251 @@ +import { renderHook, act } from '@testing-library/react'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { useFilePicker } from './use-file-picker'; + +const mockFileA = { + id: 'a', + name: 'A.jpg', + size: 100, + src: 'blob:a', + file: new File([''], 'A.jpg'), + isPrimary: true +}; + +const mockFileB = { + id: 'b', + name: 'B.png', + size: 50, + src: 'blob:b', + file: new File([''], 'B.png'), + isPrimary: false +}; + +const mockFileC = { + id: 'c', + name: 'C.gif', + size: 200, + src: 'blob:c', + file: new File([''], 'C.gif'), + isPrimary: false +}; + +const { mockProcessNewFiles } = vi.hoisted(() => { + const mockProcessNewFiles = vi.fn(); + + return { + mockProcessNewFiles + }; +}); + +vi.mock('../utils/', () => ({ + processNewFiles: mockProcessNewFiles +})); + +globalThis.URL.revokeObjectURL = vi.fn(); + +describe('useFilePicker', () => { + const mockOnFilesChange = vi.fn(); + const mockOnFilesAdded = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + const renderMultiHook = (initialFiles = [], options = {}) => { + return renderHook(() => + useFilePicker({ + files: initialFiles as any, + onFilesChange: mockOnFilesChange, + onFilesAdded: mockOnFilesAdded, + maxFiles: 5, + maxSize: 1000, + multiple: true, + accept: 'image/*', + ...options + }) + ); + }; + + describe('addFiles / handleFileChange (Array & Validation Logic)', () => { + it('should ignore adding files if the list is null or empty', () => { + const { result } = renderMultiHook(); + const { addFiles } = result.current[1]; + + addFiles(null as any); + addFiles([] as any); + + expect(mockProcessNewFiles).not.toHaveBeenCalled(); + expect(mockOnFilesChange).not.toHaveBeenCalled(); + }); + + it('should call clearFiles and replace array when "multiple" is false', () => { + const { result } = renderMultiHook([mockFileA] as never[], { + multiple: false + }); + const { addFiles } = result.current[1]; + + mockProcessNewFiles.mockReturnValue({ + validFiles: [mockFileB], + errors: [] + }); + + act(() => { + addFiles([mockFileB.file] as any); + }); + + expect(mockOnFilesChange).toHaveBeenCalledWith([mockFileB]); + }); + + it('should concatenate files when "multiple" is true', () => { + const { result } = renderMultiHook([mockFileA] as never[], { + multiple: true + }); + const { addFiles } = result.current[1]; + + mockProcessNewFiles.mockReturnValue({ + validFiles: [mockFileB], + errors: [] + }); + + act(() => { + addFiles([mockFileB.file] as any); + }); + + expect(mockOnFilesChange).toHaveBeenCalledWith([mockFileA, mockFileB]); + }); + + it('should set error and NOT call onFilesChange if maxFiles limit is exceeded', () => { + const initialFiles = [mockFileA, mockFileB, mockFileA, mockFileB]; + + const { result } = renderMultiHook(initialFiles as never[], { + maxFiles: 5, + multiple: true + }); + const { addFiles } = result.current[1]; + + act(() => { + addFiles([mockFileC.file, mockFileC.file] as any); + }); + + expect(result.current[0].errors).toEqual([ + 'Você pode enviar no máximo 5 arquivos.' + ]); + + expect(mockProcessNewFiles).not.toHaveBeenCalled(); + expect(mockOnFilesChange).not.toHaveBeenCalled(); + }); + + it('should call onFilesAdded if validFiles exist', () => { + const { result } = renderMultiHook(); + const { addFiles } = result.current[1]; + + mockProcessNewFiles.mockReturnValue({ + validFiles: [mockFileA], + errors: [] + }); + + act(() => { + addFiles([mockFileA.file] as any); + }); + + expect(mockOnFilesAdded).toHaveBeenCalledWith([mockFileA]); + }); + }); + + describe('removeFile (Revocation & Primary Rollover)', () => { + it('should remove file and revokeObjectURL for existing blob URLs', () => { + const { result } = renderMultiHook([mockFileA, mockFileB] as never[]); + const { removeFile } = result.current[1]; + const expectedRolloverFiles = [{ ...mockFileB, isPrimary: true }]; + + act(() => { + removeFile('a'); + }); + + expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:a'); + expect(mockOnFilesChange).toHaveBeenCalledWith(expectedRolloverFiles); + }); + + it('should assign isPrimary to the next file if the primary file is removed', () => { + const { result } = renderMultiHook([mockFileA, mockFileB] as never[]); + const { removeFile } = result.current[1]; + + act(() => { + removeFile('a'); + }); + + const expectedNewFiles = [{ ...mockFileB, isPrimary: true }]; + + expect(mockOnFilesChange).toHaveBeenCalledWith(expectedNewFiles); + }); + + it('should NOT assign isPrimary if another primary file exists', () => { + const mockFileCPrimary = { ...mockFileC, isPrimary: true }; + const initialFiles = [mockFileA, mockFileB, mockFileCPrimary]; + + const { result } = renderMultiHook(initialFiles as never[]); + + const { removeFile } = result.current[1]; + + act(() => { + removeFile('a'); + }); + + expect(mockOnFilesChange).toHaveBeenCalledWith([ + mockFileB, + mockFileCPrimary + ]); + }); + }); + + describe('setPrimaryFile (Sorting)', () => { + it('should mark the selected file as primary and move it to the first position', () => { + //@ts-expect-error mockFileA + const { result } = renderMultiHook([mockFileA, mockFileB]); + const { setPrimaryFile } = result.current[1]; + + act(() => { + setPrimaryFile('b'); + }); + + const expectedFiles = [ + { ...mockFileB, isPrimary: true }, + { ...mockFileA, isPrimary: false } + ]; + + expect(mockOnFilesChange).toHaveBeenCalledWith(expectedFiles); + }); + }); + + describe('clearFiles', () => { + it('should call revokeObjectURL for all files and reset state', () => { + //@ts-expect-error mockFileA + const { result } = renderMultiHook([mockFileA, mockFileB]); + const { clearFiles } = result.current[1]; + + act(() => { + clearFiles(); + }); + + expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:a'); + expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:b'); + expect(mockOnFilesChange).toHaveBeenCalledWith([]); + expect(result.current[0].errors).toEqual([]); + }); + }); + + describe('getInputProps', () => { + it('should correctly configure input props and wire the onChange handler', () => { + const { result } = renderMultiHook(); + const { getInputProps } = result.current[1]; + const inputProps = getInputProps({ className: 'custom' }); + + expect(inputProps.type).toBe('file'); + expect(inputProps.accept).toBe('image/*'); + expect(inputProps.multiple).toBe(true); + expect(inputProps.className).toBe('custom'); + expect(inputProps.onChange).toBeInstanceOf(Function); + expect(inputProps.ref.current).toBeDefined(); + }); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.ts b/UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.ts new file mode 100644 index 0000000..731ba7f --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/hooks/use-file-picker.ts @@ -0,0 +1,193 @@ +'use client'; + +import { + useCallback, + useRef, + useState, + type ChangeEvent, + type InputHTMLAttributes +} from 'react'; + +import type { + FilePickerActions, + FilePickerOptions, + FilePickerState +} from '../types'; + +import { processNewFiles } from '../utils'; + +export const useFilePicker = ( + options: FilePickerOptions +): [FilePickerState, FilePickerActions] => { + const { + files, + onFilesChange, + onFilesAdded, + maxFiles = Infinity, + maxSize = Infinity, + accept = '*', + multiple = false + } = options; + + const [errors, setErrors] = useState([]); + + const inputRef = useRef(null); + + const clearFiles = useCallback(() => { + files.forEach((file) => { + if (file.url && file.file instanceof File) { + URL.revokeObjectURL(file.url); + } + }); + + if (inputRef.current) { + inputRef.current.value = ''; + } + + onFilesChange([]); + setErrors([]); + }, [onFilesChange, files]); + + const addFiles = useCallback( + (newFiles: FileList | File[]) => { + if (!newFiles || newFiles.length === 0) return; + + const newFilesArray = Array.from(newFiles); + setErrors([]); + + if (!multiple) { + clearFiles(); + } + + if ( + multiple && + maxFiles !== Infinity && + files.length + newFilesArray.length > maxFiles + ) { + setErrors([`Você pode enviar no máximo ${maxFiles} arquivos.`]); + return; + } + + const { validFiles, errors: validationErrors } = processNewFiles( + newFilesArray, + files, + { maxSize, accept, multiple } + ); + setErrors(validationErrors); + + if (validFiles.length > 0) { + onFilesAdded?.(validFiles); + + const updatedFiles = !multiple ? validFiles : [...files, ...validFiles]; + + onFilesChange(updatedFiles); + } + + if (inputRef.current) { + inputRef.current.value = ''; + } + }, + [ + files, + maxFiles, + multiple, + maxSize, + accept, + clearFiles, + onFilesChange, + onFilesAdded + ] + ); + + const removeFile = useCallback( + (id: string) => { + const fileToRemove = files.find((file) => file.id === id); + if ( + fileToRemove && + fileToRemove.url && + fileToRemove.file instanceof File + ) { + URL.revokeObjectURL(fileToRemove.url); + } + + const newFiles = files.filter((file) => file.id !== id); + + if ( + fileToRemove?.isPrimary && + newFiles.length > 0 && + !newFiles.some((f) => f.isPrimary) + ) { + newFiles[0] = { ...newFiles[0], isPrimary: true }; + } + + onFilesChange(newFiles); + }, + [files, onFilesChange] + ); + + const setPrimaryFile = useCallback( + (fileId: string) => { + const newFiles = + files?.map((file) => ({ + ...file, + isPrimary: file.id === fileId + })) || []; + + newFiles.sort((a, b) => { + if (a.isPrimary === true) return -1; + if (b.isPrimary === true) return 1; + return 0; + }); + + onFilesChange(newFiles); + }, + [files, onFilesChange] + ); + + const clearErrors = useCallback(() => { + setErrors([]); + }, []); + + const handleFileChange = useCallback( + (e: ChangeEvent) => { + if (e.target.files && e.target.files.length > 0) { + addFiles(e.target.files); + } + }, + [addFiles] + ); + + const openFileDialog = useCallback(() => { + if (inputRef.current) { + inputRef.current.click(); + } + }, []); + + const getInputProps = useCallback( + (props: InputHTMLAttributes = {}) => { + return { + ...props, + type: 'file' as const, + onChange: handleFileChange, + accept: props.accept || accept, + multiple: props.multiple !== undefined ? props.multiple : multiple, + ref: inputRef + }; + }, + [accept, multiple, handleFileChange] + ); + + const state: FilePickerState = { errors }; + const actions: FilePickerActions = { + addFiles, + removeFile, + clearFiles, + setPrimaryFile, + clearErrors, + handleFileChange, + openFileDialog, + getInputProps + }; + + return [state, actions]; +}; diff --git a/UIs/Merchants/src/shared/components/global/file-picker/index.test.tsx b/UIs/Merchants/src/shared/components/global/file-picker/index.test.tsx new file mode 100644 index 0000000..207d749 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/index.test.tsx @@ -0,0 +1,265 @@ +import React from 'react'; +import { describe, it, expect, vi, beforeAll } from 'vitest'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { + FilePicker, + FilePickerContent, + FilePickerInput, + FilePickerHeader, + FilePickerEmpty, + FilePickerError, + FilePickerButton, + FilePickerRemoveAllButton, + FilePickerDrag, + FilePickerCount, + FilePickerAddMoreButton +} from '.'; + +vi.mock('@/app/services/image-upload/utils', () => ({ + createCloudinaryThumbnail: (publicId: string) => + `https://res.cloudinary.com/demo/image/upload/w_450/${publicId}` +})); + +beforeAll(() => { + globalThis.URL.createObjectURL = vi.fn(() => 'blob:mock-url'); + globalThis.URL.revokeObjectURL = vi.fn(); +}); + +const IntegrationFilePicker = ({ + maxSizeMB, + maxFiles, + accept, + multiple, + initialFiles = [] +}: { + maxSizeMB?: number; + maxFiles?: number; + accept?: string; + multiple?: boolean; + initialFiles?: any[]; +}) => { + const [files, setFiles] = React.useState(initialFiles); + + return ( + + + + + + + + + +

Arraste e solte seus arquivos aqui

+
+ +
+ + + + +
+ ); +}; + +describe('FilePicker Integration', () => { + it('should render empty state correctly (header hidden)', () => { + render(); + + expect(screen.getByText(/Arraste e solte/i)).toBeInTheDocument(); + expect(screen.getByText('Arquivos (0)')).toBeInTheDocument(); + + expect( + screen.queryByRole('button', { name: /Adicionar mais/i }) + ).not.toBeInTheDocument(); + }); + + it('should handle drag and drop of files and show header buttons', async () => { + render(); + + const file = new File(['dummy'], 'dropped.png', { type: 'image/png' }); + const dropzone = screen.getByText(/Arraste e solte/i).closest('div'); + + if (!dropzone) throw new Error('Dropzone not found'); + + fireEvent.drop(dropzone, { + dataTransfer: { + files: [file], + types: ['Files'] + } + }); + + await screen.findByAltText('dropped.png'); + + expect( + screen.getByRole('button', { name: /Adicionar mais/i }) + ).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: /Remover tudo/i }) + ).toBeInTheDocument(); + expect(screen.getByText('Arquivos (1)')).toBeInTheDocument(); + }); + + it('should upload a valid file via input change', async () => { + render(); + + const input = document.querySelector( + 'input[type="file"]' + ) as HTMLInputElement; + const file = new File(['dummy content'], 'teste.png', { + type: 'image/png' + }); + + fireEvent.change(input, { target: { files: [file] } }); + + await screen.findByAltText('teste.png'); + expect(screen.getByText('Arquivos (1)')).toBeInTheDocument(); + }); + + it('should show error when file is too large', async () => { + render(); + + const input = document.querySelector( + 'input[type="file"]' + ) as HTMLInputElement; + const largeFile = new File(['a'.repeat(2 * 1024 * 1024)], 'large.png', { + type: 'image/png' + }); + + fireEvent.change(input, { target: { files: [largeFile] } }); + + expect( + await screen.findByText(/excede o tamanho máximo/i) + ).toBeInTheDocument(); + expect(screen.queryByAltText('large.png')).not.toBeInTheDocument(); + }); + + it('should remove a file when clicking remove button', async () => { + const user = userEvent.setup(); + render(); + + const input = document.querySelector( + 'input[type="file"]' + ) as HTMLInputElement; + const file = new File(['content'], 'teste.jpg', { type: 'image/jpeg' }); + + fireEvent.change(input, { target: { files: [file] } }); + + await screen.findByAltText('teste.jpg'); + expect(screen.getByText('Arquivos (1)')).toBeInTheDocument(); + + const removeBtn = screen.getByRole('button', { name: /Remove image/i }); + await user.click(removeBtn); + + await waitFor(() => { + expect(screen.queryByAltText('teste.jpg')).not.toBeInTheDocument(); + }); + expect(screen.getByText('Arquivos (0)')).toBeInTheDocument(); + }); + + it('should clear all files when clicking Remove All', async () => { + const user = userEvent.setup(); + render(); + + const input = document.querySelector( + 'input[type="file"]' + ) as HTMLInputElement; + const file1 = new File(['c1'], 'a.jpg', { type: 'image/jpeg' }); + const file2 = new File(['c2'], 'b.jpg', { type: 'image/jpeg' }); + + fireEvent.change(input, { target: { files: [file1, file2] } }); + + await screen.findByAltText('a.jpg'); + + const clearBtn = screen.getByRole('button', { name: /Remover tudo/i }); + await user.click(clearBtn); + + await waitFor(() => { + expect(screen.queryByAltText('a.jpg')).not.toBeInTheDocument(); + }); + + expect( + screen.queryByRole('button', { name: /Remover tudo/i }) + ).not.toBeInTheDocument(); + }); + + it('should use default values when optional props are missing', () => { + const TestDefaults = () => { + const [files, setFiles] = React.useState([]); + return ( + + + + ); + }; + + render(); + + const input = document.querySelector( + 'input[type="file"]' + ) as HTMLInputElement; + expect(input.multiple).toBe(true); + }); + + it('should allow setting a file as primary', async () => { + const user = userEvent.setup(); + const initialFiles = [ + { + id: '1', + name: 'file1.jpg', + src: 'blob:url1', + type: 'image/jpeg', + isPrimary: true + }, + { + id: '2', + name: 'file2.jpg', + src: 'blob:url2', + type: 'image/jpeg', + isPrimary: false + } + ]; + + render(); + + const setPrimaryButtons = screen.getAllByRole('button', { + name: /Definir como principal/i + }); + expect(setPrimaryButtons).toHaveLength(1); + + await user.click(setPrimaryButtons[0]); + + const updatedPrimaryButtons = screen.queryAllByRole('button', { + name: /Definir como principal/i + }); + expect(updatedPrimaryButtons).toHaveLength(1); + }); + + it('should render Cloudinary thumbnail URL when publicId is present', () => { + const initialFiles = [ + { + id: 'cloud-1', + name: 'cloud.jpg', + src: 'http://original-src', + type: 'image/jpeg', + publicId: 'folder/image-123' + } + ]; + + render(); + + const img = screen.getByAltText('cloud.jpg') as HTMLImageElement; + expect(img.src).toContain( + 'https://res.cloudinary.com/demo/image/upload/w_450/folder/image-123' + ); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/file-picker/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/index.tsx new file mode 100644 index 0000000..6238e9e --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/index.tsx @@ -0,0 +1,12 @@ +export { FilePicker } from './pieces/file-picker/index'; +export { FilePickerContent } from './pieces/content'; +export { FilePickerCount } from './pieces/count'; +export { FilePickerDrag } from './pieces/drag'; +export { FilePickerEmpty } from './pieces/empty'; +export { FilePickerHeader } from './pieces/header'; +export { FilePickerInput } from './pieces/input'; +export { FilePickerError } from './pieces/error'; +export { FilePickerAddMoreButton } from './pieces/buttons/add-more'; +export { FilePickerButton } from './pieces/buttons/import-files'; +export { FilePickerRemoveAllButton } from './pieces/buttons/remove-all'; +export { FilePickerRemoveFileButton } from './pieces/buttons/remove-file'; diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/add-more/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/add-more/index.tsx new file mode 100644 index 0000000..a477584 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/add-more/index.tsx @@ -0,0 +1,20 @@ +import { Button } from '@/shared/components/ui/button'; +import { UploadIcon } from 'lucide-react'; +import { useFilePickerContext } from '../../../hooks'; + +export function FilePickerAddMoreButton({ label }: { label: string }) { + const [, { openFileDialog }] = useFilePickerContext(); + + return ( + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/import-files/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/import-files/index.tsx new file mode 100644 index 0000000..7bd6664 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/import-files/index.tsx @@ -0,0 +1,19 @@ +import { Button } from '@/shared/components/ui/button'; +import { UploadIcon } from 'lucide-react'; +import { useFilePickerContext } from '../../../hooks'; + +export function FilePickerButton({ label }: { label: string }) { + const [, { openFileDialog }] = useFilePickerContext(); + + return ( + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-all/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-all/index.tsx new file mode 100644 index 0000000..f80a925 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-all/index.tsx @@ -0,0 +1,14 @@ +import { Button } from '@/shared/components/ui/button'; +import { useFilePickerContext } from '../../../hooks'; +import { Trash2Icon } from 'lucide-react'; + +export function FilePickerRemoveAllButton({ label }: { label: string }) { + const [, { clearFiles }] = useFilePickerContext(); + + return ( + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-file/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-file/index.tsx new file mode 100644 index 0000000..6ba4b54 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/buttons/remove-file/index.tsx @@ -0,0 +1,21 @@ +import { Button } from '@/shared/components/ui/button'; +import { useFilePickerContext } from '../../../hooks'; +import type { FileWithPreview } from '../../../types'; +import { XIcon } from 'lucide-react'; + +export function FilePickerRemoveFileButton({ + id +}: Pick) { + const [, { removeFile }] = useFilePickerContext(); + + return ( + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/content/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/content/index.tsx new file mode 100644 index 0000000..e80f0fc --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/content/index.tsx @@ -0,0 +1,61 @@ +import type { ComponentProps } from 'react'; +import { useFilePickerContext } from '../../hooks'; +import { getFilePreview } from '../../utils'; +import { cn } from '@/shared/utils'; +import { FilePickerRemoveFileButton } from '../buttons/remove-file'; +import { Star } from 'lucide-react'; +import { Button } from '@/shared/components/ui/button'; + +export function FilePickerContent({ + className, + ...props +}: ComponentProps<'div'>) { + const [{ files }, { setPrimaryFile }] = useFilePickerContext(); + return ( +
+ {files.map((file) => ( +
+ { + //@ts-expect-error file + !file.url && getFilePreview(file) + } + {file.name} +
+ {file.isPrimary && ( +
+ +
+ )} + {!file.isPrimary && ( + + )} + + +
+
+ ))} +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/count/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/count/index.tsx new file mode 100644 index 0000000..8b8aab3 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/count/index.tsx @@ -0,0 +1,10 @@ +import { useFilePickerContext } from '../../hooks'; + +export function FilePickerCount({ label }: { label: string }) { + const [{ files }] = useFilePickerContext(); + return ( +

+ {label} ({files.length}) +

+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/drag/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/drag/index.tsx new file mode 100644 index 0000000..aa22d81 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/drag/index.tsx @@ -0,0 +1,32 @@ +import type { ReactNode, DragEvent } from 'react'; +import { useCallback } from 'react'; +import { useFilePickerContext } from '../../hooks'; +import { useDropzone } from '@/shared/hooks/use-dropzone'; + +export function FilePickerDrag({ children }: { children?: ReactNode }) { + const [{ files }, { addFiles }] = useFilePickerContext(); + + const handleDrop = useCallback( + (e: DragEvent) => { + if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { + addFiles(e.dataTransfer.files); + } + }, + [addFiles] + ); + + const { isDragging, dropzoneProps } = useDropzone({ + onDrop: handleDrop + }); + + return ( +
0 || undefined} + className="relative flex min-h-56 flex-col items-center overflow-hidden rounded-xl border-2 border-muted-foreground/50 border-dashed p-4 transition-colors not-data-[files]:justify-center has-[input:focus]:border-ring has-[input:focus]:ring-[3px] has-[input:focus]:ring-ring/50 data-[dragging=true]:bg-accent/50" + > + {children} +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/empty/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/empty/index.tsx new file mode 100644 index 0000000..8579764 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/empty/index.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from 'react'; +import { useFilePickerContext } from '../../hooks'; + +type TFilePickerEmpty = { + children?: ReactNode; +}; + +export function FilePickerEmpty({ children }: TFilePickerEmpty) { + const [{ files }] = useFilePickerContext(); + return ( + files.length < 1 && ( +
+ {children} +
+ ) + ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/error/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/error/index.tsx new file mode 100644 index 0000000..a115ac3 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/error/index.tsx @@ -0,0 +1,24 @@ +import { AlertCircleIcon } from 'lucide-react'; +import { useFilePickerContext } from '../../hooks'; + +export function FilePickerError() { + const [{ errors }] = useFilePickerContext(); + + if (errors.length === 0) { + return null; + } + + return ( +
+ {errors.map((error, index) => ( +
+ + {error} +
+ ))} +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/file-picker/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/file-picker/index.tsx new file mode 100644 index 0000000..5185bb9 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/file-picker/index.tsx @@ -0,0 +1,47 @@ +import { useMemo, type ReactNode } from 'react'; +import { useFilePicker } from '../../hooks/use-file-picker'; +import { FilePickerContext } from '../../hooks'; +import type { FilePickerOptions, FileWithPreview } from '../../types'; + +type TFilePicker = Omit< + FilePickerOptions, + 'maxSize' | 'files' | 'onFilesChange' +> & { + files: FileWithPreview[]; + onFilesChange: (files: FileWithPreview[]) => void; + maxSizeMB?: number; + children?: ReactNode; +}; + +export function FilePicker({ + children, + maxSizeMB = 5, + files, + onFilesChange, + ...options +}: TFilePicker) { + const filePickerOptions: FilePickerOptions = { + ...options, + files, + onFilesChange, + maxFiles: options.maxFiles ? options.maxFiles : 5, + multiple: options.multiple || true, + maxSize: maxSizeMB * 1024 * 1024 + }; + + const [state, actions] = useFilePicker(filePickerOptions); + + const contextValue: [typeof state & { files: typeof files }, typeof actions] = + useMemo(() => { + return [{ ...state, files }, actions] as [ + typeof state & { files: typeof files }, + typeof actions + ]; + }, [state, files, actions]); + + return ( + +
{children}
+
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/header/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/header/index.tsx new file mode 100644 index 0000000..b579d3f --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/header/index.tsx @@ -0,0 +1,13 @@ +import type { ReactNode } from 'react'; +import { useFilePickerContext } from '../../hooks'; + +export function FilePickerHeader({ children }: { children: ReactNode }) { + const [{ files }] = useFilePickerContext(); + return ( + files.length > 0 && ( +
+ {children} +
+ ) + ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/pieces/input/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/pieces/input/index.tsx new file mode 100644 index 0000000..424281d --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/pieces/input/index.tsx @@ -0,0 +1,19 @@ +import { Input } from '@/shared/components/ui/input'; +import { useFilePickerContext } from '../../hooks'; + +export function FilePickerInput({ ...props }: React.ComponentProps<'input'>) { + const [, { getInputProps }] = useFilePickerContext(); + + const { ref, ...inputProps } = getInputProps(); + + return ( + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/types/index.ts b/UIs/Merchants/src/shared/components/global/file-picker/types/index.ts new file mode 100644 index 0000000..95ce2eb --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/types/index.ts @@ -0,0 +1,49 @@ +import type { ChangeEvent, InputHTMLAttributes, RefObject } from 'react'; + +export type FileMetadata = { + name: string; + size: number; + type: string; + url: string; + id: string; +}; + +export type FileWithPreview = { + id: string; + file?: File | FileMetadata; + name: string; + url: string; + type: string; + publicId?: string; + isPrimary?: boolean; +}; + +export type FilePickerOptions = { + files: FileWithPreview[]; + onFilesChange: (files: FileWithPreview[]) => void; + onFilesAdded?: (addedFiles: FileWithPreview[]) => void; + + maxFiles?: number; + maxSize?: number; + accept?: string; + multiple?: boolean; +}; + +export type FilePickerState = { + errors: string[]; +}; + +export type FilePickerActions = { + addFiles: (files: FileList | File[]) => void; + removeFile: (id: string) => void; + setPrimaryFile: (id: string) => void; + clearFiles: () => void; + clearErrors: () => void; + handleFileChange: (e: ChangeEvent) => void; + openFileDialog: () => void; + getInputProps: ( + props?: InputHTMLAttributes + ) => InputHTMLAttributes & { + ref: RefObject; + }; +}; diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.test.ts b/UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.test.ts new file mode 100644 index 0000000..7cfa5e5 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { generateUniqueId } from '.'; +import type { FileMetadata } from '../../types'; + +const MOCK_TIME = 1600000000000; +const MOCK_FILE_NAME = 'image.png'; +const MOCK_FILE_SIZE = 50000; +const MOCK_RANDOM_VALUE = 0.5; +const mockFile = new File(['content'], MOCK_FILE_NAME, { + lastModified: MOCK_TIME +}); + +Object.defineProperty(mockFile, 'size', { + value: MOCK_FILE_SIZE, + writable: true +}); + +describe('generateUniqueId', () => { + beforeEach(() => { + vi.spyOn(Math, 'random').mockReturnValue(MOCK_RANDOM_VALUE); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should generate a composite ID using name, lastModified, size, and a random suffix', () => { + const generatedId = generateUniqueId(mockFile); + + expect(generatedId).toContain( + `${MOCK_FILE_NAME}-${MOCK_TIME}-${MOCK_FILE_SIZE}` + ); + + expect(generatedId.length).toBeGreaterThan( + MOCK_FILE_NAME.length + + String(MOCK_TIME).length + + String(MOCK_FILE_SIZE).length + + 1 + ); + + expect(Math.random).toHaveBeenCalledTimes(1); + }); + + it('should return the existing ID if the object is FileMetadata', () => { + const EXISTING_ID = 'db-id-555'; + const metadata: FileMetadata = { + id: EXISTING_ID, + name: 'db.jpg', + size: 8, + url: '', + type: 'image' + }; + + const returnedId = generateUniqueId(metadata); + + expect(returnedId).toBe(EXISTING_ID); + expect(Math.random).not.toHaveBeenCalled(); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.tsx new file mode 100644 index 0000000..16a261e --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/generate-unique-id/index.tsx @@ -0,0 +1,9 @@ +import type { FileMetadata } from '../../types'; + +export function generateUniqueId(file: File | FileMetadata): string { + if (file instanceof File) { + return `${file.name}-${file.lastModified}-${file.size}-${Math.random().toString(36).substring(2, 9)}`; + } + + return file.id; +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.test.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.test.tsx new file mode 100644 index 0000000..6ce5748 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.test.tsx @@ -0,0 +1,120 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { getFileIcon } from '.'; +import { + FileArchiveIcon, + FileSpreadsheetIcon, + FileTextIcon, + HeadphonesIcon, + ImageIcon, + VideoIcon, + FileIcon +} from 'lucide-react'; + +vi.mock('lucide-react', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + FileArchiveIcon: vi.fn(), + FileSpreadsheetIcon: vi.fn(), + FileTextIcon: vi.fn(), + HeadphonesIcon: vi.fn(), + ImageIcon: vi.fn(), + VideoIcon: vi.fn(), + FileIcon: vi.fn() + }; +}); + +const createFileInput = ( + type: string, + name: string, + isFileInstance: boolean = false +) => { + const fileData = { type, name } as { type: string; name: string }; + if (isFileInstance) { + const file = new File([], name, { type }); + + return { file }; + } + return { file: fileData }; +}; + +describe('getFileIcon', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return the correct icon for image formats', () => { + const result = getFileIcon(createFileInput('image/jpeg', 'photo.jpg')); + + expect(result?.type).toBe(ImageIcon); + }); + + it('should return the correct icon for video formats', () => { + const result = getFileIcon(createFileInput('video/mp4', 'clip.mp4')); + + expect(result?.type).toBe(VideoIcon); + }); + + it('should return the correct icon for audio formats', () => { + const result = getFileIcon(createFileInput('audio/mpeg', 'song.mp3')); + + expect(result?.type).toBe(HeadphonesIcon); + }); + + it('The PDF icon should be restored for PDF files (by type) or DOCX files (by name)', () => { + expect( + getFileIcon(createFileInput('application/pdf', 'doc.pdf'))?.type + ).toBe(FileTextIcon); + + expect( + getFileIcon(createFileInput('application/octet-stream', 'report.docx')) + ?.type + ).toBe(FileTextIcon); + + expect( + getFileIcon(createFileInput('application/msword', 'letter.doc'))?.type + ).toBe(FileTextIcon); + }); + + it('The COMPRESSED FILE icon should revert to ZIP or RAR', () => { + expect( + getFileIcon(createFileInput('application/zip', 'archive.zip'))?.type + ).toBe(FileArchiveIcon); + + expect( + getFileIcon(createFileInput('application/octet-stream', 'files.rar')) + ?.type + ).toBe(FileArchiveIcon); + }); + + it('The Excel icon should be restored for spreadsheets', () => { + expect( + getFileIcon(createFileInput('application/vnd.ms-excel', 'data.xls'))?.type + ).toBe(FileSpreadsheetIcon); + + expect( + getFileIcon(createFileInput('application/octet-stream', 'data.xlsx')) + ?.type + ).toBe(FileSpreadsheetIcon); + }); + + it('should return the fallback icon (FileIcon) when the type is unknown', () => { + const result = getFileIcon( + createFileInput('application/json', 'config.json') + ); + + expect(result?.type).toBe(FileIcon); + }); + + it('should return undefined (guard clause) if fileType is false (e.g., null or empty)', () => { + const result = getFileIcon(createFileInput('', 'no-type.bin')); + + expect(result).toBeUndefined(); + }); + + it('must correctly process a File object (instanceof File)', () => { + const result = getFileIcon(createFileInput('image/png', 'photo.png', true)); + + expect(result?.type).toBe(ImageIcon); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.tsx new file mode 100644 index 0000000..1019060 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-icon/index.tsx @@ -0,0 +1,63 @@ +import { + FileArchiveIcon, + FileIcon, + FileSpreadsheetIcon, + FileTextIcon, + HeadphonesIcon, + ImageIcon, + VideoIcon +} from 'lucide-react'; + +const iconMap = { + pdf: { + icon: FileTextIcon, + conditions: (type: string, name: string) => + type.includes('pdf') || + name.endsWith('.pdf') || + type.includes('word') || + name.endsWith('.doc') || + name.endsWith('.docx') + }, + archive: { + icon: FileArchiveIcon, + conditions: (type: string, name: string) => + type.includes('zip') || + type.includes('archive') || + name.endsWith('.zip') || + name.endsWith('.rar') + }, + excel: { + icon: FileSpreadsheetIcon, + conditions: (type: string, name: string) => + type.includes('excel') || name.endsWith('.xls') || name.endsWith('.xlsx') + }, + video: { + icon: VideoIcon, + conditions: (type: string) => type.includes('video/') + }, + audio: { + icon: HeadphonesIcon, + conditions: (type: string) => type.includes('audio/') + }, + image: { + icon: ImageIcon, + conditions: (type: string) => type.startsWith('image/') + } +}; + +export function getFileIcon(file: { + file: File | { type: string; name: string }; +}) { + const fileType = file.file instanceof File ? file.file.type : file.file.type; + const fileName = file.file instanceof File ? file.file.name : file.file.name; + + if (!fileType) return; + + for (const { icon: Icon, conditions } of Object.values(iconMap)) { + if (conditions(fileType, fileName)) { + return ; + } + } + + return ; +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-preview/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-preview/index.tsx new file mode 100644 index 0000000..205dbd3 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/get-file-preview/index.tsx @@ -0,0 +1,42 @@ +import { ImageIcon } from 'lucide-react'; +import { getFileIcon } from '../get-file-icon'; + +export const getFilePreview = ( + file: + | { + file: File | { type: string; name: string; src: string }; + } + | undefined +) => { + if (!file) return; + + const fileType = file.file instanceof File ? file.file.type : file.file.type; + const fileName = file.file instanceof File ? file.file.name : file.file.name; + + const renderImage = (src: string) => ( + {fileName} + ); + + return ( +
+ {fileType?.startsWith('image/') ? ( + file.file instanceof File ? ( + (() => { + const previewUrl = URL.createObjectURL(file.file); + return renderImage(previewUrl); + })() + ) : file.file.src ? ( + renderImage(file.file.src) + ) : ( + + ) + ) : ( + getFileIcon(file) + )} +
+ ); +}; diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/index.tsx new file mode 100644 index 0000000..dfbdc6b --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/index.tsx @@ -0,0 +1,5 @@ +export { getFileIcon } from './get-file-icon'; +export { getFilePreview } from './get-file-preview'; +export { generateUniqueId } from './generate-unique-id'; +export { validateFile } from './validate-file'; +export { processNewFiles } from './process-new-files'; diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.test.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.test.tsx new file mode 100644 index 0000000..6de68db --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.test.tsx @@ -0,0 +1,143 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { processNewFiles } from '.'; +import type { FileWithPreview } from '../../types'; + +const { mockGenerateUniqueId, mockValidateFile } = vi.hoisted(() => { + const mockGenerateUniqueId = vi.fn(() => 'unique-id-mock'); + const mockValidateFile = vi.fn(); + + return { + mockGenerateUniqueId, + mockValidateFile + }; +}); + +vi.mock('../generate-unique-id', () => ({ + generateUniqueId: mockGenerateUniqueId +})); + +vi.mock('../validate-file', () => ({ + validateFile: mockValidateFile +})); + +//@ts-expect-error global is not defined +global.URL.createObjectURL = vi.fn((file) => `blob-url-mock-${file.name}`); + +const existingFileInstance = new File(['data'], 'A.jpg', { + lastModified: 1000 +}); + +Object.defineProperty(existingFileInstance, 'size', { + value: 100, + writable: true +}); + +const mockFileA = { + name: 'A.jpg', + size: 100, + lastModified: 1000, + type: 'image/jpeg' +} as File; + +const mockFileB = { + name: 'B.png', + size: 200, + lastModified: 2000, + type: 'image/png' +} as File; + +const mockFileC = { + name: 'A.jpg', + size: 100, + lastModified: 1000, + type: 'image/jpeg' +} as File; + +const existingFileA: FileWithPreview = { + file: existingFileInstance, + id: 'idA', + url: 'blobA', + name: 'A.jpg', + type: 'image/jpeg', + isPrimary: false +}; + +const defaultOptions = { + maxSize: 500, + accept: 'image/*', + multiple: true +}; + +describe('processNewFiles', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockValidateFile.mockReturnValue(null); + }); + + it('must structure valid files with a unique ID and Blob URL', () => { + const { validFiles, errors } = processNewFiles( + [mockFileB], + [], + defaultOptions + ); + + expect(errors).toEqual([]); + expect(validFiles).toHaveLength(1); + expect(validFiles[0]).toEqual({ + file: mockFileB, + id: 'unique-id-mock', + src: 'blob-url-mock-B.png', + publicId: undefined, + isPrimary: false, + name: mockFileB.name, + type: mockFileB.type + }); + + expect(mockGenerateUniqueId).toHaveBeenCalledWith(mockFileB); + //@ts-expect-error global is not defined + expect(global.URL.createObjectURL).toHaveBeenCalledWith(mockFileB); + }); + + it('should ignore files that are exact duplicates in "multiple" mode', () => { + const newFiles = [mockFileC]; + + const { validFiles, errors } = processNewFiles( + newFiles, + [existingFileA], + defaultOptions + ); + + expect(errors).toEqual([]); + expect(validFiles).toEqual([]); + + expect(mockValidateFile).not.toHaveBeenCalled(); + }); + + it('should add the error to the list and skip the file if the validation fails', () => { + const VALIDATION_ERROR = 'Arquivo muito grande!'; + + mockValidateFile.mockReturnValue(VALIDATION_ERROR); + + const { validFiles, errors } = processNewFiles( + [mockFileB], + [], + defaultOptions + ); + + expect(errors).toEqual([VALIDATION_ERROR]); + expect(validFiles).toEqual([]); + + //@ts-expect-error global is not defined + expect(global.URL.createObjectURL).not.toHaveBeenCalled(); + }); + + it('The file should be processed in "single" mode (skipping the duplicate check)', () => { + const { validFiles } = processNewFiles([mockFileA], [existingFileA], { + ...defaultOptions, + multiple: false + }); + + expect(validFiles).toHaveLength(1); + expect(mockValidateFile).toHaveBeenCalledTimes(1); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.tsx new file mode 100644 index 0000000..e4fad6f --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/process-new-files/index.tsx @@ -0,0 +1,49 @@ +import type { FileWithPreview } from '../../types'; +import { generateUniqueId } from '../generate-unique-id'; +import { validateFile } from '../validate-file'; + +export function processNewFiles( + newFilesArray: File[], + existingFiles: FileWithPreview[], + options: { + maxSize: number; + accept: string; + multiple: boolean; + } +): { validFiles: FileWithPreview[]; errors: string[] } { + const errors: string[] = []; + const validFiles: FileWithPreview[] = []; + + newFilesArray.forEach((file) => { + if (options.multiple) { + const isDuplicate = existingFiles.some( + (existingFile) => + existingFile.file instanceof File && + existingFile.file.name === file.name && + existingFile.file.size === file.size && + existingFile.file.lastModified === file.lastModified + ); + if (isDuplicate) { + return; + } + } + + const error = validateFile(file, options.maxSize, options.accept); + + if (error) { + errors.push(error); + } else { + validFiles.push({ + file, + id: generateUniqueId(file), + url: URL.createObjectURL(file), + publicId: undefined, + isPrimary: false, + name: file.name, + type: file.type + }); + } + }); + + return { validFiles, errors }; +} diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.test.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.test.tsx new file mode 100644 index 0000000..b35984e --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.test.tsx @@ -0,0 +1,101 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { validateFile } from '.'; + +const { mockFormatBytes } = vi.hoisted(() => { + const mockFormatBytes = vi.fn((size) => `${size / 1024} KB`); + + return { + mockFormatBytes + }; +}); + +vi.mock('@/shared/utils', () => ({ formatBytes: mockFormatBytes })); + +const createMockFile = (name: string, size: number, type: string = '') => + ({ + name, + size, + type, + lastModified: 0, + slice: vi.fn() + }) as unknown as File; + +describe('validateFile', () => { + const MAX_SIZE_BYTES = 500000; // 500 KB + const FILE_NAME = 'test-file.pdf'; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return an error if the file size exceeds maxSize', () => { + const largeFile = createMockFile(FILE_NAME, MAX_SIZE_BYTES + 1000); // 501 KB + const error = validateFile(largeFile, MAX_SIZE_BYTES, '*'); + + expect(error).toContain(`O arquivo "${FILE_NAME}" excede o tamanho máximo`); + + expect(mockFormatBytes).toHaveBeenCalledWith(MAX_SIZE_BYTES); + }); + + it('should return null if the file size is within the limit', () => { + const smallFile = createMockFile(FILE_NAME, MAX_SIZE_BYTES - 100); + const error = validateFile(smallFile, MAX_SIZE_BYTES, '*'); + + expect(error).toBeNull(); + }); + + it('It should return null if "accept" is "*" (Acceptance guard)', () => { + const file = createMockFile(FILE_NAME, 100, 'application/whatever'); + const error = validateFile(file, MAX_SIZE_BYTES, '*'); + + expect(error).toBeNull(); + }); + + it('must successfully accept Wildcard type (image/*)', () => { + const file = createMockFile('photo.jpeg', 100, 'image/jpeg'); + const error = validateFile(file, MAX_SIZE_BYTES, 'image/*,application/pdf'); + + expect(error).toBeNull(); + }); + + it('must successfully accept exact MIME type', () => { + const file = createMockFile('doc.pdf', 100, 'application/pdf'); + const error = validateFile(file, MAX_SIZE_BYTES, 'image/*,application/pdf'); + + expect(error).toBeNull(); + }); + + it('should return an error indicating that the MIME type is not accepted', () => { + const file = createMockFile('video.mp4', 100, 'video/mp4'); + const error = validateFile(file, MAX_SIZE_BYTES, 'image/*,application/pdf'); + + expect(error).toContain('não tem um formato aceito.'); + }); + + it('should successfully accept files with the extension (.pdf)', () => { + const file = createMockFile('report.pdf', 100, 'application/octet-stream'); + const error = validateFile(file, MAX_SIZE_BYTES, '.pdf, .doc'); + + expect(error).toBeNull(); + }); + + it('should return an error if the extension is not accepted', () => { + const file = createMockFile('image.jpg', 100, 'image/jpeg'); + const error = validateFile(file, MAX_SIZE_BYTES, '.pdf,.png'); + + expect(error).toContain('não tem um formato aceito.'); + }); + + it('should prioritize size failure if both (size and type) fail', () => { + const largeFile = createMockFile( + 'bad.mp4', + MAX_SIZE_BYTES + 1000, + 'video/mp4' + ); + + const error = validateFile(largeFile, MAX_SIZE_BYTES, 'image/png'); + + expect(error).toContain('excede o tamanho máximo'); + expect(error).not.toContain('formato aceito'); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.tsx b/UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.tsx new file mode 100644 index 0000000..4b3187d --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/file-picker/utils/validate-file/index.tsx @@ -0,0 +1,33 @@ +import { formatBytes } from '@/shared/utils'; +export function validateFile( + file: File, + maxSize: number, + accept: string +): string | null { + if (file.size > maxSize) { + return `O arquivo "${file.name}" excede o tamanho máximo de ${formatBytes(maxSize)}.`; + } + + if (accept !== '*') { + const acceptedTypes = accept.split(',').map((type) => type.trim()); + const fileType = file.type || ''; + const fileExtension = `.${file.name.split('.').pop() || ''}`; + + const isAccepted = acceptedTypes.some((type) => { + if (type.startsWith('.')) { + return fileExtension.toLowerCase() === type.toLowerCase(); + } + if (type.endsWith('/*')) { + const baseType = type.split('/')[0]; + return fileType.startsWith(`${baseType}/`); + } + return fileType === type; + }); + + if (!isAccepted) { + return `O arquivo "${file.name}" não tem um formato aceito.`; + } + } + + return null; +} diff --git a/UIs/Merchants/src/shared/components/global/floating-navigation/index.tsx b/UIs/Merchants/src/shared/components/global/floating-navigation/index.tsx new file mode 100644 index 0000000..4f4e4b0 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/floating-navigation/index.tsx @@ -0,0 +1,64 @@ +import { Button } from '../../ui/button'; +import type{ ElementType } from 'react'; + +interface NavigationItem { + name: string; + href: string; + icon: ElementType; + active?: boolean; +} + +interface FloatingNavigationProps { + items: NavigationItem[]; +} +export const FloatingNavigation = ({ items }: FloatingNavigationProps) => { + + return ( + + ); +}; diff --git a/UIs/Merchants/src/shared/components/global/image-card/index.test.tsx b/UIs/Merchants/src/shared/components/global/image-card/index.test.tsx new file mode 100644 index 0000000..21edb58 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/image-card/index.test.tsx @@ -0,0 +1,120 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { ImageCard } from '.'; + +const mockBrowserImageCache = (isCached: boolean) => { + const originalComplete = Object.getOwnPropertyDescriptor( + HTMLImageElement.prototype, + 'complete' + ); + + if (isCached) { + Object.defineProperty(HTMLImageElement.prototype, 'complete', { + configurable: true, + get: () => true + }); + + Object.defineProperty(HTMLImageElement.prototype, 'naturalWidth', { + configurable: true, + get: () => 100 + }); + } + + return () => { + if (originalComplete) { + Object.defineProperty( + HTMLImageElement.prototype, + 'complete', + originalComplete + ); + } + }; +}; + +describe('ImageCard Integration', () => { + const MOCK_SRC = 'https://example.com/photo.jpg'; + const MOCK_ALT = 'Foto do Produto'; + + describe('Visual States', () => { + it('should render the real Skeleton while loading (Initial State)', () => { + render(); + + const img = screen.getByRole('img', { name: MOCK_ALT }); + + expect(screen.getByText('Carregando imagem...')).toBeInTheDocument(); + expect(img).toHaveClass('opacity-0'); + }); + + it('should show the image and hide Skeleton when loaded successfully', () => { + render(); + + const img = screen.getByRole('img', { name: MOCK_ALT }); + + fireEvent.load(img); + + expect(img).toHaveClass('opacity-100'); + expect( + screen.queryByText('Carregando imagem...') + ).not.toBeInTheDocument(); + }); + + it('should show error state and icon when loading fails', () => { + render(); + + const img = screen.getByRole('img', { name: MOCK_ALT }); + + fireEvent.error(img); + + expect(screen.getByText('Não disponível')).toBeInTheDocument(); + expect(img).toHaveClass('opacity-0'); + expect( + screen.queryByText('Carregando imagem...') + ).not.toBeInTheDocument(); + }); + }); + + describe('Props & Behavior', () => { + it('should not render Skeleton if showSkeleton is false', () => { + render(); + + expect( + screen.queryByText('Carregando imagem...') + ).not.toBeInTheDocument(); + }); + + it('should apply correct object-fit class based on prop', () => { + const { rerender } = render( + + ); + + const img = screen.getByRole('img', { name: MOCK_ALT }); + + expect(img).toHaveClass('object-contain'); + expect(img).not.toHaveClass('object-cover'); + + rerender(); + + expect(img).toHaveClass('object-cover'); + expect(img).not.toHaveClass('object-contain'); + }); + + it('should handle browser caching correctly (Instant Load)', async () => { + const restore = mockBrowserImageCache(true); + + try { + render(); + + await waitFor(() => { + const img = screen.getByRole('img', { name: MOCK_ALT }); + + expect(img).toHaveClass('opacity-100'); + expect( + screen.queryByText('Carregando imagem...') + ).not.toBeInTheDocument(); + }); + } finally { + restore(); + } + }); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/image-card/index.tsx b/UIs/Merchants/src/shared/components/global/image-card/index.tsx new file mode 100644 index 0000000..57fa877 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/image-card/index.tsx @@ -0,0 +1,73 @@ +import { useEffect, useRef, useState } from 'react'; +import { cn } from '@/shared/utils'; +import { ImageIcon, ImageOff } from 'lucide-react'; +import { Skeleton } from '../../ui/skeleton'; + +type TImageCard = { + src: string; + alt: string; + showSkeleton?: boolean; + className?: string; + objectFit?: 'cover' | 'contain'; +}; + +export function ImageCard({ + src, + alt, + showSkeleton = true, + className, + objectFit = 'cover' +}: TImageCard) { + const [status, setStatus] = useState<'loading' | 'loaded' | 'error'>( + 'loading' + ); + const imgRef = useRef(null); + + useEffect(() => { + setStatus('loading'); + const img = imgRef.current; + + if (img && img.complete && img.naturalWidth > 0) { + setStatus('loaded'); + } + }, [src]); + + return ( +
+ {status === 'loading' && showSkeleton && ( + + + Carregando imagem... + + )} + + {status === 'error' && ( +
+ + Não disponível +
+ )} + +
+ {alt} setStatus('loaded')} + onError={() => setStatus('error')} + /> +
+
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/input-builder/index.tsx b/UIs/Merchants/src/shared/components/global/input-builder/index.tsx new file mode 100644 index 0000000..758c70e --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/input-builder/index.tsx @@ -0,0 +1,45 @@ +import type { ComponentProps } from 'react'; +import { Input } from '@/shared/components/ui/input'; +import { InputPercent } from './inputs/input-percent'; +import { InputMonth } from './inputs/input-month'; +import { InputYear } from './inputs/input-year'; +import { InputInstallment } from './inputs/input-installment'; +import { InputMoney } from './inputs/input-money'; + +export enum InputType { + Money = 'Money', + Percent = 'Percent', + Month = 'Month', + Year = 'Year', + Installments = 'Installments' +} + +interface InputBuilderProps extends ComponentProps<'input'> { + onChangeHandler?: (value: string) => void; + inputType: InputType; +} + + +const INPUT_STRATEGIES: Record = { + [InputType.Money]: InputMoney, + [InputType.Percent]: InputPercent, + [InputType.Month]: InputMonth, + [InputType.Year]: InputYear, + [InputType.Installments]: InputInstallment +}; + +export const InputBuilder = ({ + inputType, + onChangeHandler, + ...props +}: InputBuilderProps) => { + const InputComponent = INPUT_STRATEGIES[inputType]; + + if (InputComponent) { + return ; + } + + return ( + onChangeHandler?.(e.target.value)} /> + ); +}; diff --git a/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-installment.tsx b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-installment.tsx new file mode 100644 index 0000000..eb17ca8 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-installment.tsx @@ -0,0 +1,27 @@ +import { Input } from '@/shared/components/ui/input'; +import type { ComponentProps } from 'react'; + +interface InputInstallmentProps extends ComponentProps<'input'> { + onChangeHandler?: (value: string) => void; +} + +export const InputInstallment = ({ + onChangeHandler, + ...props +}: InputInstallmentProps) => { + return ( +
+ onChangeHandler?.(e.target.value)} + /> + + parcelas + +
+ ); +}; diff --git a/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-money.tsx b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-money.tsx new file mode 100644 index 0000000..5e2e8d6 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-money.tsx @@ -0,0 +1,56 @@ +import { Input } from '@/shared/components/ui/input'; +import { maskInputMoneyBR } from '@/shared/utils/masks/mask-money'; +import type { ComponentProps } from 'react'; + +interface InputMoneyProps extends ComponentProps<'input'> { + onChangeHandler?: (value: string) => void; +} + +const parseToNumber = (value: any) => { + if (value === null || value === undefined || value === '') return undefined; + if (typeof value === 'number') return value; + + let stringValue = String(value); + + if (stringValue.includes('.') && stringValue.includes(',')) { + stringValue = stringValue.replace(/\./g, '').replace(',', '.'); + } else if (stringValue.includes(',')) { + stringValue = stringValue.replace(',', '.'); + } + + const parsed = parseFloat(stringValue); + return isNaN(parsed) ? undefined : parsed; +}; + +function formatCurrencyPTBR(value: any) { + const number = parseToNumber(value); + if (number === undefined || number === null || isNaN(number)) + return 'R$ 0,00'; + return new Intl.NumberFormat('pt-BR', { + style: 'currency', + currency: 'BRL' + }).format(number); +} + +export const InputMoney = ({ + onChangeHandler, + value, + defaultValue, + ...props +}: InputMoneyProps) => { + const maskedValue = defaultValue + ? formatCurrencyPTBR(defaultValue) + : value + ? formatCurrencyPTBR(value) + : ''; + return ( + { + const masked = maskInputMoneyBR(e.target.value); + onChangeHandler?.(masked); + }} + /> + ); +}; diff --git a/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-month.tsx b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-month.tsx new file mode 100644 index 0000000..f9cbabc --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-month.tsx @@ -0,0 +1,20 @@ +import { InputWithSuffix } from '@/shared/components/ui/input-with-sufix'; +import type { ComponentProps } from 'react'; + +interface InputMonthsProps extends ComponentProps<'input'> { + onChangeHandler?: (value: string) => void; +} + +export const InputMonth = ({ onChangeHandler, ...props }: InputMonthsProps) => { + return ( + onChangeHandler?.(e.target.value)} + suffix="meses" + /> + ); +}; diff --git a/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-percent.tsx b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-percent.tsx new file mode 100644 index 0000000..ad1be4d --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-percent.tsx @@ -0,0 +1,38 @@ +import { Input } from '@/shared/components/ui/input'; +import { maskPercent } from '@/shared/utils/masks/mask-percentage'; +import { useState, type ComponentProps } from 'react'; + +type InputPercentProps = ComponentProps<'input'> & { + onChangeHandler?: (value: string) => void; + minDecimals?: number; + maxDecimals?: number; +}; + +export const InputPercent = ({ + value, + defaultValue, + maxDecimals, + ...props +}: InputPercentProps) => { + const valueFormated = value || defaultValue; + const [percentValue, setPercentValue] = useState(valueFormated || ''); + + return ( +
+ { + const value = e.target.value; + const sanitized = maskPercent(value, maxDecimals); + setPercentValue(sanitized); + }} + className="pr-8" + /> + + % + +
+ ); +}; diff --git a/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-year.tsx b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-year.tsx new file mode 100644 index 0000000..ad04ed3 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/input-builder/inputs/input-year.tsx @@ -0,0 +1,20 @@ +import { InputWithSuffix } from '@/shared/components/ui/input-with-sufix'; +import type { ComponentProps } from 'react'; + +interface InputYearProps extends ComponentProps<'input'> { + onChangeHandler?: (value: string) => void; +} + +export const InputYear = ({ onChangeHandler, ...props }: InputYearProps) => { + return ( + onChangeHandler?.(e.target.value)} + suffix="anos" + /> + ); +}; diff --git a/UIs/Merchants/src/shared/components/global/logo/index.tsx b/UIs/Merchants/src/shared/components/global/logo/index.tsx new file mode 100644 index 0000000..a1a085c --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/logo/index.tsx @@ -0,0 +1,17 @@ +import { cn } from '@/shared/utils'; + +interface AppLogoProps { + className?: string; +} + +export function AppLogo({ className }: AppLogoProps) { + return ( + Logo da App + ); +} diff --git a/UIs/Merchants/src/shared/components/global/page-header/index.tsx b/UIs/Merchants/src/shared/components/global/page-header/index.tsx new file mode 100644 index 0000000..c0d731a --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/page-header/index.tsx @@ -0,0 +1,16 @@ +import { SidebarTrigger } from '../../ui/sidebar'; + +type pageHeaderProps = { + title: string; +}; + +export const PageHeader = ({ title }: pageHeaderProps) => { + return ( +
+
+ +

{title}

+
+
+ ); +}; diff --git a/UIs/Merchants/src/shared/components/global/placeholder-page/index.test.tsx b/UIs/Merchants/src/shared/components/global/placeholder-page/index.test.tsx new file mode 100644 index 0000000..b728362 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/placeholder-page/index.test.tsx @@ -0,0 +1,40 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { PlaceholderPage } from './index'; +import { Wrench } from 'lucide-react'; + +describe('PlaceholderPage Component', () => { + it('should render default content when no props are provided', () => { + render(); + + expect( + screen.getByRole('heading', { name: 'Página em Desenvolvimento' }) + ).toBeInTheDocument(); + + expect( + screen.getByText( + 'Esta funcionalidade ainda está sendo construída pela nossa equipe.' + ) + ).toBeInTheDocument(); + + expect(document.querySelector('svg')).toBeInTheDocument(); + }); + + it('should render custom title and message', () => { + const customTitle = 'Manutenção Programada'; + const customMessage = 'Voltamos em breve.'; + + render(); + + expect(screen.getByText(customMessage)).toBeInTheDocument(); + expect( + screen.getByRole('heading', { name: customTitle }) + ).toBeInTheDocument(); + }); + + it('should render a custom icon', () => { + render(); + + expect(document.querySelector('svg')).toBeInTheDocument(); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/placeholder-page/index.tsx b/UIs/Merchants/src/shared/components/global/placeholder-page/index.tsx new file mode 100644 index 0000000..835e6a5 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/placeholder-page/index.tsx @@ -0,0 +1,32 @@ +import { HardHat, type LucideIcon } from 'lucide-react'; +import { cn } from '@/shared/utils'; + +interface PlaceholderPageProps { + title?: string; + message?: string; + icon?: LucideIcon; + className?: string; +} + +export function PlaceholderPage({ + title = 'Página em Desenvolvimento', + message = 'Esta funcionalidade ainda está sendo construída pela nossa equipe.', + icon: Icon = HardHat, + className +}: PlaceholderPageProps) { + return ( +
+
+ +
+

{title}

+

{message}

+
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/restricted-link/index.tsx b/UIs/Merchants/src/shared/components/global/restricted-link/index.tsx new file mode 100644 index 0000000..b5f47a4 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/restricted-link/index.tsx @@ -0,0 +1,12 @@ +//import { getCurrentTenant } from "@/libs/axios"; +import { NavLink, type NavLinkProps } from 'react-router'; + +export function RestrictedLink({ children, ...props }: NavLinkProps) { + const tenant = 'smartconsig'; // Mock tenant value for demonstration + + if (tenant !== 'smartconsig') { + return null; + } + + return {children}; +} diff --git a/UIs/Merchants/src/shared/components/global/simple-data-table/index.test.tsx b/UIs/Merchants/src/shared/components/global/simple-data-table/index.test.tsx new file mode 100644 index 0000000..0840f32 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/simple-data-table/index.test.tsx @@ -0,0 +1,75 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import type { ColumnDef } from '@tanstack/react-table'; +import { SimpleDataTable } from './index'; + +interface TestData { + id: string; + name: string; + role: string; +} + +const mockData: TestData[] = [ + { id: '1', name: 'Ana Silva', role: 'Admin' }, + { id: '2', name: 'João Santos', role: 'User' } +]; + +const mockColumns: ColumnDef[] = [ + { + accessorKey: 'name', + header: 'Nome Completo' + }, + { + accessorKey: 'role', + header: 'Função' + } +]; + +describe('SimpleDataTable Component', () => { + describe('Rendering Data', () => { + it('should render headers and rows correctly when data is provided', () => { + render(); + + expect( + screen.getByRole('columnheader', { name: 'Nome Completo' }) + ).toBeInTheDocument(); + + expect( + screen.getByRole('columnheader', { name: 'Função' }) + ).toBeInTheDocument(); + + expect(screen.getByText('Ana Silva')).toBeInTheDocument(); + expect(screen.getByText('Admin')).toBeInTheDocument(); + expect(screen.getByText('João Santos')).toBeInTheDocument(); + }); + }); + + describe('Empty States (Branch Coverage)', () => { + it('should render the default empty message when data is empty and no custom message is provided', () => { + render(); + + const cell = screen.getByRole('cell'); + + expect(cell).toHaveTextContent('Nenhum registro disponível.'); + expect(cell).toHaveClass('text-muted-foreground'); + expect(cell).toHaveAttribute('colspan', '2'); + }); + + it('should render the custom empty message when provided', () => { + const customMessage = 'Não há itens para exibir neste momento.'; + + render( + + ); + + expect(screen.getByText(customMessage)).toBeInTheDocument(); + expect( + screen.queryByText('Nenhum registro disponível.') + ).not.toBeInTheDocument(); + }); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/simple-data-table/index.tsx b/UIs/Merchants/src/shared/components/global/simple-data-table/index.tsx new file mode 100644 index 0000000..979bf2f --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/simple-data-table/index.tsx @@ -0,0 +1,79 @@ +import { + useReactTable, + getCoreRowModel, + flexRender, + type ColumnDef, + type TableMeta +} from '@tanstack/react-table'; + +import { + Table, + TableHeader, + TableHead, + TableBody, + TableRow, + TableCell +} from '@/shared/components/ui/table'; + +interface TSimpleDataTable { + data: TData[]; + emptyMessage?: string; + columns: ColumnDef[]; + meta?: TableMeta; +} + +export function SimpleDataTable({ + data, + emptyMessage, + columns, + meta +}: TSimpleDataTable) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + meta: meta + }); + + return ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + {!header.isPlaceholder && + flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ))} + + ))} + + + {table.getRowModel().rows.length > 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + {emptyMessage || 'Nenhum registro disponível.'} + + + )} + +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/submiting-button/index.tsx b/UIs/Merchants/src/shared/components/global/submiting-button/index.tsx new file mode 100644 index 0000000..6643a14 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/submiting-button/index.tsx @@ -0,0 +1,31 @@ +import { Button } from '@/shared/components/ui/button'; +import { Loader2, type LucideIcon } from 'lucide-react'; +import type { ComponentProps } from 'react'; + +interface SubmitingButtonProps extends ComponentProps { + label?: string; + Icon?: LucideIcon; + showLabel?: boolean; + state: boolean; + onClick?: () => void; +} + +export const SubmitingButton = ({ + label, + state, + showLabel = true, + Icon, + ...props +}: SubmitingButtonProps) => { + return state ? ( + + ) : ( + + ); +}; diff --git a/UIs/Merchants/src/shared/components/global/wizard/hooks/index.tsx b/UIs/Merchants/src/shared/components/global/wizard/hooks/index.tsx new file mode 100644 index 0000000..9895678 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/hooks/index.tsx @@ -0,0 +1,187 @@ +import { + createContext, + useEffect, + useMemo, + useState, + useCallback, + useContext +} from 'react'; +import { useSearchParams } from 'react-router'; +import type { WizardContextType, WizardRootProps, WizardStep } from '../types'; + +export const WizardContext = createContext(null); + +export function WizardProvider({ + children, + steps, + urlParamKey = 'step', + onBeforeNextStep, + onFinish, + onCancel +}: WizardRootProps) { + const [searchParams, setSearchParams] = useSearchParams(); + const [isLoading, setIsLoading] = useState(false); + const currentStepId = searchParams.get(urlParamKey); + const stepsHash = steps.map((s) => s.id).join(','); + + const currentStepIndex = useMemo(() => { + if (!currentStepId) return 0; + + const foundIndex = steps.findIndex( + (s: WizardStep) => s.id === currentStepId + ); + + return foundIndex >= 0 ? foundIndex : 0; + }, [currentStepId, steps]); + + const currentStep = steps[currentStepIndex]; + const isFirstStep = currentStepIndex === 0; + const isLastStep = currentStepIndex === steps.length - 1; + + useEffect(() => { + const isUrlEmptyOrInvalid = + !currentStepId || + steps.findIndex((s: WizardStep) => s.id === currentStepId) === -1; + + if (isUrlEmptyOrInvalid && steps.length > 0) { + setSearchParams( + (prev) => { + const newParams = new URLSearchParams(prev); + + newParams.set(urlParamKey, steps[0].id); + + return newParams; + }, + { replace: true } + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentStepId, stepsHash, urlParamKey, setSearchParams]); + + const changeStep = useCallback( + (newIndex: number) => { + if (newIndex < 0 || newIndex >= steps.length) return; + + const step = steps[newIndex]; + + setSearchParams((prev) => { + const newParams = new URLSearchParams(prev); + + newParams.set(urlParamKey, step.id); + + return newParams; + }); + }, + [steps, urlParamKey, setSearchParams] + ); + + const nextStep = useCallback(async () => { + if (isLoading) return; + + setIsLoading(true); + + if (onBeforeNextStep) { + const canProceed = await onBeforeNextStep(currentStep); + + if (!canProceed) { + setIsLoading(false); + + return; + } + } + + if (!isLastStep) { + changeStep(currentStepIndex + 1); + } + + setIsLoading(false); + }, [ + isLoading, + onBeforeNextStep, + currentStep, + isLastStep, + changeStep, + currentStepIndex + ]); + + const prevStep = useCallback(() => { + if (isFirstStep || isLoading) return; + + changeStep(currentStepIndex - 1); + }, [isFirstStep, isLoading, changeStep, currentStepIndex]); + + const goToStep = useCallback( + (index: number) => { + if (isLoading) return; + + changeStep(index); + }, + [isLoading, changeStep] + ); + + const handleFinish = useCallback(async () => { + if (isLoading) return; + setIsLoading(true); + + try { + await onFinish(); + } catch (error) { + console.error(error); + } finally { + setIsLoading(false); + } + }, [isLoading, onFinish]); + + const handleCancel = useCallback(() => { + onCancel?.(); + }, [onCancel]); + + const value = useMemo( + () => ({ + state: { + currentStep, + currentStepIndex, + totalSteps: steps.length, + isFirstStep, + isLastStep, + isLoading + }, + actions: { + nextStep, + prevStep, + goToStep, + handleCancel, + handleFinish + } + }), + [ + currentStep, + currentStepIndex, + steps.length, + isFirstStep, + isLastStep, + isLoading, + nextStep, + prevStep, + goToStep, + handleCancel, + handleFinish + ] + ); + + return ( + {children} + ); +} + +export function useWizard() { + const context = useContext(WizardContext); + + if (!context) { + throw new Error( + 'useWizard deve ser usado dentro de um componente ' + ); + } + + return context; +} diff --git a/UIs/Merchants/src/shared/components/global/wizard/index.test.tsx b/UIs/Merchants/src/shared/components/global/wizard/index.test.tsx new file mode 100644 index 0000000..2a07290 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/index.test.tsx @@ -0,0 +1,303 @@ +import { render, screen, waitFor, act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { MemoryRouter } from 'react-router'; +import { + Wizard, + WizardHeader, + WizardContent, + WizardControl, + useWizard +} from './index'; + +const steps = [ + { id: 'step-1', label: 'Dados Pessoais', component:

Passo 1: Dados

}, + { id: 'step-2', label: 'Endereço', component:

Passo 2: Endereço

}, + { id: 'step-3', label: 'Revisão', component:

Passo 3: Revisão

} +]; + +const WizardContextSpy = ({ onContext }: { onContext: (ctx: any) => void }) => { + const context = useWizard(); + + onContext(context); + + return null; +}; + +const renderWizard = ( + initialEntries = ['/'], + props: any = {}, + extraChildren: React.ReactNode = null +) => { + return render( + + + + + {extraChildren} + + + + ); +}; + +describe('Wizard Component', () => { + afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); + }); + + describe('Navigation Flow (UI Integration)', () => { + it('should render the first step by default', () => { + renderWizard(); + + expect( + screen.getByRole('heading', { name: 'Passo 1: Dados' }) + ).toBeInTheDocument(); + + expect( + screen.queryByRole('button', { name: /Voltar/i }) + ).not.toBeInTheDocument(); + }); + + it('should navigate to the next step when clicking "Next"', async () => { + const user = userEvent.setup(); + + renderWizard(); + + const nextBtn = screen.getByRole('button', { name: /Avançar/i }); + + await user.click(nextBtn); + + expect( + await screen.findByRole('heading', { name: 'Passo 2: Endereço' }) + ).toBeInTheDocument(); + }); + + it('should navigate back when clicking "Back"', async () => { + const user = userEvent.setup(); + + renderWizard(['/?step=step-2']); + + expect( + screen.getByRole('heading', { name: 'Passo 2: Endereço' }) + ).toBeInTheDocument(); + + const prevBtn = screen.getByRole('button', { name: /Voltar/i }); + + await user.click(prevBtn); + + expect( + await screen.findByRole('heading', { name: 'Passo 1: Dados' }) + ).toBeInTheDocument(); + }); + }); + + describe('URL Synchronization', () => { + it('should initialize correctly based on URL params', () => { + renderWizard(['/?step=step-3']); + + expect( + screen.getByRole('heading', { name: 'Passo 3: Revisão' }) + ).toBeInTheDocument(); + }); + + it('should fallback to step 1 for invalid URL params', () => { + renderWizard(['/?step=invalid-step-id']); + + expect( + screen.getByRole('heading', { name: 'Passo 1: Dados' }) + ).toBeInTheDocument(); + }); + }); + + describe('Validation Logic (onBeforeNextStep)', () => { + it('should block navigation if validation returns false', async () => { + const user = userEvent.setup(); + const mockValidate = vi.fn().mockReturnValue(false); + + renderWizard(['/'], { onBeforeNextStep: mockValidate }); + + await user.click(screen.getByRole('button', { name: /Avançar/i })); + + expect(mockValidate).toHaveBeenCalledWith( + expect.objectContaining({ id: 'step-1' }) + ); + + expect( + screen.getByRole('heading', { name: 'Passo 1: Dados' }) + ).toBeInTheDocument(); + }); + + it('should show loading state and wait for async validation', async () => { + const user = userEvent.setup(); + + let resolveValidation: (value: boolean) => void = () => {}; + const validationPromise = new Promise((resolve) => { + resolveValidation = resolve; + }); + + const mockAsyncValidate = vi.fn().mockReturnValue(validationPromise); + + renderWizard(['/'], { onBeforeNextStep: mockAsyncValidate }); + + const nextBtn = screen.getByRole('button', { name: /Avançar/i }); + + const clickPromise = user.click(nextBtn); + + await waitFor(() => { + expect(nextBtn).toBeDisabled(); + }); + + resolveValidation(true); + + await clickPromise; + + expect( + await screen.findByRole('heading', { name: 'Passo 2: Endereço' }) + ).toBeInTheDocument(); + }); + }); + + describe('Completion', () => { + it('should call onFinish only on the last step', async () => { + const user = userEvent.setup(); + const handleFinish = vi.fn(); + + renderWizard(['/?step=step-3'], { onFinish: handleFinish }); + + const finishBtn = screen.getByRole('button', { name: /Finalizar/i }); + + expect( + screen.queryByRole('button', { name: /Avançar/i }) + ).not.toBeInTheDocument(); + + await user.click(finishBtn); + + expect(handleFinish).toHaveBeenCalledTimes(1); + }); + }); + + describe('Guard Clauses & Branch Coverage (Whitebox Testing)', () => { + it('should ignore prevStep() when on the first step', () => { + let context: any; + + renderWizard( + ['/'], + {}, + (context = ctx)} /> + ); + + act(() => { + context.actions.prevStep(); + }); + + expect(context.state.currentStepIndex).toBe(0); + expect( + screen.getByRole('heading', { name: 'Passo 1: Dados' }) + ).toBeInTheDocument(); + }); + + it('should ignore nextStep() when on the last step (without explicit finish)', () => { + let context: any; + + renderWizard( + ['/?step=step-3'], + {}, + (context = ctx)} /> + ); + + act(() => { + context.actions.nextStep(); + }); + + expect(context.state.currentStepIndex).toBe(2); + expect( + screen.getByRole('heading', { name: 'Passo 3: Revisão' }) + ).toBeInTheDocument(); + }); + + it('should ignore goToStep() with invalid indices (negative or out of bounds)', () => { + let context: any; + + renderWizard( + ['/'], + {}, + (context = ctx)} /> + ); + + act(() => { + context.actions.goToStep(-1); + }); + + expect(context.state.currentStepIndex).toBe(0); + + act(() => { + context.actions.goToStep(99); + }); + + expect(context.state.currentStepIndex).toBe(0); + }); + + it('should ignore all navigation actions while isLoading is true', async () => { + let context: any; + const pendingPromise = new Promise(() => {}); + const mockValidate = vi.fn().mockReturnValue(pendingPromise); + + renderWizard( + ['/'], + { onBeforeNextStep: mockValidate }, + (context = ctx)} /> + ); + + await act(async () => { + context.actions.nextStep(); + }); + + expect(context.state.isLoading).toBe(true); + + act(() => { + context.actions.goToStep(2); + context.actions.prevStep(); + context.actions.nextStep(); + context.actions.handleFinish(); + }); + + expect(context.state.currentStepIndex).toBe(0); + expect(mockValidate).toHaveBeenCalledTimes(1); + }); + + it('should handle errors thrown outside the provider', () => { + const consoleSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + const BadComponent = () => { + useWizard(); + return null; + }; + + expect(() => render()).toThrow( + 'useWizard deve ser usado dentro de um componente ' + ); + + consoleSpy.mockRestore(); + }); + + it('should handle cancel action safely when onCancel is undefined', () => { + let context: any; + + renderWizard( + ['/'], + {}, + (context = ctx)} /> + ); + + act(() => { + context.actions.handleCancel(); + }); + + expect(context.state.currentStepIndex).toBe(0); + }); + }); +}); diff --git a/UIs/Merchants/src/shared/components/global/wizard/index.tsx b/UIs/Merchants/src/shared/components/global/wizard/index.tsx new file mode 100644 index 0000000..be05bc1 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/index.tsx @@ -0,0 +1,6 @@ +export { WizardContext, WizardProvider, useWizard } from './hooks'; +export { WizardHeader } from './pieces/wizard-header'; +export { WizardContent } from './pieces/wizard-content'; +export { WizardControl } from './pieces/wizard-controls'; +export { Wizard } from './pieces/wizard'; +export type { WizardStep, WizardRootProps } from './types'; diff --git a/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-content/index.tsx b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-content/index.tsx new file mode 100644 index 0000000..adc9afd --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-content/index.tsx @@ -0,0 +1,17 @@ +import type { ComponentProps } from 'react'; +import { useWizard } from '../../hooks'; +import { Card } from '@/shared/components/ui/card'; +import { cn } from '@/shared/utils'; + +export function WizardContent({ className, ...props }: ComponentProps<'div'>) { + const { state } = useWizard(); + + return ( + + {state.currentStep?.component} + + ); +} diff --git a/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-controls/index.tsx b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-controls/index.tsx new file mode 100644 index 0000000..8e7a1b3 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-controls/index.tsx @@ -0,0 +1,78 @@ +import type { ComponentProps } from 'react'; +import { Loader2 } from 'lucide-react'; +import { Button } from '@/shared/components/ui/button'; +import { cn } from '@/shared/utils'; +import { useWizard } from '../../hooks'; + +interface WizardControlProps extends ComponentProps<'div'> { + labels?: { + next?: string; + back?: string; + finish?: string; + cancel?: string; + }; +} + +export function WizardControl({ + className, + labels, + ...props +}: WizardControlProps) { + const { state, actions } = useWizard(); + + return ( +
+
+ {!state.isFirstStep && ( + + )} +
+ +
+ + + {state.isLastStep ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-header/index.tsx b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-header/index.tsx new file mode 100644 index 0000000..f39c85a --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard-header/index.tsx @@ -0,0 +1,34 @@ +import type { ComponentProps } from 'react'; +import { useWizard } from '../../hooks'; +import { cn } from '@/shared/utils'; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator +} from '@/shared/components/ui/breadcrumb'; +import { Progress } from '@/shared/components/ui/progress'; + +export function WizardHeader({ + className, + label, + ...props +}: { label?: string } & ComponentProps<'div'>) { + const { state } = useWizard(); + + return ( +
+ + + {label} + + {state.currentStep?.label} + + + +
+ ); +} diff --git a/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard/index.tsx b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard/index.tsx new file mode 100644 index 0000000..f6fc201 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/pieces/wizard/index.tsx @@ -0,0 +1,6 @@ +import { WizardProvider } from '../../hooks'; +import type { WizardRootProps } from '../../types'; + +export function Wizard(props: WizardRootProps) { + return ; +} diff --git a/UIs/Merchants/src/shared/components/global/wizard/types/index.ts b/UIs/Merchants/src/shared/components/global/wizard/types/index.ts new file mode 100644 index 0000000..6a83386 --- /dev/null +++ b/UIs/Merchants/src/shared/components/global/wizard/types/index.ts @@ -0,0 +1,38 @@ +import type { ReactNode } from 'react'; + +export interface WizardStep { + id: string; + label: string; + component: ReactNode; +} + +type WizardState = { + currentStep: WizardStep; + currentStepIndex: number; + totalSteps: number; + isFirstStep: boolean; + isLastStep: boolean; + isLoading: boolean; +}; + +type WizardActions = { + nextStep: () => Promise; + prevStep: () => void; + goToStep: (index: number) => void; + handleCancel: () => void; + handleFinish: () => Promise; +}; + +export interface WizardContextType { + state: WizardState; + actions: WizardActions; +} + +export interface WizardRootProps { + children: ReactNode; + steps: WizardStep[]; + urlParamKey?: string; + onBeforeNextStep?: (step: WizardStep) => Promise | boolean; + onFinish: () => void | Promise; + onCancel?: () => void; +} diff --git a/UIs/Merchants/src/shared/components/ui/accordion.tsx b/UIs/Merchants/src/shared/components/ui/accordion.tsx new file mode 100644 index 0000000..46ccea1 --- /dev/null +++ b/UIs/Merchants/src/shared/components/ui/accordion.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { ChevronDownIcon } from 'lucide-react'; +import { Accordion as AccordionPrimitive } from 'radix-ui'; + +import { cn } from '@/shared/utils'; + +function Accordion({ + ...props +}: React.ComponentProps) { + return ; +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180', + className + )} + {...props} + > + {children} + + + + ); +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
{children}
+
+ ); +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; diff --git a/UIs/Merchants/src/shared/components/ui/alert-dialog.tsx b/UIs/Merchants/src/shared/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..b2eed19 --- /dev/null +++ b/UIs/Merchants/src/shared/components/ui/alert-dialog.tsx @@ -0,0 +1,194 @@ +import * as React from 'react'; +import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'; + +import { cn } from '@/shared/utils'; +import { Button } from '@/shared/components/ui/button'; + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return ; +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogContent({ + className, + size = 'default', + ...props +}: React.ComponentProps & { + size?: 'default' | 'sm'; +}) { + return ( + + + + + ); +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogMedia({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDialogAction({ + className, + variant = 'default', + size = 'default', + ...props +}: React.ComponentProps & + Pick, 'variant' | 'size'>) { + return ( + + ); +} + +function AlertDialogCancel({ + className, + variant = 'outline', + size = 'default', + ...props +}: React.ComponentProps & + Pick, 'variant' | 'size'>) { + return ( + + ); +} + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger +}; diff --git a/UIs/Merchants/src/shared/components/ui/alert.tsx b/UIs/Merchants/src/shared/components/ui/alert.tsx new file mode 100644 index 0000000..4aae193 --- /dev/null +++ b/UIs/Merchants/src/shared/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/shared/utils'; + +const alertVariants = cva( + 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90' + } + }, + defaultVariants: { + variant: 'default' + } + } +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/UIs/Merchants/src/shared/components/ui/avatar.tsx b/UIs/Merchants/src/shared/components/ui/avatar.tsx new file mode 100644 index 0000000..788bab9 --- /dev/null +++ b/UIs/Merchants/src/shared/components/ui/avatar.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import * as AvatarPrimitive from '@radix-ui/react-avatar'; + +import { cn } from '@/shared/utils'; + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/UIs/Merchants/src/shared/components/ui/badge.tsx b/UIs/Merchants/src/shared/components/ui/badge.tsx new file mode 100644 index 0000000..8735dc8 --- /dev/null +++ b/UIs/Merchants/src/shared/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/shared/utils'; + +const badgeVariants = cva( + 'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', + { + variants: { + variant: { + default: + 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: + 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60', + outline: + 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground' + } + }, + defaultVariants: { + variant: 'default' + } + } +); + +function Badge({ + className, + variant, + asChild = false, + ...props +}: React.ComponentProps<'span'> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : 'span'; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/UIs/Merchants/src/shared/components/ui/breadcrumb.tsx b/UIs/Merchants/src/shared/components/ui/breadcrumb.tsx new file mode 100644 index 0000000..e87ef15 --- /dev/null +++ b/UIs/Merchants/src/shared/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; +import { ChevronRight, MoreHorizontal } from 'lucide-react'; + +import { cn } from '@/shared/utils'; + +function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { + return