From bd2d963940718834b539916d017f0ad8324856d7 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Mon, 13 Oct 2025 20:22:47 +0200 Subject: [PATCH] Replace frontend:main with frontend:release/v0.3 --- frontend/.dockerignore | 5 +- frontend/.gitignore | 47 +- frontend/COMPONENT_GUIDE.md | 289 - frontend/Dockerfile | 22 + frontend/README.md | 87 +- frontend/app/app.css | 18 + frontend/{src => app}/components/Spinner.tsx | 0 .../components/features/OnboardingBanner.tsx | 0 .../components/features/auth/LoginForm.tsx | 98 + .../components/features/auth/RegisterForm.tsx | 107 + .../features/dishes/AddUserToDishForm.tsx | 16 +- .../features/dishes/CreateDishForm.tsx | 39 +- .../components/features/dishes/Dish.tsx | 0 .../components/features/dishes/DishCard.tsx | 0 .../features/dishes/EditDishForm.tsx | 24 +- .../dishes/EditDishUserCardEditForm.tsx | 9 +- .../features/dishes/RecurrenceLabels.tsx | 0 .../features/dishes/SyncUsersForm.tsx | 0 .../features/dishes/UserDishCard.tsx | 0 .../features/navbar/MobileDropdownMenu.tsx | 2 +- .../features/schedule/HistoricalDishes.tsx | 0 .../features/schedule/ScheduleCalendar.tsx | 0 .../features/schedule/ScheduleEditForm.tsx | 4 +- .../schedule/ScheduleRegenerateButton.tsx | 35 + .../schedule/ScheduleRegenerateForm.tsx | 19 +- .../features/schedule/UpcomingDishes.tsx | 0 .../features/schedule/UserDishEditCard.tsx | 2 +- .../features/schedule/dayCard/DateBadge.tsx | 2 +- .../schedule/dayCard/ScheduleDayCard.tsx | 2 +- .../dayCard/ScheduleDayCardUserDish.tsx | 6 +- .../features/users/EditUserForm.tsx | 21 +- frontend/app/components/layout/AuthGuard.tsx | 45 + frontend/app/components/layout/Card.tsx | 15 + .../{src => app}/components/layout/NavBar.tsx | 10 +- frontend/app/components/pages/PrivatePage.tsx | 9 + frontend/app/components/pages/PublicPage.tsx | 11 + frontend/app/components/ui/Alert.tsx | 35 + frontend/app/components/ui/Button.tsx | 62 + .../components/ui/Buttons/OutlineButton.tsx | 37 + .../ui/Buttons/OutlineLinkButton.tsx | 49 + .../app/components/ui/Buttons/SolidButton.tsx | 40 + .../components/ui/Buttons/SolidLinkButton.tsx | 46 + .../components/ui/Description.tsx | 2 +- frontend/{src => app}/components/ui/Hr.tsx | 0 frontend/{src => app}/components/ui/Label.tsx | 0 frontend/app/components/ui/Modal.tsx | 60 + frontend/app/components/ui/PageTitle.tsx | 18 + .../components/ui/RecurrenceInput.tsx | 0 .../components/ui/SectionTitle.tsx | 2 +- frontend/app/components/ui/Toggle.tsx | 42 + frontend/{src => app}/context/AuthContext.tsx | 0 frontend/{src => app}/helpers/Date.ts | 0 frontend/{src => app}/hooks/useFetchDishes.ts | 0 frontend/{src => app}/hooks/useFetchUsers.ts | 0 frontend/app/hooks/useRoutes.ts | 32 + frontend/app/root.tsx | 82 + frontend/app/routes.ts | 7 + frontend/app/routes/home.tsx | 13 + frontend/{src => app}/styles/base/globals.css | 0 frontend/app/styles/components/buttons.css | 42 + .../{src => app}/styles/components/select.css | 0 frontend/app/styles/main.css | 6 + .../{src => app}/styles/theme/borders.css | 0 frontend/app/styles/theme/colors.css | 10 + .../styles/theme/colors/background.css | 0 .../styles/theme/colors/border.css | 0 .../{src => app}/styles/theme/colors/root.css | 0 frontend/app/styles/theme/colors/text.css | 216 + frontend/app/styles/theme/fonts.css | 93 + frontend/app/types/DishType.ts | 20 + frontend/app/types/ScheduleType.ts | 27 + frontend/app/types/ScheduledUserDishType.ts | 21 + frontend/{src => app}/types/UserDishType.ts | 0 frontend/app/types/UserType.ts | 7 + frontend/app/utils/api/apiRequest.ts | 107 + frontend/{src => app}/utils/api/auth.ts | 0 frontend/{src => app}/utils/api/dishApi.ts | 0 .../{src => app}/utils/api/scheduleApi.ts | 0 .../utils/api/scheduledUserDishesApi.ts | 0 .../{src => app}/utils/api/userDishApi.ts | 0 frontend/{src => app}/utils/api/usersApi.ts | 0 frontend/{src => app}/utils/dateBuilder.ts | 0 .../{src => app}/utils/scheduleBuilder.ts | 0 frontend/app/welcome/logo-dark.svg | 23 + frontend/app/welcome/logo-light.svg | 23 + frontend/app/welcome/welcome.tsx | 89 + frontend/archive/.dockerignore | 1 + frontend/{ => archive}/.env.local.example | 0 frontend/{ => archive}/.env.production | 0 frontend/archive/.gitignore | 45 + frontend/archive/README.md | 2 + frontend/{ => archive}/bin/update.sh | 0 frontend/{ => archive}/build_and_push.sh | 0 frontend/{ => archive}/eslint.config.mjs | 0 frontend/{ => archive}/next.config.ts | 0 frontend/archive/package.json | 33 + frontend/{ => archive}/postcss.config.mjs | 0 .../{ => archive}/public/dish-planner.webp | Bin frontend/{ => archive}/public/file.svg | 0 frontend/{ => archive}/public/globe.svg | 0 frontend/{ => archive}/public/next.svg | 0 frontend/{ => archive}/public/vercel.svg | 0 frontend/{ => archive}/public/window.svg | 0 .../src/app/dishes/[id]/delete/page.tsx | 4 +- .../src/app/dishes/[id]/edit/page.tsx | 7 +- .../src/app/dishes/create/page.tsx | 0 .../{ => archive}/src/app/dishes/page.tsx | 0 frontend/{ => archive}/src/app/favicon.ico | Bin frontend/{ => archive}/src/app/layout.tsx | 0 frontend/{ => archive}/src/app/login/page.tsx | 0 frontend/{ => archive}/src/app/page.tsx | 0 .../{ => archive}/src/app/register/page.tsx | 0 .../src/app/schedule/[date]/edit/page.tsx | 0 .../scheduled-user-dishes/history/page.tsx | 0 .../src/app/users/[id]/edit/page.tsx | 0 .../src/app/users/create/page.tsx | 23 +- frontend/{ => archive}/src/app/users/page.tsx | 9 +- frontend/archive/src/components/Spinner.tsx | 15 + .../components/features/OnboardingBanner.tsx | 49 + .../components/features/auth/LoginForm.tsx | 13 +- .../features/auth/RegistrationForm.tsx | 23 +- .../features/dishes/AddUserToDishForm.tsx | 101 + .../features/dishes/CreateDishForm.tsx | 95 + .../src/components/features/dishes/Dish.tsx | 39 + .../components/features/dishes/DishCard.tsx | 23 + .../features/dishes/EditDishForm.tsx | 87 + .../dishes/EditDishUserCardEditForm.tsx | 119 + .../features/dishes/RecurrenceLabels.tsx | 54 + .../features/dishes/SyncUsersForm.tsx | 32 + .../features/dishes/UserDishCard.tsx | 83 + .../features/navbar/MobileDropdownMenu.tsx | 69 + .../features/schedule/HistoricalDishes.tsx | 44 + .../features/schedule/ScheduleCalendar.tsx | 75 + .../features/schedule/ScheduleEditForm.tsx | 142 + .../schedule/ScheduleRegenerateButton.tsx | 0 .../schedule/ScheduleRegenerateForm.tsx | 78 + .../features/schedule/UpcomingDishes.tsx | 62 + .../features/schedule/UserDishEditCard.tsx | 79 + .../features/schedule/dayCard/DateBadge.tsx | 27 + .../schedule/dayCard/ScheduleDayCard.tsx | 50 + .../dayCard/ScheduleDayCardUserDish.tsx | 32 + .../features/users/EditUserForm.tsx | 63 + .../src/components/layout/AuthGuard.tsx | 0 .../archive/src/components/layout/Card.tsx | 15 + .../archive/src/components/layout/NavBar.tsx | 83 + frontend/archive/src/components/ui/Alert.tsx | 34 + frontend/archive/src/components/ui/Button.tsx | 62 + .../components/ui/Buttons/OutlineButton.tsx | 37 + .../ui/Buttons/OutlineLinkButton.tsx | 49 + .../src/components/ui/Buttons/SolidButton.tsx | 39 + .../components/ui/Buttons/SolidLinkButton.tsx | 46 + .../archive/src/components/ui/Description.tsx | 17 + frontend/archive/src/components/ui/Hr.tsx | 14 + frontend/archive/src/components/ui/Label.tsx | 25 + .../{ => archive}/src/components/ui/Modal.tsx | 0 .../src/components/ui/PageTitle.tsx | 2 +- .../src/components/ui/RecurrenceInput.tsx | 65 + .../src/components/ui/SectionTitle.tsx | 16 + frontend/archive/src/components/ui/Toggle.tsx | 42 + frontend/archive/src/context/AuthContext.tsx | 46 + frontend/archive/src/helpers/Date.ts | 18 + frontend/archive/src/hooks/useFetchDishes.ts | 22 + frontend/archive/src/hooks/useFetchUsers.ts | 22 + frontend/{ => archive}/src/hooks/useRoutes.ts | 0 frontend/archive/src/styles/base/globals.css | 19 + .../archive/src/styles/components/buttons.css | 42 + .../archive/src/styles/components/select.css | 0 frontend/{ => archive}/src/styles/main.css | 0 frontend/archive/src/styles/theme/borders.css | 14 + .../{ => archive}/src/styles/theme/colors.css | 0 .../src/styles/theme/colors/background.css | 226 + .../src/styles/theme/colors/border.css | 286 + .../archive/src/styles/theme/colors/root.css | 193 + .../src/styles/theme/colors/text.css | 0 .../{ => archive}/src/styles/theme/fonts.css | 0 frontend/{ => archive}/src/types/DishType.ts | 0 .../{ => archive}/src/types/ScheduleType.ts | 0 .../src/types/ScheduledUserDishType.ts | 0 frontend/archive/src/types/UserDishType.ts | 8 + frontend/{ => archive}/src/types/UserType.ts | 0 .../{ => archive}/src/utils/api/apiRequest.ts | 0 frontend/archive/src/utils/api/auth.ts | 28 + frontend/archive/src/utils/api/dishApi.ts | 149 + frontend/archive/src/utils/api/scheduleApi.ts | 112 + .../src/utils/api/scheduledUserDishesApi.ts | 77 + frontend/archive/src/utils/api/userDishApi.ts | 18 + frontend/archive/src/utils/api/usersApi.ts | 139 + frontend/archive/src/utils/dateBuilder.ts | 24 + frontend/archive/src/utils/scheduleBuilder.ts | 19 + frontend/archive/tailwind.config.ts | 18 + frontend/archive/tsconfig.json | 27 + frontend/package-lock.json | 4845 +++++++++++++++++ frontend/package.json | 41 +- frontend/public/favicon.ico | Bin 0 -> 15086 bytes frontend/react-router.config.ts | 7 + frontend/src/components/layout/Card.tsx | 22 - frontend/src/components/ui/Alert.tsx | 29 - frontend/src/components/ui/Button.tsx | 118 - frontend/src/components/ui/Checkbox.tsx | 64 - frontend/src/components/ui/Input.tsx | 68 - frontend/src/components/ui/Select.tsx | 82 - frontend/src/components/ui/Toggle.tsx | 75 - frontend/tailwind.config.ts | 148 - frontend/tsconfig.json | 46 +- frontend/vite.config.ts | 8 + 205 files changed, 10235 insertions(+), 1104 deletions(-) delete mode 100644 frontend/COMPONENT_GUIDE.md create mode 100644 frontend/Dockerfile create mode 100644 frontend/app/app.css rename frontend/{src => app}/components/Spinner.tsx (100%) rename frontend/{src => app}/components/features/OnboardingBanner.tsx (100%) create mode 100644 frontend/app/components/features/auth/LoginForm.tsx create mode 100644 frontend/app/components/features/auth/RegisterForm.tsx rename frontend/{src => app}/components/features/dishes/AddUserToDishForm.tsx (89%) rename frontend/{src => app}/components/features/dishes/CreateDishForm.tsx (69%) rename frontend/{src => app}/components/features/dishes/Dish.tsx (100%) rename frontend/{src => app}/components/features/dishes/DishCard.tsx (100%) rename frontend/{src => app}/components/features/dishes/EditDishForm.tsx (75%) rename frontend/{src => app}/components/features/dishes/EditDishUserCardEditForm.tsx (92%) rename frontend/{src => app}/components/features/dishes/RecurrenceLabels.tsx (100%) rename frontend/{src => app}/components/features/dishes/SyncUsersForm.tsx (100%) rename frontend/{src => app}/components/features/dishes/UserDishCard.tsx (100%) rename frontend/{src => app}/components/features/navbar/MobileDropdownMenu.tsx (96%) rename frontend/{src => app}/components/features/schedule/HistoricalDishes.tsx (100%) rename frontend/{src => app}/components/features/schedule/ScheduleCalendar.tsx (100%) rename frontend/{src => app}/components/features/schedule/ScheduleEditForm.tsx (95%) create mode 100644 frontend/app/components/features/schedule/ScheduleRegenerateButton.tsx rename frontend/{src => app}/components/features/schedule/ScheduleRegenerateForm.tsx (88%) rename frontend/{src => app}/components/features/schedule/UpcomingDishes.tsx (100%) rename frontend/{src => app}/components/features/schedule/UserDishEditCard.tsx (98%) rename frontend/{src => app}/components/features/schedule/dayCard/DateBadge.tsx (95%) rename frontend/{src => app}/components/features/schedule/dayCard/ScheduleDayCard.tsx (96%) rename frontend/{src => app}/components/features/schedule/dayCard/ScheduleDayCardUserDish.tsx (75%) rename frontend/{src => app}/components/features/users/EditUserForm.tsx (73%) create mode 100644 frontend/app/components/layout/AuthGuard.tsx create mode 100644 frontend/app/components/layout/Card.tsx rename frontend/{src => app}/components/layout/NavBar.tsx (93%) create mode 100644 frontend/app/components/pages/PrivatePage.tsx create mode 100644 frontend/app/components/pages/PublicPage.tsx create mode 100644 frontend/app/components/ui/Alert.tsx create mode 100644 frontend/app/components/ui/Button.tsx create mode 100644 frontend/app/components/ui/Buttons/OutlineButton.tsx create mode 100644 frontend/app/components/ui/Buttons/OutlineLinkButton.tsx create mode 100644 frontend/app/components/ui/Buttons/SolidButton.tsx create mode 100644 frontend/app/components/ui/Buttons/SolidLinkButton.tsx rename frontend/{src => app}/components/ui/Description.tsx (85%) rename frontend/{src => app}/components/ui/Hr.tsx (100%) rename frontend/{src => app}/components/ui/Label.tsx (100%) create mode 100644 frontend/app/components/ui/Modal.tsx create mode 100644 frontend/app/components/ui/PageTitle.tsx rename frontend/{src => app}/components/ui/RecurrenceInput.tsx (100%) rename frontend/{src => app}/components/ui/SectionTitle.tsx (75%) create mode 100644 frontend/app/components/ui/Toggle.tsx rename frontend/{src => app}/context/AuthContext.tsx (100%) rename frontend/{src => app}/helpers/Date.ts (100%) rename frontend/{src => app}/hooks/useFetchDishes.ts (100%) rename frontend/{src => app}/hooks/useFetchUsers.ts (100%) create mode 100644 frontend/app/hooks/useRoutes.ts create mode 100644 frontend/app/root.tsx create mode 100644 frontend/app/routes.ts create mode 100644 frontend/app/routes/home.tsx rename frontend/{src => app}/styles/base/globals.css (100%) create mode 100644 frontend/app/styles/components/buttons.css rename frontend/{src => app}/styles/components/select.css (100%) create mode 100644 frontend/app/styles/main.css rename frontend/{src => app}/styles/theme/borders.css (100%) create mode 100644 frontend/app/styles/theme/colors.css rename frontend/{src => app}/styles/theme/colors/background.css (100%) rename frontend/{src => app}/styles/theme/colors/border.css (100%) rename frontend/{src => app}/styles/theme/colors/root.css (100%) create mode 100644 frontend/app/styles/theme/colors/text.css create mode 100644 frontend/app/styles/theme/fonts.css create mode 100644 frontend/app/types/DishType.ts create mode 100644 frontend/app/types/ScheduleType.ts create mode 100644 frontend/app/types/ScheduledUserDishType.ts rename frontend/{src => app}/types/UserDishType.ts (100%) create mode 100644 frontend/app/types/UserType.ts create mode 100644 frontend/app/utils/api/apiRequest.ts rename frontend/{src => app}/utils/api/auth.ts (100%) rename frontend/{src => app}/utils/api/dishApi.ts (100%) rename frontend/{src => app}/utils/api/scheduleApi.ts (100%) rename frontend/{src => app}/utils/api/scheduledUserDishesApi.ts (100%) rename frontend/{src => app}/utils/api/userDishApi.ts (100%) rename frontend/{src => app}/utils/api/usersApi.ts (100%) rename frontend/{src => app}/utils/dateBuilder.ts (100%) rename frontend/{src => app}/utils/scheduleBuilder.ts (100%) create mode 100644 frontend/app/welcome/logo-dark.svg create mode 100644 frontend/app/welcome/logo-light.svg create mode 100644 frontend/app/welcome/welcome.tsx create mode 100644 frontend/archive/.dockerignore rename frontend/{ => archive}/.env.local.example (100%) rename frontend/{ => archive}/.env.production (100%) create mode 100644 frontend/archive/.gitignore create mode 100644 frontend/archive/README.md rename frontend/{ => archive}/bin/update.sh (100%) rename frontend/{ => archive}/build_and_push.sh (100%) rename frontend/{ => archive}/eslint.config.mjs (100%) rename frontend/{ => archive}/next.config.ts (100%) create mode 100644 frontend/archive/package.json rename frontend/{ => archive}/postcss.config.mjs (100%) rename frontend/{ => archive}/public/dish-planner.webp (100%) rename frontend/{ => archive}/public/file.svg (100%) rename frontend/{ => archive}/public/globe.svg (100%) rename frontend/{ => archive}/public/next.svg (100%) rename frontend/{ => archive}/public/vercel.svg (100%) rename frontend/{ => archive}/public/window.svg (100%) rename frontend/{ => archive}/src/app/dishes/[id]/delete/page.tsx (95%) rename frontend/{ => archive}/src/app/dishes/[id]/edit/page.tsx (87%) rename frontend/{ => archive}/src/app/dishes/create/page.tsx (100%) rename frontend/{ => archive}/src/app/dishes/page.tsx (100%) rename frontend/{ => archive}/src/app/favicon.ico (100%) rename frontend/{ => archive}/src/app/layout.tsx (100%) rename frontend/{ => archive}/src/app/login/page.tsx (100%) rename frontend/{ => archive}/src/app/page.tsx (100%) rename frontend/{ => archive}/src/app/register/page.tsx (100%) rename frontend/{ => archive}/src/app/schedule/[date]/edit/page.tsx (100%) rename frontend/{ => archive}/src/app/scheduled-user-dishes/history/page.tsx (100%) rename frontend/{ => archive}/src/app/users/[id]/edit/page.tsx (100%) rename frontend/{ => archive}/src/app/users/create/page.tsx (70%) rename frontend/{ => archive}/src/app/users/page.tsx (88%) create mode 100644 frontend/archive/src/components/Spinner.tsx create mode 100644 frontend/archive/src/components/features/OnboardingBanner.tsx rename frontend/{ => archive}/src/components/features/auth/LoginForm.tsx (88%) rename frontend/{ => archive}/src/components/features/auth/RegistrationForm.tsx (82%) create mode 100644 frontend/archive/src/components/features/dishes/AddUserToDishForm.tsx create mode 100644 frontend/archive/src/components/features/dishes/CreateDishForm.tsx create mode 100644 frontend/archive/src/components/features/dishes/Dish.tsx create mode 100644 frontend/archive/src/components/features/dishes/DishCard.tsx create mode 100644 frontend/archive/src/components/features/dishes/EditDishForm.tsx create mode 100644 frontend/archive/src/components/features/dishes/EditDishUserCardEditForm.tsx create mode 100644 frontend/archive/src/components/features/dishes/RecurrenceLabels.tsx create mode 100644 frontend/archive/src/components/features/dishes/SyncUsersForm.tsx create mode 100644 frontend/archive/src/components/features/dishes/UserDishCard.tsx create mode 100644 frontend/archive/src/components/features/navbar/MobileDropdownMenu.tsx create mode 100644 frontend/archive/src/components/features/schedule/HistoricalDishes.tsx create mode 100644 frontend/archive/src/components/features/schedule/ScheduleCalendar.tsx create mode 100644 frontend/archive/src/components/features/schedule/ScheduleEditForm.tsx rename frontend/{ => archive}/src/components/features/schedule/ScheduleRegenerateButton.tsx (100%) create mode 100644 frontend/archive/src/components/features/schedule/ScheduleRegenerateForm.tsx create mode 100644 frontend/archive/src/components/features/schedule/UpcomingDishes.tsx create mode 100644 frontend/archive/src/components/features/schedule/UserDishEditCard.tsx create mode 100644 frontend/archive/src/components/features/schedule/dayCard/DateBadge.tsx create mode 100644 frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCard.tsx create mode 100644 frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCardUserDish.tsx create mode 100644 frontend/archive/src/components/features/users/EditUserForm.tsx rename frontend/{ => archive}/src/components/layout/AuthGuard.tsx (100%) create mode 100644 frontend/archive/src/components/layout/Card.tsx create mode 100644 frontend/archive/src/components/layout/NavBar.tsx create mode 100644 frontend/archive/src/components/ui/Alert.tsx create mode 100644 frontend/archive/src/components/ui/Button.tsx create mode 100644 frontend/archive/src/components/ui/Buttons/OutlineButton.tsx create mode 100644 frontend/archive/src/components/ui/Buttons/OutlineLinkButton.tsx create mode 100644 frontend/archive/src/components/ui/Buttons/SolidButton.tsx create mode 100644 frontend/archive/src/components/ui/Buttons/SolidLinkButton.tsx create mode 100644 frontend/archive/src/components/ui/Description.tsx create mode 100644 frontend/archive/src/components/ui/Hr.tsx create mode 100644 frontend/archive/src/components/ui/Label.tsx rename frontend/{ => archive}/src/components/ui/Modal.tsx (100%) rename frontend/{ => archive}/src/components/ui/PageTitle.tsx (95%) create mode 100644 frontend/archive/src/components/ui/RecurrenceInput.tsx create mode 100644 frontend/archive/src/components/ui/SectionTitle.tsx create mode 100644 frontend/archive/src/components/ui/Toggle.tsx create mode 100644 frontend/archive/src/context/AuthContext.tsx create mode 100644 frontend/archive/src/helpers/Date.ts create mode 100644 frontend/archive/src/hooks/useFetchDishes.ts create mode 100644 frontend/archive/src/hooks/useFetchUsers.ts rename frontend/{ => archive}/src/hooks/useRoutes.ts (100%) create mode 100644 frontend/archive/src/styles/base/globals.css create mode 100644 frontend/archive/src/styles/components/buttons.css create mode 100644 frontend/archive/src/styles/components/select.css rename frontend/{ => archive}/src/styles/main.css (100%) create mode 100644 frontend/archive/src/styles/theme/borders.css rename frontend/{ => archive}/src/styles/theme/colors.css (100%) create mode 100644 frontend/archive/src/styles/theme/colors/background.css create mode 100644 frontend/archive/src/styles/theme/colors/border.css create mode 100644 frontend/archive/src/styles/theme/colors/root.css rename frontend/{ => archive}/src/styles/theme/colors/text.css (100%) rename frontend/{ => archive}/src/styles/theme/fonts.css (100%) rename frontend/{ => archive}/src/types/DishType.ts (100%) rename frontend/{ => archive}/src/types/ScheduleType.ts (100%) rename frontend/{ => archive}/src/types/ScheduledUserDishType.ts (100%) create mode 100644 frontend/archive/src/types/UserDishType.ts rename frontend/{ => archive}/src/types/UserType.ts (100%) rename frontend/{ => archive}/src/utils/api/apiRequest.ts (100%) create mode 100644 frontend/archive/src/utils/api/auth.ts create mode 100644 frontend/archive/src/utils/api/dishApi.ts create mode 100644 frontend/archive/src/utils/api/scheduleApi.ts create mode 100644 frontend/archive/src/utils/api/scheduledUserDishesApi.ts create mode 100644 frontend/archive/src/utils/api/userDishApi.ts create mode 100644 frontend/archive/src/utils/api/usersApi.ts create mode 100644 frontend/archive/src/utils/dateBuilder.ts create mode 100644 frontend/archive/src/utils/scheduleBuilder.ts create mode 100644 frontend/archive/tailwind.config.ts create mode 100644 frontend/archive/tsconfig.json create mode 100644 frontend/package-lock.json create mode 100644 frontend/public/favicon.ico create mode 100644 frontend/react-router.config.ts delete mode 100644 frontend/src/components/layout/Card.tsx delete mode 100644 frontend/src/components/ui/Alert.tsx delete mode 100644 frontend/src/components/ui/Button.tsx delete mode 100644 frontend/src/components/ui/Checkbox.tsx delete mode 100644 frontend/src/components/ui/Input.tsx delete mode 100644 frontend/src/components/ui/Select.tsx delete mode 100644 frontend/src/components/ui/Toggle.tsx delete mode 100644 frontend/tailwind.config.ts create mode 100644 frontend/vite.config.ts diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 11ee758..9b8d514 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1 +1,4 @@ -.env.local +.react-router +build +node_modules +README.md \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index b601ed9..9b7c041 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,45 +1,6 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -/.idea -/.env.local -/package-lock.json - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc .DS_Store -*.pem +/node_modules/ -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts +# React Router +/.react-router/ +/build/ diff --git a/frontend/COMPONENT_GUIDE.md b/frontend/COMPONENT_GUIDE.md deleted file mode 100644 index 7027da9..0000000 --- a/frontend/COMPONENT_GUIDE.md +++ /dev/null @@ -1,289 +0,0 @@ -# Component Guide - Dish Planner - -This guide documents the standardized UI components in the Dish Planner application. All components use Tailwind CSS with our custom color variables from the design system. - -## Design System - -### Colors - -Our color palette is defined in `src/styles/theme/colors/root.css` and integrated into Tailwind via `tailwind.config.ts`: - -- **Primary (Rose)**: `bg-primary`, `text-primary`, `border-primary` with shades 50-950 -- **Secondary (Deluge)**: `bg-secondary`, `text-secondary`, `border-secondary` with shades 50-950 -- **Accent (Malibu Blue)**: `bg-accent`, `text-accent`, `border-accent` with shades 50-950 -- **Yellow (Gamboge)**: `bg-yellow`, `text-yellow`, `border-yellow` with shades 50-950 -- **Gray (Ebony Clay)**: `bg-gray`, `text-gray`, `border-gray` with shades 100-950 -- **Semantic Colors**: - - Danger (Alizarin Crimson): `bg-danger`, `text-danger`, `border-danger` - - Success (Spring Green): `bg-success`, `text-success`, `border-success` - - Warning (Burning Orange): `bg-warning`, `text-warning`, `border-warning` - -## Components - -### Button (`src/components/ui/Button.tsx`) - -Unified button component supporting multiple variants, appearances, and states. - -#### Props - -```typescript -interface ButtonProps { - appearance?: 'solid' | 'outline' | 'text'; // Default: 'solid' - variant?: 'primary' | 'secondary' | 'accent' | 'danger'; // Default: 'primary' - size?: 'small' | 'medium' | 'large'; // Default: 'medium' - type?: 'button' | 'submit' | 'reset'; // Default: 'button' - href?: string; // For link buttons - icon?: ReactNode; - disabled?: boolean; - onClick?: () => void; - className?: string; - children: ReactNode; -} -``` - -#### Examples - -```tsx -// Solid primary button - - -// Outline accent button with icon - - -// Text danger button - - -// Link button - -``` - -### Input (`src/components/ui/Input.tsx`) - -Standardized text input component with label, error, and helper text support. - -#### Props - -```typescript -interface InputProps extends InputHTMLAttributes { - label?: string; - error?: string; - helperText?: string; - fullWidth?: boolean; // Default: true -} -``` - -#### Examples - -```tsx -// Basic input with label - setEmail(e.target.value)} - required -/> - -// Input with error - - -// Input with helper text - -``` - -### Select (`src/components/ui/Select.tsx`) - -Standardized select dropdown component. - -#### Props - -```typescript -interface SelectProps extends SelectHTMLAttributes { - label?: string; - error?: string; - helperText?: string; - fullWidth?: boolean; // Default: true - options: Array<{ value: string | number; label: string }>; -} -``` - -#### Example - -```tsx - setEmail(e.target.value)} + required + className="w-full p-2 mb-4 border rounded border-secondary bg-gray-600 text-secondary" + /> + setPassword(e.target.value)} + required + className="w-full p-2 mb-4 border rounded text-secondary border-secondary bg-gray-600" + /> + Login + + Create an account + + + + + ) +} + +export default LoginForm \ No newline at end of file diff --git a/frontend/app/components/features/auth/RegisterForm.tsx b/frontend/app/components/features/auth/RegisterForm.tsx new file mode 100644 index 0000000..908d624 --- /dev/null +++ b/frontend/app/components/features/auth/RegisterForm.tsx @@ -0,0 +1,107 @@ +import React, { useState } from 'react'; +import { register } from "@/utils/api/auth"; +import useRoutes from "@/hooks/useRoutes"; +import SectionTitle from "@/components/ui/SectionTitle"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; +import { Link, useNavigate } from "react-router" + +const RegisterForm = () => { + const routes = useRoutes(); + const [name, setName] = useState(''); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [passwordAgain, setPasswordAgain] = useState(''); + const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [isRegistered, setIsRegistered] = useState(false); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (password !== passwordAgain) { + setError("Passwords do not match."); + return; + } + + try { + setIsLoading(true); + + await register(name, email, password, passwordAgain); + // navigate('/login?registered=true', { replace: true }); + } catch (err) { + const errorMessage = + err instanceof Error + ? err.message + : 'Registration\n failed'; + setError(errorMessage); + } finally { + setIsRegistered(true); + setIsLoading(false); + } + }; + + if (isRegistered) { + return
+ Registration successful! + Please continue to the login page. +
+ } + + return ( +
+
+ DISH PLANNER +
+
+
+

Register

+ { error &&

{ error }

} + setName(e.target.value) } + required + className="w-full p-2 border rounded border-secondary bg-gray-600 text-secondary" + /> + setEmail(e.target.value) } + required + className="w-full p-2 border rounded border-secondary bg-gray-600 text-secondary" + /> + setPassword(e.target.value) } + required + className="w-full p-2 border rounded border-secondary bg-gray-600 text-secondary" + /> + setPasswordAgain(e.target.value) } + required + className="w-full p-2 border rounded border-secondary bg-gray-600 text-secondary" + /> + + Create Account + + + Back to Login + +
+
+
+ ) +} + +export default RegisterForm \ No newline at end of file diff --git a/frontend/src/components/features/dishes/AddUserToDishForm.tsx b/frontend/app/components/features/dishes/AddUserToDishForm.tsx similarity index 89% rename from frontend/src/components/features/dishes/AddUserToDishForm.tsx rename to frontend/app/components/features/dishes/AddUserToDishForm.tsx index a5ed556..2e2d2c8 100644 --- a/frontend/src/components/features/dishes/AddUserToDishForm.tsx +++ b/frontend/app/components/features/dishes/AddUserToDishForm.tsx @@ -4,7 +4,8 @@ import { UserType } from "@/types/UserType"; import { useFetchUsers } from "@/hooks/useFetchUsers"; import Spinner from "@/components/Spinner"; import {addUserToDish} from "@/utils/api/dishApi"; -import Button from "@/components/ui/Button"; +import OutlineButton from "@/components/ui/Buttons/OutlineButton"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; interface Props { dish: DishType; @@ -53,15 +54,14 @@ const AddUserToDishForm: FC = ({ dish, reloadDish }) => { return ( <> - + { showAdd && (
@@ -71,7 +71,7 @@ const AddUserToDishForm: FC = ({ dish, reloadDish }) => { setName(e.target.value) } - placeholder="Enter dish name" - className="mb-4" - /> +
+ + setName(e.target.value) } // Update the name state on change + className="w-full p-2 mb-4 border rounded bg-gray-600 border-secondary text-secondary focus:bg-gray-900" + placeholder="Enter dish name" + /> +
- +
- +
); diff --git a/frontend/src/components/features/dishes/Dish.tsx b/frontend/app/components/features/dishes/Dish.tsx similarity index 100% rename from frontend/src/components/features/dishes/Dish.tsx rename to frontend/app/components/features/dishes/Dish.tsx diff --git a/frontend/src/components/features/dishes/DishCard.tsx b/frontend/app/components/features/dishes/DishCard.tsx similarity index 100% rename from frontend/src/components/features/dishes/DishCard.tsx rename to frontend/app/components/features/dishes/DishCard.tsx diff --git a/frontend/src/components/features/dishes/EditDishForm.tsx b/frontend/app/components/features/dishes/EditDishForm.tsx similarity index 75% rename from frontend/src/components/features/dishes/EditDishForm.tsx rename to frontend/app/components/features/dishes/EditDishForm.tsx index 6409b16..cfffb2a 100644 --- a/frontend/src/components/features/dishes/EditDishForm.tsx +++ b/frontend/app/components/features/dishes/EditDishForm.tsx @@ -5,8 +5,7 @@ import {updateDish} from "@/utils/api/dishApi"; import {DishType} from "@/types/DishType"; import useRoutes from "@/hooks/useRoutes"; import Spinner from "@/components/Spinner"; -import Button from "@/components/ui/Button"; -import Input from "@/components/ui/Input"; +import Button from "@/components/ui/Button" interface Props { dish: DishType @@ -58,15 +57,20 @@ const EditDishForm: FC = ({ dish }) => { error != '' && { error } } - setName(e.target.value)} - /> + {/* Dish name input */} +
+ + setName(e.target.value)} // Update the name state on change + className="p-2 border rounded w-full bg-gray-500 border-secondary background-secondary" + /> +
+ {/* Save button */} + Save ); } diff --git a/frontend/src/components/features/dishes/RecurrenceLabels.tsx b/frontend/app/components/features/dishes/RecurrenceLabels.tsx similarity index 100% rename from frontend/src/components/features/dishes/RecurrenceLabels.tsx rename to frontend/app/components/features/dishes/RecurrenceLabels.tsx diff --git a/frontend/src/components/features/dishes/SyncUsersForm.tsx b/frontend/app/components/features/dishes/SyncUsersForm.tsx similarity index 100% rename from frontend/src/components/features/dishes/SyncUsersForm.tsx rename to frontend/app/components/features/dishes/SyncUsersForm.tsx diff --git a/frontend/src/components/features/dishes/UserDishCard.tsx b/frontend/app/components/features/dishes/UserDishCard.tsx similarity index 100% rename from frontend/src/components/features/dishes/UserDishCard.tsx rename to frontend/app/components/features/dishes/UserDishCard.tsx diff --git a/frontend/src/components/features/navbar/MobileDropdownMenu.tsx b/frontend/app/components/features/navbar/MobileDropdownMenu.tsx similarity index 96% rename from frontend/src/components/features/navbar/MobileDropdownMenu.tsx rename to frontend/app/components/features/navbar/MobileDropdownMenu.tsx index 2e74d52..3e8639a 100644 --- a/frontend/src/components/features/navbar/MobileDropdownMenu.tsx +++ b/frontend/app/components/features/navbar/MobileDropdownMenu.tsx @@ -17,7 +17,7 @@ const divStyles = classNames( const linkStyles = classNames( 'border-b-2', 'border-secondary', 'uppercase', - 'text-primary', 'hover:bg-secondary', 'pb-2', 'pl-5', + 'text-primary', 'hover:background-secondary', 'pb-2', 'pl-5', 'space-grotesk', 'text-xl' ) diff --git a/frontend/src/components/features/schedule/HistoricalDishes.tsx b/frontend/app/components/features/schedule/HistoricalDishes.tsx similarity index 100% rename from frontend/src/components/features/schedule/HistoricalDishes.tsx rename to frontend/app/components/features/schedule/HistoricalDishes.tsx diff --git a/frontend/src/components/features/schedule/ScheduleCalendar.tsx b/frontend/app/components/features/schedule/ScheduleCalendar.tsx similarity index 100% rename from frontend/src/components/features/schedule/ScheduleCalendar.tsx rename to frontend/app/components/features/schedule/ScheduleCalendar.tsx diff --git a/frontend/src/components/features/schedule/ScheduleEditForm.tsx b/frontend/app/components/features/schedule/ScheduleEditForm.tsx similarity index 95% rename from frontend/src/components/features/schedule/ScheduleEditForm.tsx rename to frontend/app/components/features/schedule/ScheduleEditForm.tsx index 716f69a..0f436a9 100644 --- a/frontend/src/components/features/schedule/ScheduleEditForm.tsx +++ b/frontend/app/components/features/schedule/ScheduleEditForm.tsx @@ -77,7 +77,7 @@ const ScheduleEditForm: FC = ({ date }) => { Edit Day
- { transformDate(schedule.date) } + { transformDate(schedule.date) }
@@ -98,7 +98,7 @@ const ScheduleEditForm: FC = ({ date }) => { { scheduleData .map((scheduleData) =>
-
{ scheduleData.user.name }
+
{ scheduleData.user.name }
setName(e.target.value)} + + setName(e.target.value)} + className="w-full p-2 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - + Update
); diff --git a/frontend/app/components/layout/AuthGuard.tsx b/frontend/app/components/layout/AuthGuard.tsx new file mode 100644 index 0000000..d3029d8 --- /dev/null +++ b/frontend/app/components/layout/AuthGuard.tsx @@ -0,0 +1,45 @@ +import { useAuth } from '@/context/AuthContext'; +import React, { useEffect, useState } from 'react'; +import { useLocation, useNavigate } from "react-router" + +// Optional Loading spinner component to display while loading +const LoadingSpinner = () => ( +
+
+
+); + +export default function AuthGuard({ children }: { children: React.ReactNode }) { + const { isAuthenticated } = useAuth(); // Access the authentication state from AuthContext + const navigate = useNavigate(); + const [loading, setLoading] = useState(true); + const location = useLocation(); + + const publicRoutes = ['/login', '/register']; + const isPublic = publicRoutes.includes(location.pathname); + + useEffect(() => { + // Determine behavior based on auth state and route type + if (isAuthenticated === null) { + // Await authentication resolution (e.g., token check) + setLoading(true); + } else if (isAuthenticated && isPublic) { + // Redirect authenticated users away from public pages + navigate('/', { replace: true }); + } else if (!isAuthenticated && !isPublic) { + // Redirect unauthenticated users trying to access protected pages + navigate('/login', { replace: true }); + } else { + // Otherwise, stop loading since the state is resolved + setLoading(false); + } + }, [isAuthenticated, location.pathname, isPublic, navigate]); + + // Show a spinner while authentication state is loading + if (loading) { + return ; + } + + // Render children only when the authentication state and path are valid + return <>{children}; +} \ No newline at end of file diff --git a/frontend/app/components/layout/Card.tsx b/frontend/app/components/layout/Card.tsx new file mode 100644 index 0000000..6784526 --- /dev/null +++ b/frontend/app/components/layout/Card.tsx @@ -0,0 +1,15 @@ +import React, {FC} from "react"; + +interface Props { + children: React.ReactNode; +} + +const Card: FC = ({ children }) => { + return ( +
+ { children } +
+ ) +} + +export default Card \ No newline at end of file diff --git a/frontend/src/components/layout/NavBar.tsx b/frontend/app/components/layout/NavBar.tsx similarity index 93% rename from frontend/src/components/layout/NavBar.tsx rename to frontend/app/components/layout/NavBar.tsx index 56a9cd9..67cadb2 100644 --- a/frontend/src/components/layout/NavBar.tsx +++ b/frontend/app/components/layout/NavBar.tsx @@ -53,21 +53,21 @@ const NavBar = () => { {/* Desktop Menu */}
- + Home - + Dishes - + Users - + History + className="text-primary text-right hover:background-secondary"> Logout
diff --git a/frontend/app/components/pages/PrivatePage.tsx b/frontend/app/components/pages/PrivatePage.tsx new file mode 100644 index 0000000..3b08c86 --- /dev/null +++ b/frontend/app/components/pages/PrivatePage.tsx @@ -0,0 +1,9 @@ +const PrivatePage = () => { + return ( +
+ private +
+ ) +} + +export default PrivatePage \ No newline at end of file diff --git a/frontend/app/components/pages/PublicPage.tsx b/frontend/app/components/pages/PublicPage.tsx new file mode 100644 index 0000000..8e0f5cb --- /dev/null +++ b/frontend/app/components/pages/PublicPage.tsx @@ -0,0 +1,11 @@ +import { Outlet } from "react-router" + +const PublicPage = () => { + return ( +
+ +
+ ) +} + +export default PublicPage \ No newline at end of file diff --git a/frontend/app/components/ui/Alert.tsx b/frontend/app/components/ui/Alert.tsx new file mode 100644 index 0000000..446e233 --- /dev/null +++ b/frontend/app/components/ui/Alert.tsx @@ -0,0 +1,35 @@ +import React from "react" +import type { FC } from "react" +import classNames from "classnames"; + +interface Props { + children: React.ReactNode; + className?: string; + type: 'error' | 'warning' | 'info' | 'success'; +} + +const Alert: FC = ({ children, className, type } ) => { + let bgColor = 'bg-blue-200' + let fgColor = 'bg-blue-800' + + if (type == 'error') { + bgColor = 'bg-red-200' + fgColor = 'bg-red-800' + } else if (type == 'warning') { + bgColor = 'bg-orange-200' + fgColor = 'bg-orange-800' + } else if (type == 'success') { + bgColor = 'border-2 border-green-500' + fgColor = 'text-green-500' + } + + const styles = classNames(fgColor, bgColor, className, 'rounded') + + return ( +
+ { children} +
+ ) +} + +export default Alert \ No newline at end of file diff --git a/frontend/app/components/ui/Button.tsx b/frontend/app/components/ui/Button.tsx new file mode 100644 index 0000000..4ad006c --- /dev/null +++ b/frontend/app/components/ui/Button.tsx @@ -0,0 +1,62 @@ +import Link from "next/link"; +import React, { FC, ReactElement, ReactNode } from "react"; +import classNames from "classnames"; + +interface ButtonProps { + appearance?: 'solid' | 'outline' | 'text'; + children: ReactNode; + className?: string; + href?: string; + icon?: ReactNode; + onClick?: () => void; + disabled?: boolean; + size?: 'small' | 'medium' | 'large'; + type?: 'button' | 'submit' | 'reset'; + variant?: 'primary' | 'secondary' | 'accent'; +} + +const Button: FC = ({ appearance, children, className, disabled, href, icon, onClick, + size = 'medium', type, + variant = 'primary' +}) => { + const styles = classNames( + "flex items-center space-x-1", + "justify-center font-size-18 py-2 px-4 rounded flex", + { + 'border-2 border-primary background-red text-white': variant === 'primary' && appearance === 'solid', + 'border-2 border-primary text-primary': variant === 'primary' && appearance === 'outline', + 'text-primary': variant === 'primary' && appearance === 'text', + 'border-2 border-secondary text-secondary': variant === 'secondary' && appearance === 'outline', + 'border-2 border-accent-blue text-accent-blue': variant === 'accent' && appearance === 'outline', + }, + className + ) + + const iconClassNames = classNames({ + "h-4 w-4 mr-1": size === "small", + "h-5 w-5 mr-1": size === "medium", + "h-7 w-7 mr-2": size === "large", + }); + + const iconElement = + React.isValidElement(icon) && + React.cloneElement(icon as ReactElement<{ className?: string }>, { + className: iconClassNames, + }); + + if (href !== undefined) { + return ( + + { icon && iconElement} + { children} + + ) + } + + return +} + +export default Button \ No newline at end of file diff --git a/frontend/app/components/ui/Buttons/OutlineButton.tsx b/frontend/app/components/ui/Buttons/OutlineButton.tsx new file mode 100644 index 0000000..6c4e127 --- /dev/null +++ b/frontend/app/components/ui/Buttons/OutlineButton.tsx @@ -0,0 +1,37 @@ +import React, { type FC } from "react"; +import classNames from "classnames"; + +interface Props { + children: React.ReactNode; + className?: string; + disabled?: boolean; + onClick?: () => void; + size?: "small" | "medium" | "large"; + type: 'submit' | 'button'; +} + +const OutlineButton: FC = ({ children, className, disabled = false, onClick, size, type }) => { + const style = classNames( + "justify-center border-2 border-accent font-size-18 text-accent-blue py-2 px-4 rounded flex", + { 'text-xs': size === "small" }, + className + ) + + if (onClick === undefined) { + onClick = () => { + } + } + + return ( + + ) +} + +export default OutlineButton \ No newline at end of file diff --git a/frontend/app/components/ui/Buttons/OutlineLinkButton.tsx b/frontend/app/components/ui/Buttons/OutlineLinkButton.tsx new file mode 100644 index 0000000..4d8d0e3 --- /dev/null +++ b/frontend/app/components/ui/Buttons/OutlineLinkButton.tsx @@ -0,0 +1,49 @@ +import React, { type FC, type ReactElement } from "react"; +import classNames from "classnames"; +import { Link } from "react-router" + +interface Props { + children: React.ReactNode; + className?: string; + href: string; + icon?: React.ReactNode; + size?: "small" | "medium" | "large"; + variant?: "primary" | "secondary"; +} + +const OutlineLinkButton: FC = ({ children, className, href, icon, size = "medium", variant }) => { + const linkClassNames = classNames( + "underline font-default pt-3 pb-3 px-4 rounded mb-0 flex", + { + 'text-primary border-primary': variant === "primary", + 'text-secondary border-secondary': variant === "secondary", + 'text-accent-blue border-accent': !variant || !["primary", "secondary"].includes(variant), + }, { + 'text-size-14': size === "small", + 'font-size-18': !size || size === "medium", + 'text-2xl': size === "large", + }, + className, + ) + + const iconClassNames = classNames("mt-0.5", { + "h-4 w-4 mr-1": size === "small", + "h-5 w-5 mr-1": size === "medium", // Default size + "h-7 w-7 mr-2": size === "large", + }); + + const iconElement = + React.isValidElement(icon) && + React.cloneElement(icon as ReactElement<{ className?: string }>, { + className: iconClassNames, + }); + + return ( + + {iconElement} + {children} + + ) +} + +export default OutlineLinkButton \ No newline at end of file diff --git a/frontend/app/components/ui/Buttons/SolidButton.tsx b/frontend/app/components/ui/Buttons/SolidButton.tsx new file mode 100644 index 0000000..b341acd --- /dev/null +++ b/frontend/app/components/ui/Buttons/SolidButton.tsx @@ -0,0 +1,40 @@ +import React, { type FC } from "react"; +import classNames from "classnames"; + +interface Props { + children: React.ReactNode; + className?: string; + disabled?: boolean; + onClick?: () => void; + size?: "small" | "medium" | "large"; + type: 'submit' | 'button'; +} + +const SolidButton: FC = ({ children, className, disabled = false, onClick, size, type }) => { + const style = classNames( + "py-2 px-4 bg-primary text-white text-xl p-2 rounded hover:bg-secondary mb-0", + { + 'text-xs': size === "small", + 'font-size-18': !size || size === "medium", + }, + className + ) + + if (onClick === undefined) { + onClick = () => { + } + } + + return ( + + ) +} + +export default SolidButton \ No newline at end of file diff --git a/frontend/app/components/ui/Buttons/SolidLinkButton.tsx b/frontend/app/components/ui/Buttons/SolidLinkButton.tsx new file mode 100644 index 0000000..a0b26f5 --- /dev/null +++ b/frontend/app/components/ui/Buttons/SolidLinkButton.tsx @@ -0,0 +1,46 @@ +import React, { type FC, type ReactElement } from "react"; +import classNames from "classnames"; +import { Link } from "react-router" + +interface Props { + children: React.ReactNode; + className?: string; + href: string; + icon?: React.ReactNode; + size?: "small" | "medium" | "large"; + variant?: "primary" | "secondary"; +} + +const SolidLinkButton: FC = ({ children, className, href, icon, size = "medium", variant }) => { + const style = classNames( + "py-2 px-4 text-xl p-2 rounded hover:bg-secondary mb-0 text-center flex", + { + 'background-red text-white': variant === "primary", + 'background-secondary border-2 border-secondary': variant === "secondary", + }, + className + ) + + const iconClassNames = classNames("mt-1", { + "h-4 w-4 mr-1": size === "small", + "h-5 w-5 mr-1": size === "medium", // Default size + "h-7 w-7 mr-2": size === "large", + }); + + const iconElement = + React.isValidElement(icon) && + React.cloneElement(icon as ReactElement<{ className?: string }>, { + className: iconClassNames, + }); + + return ( + +
+ {iconElement} + {children} +
+ + ) +} + +export default SolidLinkButton \ No newline at end of file diff --git a/frontend/src/components/ui/Description.tsx b/frontend/app/components/ui/Description.tsx similarity index 85% rename from frontend/src/components/ui/Description.tsx rename to frontend/app/components/ui/Description.tsx index dea94f2..8b429c0 100644 --- a/frontend/src/components/ui/Description.tsx +++ b/frontend/app/components/ui/Description.tsx @@ -7,7 +7,7 @@ interface Props { } const Description = ({ children, className }: Props) => { - const style = classNames("italic text-base", + const style = classNames("italic font-size-16", className ) diff --git a/frontend/src/components/ui/Hr.tsx b/frontend/app/components/ui/Hr.tsx similarity index 100% rename from frontend/src/components/ui/Hr.tsx rename to frontend/app/components/ui/Hr.tsx diff --git a/frontend/src/components/ui/Label.tsx b/frontend/app/components/ui/Label.tsx similarity index 100% rename from frontend/src/components/ui/Label.tsx rename to frontend/app/components/ui/Label.tsx diff --git a/frontend/app/components/ui/Modal.tsx b/frontend/app/components/ui/Modal.tsx new file mode 100644 index 0000000..3212463 --- /dev/null +++ b/frontend/app/components/ui/Modal.tsx @@ -0,0 +1,60 @@ +import { type FC, type JSX } from "react"; +import classNames from "classnames"; +import Button from "@/components/ui/Button" + +interface ModalProps { + buttonChildren?: JSX.Element; + buttonClassName?: string; + buttonLabel?: string; + modalChildren: JSX.Element; + modalOpen?: boolean; + setModalOpen: (open: boolean) => void; +} + +const Modal: FC = ({ + buttonLabel, + buttonClassName, + modalChildren, + modalOpen, + buttonChildren, + setModalOpen, +}) => { + const buttonStyles = classNames(buttonClassName, 'anta-regular'); + + const closeModal = () => { + setModalOpen(false) + } + + return ( + <> + + + {/**/} + {/* */} + {/*
*/} + {/* */} + {/* */} + {/* closeModal()}/>*/} + {/* {modalChildren}*/} + {/* */} + {/*
*/} + {/*
*/} + {/**/} + + ) +} + +export default Modal; \ No newline at end of file diff --git a/frontend/app/components/ui/PageTitle.tsx b/frontend/app/components/ui/PageTitle.tsx new file mode 100644 index 0000000..28d6724 --- /dev/null +++ b/frontend/app/components/ui/PageTitle.tsx @@ -0,0 +1,18 @@ +import { type FC } from "react"; +import classNames from "classnames"; + +interface Props { + children: string, + className?: string, +} + +const PageTitle: FC = ({ children, className }) => { + const styles = classNames( + 'ml-4 text-2xl font-default uppercase w-full text-accent-blue font-bold', + className, + ) + + return

{ children }

+} + +export default PageTitle \ No newline at end of file diff --git a/frontend/src/components/ui/RecurrenceInput.tsx b/frontend/app/components/ui/RecurrenceInput.tsx similarity index 100% rename from frontend/src/components/ui/RecurrenceInput.tsx rename to frontend/app/components/ui/RecurrenceInput.tsx diff --git a/frontend/src/components/ui/SectionTitle.tsx b/frontend/app/components/ui/SectionTitle.tsx similarity index 75% rename from frontend/src/components/ui/SectionTitle.tsx rename to frontend/app/components/ui/SectionTitle.tsx index 5940039..93d13c4 100644 --- a/frontend/src/components/ui/SectionTitle.tsx +++ b/frontend/app/components/ui/SectionTitle.tsx @@ -6,7 +6,7 @@ interface Props { } const SectionTitle = ({ children, className }: Props) => { - const style = classNames("block text-lg uppercase w-full pl-2 text-accent", + const style = classNames("block font-size-18 uppercase w-full pl-2 text-accent-blue", className ) diff --git a/frontend/app/components/ui/Toggle.tsx b/frontend/app/components/ui/Toggle.tsx new file mode 100644 index 0000000..786b093 --- /dev/null +++ b/frontend/app/components/ui/Toggle.tsx @@ -0,0 +1,42 @@ +import {FC} from "react"; + +interface ToggleProps { + checked: boolean; + onChange: (checked: boolean) => void; +} + +const Toggle: FC = ({ checked, onChange }) => { + const handleChange = () => { + onChange(checked); + } + + return ( + + ); +}; + +export default Toggle; \ No newline at end of file diff --git a/frontend/src/context/AuthContext.tsx b/frontend/app/context/AuthContext.tsx similarity index 100% rename from frontend/src/context/AuthContext.tsx rename to frontend/app/context/AuthContext.tsx diff --git a/frontend/src/helpers/Date.ts b/frontend/app/helpers/Date.ts similarity index 100% rename from frontend/src/helpers/Date.ts rename to frontend/app/helpers/Date.ts diff --git a/frontend/src/hooks/useFetchDishes.ts b/frontend/app/hooks/useFetchDishes.ts similarity index 100% rename from frontend/src/hooks/useFetchDishes.ts rename to frontend/app/hooks/useFetchDishes.ts diff --git a/frontend/src/hooks/useFetchUsers.ts b/frontend/app/hooks/useFetchUsers.ts similarity index 100% rename from frontend/src/hooks/useFetchUsers.ts rename to frontend/app/hooks/useFetchUsers.ts diff --git a/frontend/app/hooks/useRoutes.ts b/frontend/app/hooks/useRoutes.ts new file mode 100644 index 0000000..255a545 --- /dev/null +++ b/frontend/app/hooks/useRoutes.ts @@ -0,0 +1,32 @@ +import type { DishType } from "@/types/DishType"; +import type { UserType } from "@/types/UserType"; + +const useRoutes = () => { + return { + home: () => "/", + auth: { + login: () => "/login", + register: () => "/register", + }, + dish: { + index: () => "/dishes", + create: () => "/dishes/create", + edit: (dish: DishType) => `/dishes/${ dish.id }/edit`, + delete: (dish: DishType) => `/dishes/${ dish.id }/delete`, + }, + schedule: { + date: { + edit: (date: string) => `/schedule/${ date }/edit` + }, + history: () => "/scheduled-user-dishes/history", + }, + user: { + index: () => "/users", + create: () => `/users/create`, + edit: (user: UserType) => `/users/${ user.id }/edit`, + delete: (user: UserType) => `/users/${ user.id }/delete`, + } + }; +}; + +export default useRoutes; \ No newline at end of file diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx new file mode 100644 index 0000000..30ef233 --- /dev/null +++ b/frontend/app/root.tsx @@ -0,0 +1,82 @@ +import { + isRouteErrorResponse, + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "react-router"; + +import type { Route } from "./+types/root"; +import "./app.css"; +import React from "react" +import { AuthProvider } from "~/context/AuthContext" +import AuthGuard from "~/components/layout/AuthGuard" + +export const links: Route.LinksFunction = () => [ + { rel: "preconnect", href: "https://fonts.googleapis.com" }, + { + rel: "preconnect", + href: "https://fonts.gstatic.com", + crossOrigin: "anonymous", + }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", + }, +]; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + + +
{ children }
+
+
+ + + + + ); +} + +export default function App() { + return ; +} + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = "Oops!"; + let details = "An unexpected error occurred."; + let stack: string | undefined; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? "404" : "Error"; + details = + error.status === 404 + ? "The requested page could not be found." + : error.statusText || details; + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message; + stack = error.stack; + } + + return ( +
+

{ message }

+

{ details }

+ { stack && ( +
+          { stack }
+        
+ ) } +
+ ); +} diff --git a/frontend/app/routes.ts b/frontend/app/routes.ts new file mode 100644 index 0000000..6a8d247 --- /dev/null +++ b/frontend/app/routes.ts @@ -0,0 +1,7 @@ +import { type RouteConfig, index, route } from "@react-router/dev/routes"; + +export default [ + index("routes/home.tsx"), + route("login", "./components/features/auth/LoginForm.tsx"), + route("register", "./components/features/auth/RegisterForm.tsx"), +] satisfies RouteConfig; diff --git a/frontend/app/routes/home.tsx b/frontend/app/routes/home.tsx new file mode 100644 index 0000000..398e47c --- /dev/null +++ b/frontend/app/routes/home.tsx @@ -0,0 +1,13 @@ +import type { Route } from "./+types/home"; +import { Welcome } from "../welcome/welcome"; + +export function meta({}: Route.MetaArgs) { + return [ + { title: "New React Router App" }, + { name: "description", content: "Welcome to React Router!" }, + ]; +} + +export default function Home() { + return ; +} diff --git a/frontend/src/styles/base/globals.css b/frontend/app/styles/base/globals.css similarity index 100% rename from frontend/src/styles/base/globals.css rename to frontend/app/styles/base/globals.css diff --git a/frontend/app/styles/components/buttons.css b/frontend/app/styles/components/buttons.css new file mode 100644 index 0000000..07fc4de --- /dev/null +++ b/frontend/app/styles/components/buttons.css @@ -0,0 +1,42 @@ +.button-primary-solid { + background-color: var(--color-primary); + color: var(--color-secondary-200); + border: 1px solid var(--color-primary); + text-transform: uppercase; + font-family: "Anta", serif; + font-style: normal; + font-size: 1.1rem; + font-weight: 600; + padding: 4px 16px 2px 16px; +} +.button-primary-outline { + background-color: var(--color-background); + color: var(--color-primary); + border: 1px solid var(--color-primary); + text-transform: uppercase; + font-family: "Anta", serif; + font-style: normal; + font-size: 1.1rem; + font-weight: 600; + padding: 4px 16px 2px 16px; +} + +.button-secondary-solid { + background-color: var(--color-secondary); + color: var(--color-primary); + border: 1px solid var(--color-secondary); +} + +.button-accent-solid { + background-color: var(--color-accent-blue); + color: var(--color-secondary-900); + border: 1px solid var(--color-accent-blue); +} +.button-accent-outline { + background-color: var(--color-background); + color: var(--color-accent-blue); + border: 1px solid var(--color-accent-blue); +} +.button-accent-outline:hover { + background-color: var(--color-background-400); +} \ No newline at end of file diff --git a/frontend/src/styles/components/select.css b/frontend/app/styles/components/select.css similarity index 100% rename from frontend/src/styles/components/select.css rename to frontend/app/styles/components/select.css diff --git a/frontend/app/styles/main.css b/frontend/app/styles/main.css new file mode 100644 index 0000000..83d03cc --- /dev/null +++ b/frontend/app/styles/main.css @@ -0,0 +1,6 @@ +@import "./theme/borders.css"; +@import "./theme/fonts.css"; +@import "./components/buttons.css"; + +@import "./base/globals.css"; +@import "./theme/colors.css"; diff --git a/frontend/src/styles/theme/borders.css b/frontend/app/styles/theme/borders.css similarity index 100% rename from frontend/src/styles/theme/borders.css rename to frontend/app/styles/theme/borders.css diff --git a/frontend/app/styles/theme/colors.css b/frontend/app/styles/theme/colors.css new file mode 100644 index 0000000..f283897 --- /dev/null +++ b/frontend/app/styles/theme/colors.css @@ -0,0 +1,10 @@ +@import './colors/root.css'; +@import './colors/background.css'; +@import './colors/border.css'; +@import './colors/text.css'; + +body { + color: var(--color-secondary) !important; + background: var(--color-gray-600) !important; +} + diff --git a/frontend/src/styles/theme/colors/background.css b/frontend/app/styles/theme/colors/background.css similarity index 100% rename from frontend/src/styles/theme/colors/background.css rename to frontend/app/styles/theme/colors/background.css diff --git a/frontend/src/styles/theme/colors/border.css b/frontend/app/styles/theme/colors/border.css similarity index 100% rename from frontend/src/styles/theme/colors/border.css rename to frontend/app/styles/theme/colors/border.css diff --git a/frontend/src/styles/theme/colors/root.css b/frontend/app/styles/theme/colors/root.css similarity index 100% rename from frontend/src/styles/theme/colors/root.css rename to frontend/app/styles/theme/colors/root.css diff --git a/frontend/app/styles/theme/colors/text.css b/frontend/app/styles/theme/colors/text.css new file mode 100644 index 0000000..ca38334 --- /dev/null +++ b/frontend/app/styles/theme/colors/text.css @@ -0,0 +1,216 @@ +.text-primary { + color: var(--color-primary); +} +.text-primary-100 { + color: var(--color-primary-100); +} +.text-primary-200 { + color: var(--color-primary-200); +} +.text-primary-300 { + color: var(--color-primary-300); +} +.text-primary-400 { + color: var(--color-primary-400); +} +.text-primary-500 { + color: var(--color-primary-500); +} +.text-primary-600 { + color: var(--color-primary-600); +} +.text-primary-700 { + color: var(--color-primary-700); +} +.text-primary-800 { + color: var(--color-primary-800); +} +.text-primary-900 { + color: var(--color-primary-900); +} + +.text-secondary { + color: var(--color-secondary) !important; +} +.text-secondary-100 { + color: var(--color-secondary-100); +} +.text-secondary-200 { + color: var(--color-secondary-200); +} +.text-secondary-300 { + color: var(--color-secondary-300); +} +.text-secondary-400 { + color: var(--color-secondary-400); +} +.text-secondary-500 { + color: var(--color-secondary-500); +} +.text-secondary-600 { + color: var(--color-secondary-600); +} +.text-secondary-700 { + color: var(--color-secondary-700); +} +.text-secondary-800 { + color: var(--color-secondary-800); +} +.text-secondary-900 { + color: var(--color-secondary-900); +} + +.text-accent-blue { + color: var(--color-accent-blue); +} +.text-accent-blue-100 { + color: var(--color-accent-blue-100); +} +.text-accent-blue-200 { + color: var(--color-accent-blue-200); +} +.text-accent-blue-300 { + color: var(--color-accent-blue-300); +} +.text-accent-blue-400 { + color: var(--color-accent-blue-400); +} +.text-accent-blue-500 { + color: var(--color-accent-blue-500); +} +.text-accent-blue-600 { + color: var(--color-accent-blue-600); +} +.text-accent-blue-700 { + color: var(--color-accent-blue-700); +} +.text-accent-blue-800 { + color: var(--color-accent-blue-800); +} +.text-accent-blue-900 { + color: var(--color-accent-blue-900); +} + +.text-accent-yellow { + color: var(--color-accent-yellow); +} +.text-accent-yellow-100 { + color: var(--color-accent-yellow-100); +} +.text-accent-yellow-200 { + color: var(--color-accent-yellow-200); +} +.text-accent-yellow-300 { + color: var(--color-accent-yellow-300); +} +.text-accent-yellow-400 { + color: var(--color-accent-yellow-400); +} +.text-accent-yellow-500 { + color: var(--color-accent-yellow-500); +} +.text-accent-yellow-600 { + color: var(--color-accent-yellow-600); +} +.text-accent-yellow-700 { + color: var(--color-accent-yellow-700); +} +.text-accent-yellow-800 { + color: var(--color-accent-yellow-800); +} +.text-accent-yellow-900 { + color: var(--color-accent-yellow-900); +} + +.text-danger { + color: var(--color-danger); +} +.text-danger-100 { + color: var(--color-danger-100); +} +.text-danger-200 { + color: var(--color-danger-200); +} +.text-danger-300 { + color: var(--color-danger-300); +} +.text-danger-400 { + color: var(--color-danger-400); +} +.text-danger-500 { + color: var(--color-danger-500); +} +.text-danger-600 { + color: var(--color-danger-600); +} +.text-danger-700 { + color: var(--color-danger-700); +} +.text-danger-800 { + color: var(--color-danger-800); +} +.text-danger-900 { + color: var(--color-danger-900); +} + +.text-warning { + color: var(--color-warning); +} +.text-warning-100 { + color: var(--color-warning-100); +} +.text-warning-200 { + color: var(--color-warning-200); +} +.text-warning-300 { + color: var(--color-warning-300); +} +.text-warning-400 { + color: var(--color-warning-400); +} +.text-warning-500 { + color: var(--color-warning-500); +} +.text-warning-600 { + color: var(--color-warning-600); +} +.text-warning-700 { + color: var(--color-warning-700); +} +.text-warning-800 { + color: var(--color-warning-800); +} +.text-warning-900 { + color: var(--color-warning-900); +} + +.text-success { + color: var(--color-success); +} +.text-success-100 { + color: var(--color-success-100); +} +.text-success-200 { + color: var(--color-success-200); +} +.text-success-300 { + color: var(--color-success-300); +} +.text-success-400 { + color: var(--color-success-400); +} +.text-success-500 { + color: var(--color-success-500); +} +.text-success-600 { + color: var(--color-success-600); +} +.text-success-700 { + color: var(--color-success-700); +} +.text-success-800 { + color: var(--color-success-800); +} +.text-success-900 { + color: var(--color-success-900); +} \ No newline at end of file diff --git a/frontend/app/styles/theme/fonts.css b/frontend/app/styles/theme/fonts.css new file mode 100644 index 0000000..eed2248 --- /dev/null +++ b/frontend/app/styles/theme/fonts.css @@ -0,0 +1,93 @@ + +/* Global font settings */ + +/* Set Space Grotesk as the default font */ +body { + font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + line-height: 1.6; + color: #333; +} + +/* Use Anta for headings */ +h1, h2, h3 { + font-family: 'Syncopate', sans-serif; + color: #111; +} + +/* Use Space Grotesk for smaller text like paragraphs */ +p { + font-family: 'Space Grotesk', sans-serif; +} + + +.font-default { + font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; +} + +.font-syncopate { + font-family: "Syncopate", serif !important; +} + +.font-space-grotesk { + font-family: 'Space Grotesk', sans-serif; +} + +.font-weight-100 { + font-weight: 100; +} +.font-weight-200 { + font-weight: 200; +} +.font-weight-300 { + font-weight: 300; +} +.font-weight-400 { + font-weight: 400; +} +.font-weight-500 { + font-weight: 500; +} +.font-weight-600 { + font-weight: 600; +} +.font-weight-700 { + font-weight: 700; +} +.font-weight-800 { + font-weight: 800; +} +.font-weight-900 { + font-weight: 900; +} + +.font-size-12 { + font-size: 12px !important; +} + +.font-size-14 { + font-size: 14px !important; +} + +.font-size-16 { + font-size: 16px !important; +} + +.font-size-18 { + font-size: 18px !important; +} + +.font-size-20 { + font-size: 20px !important; +} + +.font-size-24 { + font-size: 24px !important; +} + +.font-size-32 { + font-size: 32px !important; +} +.font-size-48 { + font-size: 48px !important; +} \ No newline at end of file diff --git a/frontend/app/types/DishType.ts b/frontend/app/types/DishType.ts new file mode 100644 index 0000000..99bd7f2 --- /dev/null +++ b/frontend/app/types/DishType.ts @@ -0,0 +1,20 @@ +import type { UserType } from "@/types/UserType"; + +export type DishType = { + id: number + name: string, + recurrence: number, + users: UserType[], +} + +export type DishDateType = { + id: number; + date: string; + dish: DishType; + user: UserType; +} + +export type ScheduledDishesType = { + date: string; + dishes: { dish: DishType, user: UserType }[]; +} diff --git a/frontend/app/types/ScheduleType.ts b/frontend/app/types/ScheduleType.ts new file mode 100644 index 0000000..f28acf1 --- /dev/null +++ b/frontend/app/types/ScheduleType.ts @@ -0,0 +1,27 @@ +import type { ScheduledUserDishType, UserDishType } from "@/types/ScheduledUserDishType"; +import type { UserType } from "@/types/UserType"; + +export type RecurrenceType = { + type: "App\\Models\\WeeklyRecurrence" | "App\\Models\\MinimumRecurrence"; + value: number; +} + +export type ScheduleType = { + id: number; + date: string; + scheduled_user_dishes: ScheduledUserDishType[]; + is_skipped: boolean; +} + +export type FilledScheduleType = { + id?: number; + date: string; + is_skipped?: boolean; + scheduled_user_dishes: ScheduledUserDishType[]; +} + +export type ScheduleDataType = { + user: UserType; + scheduled_user_dish: UserDishType | null; + user_dishes: UserDishType[]; +} \ No newline at end of file diff --git a/frontend/app/types/ScheduledUserDishType.ts b/frontend/app/types/ScheduledUserDishType.ts new file mode 100644 index 0000000..53d077c --- /dev/null +++ b/frontend/app/types/ScheduledUserDishType.ts @@ -0,0 +1,21 @@ +import type { UserType } from "@/types/UserType"; +import type { DishType } from "@/types/DishType"; +import type { RecurrenceType } from "@/types/ScheduleType"; + +export type UserDishType = { + id: number; + dish: DishType; + user: UserType; + recurrences: RecurrenceType[]; +} + +export type UserDishWithoutUserType = { + id: number; + dish: DishType; + recurrences: RecurrenceType[]; +} + +export type ScheduledUserDishType = { + id: number; + user_dish: UserDishType; +} \ No newline at end of file diff --git a/frontend/src/types/UserDishType.ts b/frontend/app/types/UserDishType.ts similarity index 100% rename from frontend/src/types/UserDishType.ts rename to frontend/app/types/UserDishType.ts diff --git a/frontend/app/types/UserType.ts b/frontend/app/types/UserType.ts new file mode 100644 index 0000000..f787bed --- /dev/null +++ b/frontend/app/types/UserType.ts @@ -0,0 +1,7 @@ +import type { UserDishWithoutUserType } from "@/types/ScheduledUserDishType"; + +export type UserType = { + id: number; + name: string; + user_dishes: UserDishWithoutUserType[]; +}; \ No newline at end of file diff --git a/frontend/app/utils/api/apiRequest.ts b/frontend/app/utils/api/apiRequest.ts new file mode 100644 index 0000000..01acc70 --- /dev/null +++ b/frontend/app/utils/api/apiRequest.ts @@ -0,0 +1,107 @@ +export const apiRequest = async (url: string, options: RequestInit = {}) => { + const token = localStorage.getItem('token'); + + const allowedRequests = [ + '/api/auth/login', + '/api/auth/register', + ] + + if (allowedRequests.includes(url)) { + return publicRequest(url, options) + } + + if (!token) { + throw new Error('No authentication token found.' + url); + } + + return privateRequest(url, token, options); +}; + +export const publicRequest = async (url: string, options: RequestInit = {}) => { + console.log('→ Sending request', url, options.method); + + url = 'http://localhost' + url; + + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + ...(options.headers || {}), + }, + ...options, + }); + + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + +export const privateRequest = async (fullUrl: string, token: string, options: RequestInit = {}) => { + const headers = { + ...(options.headers || {}), + Authorization: `Bearer ${token}`, + }; + + const url = `${process.env.NEXT_PUBLIC_API_URL}${fullUrl}`; + + const response = await fetch(url, { headers, ...options }); + + // Authentication failure - token invalid - redirect to login + if (response.status === 401) { + localStorage.removeItem('token'); + localStorage.removeItem('refreshToken'); + + window.location.href = '/login'; + + throw new Error('Unauthorized. Redirecting to login.'); + } + + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); + } + + return response.json(); +} + + +// Add shorthand HTTP methods +apiRequest.get = (url: string, options: RequestInit = {}) => { + return apiRequest(url, { ...options, method: 'GET' }); +}; + +apiRequest.post = | undefined>( + url: string, + body: TBody, + options: RequestInit = {}, +) => { + return apiRequest(url, { + ...options, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(options.headers || {}), + }, + body: body ? JSON.stringify(body) : undefined, + }); +}; + +apiRequest.put = | undefined>( + url: string, + body: TBody, + options: RequestInit = {} +) => { + return apiRequest(url, { + ...options, + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...(options.headers || {}), + }, + body: body ? JSON.stringify(body) : undefined, + }); +}; + +apiRequest.delete = (url: string, options: RequestInit = {}) => { + return apiRequest(url, { ...options, method: 'DELETE' }); +}; \ No newline at end of file diff --git a/frontend/src/utils/api/auth.ts b/frontend/app/utils/api/auth.ts similarity index 100% rename from frontend/src/utils/api/auth.ts rename to frontend/app/utils/api/auth.ts diff --git a/frontend/src/utils/api/dishApi.ts b/frontend/app/utils/api/dishApi.ts similarity index 100% rename from frontend/src/utils/api/dishApi.ts rename to frontend/app/utils/api/dishApi.ts diff --git a/frontend/src/utils/api/scheduleApi.ts b/frontend/app/utils/api/scheduleApi.ts similarity index 100% rename from frontend/src/utils/api/scheduleApi.ts rename to frontend/app/utils/api/scheduleApi.ts diff --git a/frontend/src/utils/api/scheduledUserDishesApi.ts b/frontend/app/utils/api/scheduledUserDishesApi.ts similarity index 100% rename from frontend/src/utils/api/scheduledUserDishesApi.ts rename to frontend/app/utils/api/scheduledUserDishesApi.ts diff --git a/frontend/src/utils/api/userDishApi.ts b/frontend/app/utils/api/userDishApi.ts similarity index 100% rename from frontend/src/utils/api/userDishApi.ts rename to frontend/app/utils/api/userDishApi.ts diff --git a/frontend/src/utils/api/usersApi.ts b/frontend/app/utils/api/usersApi.ts similarity index 100% rename from frontend/src/utils/api/usersApi.ts rename to frontend/app/utils/api/usersApi.ts diff --git a/frontend/src/utils/dateBuilder.ts b/frontend/app/utils/dateBuilder.ts similarity index 100% rename from frontend/src/utils/dateBuilder.ts rename to frontend/app/utils/dateBuilder.ts diff --git a/frontend/src/utils/scheduleBuilder.ts b/frontend/app/utils/scheduleBuilder.ts similarity index 100% rename from frontend/src/utils/scheduleBuilder.ts rename to frontend/app/utils/scheduleBuilder.ts diff --git a/frontend/app/welcome/logo-dark.svg b/frontend/app/welcome/logo-dark.svg new file mode 100644 index 0000000..dd82028 --- /dev/null +++ b/frontend/app/welcome/logo-dark.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app/welcome/logo-light.svg b/frontend/app/welcome/logo-light.svg new file mode 100644 index 0000000..7328492 --- /dev/null +++ b/frontend/app/welcome/logo-light.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app/welcome/welcome.tsx b/frontend/app/welcome/welcome.tsx new file mode 100644 index 0000000..8ac6e1d --- /dev/null +++ b/frontend/app/welcome/welcome.tsx @@ -0,0 +1,89 @@ +import logoDark from "./logo-dark.svg"; +import logoLight from "./logo-light.svg"; + +export function Welcome() { + return ( +
+
+
+
+ React Router + React Router +
+
+
+ +
+
+
+ ); +} + +const resources = [ + { + href: "https://reactrouter.com/docs", + text: "React Router Docs", + icon: ( + + + + ), + }, + { + href: "https://rmx.as/discord", + text: "Join Discord", + icon: ( + + + + ), + }, +]; diff --git a/frontend/archive/.dockerignore b/frontend/archive/.dockerignore new file mode 100644 index 0000000..11ee758 --- /dev/null +++ b/frontend/archive/.dockerignore @@ -0,0 +1 @@ +.env.local diff --git a/frontend/.env.local.example b/frontend/archive/.env.local.example similarity index 100% rename from frontend/.env.local.example rename to frontend/archive/.env.local.example diff --git a/frontend/.env.production b/frontend/archive/.env.production similarity index 100% rename from frontend/.env.production rename to frontend/archive/.env.production diff --git a/frontend/archive/.gitignore b/frontend/archive/.gitignore new file mode 100644 index 0000000..b601ed9 --- /dev/null +++ b/frontend/archive/.gitignore @@ -0,0 +1,45 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +/.idea +/.env.local +/package-lock.json + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/archive/README.md b/frontend/archive/README.md new file mode 100644 index 0000000..da15af8 --- /dev/null +++ b/frontend/archive/README.md @@ -0,0 +1,2 @@ +# DishPlanner Front End + diff --git a/frontend/bin/update.sh b/frontend/archive/bin/update.sh similarity index 100% rename from frontend/bin/update.sh rename to frontend/archive/bin/update.sh diff --git a/frontend/build_and_push.sh b/frontend/archive/build_and_push.sh similarity index 100% rename from frontend/build_and_push.sh rename to frontend/archive/build_and_push.sh diff --git a/frontend/eslint.config.mjs b/frontend/archive/eslint.config.mjs similarity index 100% rename from frontend/eslint.config.mjs rename to frontend/archive/eslint.config.mjs diff --git a/frontend/next.config.ts b/frontend/archive/next.config.ts similarity index 100% rename from frontend/next.config.ts rename to frontend/archive/next.config.ts diff --git a/frontend/archive/package.json b/frontend/archive/package.json new file mode 100644 index 0000000..62e44db --- /dev/null +++ b/frontend/archive/package.json @@ -0,0 +1,33 @@ +{ + "name": "dish-planner", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint", + "export": "next export" + }, + "dependencies": { + "@headlessui/react": "^2.2.0", + "@heroicons/react": "^2.2.0", + "classnames": "^2.5.1", + "luxon": "^3.5.0", + "next": "15.2.4", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@types/luxon": "^3.4.2", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.1.5", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/archive/postcss.config.mjs similarity index 100% rename from frontend/postcss.config.mjs rename to frontend/archive/postcss.config.mjs diff --git a/frontend/public/dish-planner.webp b/frontend/archive/public/dish-planner.webp similarity index 100% rename from frontend/public/dish-planner.webp rename to frontend/archive/public/dish-planner.webp diff --git a/frontend/public/file.svg b/frontend/archive/public/file.svg similarity index 100% rename from frontend/public/file.svg rename to frontend/archive/public/file.svg diff --git a/frontend/public/globe.svg b/frontend/archive/public/globe.svg similarity index 100% rename from frontend/public/globe.svg rename to frontend/archive/public/globe.svg diff --git a/frontend/public/next.svg b/frontend/archive/public/next.svg similarity index 100% rename from frontend/public/next.svg rename to frontend/archive/public/next.svg diff --git a/frontend/public/vercel.svg b/frontend/archive/public/vercel.svg similarity index 100% rename from frontend/public/vercel.svg rename to frontend/archive/public/vercel.svg diff --git a/frontend/public/window.svg b/frontend/archive/public/window.svg similarity index 100% rename from frontend/public/window.svg rename to frontend/archive/public/window.svg diff --git a/frontend/src/app/dishes/[id]/delete/page.tsx b/frontend/archive/src/app/dishes/[id]/delete/page.tsx similarity index 95% rename from frontend/src/app/dishes/[id]/delete/page.tsx rename to frontend/archive/src/app/dishes/[id]/delete/page.tsx index 52f05f2..95627e2 100644 --- a/frontend/src/app/dishes/[id]/delete/page.tsx +++ b/frontend/archive/src/app/dishes/[id]/delete/page.tsx @@ -58,7 +58,7 @@ export default function EditDishPage({params}: { params: Promise<{ id: number }>
Are you sure you want to delete this dish?
-
+
name: {name}
recurrence: {recurrence} users: {users.map((user) => user.name).join(', ')} @@ -66,7 +66,7 @@ export default function EditDishPage({params}: { params: Promise<{ id: number }>
No, take me back
diff --git a/frontend/src/app/dishes/[id]/edit/page.tsx b/frontend/archive/src/app/dishes/[id]/edit/page.tsx similarity index 87% rename from frontend/src/app/dishes/[id]/edit/page.tsx rename to frontend/archive/src/app/dishes/[id]/edit/page.tsx index 54c1719..364f9b1 100644 --- a/frontend/src/app/dishes/[id]/edit/page.tsx +++ b/frontend/archive/src/app/dishes/[id]/edit/page.tsx @@ -9,7 +9,7 @@ import {fetchDish} from "@/utils/api/dishApi"; import SyncUsersForm from "@/components/features/dishes/SyncUsersForm"; import {ChevronLeftIcon} from "@heroicons/react/16/solid"; import useRoutes from "@/hooks/useRoutes"; -import Button from "@/components/ui/Button"; +import OutlineLinkButton from "@/components/ui/Buttons/OutlineLinkButton"; import Hr from "@/components/ui/Hr" export default function EditDishPage({ params }: { params: Promise<{ id: number }> }) { @@ -43,9 +43,10 @@ export default function EditDishPage({ params }: { params: Promise<{ id: number
Edit Dish - +
diff --git a/frontend/src/app/dishes/create/page.tsx b/frontend/archive/src/app/dishes/create/page.tsx similarity index 100% rename from frontend/src/app/dishes/create/page.tsx rename to frontend/archive/src/app/dishes/create/page.tsx diff --git a/frontend/src/app/dishes/page.tsx b/frontend/archive/src/app/dishes/page.tsx similarity index 100% rename from frontend/src/app/dishes/page.tsx rename to frontend/archive/src/app/dishes/page.tsx diff --git a/frontend/src/app/favicon.ico b/frontend/archive/src/app/favicon.ico similarity index 100% rename from frontend/src/app/favicon.ico rename to frontend/archive/src/app/favicon.ico diff --git a/frontend/src/app/layout.tsx b/frontend/archive/src/app/layout.tsx similarity index 100% rename from frontend/src/app/layout.tsx rename to frontend/archive/src/app/layout.tsx diff --git a/frontend/src/app/login/page.tsx b/frontend/archive/src/app/login/page.tsx similarity index 100% rename from frontend/src/app/login/page.tsx rename to frontend/archive/src/app/login/page.tsx diff --git a/frontend/src/app/page.tsx b/frontend/archive/src/app/page.tsx similarity index 100% rename from frontend/src/app/page.tsx rename to frontend/archive/src/app/page.tsx diff --git a/frontend/src/app/register/page.tsx b/frontend/archive/src/app/register/page.tsx similarity index 100% rename from frontend/src/app/register/page.tsx rename to frontend/archive/src/app/register/page.tsx diff --git a/frontend/src/app/schedule/[date]/edit/page.tsx b/frontend/archive/src/app/schedule/[date]/edit/page.tsx similarity index 100% rename from frontend/src/app/schedule/[date]/edit/page.tsx rename to frontend/archive/src/app/schedule/[date]/edit/page.tsx diff --git a/frontend/src/app/scheduled-user-dishes/history/page.tsx b/frontend/archive/src/app/scheduled-user-dishes/history/page.tsx similarity index 100% rename from frontend/src/app/scheduled-user-dishes/history/page.tsx rename to frontend/archive/src/app/scheduled-user-dishes/history/page.tsx diff --git a/frontend/src/app/users/[id]/edit/page.tsx b/frontend/archive/src/app/users/[id]/edit/page.tsx similarity index 100% rename from frontend/src/app/users/[id]/edit/page.tsx rename to frontend/archive/src/app/users/[id]/edit/page.tsx diff --git a/frontend/src/app/users/create/page.tsx b/frontend/archive/src/app/users/create/page.tsx similarity index 70% rename from frontend/src/app/users/create/page.tsx rename to frontend/archive/src/app/users/create/page.tsx index 6a76d8d..80480f9 100644 --- a/frontend/src/app/users/create/page.tsx +++ b/frontend/archive/src/app/users/create/page.tsx @@ -7,8 +7,7 @@ import {useState} from "react"; import Alert from "@/components/ui/Alert"; import {createUser} from "@/utils/api/usersApi"; import Link from "next/link"; -import Button from "@/components/ui/Button"; -import Input from "@/components/ui/Input"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; export const dynamic = 'force-dynamic'; const CreateUsersPage = () => { @@ -41,18 +40,18 @@ const CreateUsersPage = () => { error != '' && { error } } - setName(e.target.value)} + + setName(e.target.value)} + className="w-full p-2 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - + Create
); diff --git a/frontend/src/app/users/page.tsx b/frontend/archive/src/app/users/page.tsx similarity index 88% rename from frontend/src/app/users/page.tsx rename to frontend/archive/src/app/users/page.tsx index f58b450..0943dd8 100644 --- a/frontend/src/app/users/page.tsx +++ b/frontend/archive/src/app/users/page.tsx @@ -10,7 +10,7 @@ import React from "react"; import {deleteUser} from "@/utils/api/usersApi"; import {UserType} from "@/types/UserType"; import Card from "@/components/layout/Card"; -import Button from "@/components/ui/Button"; +import OutlineLinkButton from "@/components/ui/Buttons/OutlineLinkButton"; const UsersPage = () => { const { users, isLoading } = useFetchUsers(); @@ -59,9 +59,10 @@ const UsersPage = () => {
- + + +

Add User

+
diff --git a/frontend/archive/src/components/Spinner.tsx b/frontend/archive/src/components/Spinner.tsx new file mode 100644 index 0000000..f170616 --- /dev/null +++ b/frontend/archive/src/components/Spinner.tsx @@ -0,0 +1,15 @@ +const Spinner = () => { + + return ( +
+ + + + +
+ ) + +} + +export default Spinner \ No newline at end of file diff --git a/frontend/archive/src/components/features/OnboardingBanner.tsx b/frontend/archive/src/components/features/OnboardingBanner.tsx new file mode 100644 index 0000000..9bbc003 --- /dev/null +++ b/frontend/archive/src/components/features/OnboardingBanner.tsx @@ -0,0 +1,49 @@ +import { FC } from "react" +import Link from "next/link" +import useRoutes from "@/hooks/useRoutes" +import { UserType } from "@/types/UserType" +import { DishType } from "@/types/DishType" + +interface Props { + dishes: DishType[], + users: UserType[] +} + +const OnboardingBanner: FC = ({ dishes, users }) => { + const routes = useRoutes(); + + const steps = [ + { + label: "Create a user", + href: routes.user.create(), + count: users.length + }, { + label: "Create a dish", + href: routes.dish.create(), + count: dishes.length + } + ] + + return ( +
+
Welcome to DishPlanner
+
To get you started, please follow these steps to set up your account. This will ensure a better + experience. +
+ + { + steps.map((step, index) => ( +
+ { + step.count === 0 + ? { step.label } + :
{ step.label }
+ } +
+ )) + } +
+ ) +} + +export default OnboardingBanner; \ No newline at end of file diff --git a/frontend/src/components/features/auth/LoginForm.tsx b/frontend/archive/src/components/features/auth/LoginForm.tsx similarity index 88% rename from frontend/src/components/features/auth/LoginForm.tsx rename to frontend/archive/src/components/features/auth/LoginForm.tsx index c02be27..2189c36 100644 --- a/frontend/src/components/features/auth/LoginForm.tsx +++ b/frontend/archive/src/components/features/auth/LoginForm.tsx @@ -6,10 +6,9 @@ import { login } from "@/utils/api/auth"; import { useRouter } from 'next/navigation'; import Link from "next/link"; import useRoutes from "@/hooks/useRoutes"; -import Button from "@/components/ui/Button"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; import { useSearchParams } from 'next/navigation'; import Alert from "@/components/ui/Alert"; -import Input from "@/components/ui/Input"; export default function LoginForm() { const { login: authLogin } = useAuth(); @@ -71,23 +70,23 @@ export default function LoginForm() { }
{error &&

{error}

} - setEmail(e.target.value)} required - className="mb-4" + className="w-full p-2 mb-4 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - setPassword(e.target.value)} required - className="mb-4" + className="w-full p-2 mb-4 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - + Login Create an account diff --git a/frontend/src/components/features/auth/RegistrationForm.tsx b/frontend/archive/src/components/features/auth/RegistrationForm.tsx similarity index 82% rename from frontend/src/components/features/auth/RegistrationForm.tsx rename to frontend/archive/src/components/features/auth/RegistrationForm.tsx index ea9df3c..0738381 100644 --- a/frontend/src/components/features/auth/RegistrationForm.tsx +++ b/frontend/archive/src/components/features/auth/RegistrationForm.tsx @@ -6,8 +6,7 @@ import { useRouter } from 'next/navigation'; import useRoutes from "@/hooks/useRoutes"; import Link from "next/link"; import SectionTitle from "@/components/ui/SectionTitle"; -import Button from "@/components/ui/Button"; -import Input from "@/components/ui/Input"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; export default function LoginForm() { const router = useRouter(); @@ -58,42 +57,44 @@ export default function LoginForm() {

Register

{ error &&

{ error }

} - setName(e.target.value) } required + className="w-full p-2 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - setEmail(e.target.value) } required + className="w-full p-2 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - setPassword(e.target.value) } required + className="w-full p-2 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - setPasswordAgain(e.target.value) } required + className="w-full p-2 border rounded bg-primary border-secondary bg-gray-600 text-secondary" /> - + Back to Login diff --git a/frontend/archive/src/components/features/dishes/AddUserToDishForm.tsx b/frontend/archive/src/components/features/dishes/AddUserToDishForm.tsx new file mode 100644 index 0000000..2e2d2c8 --- /dev/null +++ b/frontend/archive/src/components/features/dishes/AddUserToDishForm.tsx @@ -0,0 +1,101 @@ +import React, { FC, useState } from "react"; +import { DishType } from "@/types/DishType"; +import { UserType } from "@/types/UserType"; +import { useFetchUsers } from "@/hooks/useFetchUsers"; +import Spinner from "@/components/Spinner"; +import {addUserToDish} from "@/utils/api/dishApi"; +import OutlineButton from "@/components/ui/Buttons/OutlineButton"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; + +interface Props { + dish: DishType; + reloadDish: () => void; +} + +const AddUserToDishForm: FC = ({ dish, reloadDish }) => { + const [showAdd, setShowAdd] = useState(false); + const [selectedUser, setSelectedUser] = useState("-1"); + const { users, isLoading: isUsersLoading } = useFetchUsers(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (selectedUser === "-1") { + alert("Please select a valid user."); + return; + } + + const userToAdd = users.find((user: UserType) => user.id === parseInt(selectedUser)); + + if (!userToAdd) { + alert("User not found."); + return; + } + + addUserToDish(dish.id, userToAdd.id) + .then(() => { + setShowAdd(false); + setSelectedUser("-1"); + reloadDish(); + }) + .catch(() => { + alert("Failed to add user, please try again."); + }); + }; + + if (isUsersLoading) { + return ; + } + + const remainingUsers = users.filter( + (user: UserType) => + !dish.users.find((dishUser: UserType) => dishUser.id === user.id) + ); + + return ( + <> + setShowAdd(!showAdd)} + disabled={remainingUsers.length === 0} + type="button" + > + Add User + + + { showAdd && ( +
+ +
+
+ +
+ +
+ + Add User + +
+
+ +
+ )} + + ); +}; + +export default AddUserToDishForm; \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/CreateDishForm.tsx b/frontend/archive/src/components/features/dishes/CreateDishForm.tsx new file mode 100644 index 0000000..242c1f2 --- /dev/null +++ b/frontend/archive/src/components/features/dishes/CreateDishForm.tsx @@ -0,0 +1,95 @@ +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import { createDish } from "@/utils/api/dishApi"; +import PageTitle from "@/components/ui/PageTitle"; +import Alert from "@/components/ui/Alert"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; +import OutlineLinkButton from "@/components/ui/Buttons/OutlineLinkButton"; +import { ChevronLeftIcon } from "@heroicons/react/16/solid"; +import Hr from "@/components/ui/Hr" + +const CreateDishForm = () => { + const router = useRouter() + const [name, setName] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + + const validateForm = () => { + if (!name.trim()) { + setError("Dish name cannot be empty."); + return false; + } + + return true; + }; + + const submitForm = async (e: React.FormEvent) => { + e.preventDefault() + + // Validate client-side input + if (!validateForm()) return; + + setError(""); + setLoading(true); + + try { + const result = await createDish(name); + if (result) { + router.push('/dishes') + } + } catch (error: unknown) { + setError(error instanceof Error ? error.message : "An unexpected error occurred."); + } finally { + setLoading(false); + } + } + + return ( +
+
+ Create Dish +
+ +
+ { error && ( + { error } + ) } + +
+ + setName(e.target.value) } // Update the name state on change + className="w-full p-2 mb-4 border rounded bg-gray-600 border-secondary text-secondary focus:bg-gray-900" + placeholder="Enter dish name" + /> +
+ + + { loading ? "Saving..." : "Save Changes" } + +
+ +
+ + } + > + Back to dishes + +
+ ); + + +}; + +export default CreateDishForm; \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/Dish.tsx b/frontend/archive/src/components/features/dishes/Dish.tsx new file mode 100644 index 0000000..7933a41 --- /dev/null +++ b/frontend/archive/src/components/features/dishes/Dish.tsx @@ -0,0 +1,39 @@ +import {DishType} from "@/types/DishType"; + +import {PencilIcon, TrashIcon} from '@heroicons/react/24/solid' +import Link from "next/link"; +import useRoutes from "@/hooks/useRoutes"; +import {UserType} from "@/types/UserType"; +import Card from "@/components/layout/Card"; + +const Dish = ({ dish }: { dish: DishType}) => { + const routes = useRoutes(); + + return ( + +
+

{ dish.name }

+ + { + dish.users.map((user: UserType) => ( +
{user.name.slice(0, 1)}
+ )) + } +
+
+ +
+ +
+ + +
+ +
+ +
+
+ ) +} + +export default Dish \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/DishCard.tsx b/frontend/archive/src/components/features/dishes/DishCard.tsx new file mode 100644 index 0000000..66b2c52 --- /dev/null +++ b/frontend/archive/src/components/features/dishes/DishCard.tsx @@ -0,0 +1,23 @@ +import {UserType} from "@/types/UserType"; +import {FC} from "react"; +import {DishType} from "@/types/DishType"; + +interface Props { + user: UserType, + dish: DishType, +} + +const DishCard: FC = ({ user, dish }: Props) => { + return ( +
+
+ { user.name.slice(0, 1) } +
+
+ { dish ? dish.name : '-' } +
+
+ ) +} + +export default DishCard \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/EditDishForm.tsx b/frontend/archive/src/components/features/dishes/EditDishForm.tsx new file mode 100644 index 0000000..cfffb2a --- /dev/null +++ b/frontend/archive/src/components/features/dishes/EditDishForm.tsx @@ -0,0 +1,87 @@ +import React, {FC, useState} from "react"; +import {useRouter} from "next/navigation"; +import Alert from "@/components/ui/Alert"; +import {updateDish} from "@/utils/api/dishApi"; +import {DishType} from "@/types/DishType"; +import useRoutes from "@/hooks/useRoutes"; +import Spinner from "@/components/Spinner"; +import Button from "@/components/ui/Button" + +interface Props { + dish: DishType +} + +const EditDishForm: FC = ({ dish }) => { + const [name, setName] = useState(dish.name); + const [error, setError] = useState(""); + const router = useRouter() + const [loading, setLoading] = useState(false); + const routes = useRoutes(); + + const validateForm = () => { + if (!name.trim()) { + setError("Dish name cannot be empty."); + return false; + } + + return true; + }; + + const submitForm = async (e: React.FormEvent) => { + e.preventDefault() + + if (!validateForm()) return; + + setError(""); + setLoading(true); + + try { + const result = await updateDish(dish.id, name); + if (result) { + router.push(routes.dish.index()) + } + } catch (error: unknown) { + setError(error instanceof Error ? error.message : "An unexpected error occurred"); + } finally { + setLoading(false); // Reset loading state + } + } + + if (loading) { + return ; + } + + return ( +
+ { + error != '' && { error } + } + + {/* Dish name input */} +
+ + setName(e.target.value)} // Update the name state on change + className="p-2 border rounded w-full bg-gray-500 border-secondary background-secondary" + /> +
+ + {/* Save button */} + +
+ ); +} + +export default EditDishForm; \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/EditDishUserCardEditForm.tsx b/frontend/archive/src/components/features/dishes/EditDishUserCardEditForm.tsx new file mode 100644 index 0000000..eb4bc06 --- /dev/null +++ b/frontend/archive/src/components/features/dishes/EditDishUserCardEditForm.tsx @@ -0,0 +1,119 @@ +import React, {FC} from "react"; +import SectionTitle from "@/components/ui/SectionTitle"; +import {syncUserDishRecurrences} from "@/utils/api/usersApi"; +import Spinner from "@/components/Spinner"; +import {UserDishType} from "@/types/ScheduledUserDishType"; +import {RecurrenceType} from "@/types/ScheduleType"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; + +interface Props { + userDish: UserDishType + onSubmit: () => void +} + +const EditDishUserCardEditForm: FC = ({ userDish, onSubmit}) => { + const weeklyRecurrence = userDish.recurrences.find((recurrence) => recurrence.type === 'App\\Models\\WeeklyRecurrence') + const minimumRecurrence = userDish.recurrences.find((recurrence) => recurrence.type === 'App\\Models\\MinimumRecurrence') + + const wv = weeklyRecurrence ? weeklyRecurrence.value : undefined + const mv = minimumRecurrence ? minimumRecurrence.value : undefined + + const [isWeeklyOn, setIsWeeklyOn] = React.useState(weeklyRecurrence !== undefined); + const [isMinimumOn, setIsMinimumOn] = React.useState(minimumRecurrence !== undefined); + const [weekday, setWeekday] = React.useState(wv ?? 0); + const [minimumValue, setMinimumValue] = React.useState(mv ?? 7); + const [loading, setLoading] = React.useState(false); + + const handleSubmit = () => { + const recurrences = [] + + if (isWeeklyOn) { + recurrences.push({ + type: 'App\\Models\\WeeklyRecurrence', + value: weekday, + }); + } + + if (isMinimumOn) { + recurrences.push({ + type: 'App\\Models\\MinimumRecurrence', + value: minimumValue, + }); + } + + setLoading(true) + syncUserDishRecurrences(userDish.dish.id, userDish.user.id, recurrences as RecurrenceType[]) + .then((data) => console.log('request data', data)) + .finally(() => { + setLoading(false) + onSubmit() + }) + } + + if (loading) { + return ; + } + + return ( +
+ Recurrences + +
+
+ setIsWeeklyOn(!isWeeklyOn)} + className="w-4 h-4 border border-gray-300 rounded-sm bg-gray-50 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800" + /> + +
+ { + isWeeklyOn && ( +
+ + +
+ ) + } +
+ +
+
+ setIsMinimumOn(!isMinimumOn)} + className="w-4 h-4 border border-gray-300 rounded-sm bg-gray-500 focus:ring-3 focus:ring-blue-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-blue-600 dark:ring-offset-gray-800 dark:focus:ring-offset-gray-800" + /> + +
+ + { + isMinimumOn && ( +
+ setMinimumValue(parseInt(e.currentTarget.value))} min="0" max="365" className="background-secondary border-secondary border-2 w-12 px-2" /> + +
+ ) + } +
+ + Save +
+ ); +} + +export default EditDishUserCardEditForm; \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/RecurrenceLabels.tsx b/frontend/archive/src/components/features/dishes/RecurrenceLabels.tsx new file mode 100644 index 0000000..bba81ba --- /dev/null +++ b/frontend/archive/src/components/features/dishes/RecurrenceLabels.tsx @@ -0,0 +1,54 @@ +import {FC} from "react"; +import {RecurrenceType} from "@/types/ScheduleType"; + +interface Props { + recurrences: RecurrenceType[]; +} + +const RecurrenceLabels: FC = ({recurrences}) => { + const weeklyRecurrences = recurrences.filter(recurrence => recurrence.type === 'App\\Models\\WeeklyRecurrence'); + const minimumRecurrences = recurrences.filter(recurrence => recurrence.type === 'App\\Models\\MinimumRecurrence'); + + const renderWeeklyRecurrence = () => { + if (weeklyRecurrences == undefined || weeklyRecurrences.length == 0) { + return ''; + } + + const weekdayString = (() => { + switch (weeklyRecurrences[0].value) { + case 0: return "Sunday" + case 1: return "Monday" + case 2: return "Tuesday"; + case 3: return "Wednesday"; + case 4: return "Thursday"; + case 5: return "Friday"; + case 6: return "Saturday"; + default: return "Invalid day"; + } + }) + + return ( +
+ { weekdayString() } +
+ ) + } + const renderMinimumRecurrence = () => { + if (minimumRecurrences == undefined || minimumRecurrences.length == 0) { + return ''; + } + + return ( +
+ min: { minimumRecurrences[0].value } +
+ ) + } + + return <> + { renderWeeklyRecurrence() } + { renderMinimumRecurrence() } + ; +}; + +export default RecurrenceLabels; \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/SyncUsersForm.tsx b/frontend/archive/src/components/features/dishes/SyncUsersForm.tsx new file mode 100644 index 0000000..296167d --- /dev/null +++ b/frontend/archive/src/components/features/dishes/SyncUsersForm.tsx @@ -0,0 +1,32 @@ +import React, { FC } from "react"; +import { DishType } from "@/types/DishType"; +import { UserType } from "@/types/UserType"; +import UserDishCard from "@/components/features/dishes/UserDishCard"; +import SectionTitle from "@/components/ui/SectionTitle"; +import AddUserToDishForm from "@/components/features/dishes/AddUserToDishForm"; + +interface Props { + dish: DishType; + reloadDish: () => void; +} + +const SyncUsersForm: FC = ({ dish, reloadDish }) => { + return ( +
+ Users + + + + {dish.users.map((user: UserType) => ( + + ))} +
+ ); +}; + +export default SyncUsersForm; \ No newline at end of file diff --git a/frontend/archive/src/components/features/dishes/UserDishCard.tsx b/frontend/archive/src/components/features/dishes/UserDishCard.tsx new file mode 100644 index 0000000..4e06e8c --- /dev/null +++ b/frontend/archive/src/components/features/dishes/UserDishCard.tsx @@ -0,0 +1,83 @@ +import React, {FC, useEffect} from "react"; +import {DishType} from "@/types/DishType"; +import {UserType} from "@/types/UserType"; +import Link from "next/link"; +import {PencilIcon, TrashIcon} from "@heroicons/react/24/solid"; +import {removeUserFromDish} from "@/utils/api/dishApi"; +import EditDishUserCardEditForm from "@/components/features/dishes/EditDishUserCardEditForm"; +import {getUserDishForUserAndDish} from "@/utils/api/usersApi"; +import Spinner from "@/components/Spinner"; +import RecurrenceLabels from "@/components/features/dishes/RecurrenceLabels"; +import {UserDishType} from "@/types/ScheduledUserDishType"; + +interface Props { + dish: DishType + user: UserType + reloadDish: () => void +} + +const UserDishCard: FC = ({dish, user, reloadDish}) => { + const [userDish, setUserDish] = React.useState(null); + const [userDishLoading, setUserDishLoading] = React.useState(true); + const [isEditMode, setIsEditMode] = React.useState(false); + + useEffect(() => { + getUserDishForUserAndDish(user.id, dish.id) + .then((userDish) => setUserDish(userDish)) + .finally(() => setUserDishLoading(false)) + }, [dish, user]); + + const handleRemove = () => { + removeUserFromDish(dish.id, user.id) + .then(() => reloadDish()) + .catch(() => { + alert("Failed to remove user, please try again."); + }); + }; + + if (userDishLoading || !userDish) { + return + } + + const onUserCardSubmit = () => { + setIsEditMode(false); + reloadDish() + } + + return ( +
+
+
+ {user.name} +
+ +
+ +
+ +
+ setIsEditMode(!isEditMode)} href="#"> +
+ +
+ +
+
+ +
+ +
+ +
+
+ + {isEditMode && ( +
+ +
+ )} +
+ ); +} + +export default UserDishCard; \ No newline at end of file diff --git a/frontend/archive/src/components/features/navbar/MobileDropdownMenu.tsx b/frontend/archive/src/components/features/navbar/MobileDropdownMenu.tsx new file mode 100644 index 0000000..3e8639a --- /dev/null +++ b/frontend/archive/src/components/features/navbar/MobileDropdownMenu.tsx @@ -0,0 +1,69 @@ +import Link from "next/link"; +import React, {FC} from "react"; +import useRoutes from "@/hooks/useRoutes"; +import classNames from "classnames"; + +interface Props { + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; + handleLogout: (e: React.MouseEvent) => void; +} + +const divStyles = classNames( + 'absolute', 'text-xxl', 'rounded-b', 'top-full mt-1', 'left-0', 'right-0', '', 'py-2', + 'bg-gray-600', 'border-secondary', 'shadow-md', 'flex', 'flex-col', 'space-y-3', + 'md:hidden' +) + +const linkStyles = classNames( + 'border-b-2', 'border-secondary', 'uppercase', + 'text-primary', 'hover:background-secondary', 'pb-2', 'pl-5', + 'space-grotesk', 'text-xl' +) + +const MobileDropdownMenu: FC = ({ isOpen, setIsOpen, handleLogout }) => { + const routes = useRoutes(); + + if (!isOpen) return null; + + return ( +
+ setIsOpen(false)} + > + Home + + setIsOpen(false)} + > + Dishes + + setIsOpen(false)} + > + Users + + setIsOpen(false)} + > + History + + + Logout + +
+ ) +} + +export default MobileDropdownMenu \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/HistoricalDishes.tsx b/frontend/archive/src/components/features/schedule/HistoricalDishes.tsx new file mode 100644 index 0000000..ad3718f --- /dev/null +++ b/frontend/archive/src/components/features/schedule/HistoricalDishes.tsx @@ -0,0 +1,44 @@ +"use client" + +import {useEffect, useState} from "react"; +import {DateTime} from "luxon"; +import ScheduleCalendar from "@/components/features/schedule/ScheduleCalendar"; +import PageTitle from "@/components/ui/PageTitle"; +import {ScheduleType} from "@/types/ScheduleType"; +import Spinner from "@/components/Spinner"; +import {listSchedule} from "@/utils/api/scheduleApi"; + +const HistoricalDishes = () => { + const [schedule, setSchedule] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + const yesterday = DateTime.now().minus({ days: 1 }).toFormat('yyyy-LL-dd'); + + useEffect(() => { + listSchedule(undefined, yesterday) + .then((dishes: ScheduleType[]) => dishes + .sort((a: ScheduleType, b: ScheduleType) => new Date(b.date).getTime() - new Date(a.date).getTime()) + ) + .then((dishes) => setSchedule(dishes)) + .finally(() => setIsLoading(false)) + }, [yesterday]); + + if (isLoading) { + return ; + } + + if (!schedule || Object.keys(schedule).length === 0) { + return ( +
+ No dishes scheduled +
+ ); + } + + return
+ History + +
+} + +export default HistoricalDishes \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/ScheduleCalendar.tsx b/frontend/archive/src/components/features/schedule/ScheduleCalendar.tsx new file mode 100644 index 0000000..b146f6a --- /dev/null +++ b/frontend/archive/src/components/features/schedule/ScheduleCalendar.tsx @@ -0,0 +1,75 @@ +"use client" + +import {FC} from "react"; +import ScheduleDayCard from "@/components/features/schedule/dayCard/ScheduleDayCard"; +import { FilledScheduleType, ScheduleType } from "@/types/ScheduleType"; +import {useFetchUsers} from "@/hooks/useFetchUsers"; +import Spinner from "@/components/Spinner"; + +const generateDates = (startDate: string, days: number): string[] => { + const dates = []; + const start = new Date(startDate); + + for (let i = 0; i < days; i++) { + const currentDate = new Date(start); + currentDate.setDate(start.getDate() + i); + dates.push(currentDate.toISOString().split('T')[0]); + } + + return dates; +}; + + +const fillCalendar = (schedules: ScheduleType[]): FilledScheduleType[] => { + /* +Array(14) + 0: + date: "2025-05-05" + id: 2 + is_skipped: false + scheduled_user_dishes: [] + */ + + const dates = generateDates((new Date()).toISOString().split('T')[0], 31) + + return dates.map((date): FilledScheduleType => { + console.log(date) + + const schedule = schedules.find((schedule: ScheduleType) => schedule.date == date) + + if (schedule) { + return schedule + } + + return { + date, + scheduled_user_dishes: [] + } + }) +} + +interface Props { + schedule: ScheduleType[]; +} + +const ScheduleCalendar: FC = ({ schedule }: Props) => { + const {users, isLoading: areUsersLoading} = useFetchUsers(); + + if (areUsersLoading) return + + const fullCalendar = fillCalendar(schedule) + + return ( +
+ { fullCalendar.map((schedule, index) => ( + + ))} +
+ ) +} + +export default ScheduleCalendar \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/ScheduleEditForm.tsx b/frontend/archive/src/components/features/schedule/ScheduleEditForm.tsx new file mode 100644 index 0000000..0f436a9 --- /dev/null +++ b/frontend/archive/src/components/features/schedule/ScheduleEditForm.tsx @@ -0,0 +1,142 @@ +"use client"; + +import React, { FC, useEffect, useState } from "react"; +import { ScheduleType } from "@/types/ScheduleType"; +import Spinner from "@/components/Spinner"; +import PageTitle from "@/components/ui/PageTitle"; +import { getScheduleForDate, scheduleUserDish, updateScheduleForDate } from "@/utils/api/scheduleApi"; +import { UserDishType } from "@/types/ScheduledUserDishType"; +import Label from "@/components/ui/Label"; +import SectionTitle from "@/components/ui/SectionTitle"; +import { useFetchUsers } from "@/hooks/useFetchUsers"; +import { listUserDishes } from "@/utils/api/userDishApi"; +import scheduleBuilder from "@/utils/scheduleBuilder"; +import transformDate from "@/utils/dateBuilder"; +import { ChevronLeftIcon } from "@heroicons/react/16/solid"; +import Hr from "@/components/ui/Hr" +import Button from "@/components/ui/Button" + +interface Props { + date: string; +} + +const ScheduleEditForm: FC = ({ date }) => { + const [schedule, setSchedule] = useState() + const [userDishes, setUserDishes] = useState([]) + const [isScheduleLoading, setIsScheduleLoading] = useState(true); + const [areUserDishesLoading, setAreUserDishesLoading] = useState(true); + const { users } = useFetchUsers(); + + useEffect(() => { + getScheduleForDate(date) + .then((sched: ScheduleType) => setSchedule(sched)) + .finally(() => setIsScheduleLoading(false)) + }, [date]); + + + useEffect(() => { + listUserDishes() + .then((user_dishes: UserDishType[]) => setUserDishes(user_dishes)) + .finally(() => setAreUserDishesLoading(false)) + }, []); + + const handleSkipDay = () => { + updateScheduleForDate(date, true) + .then((schedule: ScheduleType) => { + setSchedule(schedule) + }) + } + + const handleUnskipDay = () => { + updateScheduleForDate(date, false) + .then((schedule: ScheduleType) => { + setSchedule(schedule) + }) + } + + const handleChange = (e: React.ChangeEvent, userId: number) => { + const userDishId = parseInt(e.currentTarget.value); + + if (userDishId === 0) { + scheduleUserDish(date, userId, null, true).then(() => window.location.reload()); + return; + } + + scheduleUserDish(date, userId, userDishId).then(() => window.location.reload()); + } + + if (isScheduleLoading || areUserDishesLoading || !schedule) { + return + } + + const scheduleData = scheduleBuilder(schedule, users, userDishes) + + return
+
+
+ Edit Day +
+
+ { transformDate(schedule.date) } +
+
+ +
+ + { + userDishes.length === 0 &&
+
No dishes found assigned to this user.
+
Go ahead and add some first, or choose to skip the day.
+
(dishes ={`>`} edit ={`>`} add user)
+
+ } + + { schedule.is_skipped + ? + : ( + <> + { + scheduleData + .map((scheduleData) =>
+
{ scheduleData.user.name }
+
+ +
+
) + } + + ) + } + +
Changes are saved automatically
+ +
+ +
+ +
{ + schedule.is_skipped + ? + : + }
+
+
+} + +export default ScheduleEditForm \ No newline at end of file diff --git a/frontend/src/components/features/schedule/ScheduleRegenerateButton.tsx b/frontend/archive/src/components/features/schedule/ScheduleRegenerateButton.tsx similarity index 100% rename from frontend/src/components/features/schedule/ScheduleRegenerateButton.tsx rename to frontend/archive/src/components/features/schedule/ScheduleRegenerateButton.tsx diff --git a/frontend/archive/src/components/features/schedule/ScheduleRegenerateForm.tsx b/frontend/archive/src/components/features/schedule/ScheduleRegenerateForm.tsx new file mode 100644 index 0000000..017b8c2 --- /dev/null +++ b/frontend/archive/src/components/features/schedule/ScheduleRegenerateForm.tsx @@ -0,0 +1,78 @@ +import {DialogTitle} from "@headlessui/react"; +import Toggle from "@/components/ui/Toggle"; +import {FC, useEffect, useState} from "react"; +import {generateSchedule} from "@/utils/api/scheduleApi"; +import Alert from "@/components/ui/Alert"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; + +interface ScheduleRegenerateFormProps { + closeModal: () => void; +} + +const ScheduleRegenerateForm: FC = ({closeModal}) => { + const [overwrite, setOverwrite] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + }, [overwrite]); + + const close = () => { + closeModal(); + } + + const handleToggle = () => { + setOverwrite(!overwrite) + } + + const handleSubmit = () => { + generateSchedule(overwrite) + .then(() => close()) + .catch((err) => setError(err)) + } + + return <> +
+
+
+ + Regenerate Schedule + +
+
+ { + error && { error } + } +
+ +
+
+ +
+
+
+ + +
+
+
+
+ handleSubmit()} + className="inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-semibold shadow-xs sm:ml-3 sm:w-auto" + > + Regenerate + + close()} + className="mt-3 inline-flex w-full justify-center rounded-md bg-gray-500 px-3 py-2 text-sm font-semibold text-gray-900 ring-1 shadow-xs border-secondary ring-inset sm:mt-0 sm:w-auto" + > + Cancel + +
+ ; +}; + +export default ScheduleRegenerateForm; \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/UpcomingDishes.tsx b/frontend/archive/src/components/features/schedule/UpcomingDishes.tsx new file mode 100644 index 0000000..c7731d0 --- /dev/null +++ b/frontend/archive/src/components/features/schedule/UpcomingDishes.tsx @@ -0,0 +1,62 @@ +"use client" + +import { useCallback, useEffect, useState } from "react"; +import { DateTime } from "luxon"; +import ScheduleCalendar from "@/components/features/schedule/ScheduleCalendar"; +import PageTitle from "@/components/ui/PageTitle"; +import Spinner from "@/components/Spinner"; +import { ScheduleType } from "@/types/ScheduleType"; +import { listSchedule } from "@/utils/api/scheduleApi"; +import OnboardingBanner from "@/components/features/OnboardingBanner" +import { useFetchUsers } from "@/hooks/useFetchUsers" +import { useFetchDishes } from "@/hooks/useFetchDishes" +import ScheduleRegenerateButton from "@/components/features/schedule/ScheduleRegenerateButton"; + +const UpcomingDishes = () => { + const [schedule, setSchedule] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + const today = DateTime.now().toFormat("yyyy-LL-dd"); + + const fetchSchedule = useCallback(() => { + setIsLoading(true); + listSchedule(today) + .then((dishes) => setSchedule(dishes)) + .finally(() => setIsLoading(false)); + }, [today]); + + useEffect(() => { + fetchSchedule(); + }, [fetchSchedule]); + + const { users, isLoading: areUsersLoading } = useFetchUsers(); + const { dishes, isLoading: areDishesLoading } = useFetchDishes(); + + if (isLoading || areUsersLoading || areDishesLoading) { + return ; + } + + if (users.length === 0 || dishes.length === 0) { + return + } + + return ( +
+
+
+ Schedule +
+
+ +
+
+ { + !schedule || Object.keys(schedule).length === 0 + ?
No dishes scheduled
+ : + } +
+ ); +}; + +export default UpcomingDishes; \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/UserDishEditCard.tsx b/frontend/archive/src/components/features/schedule/UserDishEditCard.tsx new file mode 100644 index 0000000..b82fc1b --- /dev/null +++ b/frontend/archive/src/components/features/schedule/UserDishEditCard.tsx @@ -0,0 +1,79 @@ +import {FC, FormEvent, useMemo, useState} from "react"; +import {DishType} from "@/types/DishType"; +import {ScheduledUserDishType} from "@/types/ScheduledUserDishType"; +import {updateScheduledUserDish} from "@/utils/api/scheduledUserDishesApi"; +import Alert from "@/components/ui/Alert"; +import classNames from "classnames"; + +interface Props { + scheduledUserDish: ScheduledUserDishType + allDishes: DishType[] +} + +const UserDishEditCard: FC = ({ scheduledUserDish, allDishes }) => { + const [selectedUserDishId, setSelectedUserDishId] = useState(scheduledUserDish.user_dish ? scheduledUserDish.user_dish.id : 0) + const [errorMessage, setErrorMessage] = useState("") + const [isSuccess, setIsSuccess] = useState(false); + + const selectStyle = classNames( + 'p-2', 'rounded', 'w-full', 'background-secondary', + 'focus:outline-none', + 'transition-[border-color] ease-out duration-1000', 'border-2', // Keep consistent base styles + { + 'border-green-500': isSuccess, // Green border when successful + 'border-red-500': !isSuccess && errorMessage !== "", // Red border when there's an error + 'border-secondary': !isSuccess && errorMessage === "", // Default border for neutral state + } + ) + + const handleOnChange = (e: FormEvent) => { + const userDishId = parseInt(e.currentTarget.value); + setSelectedUserDishId(userDishId); + + updateScheduledUserDish(scheduledUserDish.id, userDishId) + .then(() => { + setIsSuccess(false); + setTimeout(() => { + setIsSuccess(true); + setTimeout(() => setIsSuccess(false), 1000); + }, 0); + }) + .catch((error) => { + setErrorMessage(error); // Log API errors + }); + }; + + const filteredDishes = useMemo(() => + allDishes.filter((dish: DishType) => + dish.users.some((user) => user.id === scheduledUserDish.user_dish.user.id) + ), + [allDishes, scheduledUserDish.user_dish.user.id] + ) + + return ( +
+
{scheduledUserDish.user_dish.user.name}
+ + { errorMessage !== "" && { errorMessage } } + + + +
+ ); +}; + +export default UserDishEditCard; \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/dayCard/DateBadge.tsx b/frontend/archive/src/components/features/schedule/dayCard/DateBadge.tsx new file mode 100644 index 0000000..9a61d87 --- /dev/null +++ b/frontend/archive/src/components/features/schedule/dayCard/DateBadge.tsx @@ -0,0 +1,27 @@ +import {DateTime} from "luxon"; +import React, {FC} from "react"; +import classNames from "classnames"; + +interface Props { + date: string + className?: string; +} + +const DateBadge: FC = ({ className, date }) => { + const isToday = DateTime.fromISO(date).toFormat("yyyy-LL-dd") == DateTime.now().toFormat("yyyy-LL-dd") + + const textStyle = classNames("inline font-bold", { + 'text-accent-blue': isToday, + 'text-secondary': !isToday, + }, className) + + return ( +
+
{DateTime.fromISO(date).toFormat("dd")}
+
+
{DateTime.fromISO(date).toFormat("LLL")}
+
+ ) +} + +export default DateBadge \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCard.tsx b/frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCard.tsx new file mode 100644 index 0000000..77c68e0 --- /dev/null +++ b/frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCard.tsx @@ -0,0 +1,50 @@ +import React, {FC} from "react"; +import {UserType} from "@/types/UserType"; +import ScheduleDayCardUserDish from "@/components/features/schedule/dayCard/ScheduleDayCardUserDish"; +import { FilledScheduleType, ScheduleType } from "@/types/ScheduleType"; +import Link from "next/link"; +import {PencilSquareIcon} from "@heroicons/react/24/outline"; +import useRoutes from "@/hooks/useRoutes"; +import DateBadge from "@/components/features/schedule/dayCard/DateBadge"; +import { DateTime } from "luxon" +import classNames from "classnames" + +interface Props { + schedule: ScheduleType|FilledScheduleType; + users: UserType[]; +} + +const ScheduleDayCard: FC = ({schedule, users}) => { + const routes = useRoutes() + const isToday = DateTime.fromISO(schedule.date).toFormat("yyyy-LL-dd") == DateTime.now().toFormat("yyyy-LL-dd") + + const containerStyles = classNames( + 'w-full bg-gray-500 pt-5 pb-2 rounded-2xl text-xl', { + 'border-2 text-accent-blue border-accent-blue': isToday, + } + ) + + return ( +
+ + +
+ { + users.map((user) => ) + } + +
+ + Edit + +
+
+
+ ); +}; + +export default ScheduleDayCard; \ No newline at end of file diff --git a/frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCardUserDish.tsx b/frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCardUserDish.tsx new file mode 100644 index 0000000..64f0f0e --- /dev/null +++ b/frontend/archive/src/components/features/schedule/dayCard/ScheduleDayCardUserDish.tsx @@ -0,0 +1,32 @@ +import React, { FC } from "react"; +import { ScheduledUserDishType } from "@/types/ScheduledUserDishType"; +import { UserType } from "@/types/UserType"; +import { FilledScheduleType, ScheduleType } from "@/types/ScheduleType"; + +interface Props { + schedule: ScheduleType|FilledScheduleType; + user: UserType; +} + +const ScheduleDayCardUserDish: FC = ({ schedule, user }) => { + const getDish = (user: UserType) => { + const scheduled_dishes = schedule.scheduled_user_dishes.filter((scheduled_user_dish: ScheduledUserDishType) => ( + scheduled_user_dish.user_dish?.user.id == user.id + )) + + if (scheduled_dishes.length > 0) { + return scheduled_dishes[0].user_dish.dish.name + } + + return '/' + } + + return ( +
+
{ user.name } :
+
{ getDish(user) }
+
+ ); +}; + +export default ScheduleDayCardUserDish; \ No newline at end of file diff --git a/frontend/archive/src/components/features/users/EditUserForm.tsx b/frontend/archive/src/components/features/users/EditUserForm.tsx new file mode 100644 index 0000000..1593dd9 --- /dev/null +++ b/frontend/archive/src/components/features/users/EditUserForm.tsx @@ -0,0 +1,63 @@ +import React, {FC, useState} from "react"; +import {useRouter} from "next/navigation"; +import useRoutes from "@/hooks/useRoutes"; +import {updateUser} from "@/utils/api/usersApi"; +import PageTitle from "@/components/ui/PageTitle"; +import Link from "next/link"; +import Alert from "@/components/ui/Alert"; +import {UserType} from "@/types/UserType"; +import SolidButton from "@/components/ui/Buttons/SolidButton"; + +interface Props { + user: UserType; +} + +const EditUserForm: FC = ({ user }) => { + + const [name, setName] = useState(user.name); + const [error, setError] = useState(''); + const router = useRouter(); + const routes = useRoutes(); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + // validateName + if (!name.trim()) { + setError('Name cannot be empty.'); + return; + } + + updateUser(user, name) + .then(() => { + router.push(routes.user.index()) + }) + } + + return ( +
+ Create User + Back to users + +
+ { + error != '' && { error } + } + + + setName(e.target.value)} + className="w-full p-2 border rounded bg-primary border-secondary bg-gray-600 text-secondary" + /> + + Update +
+
+ ); +} + +export default EditUserForm; \ No newline at end of file diff --git a/frontend/src/components/layout/AuthGuard.tsx b/frontend/archive/src/components/layout/AuthGuard.tsx similarity index 100% rename from frontend/src/components/layout/AuthGuard.tsx rename to frontend/archive/src/components/layout/AuthGuard.tsx diff --git a/frontend/archive/src/components/layout/Card.tsx b/frontend/archive/src/components/layout/Card.tsx new file mode 100644 index 0000000..6784526 --- /dev/null +++ b/frontend/archive/src/components/layout/Card.tsx @@ -0,0 +1,15 @@ +import React, {FC} from "react"; + +interface Props { + children: React.ReactNode; +} + +const Card: FC = ({ children }) => { + return ( +
+ { children } +
+ ) +} + +export default Card \ No newline at end of file diff --git a/frontend/archive/src/components/layout/NavBar.tsx b/frontend/archive/src/components/layout/NavBar.tsx new file mode 100644 index 0000000..67cadb2 --- /dev/null +++ b/frontend/archive/src/components/layout/NavBar.tsx @@ -0,0 +1,83 @@ +"use client"; + +import React, { useState } from "react"; +import Link from "next/link"; +import useRoutes from "@/hooks/useRoutes"; +import MobileDropdownMenu from "@/components/features/navbar/MobileDropdownMenu"; +import {useRouter} from "next/navigation"; +import {useAuth} from "@/context/AuthContext"; + +const NavBar = () => { + const [isOpen, setIsOpen] = useState(false); + const routes = useRoutes(); + const router = useRouter(); + const {isAuthenticated, logout} = useAuth(); + + const handleLogout = (e: React.MouseEvent) => { + e.preventDefault(); + logout(); + router.replace('/login'); + }; + + return ( + + ); +}; + +export default NavBar; \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Alert.tsx b/frontend/archive/src/components/ui/Alert.tsx new file mode 100644 index 0000000..c32070c --- /dev/null +++ b/frontend/archive/src/components/ui/Alert.tsx @@ -0,0 +1,34 @@ +import React, {FC} from "react"; +import classNames from "classnames"; + +interface Props { + children: React.ReactNode; + className?: string; + type: 'error' | 'warning' | 'info' | 'success'; +} + +const Alert: FC = ({ children, className, type } ) => { + let bgColor = 'bg-blue-200' + let fgColor = 'bg-blue-800' + + if (type == 'error') { + bgColor = 'bg-red-200' + fgColor = 'bg-red-800' + } else if (type == 'warning') { + bgColor = 'bg-orange-200' + fgColor = 'bg-orange-800' + } else if (type == 'success') { + bgColor = 'border-2 border-green-500' + fgColor = 'text-green-500' + } + + const styles = classNames(fgColor, bgColor, className, 'rounded') + + return ( +
+ { children} +
+ ) +} + +export default Alert \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Button.tsx b/frontend/archive/src/components/ui/Button.tsx new file mode 100644 index 0000000..4ad006c --- /dev/null +++ b/frontend/archive/src/components/ui/Button.tsx @@ -0,0 +1,62 @@ +import Link from "next/link"; +import React, { FC, ReactElement, ReactNode } from "react"; +import classNames from "classnames"; + +interface ButtonProps { + appearance?: 'solid' | 'outline' | 'text'; + children: ReactNode; + className?: string; + href?: string; + icon?: ReactNode; + onClick?: () => void; + disabled?: boolean; + size?: 'small' | 'medium' | 'large'; + type?: 'button' | 'submit' | 'reset'; + variant?: 'primary' | 'secondary' | 'accent'; +} + +const Button: FC = ({ appearance, children, className, disabled, href, icon, onClick, + size = 'medium', type, + variant = 'primary' +}) => { + const styles = classNames( + "flex items-center space-x-1", + "justify-center font-size-18 py-2 px-4 rounded flex", + { + 'border-2 border-primary background-red text-white': variant === 'primary' && appearance === 'solid', + 'border-2 border-primary text-primary': variant === 'primary' && appearance === 'outline', + 'text-primary': variant === 'primary' && appearance === 'text', + 'border-2 border-secondary text-secondary': variant === 'secondary' && appearance === 'outline', + 'border-2 border-accent-blue text-accent-blue': variant === 'accent' && appearance === 'outline', + }, + className + ) + + const iconClassNames = classNames({ + "h-4 w-4 mr-1": size === "small", + "h-5 w-5 mr-1": size === "medium", + "h-7 w-7 mr-2": size === "large", + }); + + const iconElement = + React.isValidElement(icon) && + React.cloneElement(icon as ReactElement<{ className?: string }>, { + className: iconClassNames, + }); + + if (href !== undefined) { + return ( + + { icon && iconElement} + { children} + + ) + } + + return +} + +export default Button \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Buttons/OutlineButton.tsx b/frontend/archive/src/components/ui/Buttons/OutlineButton.tsx new file mode 100644 index 0000000..b668b6b --- /dev/null +++ b/frontend/archive/src/components/ui/Buttons/OutlineButton.tsx @@ -0,0 +1,37 @@ +import React, { FC } from "react"; +import classNames from "classnames"; + +interface Props { + children: React.ReactNode; + className?: string; + disabled?: boolean; + onClick?: () => void; + size?: "small" | "medium" | "large"; + type: 'submit' | 'button'; +} + +const OutlineButton: FC = ({ children, className, disabled = false, onClick, size, type }) => { + const style = classNames( + "justify-center border-2 border-accent font-size-18 text-accent-blue py-2 px-4 rounded flex", + { 'text-xs': size === "small" }, + className + ) + + if (onClick === undefined) { + onClick = () => { + } + } + + return ( + + ) +} + +export default OutlineButton \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Buttons/OutlineLinkButton.tsx b/frontend/archive/src/components/ui/Buttons/OutlineLinkButton.tsx new file mode 100644 index 0000000..ab0e9b1 --- /dev/null +++ b/frontend/archive/src/components/ui/Buttons/OutlineLinkButton.tsx @@ -0,0 +1,49 @@ +import React, { FC, ReactElement } from "react"; +import classNames from "classnames"; +import Link from "next/link"; + +interface Props { + children: React.ReactNode; + className?: string; + href: string; + icon?: React.ReactNode; + size?: "small" | "medium" | "large"; + variant?: "primary" | "secondary"; +} + +const OutlineLinkButton: FC = ({ children, className, href, icon, size = "medium", variant }) => { + const linkClassNames = classNames( + "underline font-default pt-3 pb-3 px-4 rounded mb-0 flex", + { + 'text-primary border-primary': variant === "primary", + 'text-secondary border-secondary': variant === "secondary", + 'text-accent-blue border-accent': !variant || !["primary", "secondary"].includes(variant), + }, { + 'text-size-14': size === "small", + 'font-size-18': !size || size === "medium", + 'text-2xl': size === "large", + }, + className, + ) + + const iconClassNames = classNames("mt-0.5", { + "h-4 w-4 mr-1": size === "small", + "h-5 w-5 mr-1": size === "medium", // Default size + "h-7 w-7 mr-2": size === "large", + }); + + const iconElement = + React.isValidElement(icon) && + React.cloneElement(icon as ReactElement<{ className?: string }>, { + className: iconClassNames, + }); + + return ( + + {iconElement} + {children} + + ) +} + +export default OutlineLinkButton \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Buttons/SolidButton.tsx b/frontend/archive/src/components/ui/Buttons/SolidButton.tsx new file mode 100644 index 0000000..62ac01d --- /dev/null +++ b/frontend/archive/src/components/ui/Buttons/SolidButton.tsx @@ -0,0 +1,39 @@ +import React, {FC} from "react"; +import classNames from "classnames"; + +interface Props { + children: React.ReactNode; + className?: string; + disabled?: boolean; + onClick?: () => void; + size?: "small" | "medium" | "large"; + type: 'submit' | 'button'; +} + +const SolidButton: FC = ({ children, className, disabled = false, onClick, size, type }) => { + const style = classNames( + "py-2 px-4 bg-primary text-white text-xl p-2 rounded hover:bg-secondary mb-0", + { + 'text-xs' : size === "small", + 'font-size-18' : !size || size === "medium", + }, + className + ) + + if (onClick === undefined) { + onClick = () => {} + } + + return ( + + ) +} + +export default SolidButton \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Buttons/SolidLinkButton.tsx b/frontend/archive/src/components/ui/Buttons/SolidLinkButton.tsx new file mode 100644 index 0000000..77a78dd --- /dev/null +++ b/frontend/archive/src/components/ui/Buttons/SolidLinkButton.tsx @@ -0,0 +1,46 @@ +import React, { FC, ReactElement } from "react"; +import classNames from "classnames"; +import Link from "next/link"; + +interface Props { + children: React.ReactNode; + className?: string; + href: string; + icon?: React.ReactNode; + size?: "small" | "medium" | "large"; + variant?: "primary" | "secondary"; +} + +const SolidLinkButton: FC = ({ children, className, href, icon, size = "medium", variant }) => { + const style = classNames( + "py-2 px-4 text-xl p-2 rounded hover:bg-secondary mb-0 text-center flex", + { + 'background-red text-white': variant === "primary", + 'background-secondary border-2 border-secondary': variant === "secondary", + }, + className + ) + + const iconClassNames = classNames("mt-1", { + "h-4 w-4 mr-1": size === "small", + "h-5 w-5 mr-1": size === "medium", // Default size + "h-7 w-7 mr-2": size === "large", + }); + + const iconElement = + React.isValidElement(icon) && + React.cloneElement(icon as ReactElement<{ className?: string }>, { + className: iconClassNames, + }); + + return ( + +
+ {iconElement} + {children} +
+ + ) +} + +export default SolidLinkButton \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Description.tsx b/frontend/archive/src/components/ui/Description.tsx new file mode 100644 index 0000000..8b429c0 --- /dev/null +++ b/frontend/archive/src/components/ui/Description.tsx @@ -0,0 +1,17 @@ +import classNames from "classnames"; +import React from "react"; + +interface Props { + children: React.ReactNode; + className?: string; +} + +const Description = ({ children, className }: Props) => { + const style = classNames("italic font-size-16", + className + ) + + return

{ children }

+} + +export default Description \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Hr.tsx b/frontend/archive/src/components/ui/Hr.tsx new file mode 100644 index 0000000..59f0d2d --- /dev/null +++ b/frontend/archive/src/components/ui/Hr.tsx @@ -0,0 +1,14 @@ +import { FC } from "react" +import classNames from "classnames" + +interface HrProps { + className?: string; +} + +const Hr: FC = ({ className }) => { + const styles = classNames("my-4 border-secondary", className) + + return
+} + +export default Hr \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Label.tsx b/frontend/archive/src/components/ui/Label.tsx new file mode 100644 index 0000000..80bea0b --- /dev/null +++ b/frontend/archive/src/components/ui/Label.tsx @@ -0,0 +1,25 @@ +import React, {FC, ReactNode} from "react"; + +interface LabelProps { + href?: string; + children: ReactNode; + onClick?: () => void; +} + +const Label: FC = ({ href, children, onClick }) => { + const styles = "items-center space-x-1 background-accent p-2 rounded" + + if (href !== undefined) { + return ( +
+ { children} +
+ ) + } + + return +} + +export default Label \ No newline at end of file diff --git a/frontend/src/components/ui/Modal.tsx b/frontend/archive/src/components/ui/Modal.tsx similarity index 100% rename from frontend/src/components/ui/Modal.tsx rename to frontend/archive/src/components/ui/Modal.tsx diff --git a/frontend/src/components/ui/PageTitle.tsx b/frontend/archive/src/components/ui/PageTitle.tsx similarity index 95% rename from frontend/src/components/ui/PageTitle.tsx rename to frontend/archive/src/components/ui/PageTitle.tsx index abeafa1..ca65f18 100644 --- a/frontend/src/components/ui/PageTitle.tsx +++ b/frontend/archive/src/components/ui/PageTitle.tsx @@ -8,7 +8,7 @@ interface Props { const PageTitle: FC = ({ children, className }) => { const styles = classNames( - 'ml-4 text-2xl font-default uppercase w-full text-accent font-bold', + 'ml-4 text-2xl font-default uppercase w-full text-accent-blue font-bold', className, ) diff --git a/frontend/archive/src/components/ui/RecurrenceInput.tsx b/frontend/archive/src/components/ui/RecurrenceInput.tsx new file mode 100644 index 0000000..756715d --- /dev/null +++ b/frontend/archive/src/components/ui/RecurrenceInput.tsx @@ -0,0 +1,65 @@ +import React, {FC, useState} from "react"; + +interface Props { + value: number; + setValue: (value: number) => void; +} + +const RecurrenceInput: FC = ({ value, setValue}) => { + const [openInput, setOpenInput] = useState<'category' | 'number'>([7, 365].includes(value) ? 'category' : 'number') + + const toggleInput = (e: React.MouseEvent) => { + e.preventDefault() + setOpenInput(openInput == 'category' ? 'number' : 'category') + } + + const toggleButton = () => { + return ( + + ) + } + + const prepareValue = (v: string) => { + setValue(parseInt(v)) + } + + return ( +
+
+ + + { toggleButton() } +
+ +
+ + prepareValue(e.target.value)} + className="p-2 border rounded w-full bg-gray-500 border-secondary" + /> + { toggleButton() } +
+
+ ) +} + +export default RecurrenceInput \ No newline at end of file diff --git a/frontend/archive/src/components/ui/SectionTitle.tsx b/frontend/archive/src/components/ui/SectionTitle.tsx new file mode 100644 index 0000000..93d13c4 --- /dev/null +++ b/frontend/archive/src/components/ui/SectionTitle.tsx @@ -0,0 +1,16 @@ +import classNames from "classnames"; + +interface Props { + children: string; + className?: string; +} + +const SectionTitle = ({ children, className }: Props) => { + const style = classNames("block font-size-18 uppercase w-full pl-2 text-accent-blue", + className + ) + + return

{ children }

+} + +export default SectionTitle \ No newline at end of file diff --git a/frontend/archive/src/components/ui/Toggle.tsx b/frontend/archive/src/components/ui/Toggle.tsx new file mode 100644 index 0000000..786b093 --- /dev/null +++ b/frontend/archive/src/components/ui/Toggle.tsx @@ -0,0 +1,42 @@ +import {FC} from "react"; + +interface ToggleProps { + checked: boolean; + onChange: (checked: boolean) => void; +} + +const Toggle: FC = ({ checked, onChange }) => { + const handleChange = () => { + onChange(checked); + } + + return ( + + ); +}; + +export default Toggle; \ No newline at end of file diff --git a/frontend/archive/src/context/AuthContext.tsx b/frontend/archive/src/context/AuthContext.tsx new file mode 100644 index 0000000..8d77eb3 --- /dev/null +++ b/frontend/archive/src/context/AuthContext.tsx @@ -0,0 +1,46 @@ +"use client" + +import React, { createContext, useContext, useEffect, useState } from 'react'; + +interface AuthContextProps { + isAuthenticated: boolean | null; + login: () => void; + logout: () => void; +} + +const AuthContext = createContext({ + isAuthenticated: null, + login: () => {}, + logout: () => {}, +}); + +export const AuthProvider = ({ children }: { children: React.ReactNode }) => { + const [isAuthenticated, setIsAuthenticated] = useState(null); + + useEffect(() => { + const token = localStorage.getItem('token'); + if (token) { + // You could add any token validation logic here + setIsAuthenticated(true); + } else { + setIsAuthenticated(false); + } + }, []); + + const login = () => { + setIsAuthenticated(true); + }; + + const logout = () => { + setIsAuthenticated(false); + localStorage.removeItem('token'); + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); \ No newline at end of file diff --git a/frontend/archive/src/helpers/Date.ts b/frontend/archive/src/helpers/Date.ts new file mode 100644 index 0000000..f183c86 --- /dev/null +++ b/frontend/archive/src/helpers/Date.ts @@ -0,0 +1,18 @@ +import { DateTime } from 'luxon'; + +// Validate if a given string matches the "yyyy-MM-dd" format and is a valid date +export const isValidDate = (date: string): boolean => { + const parsedDate = DateTime.fromFormat(date, 'yyyy-MM-dd'); + return parsedDate.isValid && parsedDate.toFormat('yyyy-MM-dd') === date; +}; + +// Format a date to a specific string format +export const formatDate = (date: Date | string, format: string = 'yyyy-MM-dd'): string => { + const parsedDate = typeof date === 'string' ? DateTime.fromISO(date) : DateTime.fromJSDate(date); + return parsedDate.toFormat(format); +}; + +// Compare two dates to see if one is before the other +export const isBefore = (date1: string, date2: string): boolean => { + return DateTime.fromISO(date1) < DateTime.fromISO(date2); +}; diff --git a/frontend/archive/src/hooks/useFetchDishes.ts b/frontend/archive/src/hooks/useFetchDishes.ts new file mode 100644 index 0000000..a7ab747 --- /dev/null +++ b/frontend/archive/src/hooks/useFetchDishes.ts @@ -0,0 +1,22 @@ +import { useState, useEffect } from "react"; +import { listDishes } from "@/utils/api/dishApi" +import { DishType } from "@/types/DishType" + +export const useFetchDishes = () => { + const [dishes, setDishes] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchDishes = async () => { + listDishes() + .then((dishes: DishType[]) => setDishes(dishes)) + .catch((err) => setError((err as Error).message || "An error occurred.")) + .finally(() => setIsLoading(false)); + }; + + fetchDishes(); + }, []); + + return { dishes, isLoading, error }; +}; \ No newline at end of file diff --git a/frontend/archive/src/hooks/useFetchUsers.ts b/frontend/archive/src/hooks/useFetchUsers.ts new file mode 100644 index 0000000..f6df05b --- /dev/null +++ b/frontend/archive/src/hooks/useFetchUsers.ts @@ -0,0 +1,22 @@ +import { useState, useEffect } from "react"; +import {UserType} from "@/types/UserType"; +import {listUsers} from "@/utils/api/usersApi"; + +export const useFetchUsers = () => { + const [users, setUsers] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchUsers = async () => { + listUsers() + .then((users: UserType[]) => setUsers(users)) + .catch((err) => setError((err as Error).message || "An error occurred.")) + .finally(() => setIsLoading(false)); + }; + + fetchUsers(); + }, []); + + return { users, isLoading, error }; +}; \ No newline at end of file diff --git a/frontend/src/hooks/useRoutes.ts b/frontend/archive/src/hooks/useRoutes.ts similarity index 100% rename from frontend/src/hooks/useRoutes.ts rename to frontend/archive/src/hooks/useRoutes.ts diff --git a/frontend/archive/src/styles/base/globals.css b/frontend/archive/src/styles/base/globals.css new file mode 100644 index 0000000..918cbfb --- /dev/null +++ b/frontend/archive/src/styles/base/globals.css @@ -0,0 +1,19 @@ +html, body { + margin: 0; + padding: 0; + width: 100%; + overflow-x: hidden; +} + +body { + font-family: Arial, Helvetica, sans-serif; +} + + +.toggle-input:checked { + background-color: #22c55e; /* bg-green-500 */ +} + +.toggle-input:checked ~ span:last-child { + --tw-translate-x: 1.75rem; /* translate-x-7 */ +} \ No newline at end of file diff --git a/frontend/archive/src/styles/components/buttons.css b/frontend/archive/src/styles/components/buttons.css new file mode 100644 index 0000000..07fc4de --- /dev/null +++ b/frontend/archive/src/styles/components/buttons.css @@ -0,0 +1,42 @@ +.button-primary-solid { + background-color: var(--color-primary); + color: var(--color-secondary-200); + border: 1px solid var(--color-primary); + text-transform: uppercase; + font-family: "Anta", serif; + font-style: normal; + font-size: 1.1rem; + font-weight: 600; + padding: 4px 16px 2px 16px; +} +.button-primary-outline { + background-color: var(--color-background); + color: var(--color-primary); + border: 1px solid var(--color-primary); + text-transform: uppercase; + font-family: "Anta", serif; + font-style: normal; + font-size: 1.1rem; + font-weight: 600; + padding: 4px 16px 2px 16px; +} + +.button-secondary-solid { + background-color: var(--color-secondary); + color: var(--color-primary); + border: 1px solid var(--color-secondary); +} + +.button-accent-solid { + background-color: var(--color-accent-blue); + color: var(--color-secondary-900); + border: 1px solid var(--color-accent-blue); +} +.button-accent-outline { + background-color: var(--color-background); + color: var(--color-accent-blue); + border: 1px solid var(--color-accent-blue); +} +.button-accent-outline:hover { + background-color: var(--color-background-400); +} \ No newline at end of file diff --git a/frontend/archive/src/styles/components/select.css b/frontend/archive/src/styles/components/select.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/styles/main.css b/frontend/archive/src/styles/main.css similarity index 100% rename from frontend/src/styles/main.css rename to frontend/archive/src/styles/main.css diff --git a/frontend/archive/src/styles/theme/borders.css b/frontend/archive/src/styles/theme/borders.css new file mode 100644 index 0000000..b4ed5db --- /dev/null +++ b/frontend/archive/src/styles/theme/borders.css @@ -0,0 +1,14 @@ +.border-primary { + border-color: var(--color-primary); +} + +.border-secondary { + border-color: var(--color-secondary); +} + +.border-accent-blue { + border-color: var(--color-accent-blue); +} +.border-accent-800 { + border-color: var(--color-accent-blue-800); +} \ No newline at end of file diff --git a/frontend/src/styles/theme/colors.css b/frontend/archive/src/styles/theme/colors.css similarity index 100% rename from frontend/src/styles/theme/colors.css rename to frontend/archive/src/styles/theme/colors.css diff --git a/frontend/archive/src/styles/theme/colors/background.css b/frontend/archive/src/styles/theme/colors/background.css new file mode 100644 index 0000000..e8548f5 --- /dev/null +++ b/frontend/archive/src/styles/theme/colors/background.css @@ -0,0 +1,226 @@ +.bg-gray-100 { + background-color: var(--color-gray-100) !important; +} +.bg-gray-200 { + background-color: var(--color-gray-200) !important; +} +.bg-gray-300 { + background-color: var(--color-gray-300) !important; +} +.bg-gray-400 { + background-color: var(--color-gray-400) !important; +} +.bg-gray-500 { + background-color: var(--color-gray-500) !important; +} +.bg-gray-600 { + background-color: var(--color-gray-600) !important; +} +.bg-gray-700 { + background-color: var(--color-gray-700) !important; +} +.bg-gray-800 { + background-color: var(--color-gray-800) !important; +} +.bg-gray-900 { + background-color: var(--color-gray-900) !important; +} + + +.bg-primary { + background-color: var(--color-primary) !important; +} + + +.bg-accent-blue { + background-color: var(--color-accent-blue-500) !important; +} +.bg-accent-blue-100 { + background-color: var(--color-accent-blue-100) !important; +} +.bg-accent-blue-200 { + background-color: var(--color-accent-blue-200) !important; +} +.bg-accent-blue-300 { + background-color: var(--color-accent-blue-300) !important; +} +.bg-accent-blue-400 { + background-color: var(--color-accent-blue-400) !important; +} +.bg-accent-blue-500 { + background-color: var(--color-accent-blue-500) !important; +} +.bg-accent-blue-600 { + background-color: var(--color-accent-blue-600) !important; +} +.bg-accent-blue-700 { + background-color: var(--color-accent-blue-700) !important; +} +.bg-accent-blue-800 { + background-color: var(--color-accent-blue-800) !important; +} +.bg-accent-blue-900 { + background-color: var(--color-accent-blue-900) !important; +} + + +.bg-accent-yellow { + background-color: var(--color-accent-yellow) !important; +} + +.bg-accent-yellow-100 { + background-color: var(--color-accent-yellow-100) !important; +} + +.bg-accent-yellow-200 { + background-color: var(--color-accent-yellow-200) !important; +} + +.bg-accent-yellow-300 { + background-color: var(--color-accent-yellow-300) !important; +} + +.bg-accent-yellow-400 { + background-color: var(--color-accent-yellow-400) !important; +} + +.bg-accent-yellow-500 { + background-color: var(--color-accent-yellow-500) !important; +} + +.bg-accent-yellow-600 { + background-color: var(--color-accent-yellow-600) !important; +} + +.bg-accent-yellow-700 { + background-color: var(--color-accent-yellow-700) !important; +} + +.bg-accent-yellow-800 { + background-color: var(--color-accent-yellow-800) !important; +} + +.bg-accent-yellow-900 { + background-color: var(--color-accent-yellow-900) !important; +} + + +.bg-success { + background-color: var(--color-success) !important; +} + +.bg-success-100 { + background-color: var(--color-success-100) !important; +} + +.bg-success-200 { + background-color: var(--color-success-200) !important; +} + +.bg-success-300 { + background-color: var(--color-success-300) !important; +} + +.bg-success-400 { + background-color: var(--color-success-400) !important; +} + +.bg-success-500 { + background-color: var(--color-success-500) !important; +} + +.bg-success-600 { + background-color: var(--color-success-600) !important; +} + +.bg-success-700 { + background-color: var(--color-success-700) !important; +} + +.bg-success-800 { + background-color: var(--color-success-800) !important; +} + +.bg-success-900 { + background-color: var(--color-success-900) !important; +} + +.bg-warning { + background-color: var(--color-warning) !important; +} + +.bg-warning-100 { + background-color: var(--color-warning-100) !important; +} + +.bg-warning-200 { + background-color: var(--color-warning-200) !important; +} + +.bg-warning-300 { + background-color: var(--color-warning-300) !important; +} + +.bg-warning-400 { + background-color: var(--color-warning-400) !important; +} + +.bg-warning-500 { + background-color: var(--color-warning-500) !important; +} + +.bg-warning-600 { + background-color: var(--color-warning-600) !important; +} + +.bg-warning-700 { + background-color: var(--color-warning-700) !important; +} + +.bg-warning-800 { + background-color: var(--color-warning-800) !important; +} + +.bg-warning-900 { + background-color: var(--color-warning-900) !important; +} + +.bg-danger { + background-color: var(--color-danger) !important; +} + +.bg-danger-100 { + background-color: var(--color-danger-100) !important; +} + +.bg-danger-200 { + background-color: var(--color-danger-200) !important; +} + +.bg-danger-300 { + background-color: var(--color-danger-300) !important; +} + +.bg-danger-400 { + background-color: var(--color-danger-400) !important; +} + +.bg-danger-500 { + background-color: var(--color-danger-500) !important; +} + +.bg-danger-600 { + background-color: var(--color-danger-600) !important; +} + +.bg-danger-700 { + background-color: var(--color-danger-700) !important; +} + +.bg-danger-800 { + background-color: var(--color-danger-800) !important; +} + +.bg-danger-900 { + background-color: var(--color-danger-900) !important; +} \ No newline at end of file diff --git a/frontend/archive/src/styles/theme/colors/border.css b/frontend/archive/src/styles/theme/colors/border.css new file mode 100644 index 0000000..8975296 --- /dev/null +++ b/frontend/archive/src/styles/theme/colors/border.css @@ -0,0 +1,286 @@ +.border-primary { + border-color: var(--color-primary); +} + +.border-primary-100 { + border-color: var(--color-primary-100); +} + +.border-primary-200 { + border-color: var(--color-primary-200); +} + +.border-primary-300 { + border-color: var(--color-primary-300); +} + +.border-primary-400 { + border-color: var(--color-primary-400); +} + +.border-primary-500 { + border-color: var(--color-primary-500); +} + +.border-primary-600 { + border-color: var(--color-primary-600); +} + +.border-primary-700 { + border-color: var(--color-primary-700); +} + +.border-primary-800 { + border-color: var(--color-primary-800); +} + +.border-primary-900 { + border-color: var(--color-primary-900); +} + + +.border-secondary { + border-color: var(--color-secondary); +} + +.border-secondary-100 { + border-color: var(--color-secondary-100); +} + +.border-secondary-200 { + border-color: var(--color-secondary-200); +} + +.border-secondary-300 { + border-color: var(--color-secondary-300); +} + +.border-secondary-400 { + border-color: var(--color-secondary-400); +} + +.border-secondary-500 { + border-color: var(--color-secondary-500); +} + +.border-secondary-600 { + border-color: var(--color-secondary-600); +} + +.border-secondary-700 { + border-color: var(--color-secondary-700); +} + +.border-secondary-800 { + border-color: var(--color-secondary-800); +} + +.border-secondary-900 { + border-color: var(--color-secondary-900); +} + +.border-accent-blue { + border-color: var(--color-accent-blue); +} + +.border-accent-blue-100 { + border-color: var(--color-accent-blue-100); +} + +.border-accent-blue-200 { + border-color: var(--color-accent-blue-200); +} + +.border-accent-blue-300 { + border-color: var(--color-accent-blue-300); +} + +.border-accent-blue-400 { + border-color: var(--color-accent-blue-400); +} + +.border-accent-blue-500 { + border-color: var(--color-accent-blue-500); +} + +.border-accent-blue-600 { + border-color: var(--color-accent-blue-600); +} + +.border-accent-blue-700 { + border-color: var(--color-accent-blue-700); +} + +.border-accent-blue-800 { + border-color: var(--color-accent-blue-800); +} + +.border-accent-blue-900 { + border-color: var(--color-accent-blue-900); +} + + +.border-accent-yellow { + border-color: var(--color-accent-yellow); +} + +.border-accent-yellow-100 { + border-color: var(--color-accent-yellow-100); +} + +.border-accent-yellow-200 { + border-color: var(--color-accent-yellow-200); +} + +.border-accent-yellow-300 { + border-color: var(--color-accent-yellow-300); +} + +.border-accent-yellow-400 { + border-color: var(--color-accent-yellow-400); +} + +.border-accent-yellow-500 { + border-color: var(--color-accent-yellow-500); +} + +.border-accent-yellow-600 { + border-color: var(--color-accent-yellow-600); +} + +.border-accent-yellow-700 { + border-color: var(--color-accent-yellow-700); +} + +.border-accent-yellow-800 { + border-color: var(--color-accent-yellow-800); +} + +.border-accent-yellow-900 { + border-color: var(--color-accent-yellow-900); +} + + +.border-background { + border-color: var(--color-background) !important; +} + +.border-danger { + border-color: var(--color-danger); +} + +.border-danger-100 { + border-color: var(--color-danger-100); +} + +.border-danger-200 { + border-color: var(--color-danger-200); +} + +.border-danger-300 { + border-color: var(--color-danger-300); +} + +.border-danger-400 { + border-color: var(--color-danger-400); +} + +.border-danger-500 { + border-color: var(--color-danger-500); +} + +.border-danger-600 { + border-color: var(--color-danger-600); +} + +.border-danger-700 { + border-color: var(--color-danger-700); +} + +.border-danger-800 { + border-color: var(--color-danger-800); +} + +.border-danger-900 { + border-color: var(--color-danger-900); +} + +.border-success { + border-color: var(--color-success); +} + +.border-success-100 { + border-color: var(--color-success-100); +} + +.border-success-200 { + border-color: var(--color-success-200); +} + +.border-success-300 { + border-color: var(--color-success-300); +} + +.border-success-400 { + border-color: var(--color-success-400); +} + +.border-success-500 { + border-color: var(--color-success-500); +} + +.border-success-600 { + border-color: var(--color-success-600); +} + +.border-success-700 { + border-color: var(--color-success-700); +} + +.border-success-800 { + border-color: var(--color-success-800); +} + +.border-success-900 { + border-color: var(--color-success-900); +} + +.border-warning { + border-color: var(--color-warning); +} + +.border-warning-100 { + border-color: var(--color-warning-100); +} + +.border-warning-200 { + border-color: var(--color-warning-200); +} + +.border-warning-300 { + border-color: var(--color-warning-300); +} + +.border-warning-400 { + border-color: var(--color-warning-400); +} + +.border-warning-500 { + border-color: var(--color-warning-500); +} + +.border-warning-600 { + border-color: var(--color-warning-600); +} + +.border-warning-700 { + border-color: var(--color-warning-700); +} + +.border-warning-800 { + border-color: var(--color-warning-800); +} + +.border-warning-900 { + border-color: var(--color-warning-900); +} \ No newline at end of file diff --git a/frontend/archive/src/styles/theme/colors/root.css b/frontend/archive/src/styles/theme/colors/root.css new file mode 100644 index 0000000..acffac0 --- /dev/null +++ b/frontend/archive/src/styles/theme/colors/root.css @@ -0,0 +1,193 @@ +:root { + --color-rose-50: #FFF5FC; + --color-rose-100: #FCE6F5; + --color-rose-200: #FAC3E7; + --color-rose-300: #F7A1D5; + --color-rose-400: #F25EAB; + --color-rose-500: #ED1F79; + --color-rose-600: #D61A68; + --color-rose-700: #B3124F; + --color-rose-800: #8F0B39; + --color-rose-900: #6B0626; + --color-rose-950: #450315; + + --color-deluge-50: #FAF7FC; + --color-deluge-100: #F2EDF7; + --color-deluge-200: #E2DAF0; + --color-deluge-300: #CEC3E6; + --color-deluge-400: #A49BD1; + --color-deluge-500: #7776BC; + --color-deluge-600: #6361AB; + --color-deluge-700: #43428C; + --color-deluge-800: #2C2B70; + --color-deluge-900: #191854; + --color-deluge-950: #0A0A36; + + --color-malibu-50: #FAFEFF; + --color-malibu-100: #F5FDFF; + --color-malibu-200: #E1F6FC; + --color-malibu-300: #CDEDFA; + --color-malibu-400: #ABDEF7; + --color-malibu-500: #85C7F2; + --color-malibu-600: #6EACDB; + --color-malibu-700: #4A81B5; + --color-malibu-800: #305F91; + --color-malibu-900: #1B3F6E; + --color-malibu-950: #0B2247; + + --color-gamboge-50: #FFFDF2; + --color-gamboge-100: #FCF7E3; + --color-gamboge-200: #FAECBB; + --color-gamboge-300: #F5DC93; + --color-gamboge-400: #EDBB47; + --color-gamboge-500: #E59500; + --color-gamboge-600: #CF7F00; + --color-gamboge-700: #AB6100; + --color-gamboge-800: #8A4700; + --color-gamboge-900: #663000; + --color-gamboge-950: #421C00; + + --color-ebony-clay-100: #9AA2B3; /* Soft slate */ + --color-ebony-clay-200: #7A8093; /* Balanced midtone */ + --color-ebony-clay-300: #5D637A; /* Former 400 */ + --color-ebony-clay-400: #444760; /* New shadowed steel */ + --color-ebony-clay-500: #2B2C41; + --color-ebony-clay-600: #24263C; /* Adjusted — less jumpy */ + --color-ebony-clay-700: #1D1E36; /* Interpolated midpoint */ + --color-ebony-clay-800: #131427; /* Slightly lifted from old 800 */ + --color-ebony-clay-900: #0A0B1C; + --color-ebony-clay-950: #030412; + + --color-alizarin-crimson-50: #FFF5FA; + --color-alizarin-crimson-100: #FCE6F1; + --color-alizarin-crimson-200: #FAC3DC; + --color-alizarin-crimson-300: #F59FC0; + --color-alizarin-crimson-400: #F05D82; + --color-alizarin-crimson-500: #E71D36; + --color-alizarin-crimson-600: #D1192F; + --color-alizarin-crimson-700: #AD1121; + --color-alizarin-crimson-800: #8C0B18; + --color-alizarin-crimson-900: #69060E; + --color-alizarin-crimson-950: #420308; + + --color-spring-green-50: #F5FFFC; + --color-spring-green-100: #E8FFF9; + --color-spring-green-200: #C7FFEE; + --color-spring-green-300: #A4FCDF; + --color-spring-green-400: #62FCBC; + --color-spring-green-500: #21FA90; + --color-spring-green-600: #1BE07A; + --color-spring-green-700: #13BA5E; + --color-spring-green-800: #0C9646; + --color-spring-green-900: #07702D; + --color-spring-green-950: #03471A; + + --color-burning-orange-50: #FFFBF5; + --color-burning-orange-100: #FFF7EB; + --color-burning-orange-200: #FFE8CC; + --color-burning-orange-300: #FFD5AD; + --color-burning-orange-400: #FFA973; + --color-burning-orange-500: #FF6B35; + --color-burning-orange-600: #E65A2C; + --color-burning-orange-700: #BF441F; + --color-burning-orange-800: #993114; + --color-burning-orange-900: #731F0A; + --color-burning-orange-950: #4A1004; + + /* Standard naming */ + + --color-primary: var(--color-rose-500); + --color-primary-100: var(--color-rose-100); + --color-primary-200: var(--color-rose-200); + --color-primary-300: var(--color-rose-300); + --color-primary-400: var(--color-rose-400); + --color-primary-500: var(--color-rose-500); + --color-primary-600: var(--color-rose-600); + --color-primary-700: var(--color-rose-700); + --color-primary-800: var(--color-rose-800); + --color-primary-900: var(--color-rose-900); + + --color-secondary: var(--color-deluge-500); + --color-secondary-100: var(--color-deluge-100); + --color-secondary-200: var(--color-deluge-200); + --color-secondary-300: var(--color-deluge-300); + --color-secondary-400: var(--color-deluge-400); + --color-secondary-500: var(--color-deluge-500); + --color-secondary-600: var(--color-deluge-600); + --color-secondary-700: var(--color-deluge-700); + --color-secondary-800: var(--color-deluge-800); + --color-secondary-900: var(--color-deluge-900); + + --color-accent-blue: var(--color-malibu-500); + --color-accent-blue-100: var(--color-malibu-100); + --color-accent-blue-200: var(--color-malibu-200); + --color-accent-blue-300: var(--color-malibu-300); + --color-accent-blue-400: var(--color-malibu-400); + --color-accent-blue-500: var(--color-malibu-500); + --color-accent-blue-600: var(--color-malibu-600); + --color-accent-blue-700: var(--color-malibu-700); + --color-accent-blue-800: var(--color-malibu-800); + --color-accent-blue-900: var(--color-malibu-900); + + --color-accent-yellow: var(--color-gamboge-500); + --color-accent-yellow-50: var(--color-gamboge-50); + --color-accent-yellow-100: var(--color-gamboge-100); + --color-accent-yellow-200: var(--color-gamboge-200); + --color-accent-yellow-300: var(--color-gamboge-300); + --color-accent-yellow-400: var(--color-gamboge-400); + --color-accent-yellow-500: var(--color-gamboge-500); + --color-accent-yellow-600: var(--color-gamboge-600); + --color-accent-yellow-700: var(--color-gamboge-700); + --color-accent-yellow-800: var(--color-gamboge-800); + --color-accent-yellow-900: var(--color-gamboge-900); + --color-accent-yellow-950: var(--color-gamboge-950); + + --color-gray-100: var(--color-ebony-clay-100); + --color-gray-200: var(--color-ebony-clay-200); + --color-gray-300: var(--color-ebony-clay-300); + --color-gray-400: var(--color-ebony-clay-400); + --color-gray-500: var(--color-ebony-clay-500); + --color-gray-600: var(--color-ebony-clay-600); + --color-gray-700: var(--color-ebony-clay-700); + --color-gray-800: var(--color-ebony-clay-800); + --color-gray-900: var(--color-ebony-clay-900); + + --color-danger: var(--color-alizarin-crimson-500); + --color-danger-50: var(--color-alizarin-crimson-50); + --color-danger-100: var(--color-alizarin-crimson-100); + --color-danger-200: var(--color-alizarin-crimson-200); + --color-danger-300: var(--color-alizarin-crimson-300); + --color-danger-400: var(--color-alizarin-crimson-400); + --color-danger-500: var(--color-alizarin-crimson-500); + --color-danger-600: var(--color-alizarin-crimson-600); + --color-danger-700: var(--color-alizarin-crimson-700); + --color-danger-800: var(--color-alizarin-crimson-800); + --color-danger-900: var(--color-alizarin-crimson-900); + --color-danger-950: var(--color-alizarin-crimson-950); + + --color-success: var(--color-spring-green-500); + --color-success-50: var(--color-spring-green-50); + --color-success-100: var(--color-spring-green-100); + --color-success-200: var(--color-spring-green-200); + --color-success-300: var(--color-spring-green-300); + --color-success-400: var(--color-spring-green-400); + --color-success-500: var(--color-spring-green-500); + --color-success-600: var(--color-spring-green-600); + --color-success-700: var(--color-spring-green-700); + --color-success-800: var(--color-spring-green-800); + --color-success-900: var(--color-spring-green-900); + --color-success-950: var(--color-spring-green-950); + + --color-warning: var(--color-burning-orange-500); + --color-warning-50: var(--color-burning-orange-50); + --color-warning-100: var(--color-burning-orange-100); + --color-warning-200: var(--color-burning-orange-200); + --color-warning-300: var(--color-burning-orange-300); + --color-warning-400: var(--color-burning-orange-400); + --color-warning-500: var(--color-burning-orange-500); + --color-warning-600: var(--color-burning-orange-600); + --color-warning-700: var(--color-burning-orange-700); + --color-warning-800: var(--color-burning-orange-800); + --color-warning-900: var(--color-burning-orange-900); + --color-warning-950: var(--color-burning-orange-950); +} diff --git a/frontend/src/styles/theme/colors/text.css b/frontend/archive/src/styles/theme/colors/text.css similarity index 100% rename from frontend/src/styles/theme/colors/text.css rename to frontend/archive/src/styles/theme/colors/text.css diff --git a/frontend/src/styles/theme/fonts.css b/frontend/archive/src/styles/theme/fonts.css similarity index 100% rename from frontend/src/styles/theme/fonts.css rename to frontend/archive/src/styles/theme/fonts.css diff --git a/frontend/src/types/DishType.ts b/frontend/archive/src/types/DishType.ts similarity index 100% rename from frontend/src/types/DishType.ts rename to frontend/archive/src/types/DishType.ts diff --git a/frontend/src/types/ScheduleType.ts b/frontend/archive/src/types/ScheduleType.ts similarity index 100% rename from frontend/src/types/ScheduleType.ts rename to frontend/archive/src/types/ScheduleType.ts diff --git a/frontend/src/types/ScheduledUserDishType.ts b/frontend/archive/src/types/ScheduledUserDishType.ts similarity index 100% rename from frontend/src/types/ScheduledUserDishType.ts rename to frontend/archive/src/types/ScheduledUserDishType.ts diff --git a/frontend/archive/src/types/UserDishType.ts b/frontend/archive/src/types/UserDishType.ts new file mode 100644 index 0000000..32f9dfc --- /dev/null +++ b/frontend/archive/src/types/UserDishType.ts @@ -0,0 +1,8 @@ +import {UserType} from "@/types/UserType"; +import {RecurrenceType} from "@/types/ScheduleType"; + +export type DishType = { + user: UserType; + dish: DishType; + recurrences: RecurrenceType[]; +} \ No newline at end of file diff --git a/frontend/src/types/UserType.ts b/frontend/archive/src/types/UserType.ts similarity index 100% rename from frontend/src/types/UserType.ts rename to frontend/archive/src/types/UserType.ts diff --git a/frontend/src/utils/api/apiRequest.ts b/frontend/archive/src/utils/api/apiRequest.ts similarity index 100% rename from frontend/src/utils/api/apiRequest.ts rename to frontend/archive/src/utils/api/apiRequest.ts diff --git a/frontend/archive/src/utils/api/auth.ts b/frontend/archive/src/utils/api/auth.ts new file mode 100644 index 0000000..f4511aa --- /dev/null +++ b/frontend/archive/src/utils/api/auth.ts @@ -0,0 +1,28 @@ +import { apiRequest } from '@/utils/api/apiRequest'; + +export const login = async (email: string, password: string) => { + const data = await apiRequest.post('/api/auth/login', { email, password }); + + if (!data.access_token) { + throw new Error('No access token returned from login.'); + } + + localStorage.setItem('token', data.access_token); + + return data; +}; + + +export const register = async (name: string, email: string, password: string, passwordConfirmation: string) => { + const data = await apiRequest.post('/api/auth/register', { + name, + email, + password, + password_confirmation: passwordConfirmation, // Match the backend's expected parameter + }); + + // Store the token (if returned by the backend) similarly to login + localStorage.setItem('token', data.access_token); + + return data; +}; diff --git a/frontend/archive/src/utils/api/dishApi.ts b/frontend/archive/src/utils/api/dishApi.ts new file mode 100644 index 0000000..8d38dba --- /dev/null +++ b/frontend/archive/src/utils/api/dishApi.ts @@ -0,0 +1,149 @@ +import {DishType} from "@/types/DishType"; +import {apiRequest} from "@/utils/api/apiRequest"; + +export const listDishes = async (): Promise => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(`/api/dishes`, { + headers: { + Authorization: `Bearer ${token}`, + }}) + .then((data) => { + if (data?.payload?.dishes) { + return data.payload.dishes as DishType[]; + } + throw new Error('SOMETHING WENT WRONG'); + }) + .catch((error) => { + throw error; + }); +}; + +export const fetchDish = async (id: number): Promise => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(`/api/dishes/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }}) + .then((data) => { + if (data?.payload?.dish) { + return data.payload.dish as DishType; + } + throw new Error('SOMETHING WENT WRONG'); + }) + .catch((error) => { + throw error; + }); +}; + +export const createDish = async ( + name: string, + // recurrence: number, + // userIds: number[] +) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.post(`/api/dishes`, { + name, + // recurrence, + // users: userIds, + }, { + headers: { + Authorization: `Bearer ${token}`, + }, + }).catch(() => { + throw new Error("Failed to create dish. Please try again later."); + }); +}; + +export const updateDish = async (dish_id: number, name: string) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.put(`/api/dishes/${dish_id}`, {name}, { + headers: { + Authorization: `Bearer ${token}`, + }}) + .catch((error) => { + throw error; + }); +}; + +export const deleteDish = async (id: number) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.delete(`/api/dishes/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + }}) + .then((data) => { + return data; + }) + .catch((error) => { + throw error; + }); +}; + +export const addUserToDish = async (dish_id: number, user_id: number) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.post(`/api/dishes/${dish_id}/users/add`, { + users: [user_id], + }, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((data) => { + return data; + }) + .catch((error) => { + throw error; + }); +}; + +export const removeUserFromDish = async (dish_id: number, user_id: number) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.post(`/api/dishes/${dish_id}/users/remove`, { + users: [user_id], + }, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((data) => { + return data; + }) + .catch((error) => { + throw error; + }); +}; diff --git a/frontend/archive/src/utils/api/scheduleApi.ts b/frontend/archive/src/utils/api/scheduleApi.ts new file mode 100644 index 0000000..d7fcbf3 --- /dev/null +++ b/frontend/archive/src/utils/api/scheduleApi.ts @@ -0,0 +1,112 @@ +import { apiRequest } from "@/utils/api/apiRequest"; +import { isValidDate } from "@/helpers/Date"; + +export const listSchedule = async (startDate?: string, endDate?: string) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + if (startDate && !isValidDate(startDate)) { + throw new Error('Invalid start date'); + } + if (endDate && !isValidDate(endDate)) { + throw new Error('Invalid end date'); + } + + const params = new URLSearchParams(); + if (startDate) params.append('start', startDate); + if (endDate) params.append('end', endDate); + + const endpoint = `/api/schedule${params.toString() ? `?${params.toString()}` : ''}`; + + return apiRequest.get(endpoint, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduleData) => scheduleData?.payload?.schedule); +}; + +export const getScheduleForDate = async (date: string) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + if (!isValidDate(date)) { + throw new Error('Invalid date'); + } + + const endpoint = `/api/schedule/${date}`; + + return apiRequest.get(endpoint, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduleData) => scheduleData?.payload?.schedule) + .catch((err) => { + throw err; + }); +}; + +// Update the schedule for a specific date (e.g., mark as skipped) +export const updateScheduleForDate = async (date: string, isSkipped: boolean) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + if (!isValidDate(date)) { + throw new Error('Invalid date'); + } + + const endpoint = `/api/schedule/${date}`; + + return apiRequest.put(endpoint, { is_skipped: isSkipped }, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduleData) => scheduleData?.payload?.schedule) + .catch((err) => { + throw err; + }); +}; + +// Generate a new schedule (optional: overwrite the existing one) +export const generateSchedule = async (overwrite: boolean) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + const endpoint = `/api/schedule/generate`; + + return apiRequest.post(endpoint, { overwrite }, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduleData) => scheduleData?.payload?.schedule) + .catch((err) => { + throw err; + }); +}; + +export const scheduleUserDish = async (date: string, user_id: number, user_dish_id: number|null, skipped: boolean = false) => { + const token = localStorage.getItem('token'); + + if (!token) throw new Error('No token found in localStorage.'); + + const endpoint = `/api/schedule/${date}/user-dishes`; + + return apiRequest.post(endpoint, { user_dish_id, user_id, skipped }, { headers: { Authorization: `Bearer ${token}`} }) + .then((scheduleData) => scheduleData?.payload?.schedule) + .catch((err) => { throw err }); +}; \ No newline at end of file diff --git a/frontend/archive/src/utils/api/scheduledUserDishesApi.ts b/frontend/archive/src/utils/api/scheduledUserDishesApi.ts new file mode 100644 index 0000000..8a9b66c --- /dev/null +++ b/frontend/archive/src/utils/api/scheduledUserDishesApi.ts @@ -0,0 +1,77 @@ +import { apiRequest } from "@/utils/api/apiRequest"; + +export const listScheduledUserDishesStartingFromDate = async (startDate: string) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(`/api/scheduled-user-dishes?start=${startDate}`, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduledDishesData) => scheduledDishesData?.payload?.schedule) + .catch((err) => { + throw err; + }); +}; + +export const listScheduledUserDishesEndingAtDate = async (endDate: string) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(`/api/scheduled-user-dishes?end=${endDate}`, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduledDishesData) => scheduledDishesData?.payload?.schedule) + .catch((err) => { + throw err; + }); +}; + +export const getScheduledUserDish = async (id: number) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(`/api/scheduled-user-dishes/${id}`, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduledDishesData) => scheduledDishesData?.payload?.scheduled_user_dish) + .catch((err) => { + throw err; + }); +}; + +export const updateScheduledUserDish = async (id: number, userDishId: number) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + const payload = userDishId > 0 + ? { user_dish_id: userDishId } + : { user_dish_id: null, is_skipped: true }; + + return apiRequest.put(`/api/scheduled-user-dishes/${id}`, payload, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((scheduledDishesData) => scheduledDishesData?.payload?.scheduled_user_dish) + .catch((err) => { + throw err; + }); +}; \ No newline at end of file diff --git a/frontend/archive/src/utils/api/userDishApi.ts b/frontend/archive/src/utils/api/userDishApi.ts new file mode 100644 index 0000000..39ba27f --- /dev/null +++ b/frontend/archive/src/utils/api/userDishApi.ts @@ -0,0 +1,18 @@ +import { apiRequest } from "@/utils/api/apiRequest"; +import { UserDishType } from "@/types/ScheduledUserDishType"; + +export const listUserDishes = async (): Promise => { + const token = localStorage.getItem('token'); + + if (!token) throw new Error('No token found in localStorage.'); + + return apiRequest.get(`/api/user-dishes`, { headers: { Authorization: `Bearer ${ token }` } }) + .then((data) => { + if (data?.payload?.user_dishes) return data.payload.user_dishes as UserDishType[]; + + throw new Error('SOMETHING WENT WRONG'); + }) + .catch((error) => { + throw error; + }); +}; diff --git a/frontend/archive/src/utils/api/usersApi.ts b/frontend/archive/src/utils/api/usersApi.ts new file mode 100644 index 0000000..8d70ed8 --- /dev/null +++ b/frontend/archive/src/utils/api/usersApi.ts @@ -0,0 +1,139 @@ +import {RecurrenceType} from "@/types/ScheduleType"; +import {apiRequest} from "@/utils/api/apiRequest"; +import {UserType} from "@/types/UserType"; + +export const listUsers = async () => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(`/api/users`, { + headers: { + Authorization: `Bearer ${token}`, + }}) + .then((data) => { + if (data?.payload?.users) { + return data.payload.users; + } + throw new Error('Failed to fetch users'); + }) + .catch((err) => { + throw err; + }); +}; + +export const showUser = async (userId: number) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(`/api/users/${userId}`, { + headers: { + Authorization: `Bearer ${token}`, + } + }) + .then((data) => { + if (data?.payload?.user) { + return data.payload.user; + } + throw new Error('Failed to fetch users'); + }) + .catch((err) => { + throw err; + }); +}; + +export const createUser = async (name: string) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return await apiRequest.post('/api/users', {name}, { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); +}; + +export const updateUser = async (user: UserType, name: string) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return await apiRequest.put(`/api/users/${user.id}`, { name }, { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); +}; + +export const deleteUser = async (user: UserType) => { + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return await apiRequest.delete(`/api/users/${user.id}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); +}; + + +export const getUserDishForUserAndDish = async (userId: number, dishId: number) => { + const endpoint = `/api/users/${userId}/dishes/${dishId}`; + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.get(endpoint, { + headers: { + Authorization: `Bearer ${token}`, + }}) + .then((data) => { + if (data?.payload?.user_dish) { + return data.payload.user_dish; + } + throw new Error('Failed to fetch user dish'); + }) + .catch((err) => { + throw err; + }); +}; + +export const syncUserDishRecurrences = async ( + dish_id: number, + user_id: number, + recurrenceData: RecurrenceType[] +) => { + const url = `/api/users/${user_id}/dishes/${dish_id}/recurrences`; + const token = localStorage.getItem('token'); + + if (!token) { + throw new Error('No token found in localStorage.'); + } + + return apiRequest.post(url, { recurrences: recurrenceData }, { + headers: { + Authorization: `Bearer ${token}`, + }, + }).catch((err) => { + throw err; + }); +}; + diff --git a/frontend/archive/src/utils/dateBuilder.ts b/frontend/archive/src/utils/dateBuilder.ts new file mode 100644 index 0000000..e41ce15 --- /dev/null +++ b/frontend/archive/src/utils/dateBuilder.ts @@ -0,0 +1,24 @@ +import { DateTime } from "luxon"; + +const transformDate = (inputDate: string, addSuffix = false): string => { + const date = DateTime.fromISO(inputDate) + const day = date.day + const suffix = addSuffix ? getDaySuffix(day) : '' + return date.toFormat("MMMM") + ` ${ day }${ suffix }, ` + date.toFormat("yyyy"); +} + +const getDaySuffix = (day: number): string => { + if (day >= 11 && day <= 13) return "th"; + switch (day % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + default: + return "th"; + } +}; + +export default transformDate; \ No newline at end of file diff --git a/frontend/archive/src/utils/scheduleBuilder.ts b/frontend/archive/src/utils/scheduleBuilder.ts new file mode 100644 index 0000000..8bbf15f --- /dev/null +++ b/frontend/archive/src/utils/scheduleBuilder.ts @@ -0,0 +1,19 @@ +import { ScheduleDataType, ScheduleType } from "@/types/ScheduleType"; +import { ScheduledUserDishType, UserDishType } from "@/types/ScheduledUserDishType"; +import { UserType } from "@/types/UserType"; + +const ScheduleBuilder = ( + schedule: ScheduleType, + users: UserType[], + userDishes: UserDishType[] +): ScheduleDataType[] => users.map(user => { + return { + user, + scheduled_user_dish: schedule.scheduled_user_dishes + .filter((scheduledUserDish: ScheduledUserDishType) => scheduledUserDish.user_dish?.user.id === user.id) + .shift()?.user_dish ?? null, + user_dishes: userDishes.filter((userDish: UserDishType) => userDish.user.id === user.id) + } +}) + +export default ScheduleBuilder \ No newline at end of file diff --git a/frontend/archive/tailwind.config.ts b/frontend/archive/tailwind.config.ts new file mode 100644 index 0000000..109807b --- /dev/null +++ b/frontend/archive/tailwind.config.ts @@ -0,0 +1,18 @@ +import type { Config } from "tailwindcss"; + +export default { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + background: "var(--background)", + foreground: "var(--foreground)", + }, + }, + }, + plugins: [], +} satisfies Config; diff --git a/frontend/archive/tsconfig.json b/frontend/archive/tsconfig.json new file mode 100644 index 0000000..c133409 --- /dev/null +++ b/frontend/archive/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..35d51c3 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,4845 @@ +{ + "name": "my-react-router-app", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "my-react-router-app", + "dependencies": { + "@heroicons/react": "^2.2.0", + "@react-router/node": "^7.5.3", + "@react-router/serve": "^7.5.3", + "classnames": "^2.5.1", + "isbot": "^5.1.27", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router": "^7.5.3" + }, + "devDependencies": { + "@react-router/dev": "^7.5.3", + "@tailwindcss/vite": "^4.1.6", + "@types/node": "^20", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "tailwindcss": "^4.1.6", + "typescript": "^5.8.3", + "vite": "^6.3.3", + "vite-tsconfig-paths": "^5.1.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mjackson/node-fetch-server": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@mjackson/node-fetch-server/-/node-fetch-server-0.2.0.tgz", + "integrity": "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==", + "license": "MIT" + }, + "node_modules/@npmcli/git": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", + "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^4.1.0", + "glob": "^10.2.2", + "hosted-git-info": "^6.1.1", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@react-router/dev": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.6.0.tgz", + "integrity": "sha512-XSxEslex0ddJPxNNgdU1Eqmc9lsY/lhcLNCcRLAtlrOPyOz3Y8kIPpAf5T/U2AG3HGXFVBa9f8aQ7wXU3wTJSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.8", + "@babel/generator": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/plugin-syntax-decorators": "^7.22.10", + "@babel/plugin-syntax-jsx": "^7.21.4", + "@babel/preset-typescript": "^7.21.5", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.22.5", + "@npmcli/package-json": "^4.0.1", + "@react-router/node": "7.6.0", + "arg": "^5.0.1", + "babel-dead-code-elimination": "^1.0.6", + "chokidar": "^4.0.0", + "dedent": "^1.5.3", + "es-module-lexer": "^1.3.1", + "exit-hook": "2.2.1", + "fs-extra": "^10.0.0", + "jsesc": "3.0.2", + "lodash": "^4.17.21", + "pathe": "^1.1.2", + "picocolors": "^1.1.1", + "prettier": "^2.7.1", + "react-refresh": "^0.14.0", + "semver": "^7.3.7", + "set-cookie-parser": "^2.6.0", + "valibot": "^0.41.0", + "vite-node": "3.0.0-beta.2" + }, + "bin": { + "react-router": "bin.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@react-router/serve": "^7.6.0", + "react-router": "^7.6.0", + "typescript": "^5.1.0", + "vite": "^5.1.0 || ^6.0.0", + "wrangler": "^3.28.2 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@react-router/serve": { + "optional": true + }, + "typescript": { + "optional": true + }, + "wrangler": { + "optional": true + } + } + }, + "node_modules/@react-router/express": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.6.0.tgz", + "integrity": "sha512-nxSTCcTsVx94bXOI9JjG7Cg338myi8EdQWTOjA97v2ApX35wZm/ZDYos5MbrvZiMi0aB4KgAD62o4byNqF9Z1A==", + "license": "MIT", + "dependencies": { + "@react-router/node": "7.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "express": "^4.17.1 || ^5", + "react-router": "7.6.0", + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-router/node": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.6.0.tgz", + "integrity": "sha512-agjDPUzisLdGJ7Q2lx/Z3OfdS2t1k6qv/nTvA45iahGsQJCMDvMqVoIi7iIULKQJwrn4HWjM9jqEp75+WsMOXg==", + "license": "MIT", + "dependencies": { + "@mjackson/node-fetch-server": "^0.2.0", + "source-map-support": "^0.5.21", + "stream-slice": "^0.1.2", + "undici": "^6.19.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react-router": "7.6.0", + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@react-router/serve": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.6.0.tgz", + "integrity": "sha512-2O8ALEYgJfimvEdNRqMpnZb2N+DQ5UK/SKo9Xo3mTkt3no0rNTcNxzmhzD2tm92Q/HI7kHmMY1nBegNB2i1abA==", + "license": "MIT", + "dependencies": { + "@react-router/express": "7.6.0", + "@react-router/node": "7.6.0", + "compression": "^1.7.4", + "express": "^4.19.2", + "get-port": "5.1.1", + "morgan": "^1.10.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "react-router-serve": "bin.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react-router": "7.6.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz", + "integrity": "sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz", + "integrity": "sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz", + "integrity": "sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz", + "integrity": "sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz", + "integrity": "sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz", + "integrity": "sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz", + "integrity": "sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz", + "integrity": "sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz", + "integrity": "sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz", + "integrity": "sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz", + "integrity": "sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz", + "integrity": "sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz", + "integrity": "sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz", + "integrity": "sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz", + "integrity": "sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz", + "integrity": "sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz", + "integrity": "sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz", + "integrity": "sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz", + "integrity": "sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz", + "integrity": "sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.6.tgz", + "integrity": "sha512-ed6zQbgmKsjsVvodAS1q1Ld2BolEuxJOSyyNc+vhkjdmfNUDCmQnlXBfQkHrlzNmslxHsQU/bFmzcEbv4xXsLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "enhanced-resolve": "^5.18.1", + "jiti": "^2.4.2", + "lightningcss": "1.29.2", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.6" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.6.tgz", + "integrity": "sha512-0bpEBQiGx+227fW4G0fLQ8vuvyy5rsB1YIYNapTq3aRsJ9taF3f5cCaovDjN5pUGKKzcpMrZst/mhNaKAPOHOA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.6", + "@tailwindcss/oxide-darwin-arm64": "4.1.6", + "@tailwindcss/oxide-darwin-x64": "4.1.6", + "@tailwindcss/oxide-freebsd-x64": "4.1.6", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.6", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.6", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.6", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.6", + "@tailwindcss/oxide-linux-x64-musl": "4.1.6", + "@tailwindcss/oxide-wasm32-wasi": "4.1.6", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.6", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.6" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.6.tgz", + "integrity": "sha512-VHwwPiwXtdIvOvqT/0/FLH/pizTVu78FOnI9jQo64kSAikFSZT7K4pjyzoDpSMaveJTGyAKvDjuhxJxKfmvjiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.6.tgz", + "integrity": "sha512-weINOCcqv1HVBIGptNrk7c6lWgSFFiQMcCpKM4tnVi5x8OY2v1FrV76jwLukfT6pL1hyajc06tyVmZFYXoxvhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.6.tgz", + "integrity": "sha512-3FzekhHG0ww1zQjQ1lPoq0wPrAIVXAbUkWdWM8u5BnYFZgb9ja5ejBqyTgjpo5mfy0hFOoMnMuVDI+7CXhXZaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.6.tgz", + "integrity": "sha512-4m5F5lpkBZhVQJq53oe5XgJ+aFYWdrgkMwViHjRsES3KEu2m1udR21B1I77RUqie0ZYNscFzY1v9aDssMBZ/1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.6.tgz", + "integrity": "sha512-qU0rHnA9P/ZoaDKouU1oGPxPWzDKtIfX7eOGi5jOWJKdxieUJdVV+CxWZOpDWlYTd4N3sFQvcnVLJWJ1cLP5TA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.6.tgz", + "integrity": "sha512-jXy3TSTrbfgyd3UxPQeXC3wm8DAgmigzar99Km9Sf6L2OFfn/k+u3VqmpgHQw5QNfCpPe43em6Q7V76Wx7ogIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.6.tgz", + "integrity": "sha512-8kjivE5xW0qAQ9HX9reVFmZj3t+VmljDLVRJpVBEoTR+3bKMnvC7iLcoSGNIUJGOZy1mLVq7x/gerVg0T+IsYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.6.tgz", + "integrity": "sha512-A4spQhwnWVpjWDLXnOW9PSinO2PTKJQNRmL/aIl2U/O+RARls8doDfs6R41+DAXK0ccacvRyDpR46aVQJJCoCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.6.tgz", + "integrity": "sha512-YRee+6ZqdzgiQAHVSLfl3RYmqeeaWVCk796MhXhLQu2kJu2COHBkqlqsqKYx3p8Hmk5pGCQd2jTAoMWWFeyG2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.6.tgz", + "integrity": "sha512-qAp4ooTYrBQ5pk5jgg54/U1rCJ/9FLYOkkQ/nTE+bVMseMfB6O7J8zb19YTpWuu4UdfRf5zzOrNKfl6T64MNrQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@emnapi/wasi-threads": "^1.0.2", + "@napi-rs/wasm-runtime": "^0.2.9", + "@tybys/wasm-util": "^0.9.0", + "tslib": "^2.8.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.6.tgz", + "integrity": "sha512-nqpDWk0Xr8ELO/nfRUDjk1pc9wDJ3ObeDdNMHLaymc4PJBWj11gdPCWZFKSK2AVKjJQC7J2EfmSmf47GN7OuLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.6.tgz", + "integrity": "sha512-5k9xF33xkfKpo9wCvYcegQ21VwIBU1/qEbYlVukfEIyQbEA47uK8AAwS7NVjNE3vHzcmxMYwd0l6L4pPjjm1rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.6.tgz", + "integrity": "sha512-zjtqjDeY1w3g2beYQtrMAf51n5G7o+UwmyOjtsDMP7t6XyoRMOidcoKP32ps7AkNOHIXEOK0bhIC05dj8oJp4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.6", + "@tailwindcss/oxide": "4.1.6", + "tailwindcss": "4.1.6" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.46", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.46.tgz", + "integrity": "sha512-0PQHLhZPWOxGW4auogW0eOQAuNIlCYvibIpG67ja0TOJ6/sehu+1en7sfceUn+QQtx4Rk3GxbLNwPh0Cav7TWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/react": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.4.tgz", + "integrity": "sha512-EB1yiiYdvySuIITtD5lhW4yPyJ31RkJkkDw794LaQYrxCSaQV/47y5o1FMC4zF9ZyjUjzJMZwbovEnT5yHTW6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.4.tgz", + "integrity": "sha512-WxYAszDYgsMV31OVyoG4jbAgJI1Gw0Xq9V19zwhy6+hUUJlJIdZ3r/cbdmTqFv++SktQkZ/X+46yGFxp5XJBEg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.10.tgz", + "integrity": "sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001717", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", + "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", + "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.0.2", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.152", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.152.tgz", + "integrity": "sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", + "integrity": "sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isbot": { + "version": "5.1.28", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.28.tgz", + "integrity": "sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==", + "license": "Unlicense", + "engines": { + "node": ">=18" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lightningcss": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", + "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.2", + "lightningcss-darwin-x64": "1.29.2", + "lightningcss-freebsd-x64": "1.29.2", + "lightningcss-linux-arm-gnueabihf": "1.29.2", + "lightningcss-linux-arm64-gnu": "1.29.2", + "lightningcss-linux-arm64-musl": "1.29.2", + "lightningcss-linux-x64-gnu": "1.29.2", + "lightningcss-linux-x64-musl": "1.29.2", + "lightningcss-win32-arm64-msvc": "1.29.2", + "lightningcss-win32-x64-msvc": "1.29.2" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz", + "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", + "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", + "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", + "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", + "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", + "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", + "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", + "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", + "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", + "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.2.tgz", + "integrity": "sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", + "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rollup": { + "version": "4.40.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", + "integrity": "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.2", + "@rollup/rollup-android-arm64": "4.40.2", + "@rollup/rollup-darwin-arm64": "4.40.2", + "@rollup/rollup-darwin-x64": "4.40.2", + "@rollup/rollup-freebsd-arm64": "4.40.2", + "@rollup/rollup-freebsd-x64": "4.40.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", + "@rollup/rollup-linux-arm-musleabihf": "4.40.2", + "@rollup/rollup-linux-arm64-gnu": "4.40.2", + "@rollup/rollup-linux-arm64-musl": "4.40.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-gnu": "4.40.2", + "@rollup/rollup-linux-riscv64-musl": "4.40.2", + "@rollup/rollup-linux-s390x-gnu": "4.40.2", + "@rollup/rollup-linux-x64-gnu": "4.40.2", + "@rollup/rollup-linux-x64-musl": "4.40.2", + "@rollup/rollup-win32-arm64-msvc": "4.40.2", + "@rollup/rollup-win32-ia32-msvc": "4.40.2", + "@rollup/rollup-win32-x64-msvc": "4.40.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-slice": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", + "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.6.tgz", + "integrity": "sha512-j0cGLTreM6u4OWzBeLBpycK0WIh8w7kSwcUsQZoGLHZ7xDTdM69lN64AgoIEEwFi0tnhs4wSykUa5YWxAzgFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsconfck": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", + "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", + "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/valibot": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.41.0.tgz", + "integrity": "sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.0.0-beta.2", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.0-beta.2.tgz", + "integrity": "sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/frontend/package.json b/frontend/package.json index 62e44db..66c8a1b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,33 +1,32 @@ { - "name": "dish-planner", - "version": "0.1.0", + "name": "my-react-router-app", "private": true, + "type": "module", "scripts": { - "dev": "next dev --turbopack", - "build": "next build", - "start": "next start", - "lint": "next lint", - "export": "next export" + "build": "react-router build", + "dev": "react-router dev", + "start": "react-router-serve ./build/server/index.js", + "typecheck": "react-router typegen && tsc" }, "dependencies": { - "@headlessui/react": "^2.2.0", "@heroicons/react": "^2.2.0", + "@react-router/node": "^7.5.3", + "@react-router/serve": "^7.5.3", "classnames": "^2.5.1", - "luxon": "^3.5.0", - "next": "15.2.4", - "react": "^19.0.0", - "react-dom": "^19.0.0" + "isbot": "^5.1.27", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router": "^7.5.3" }, "devDependencies": { - "@eslint/eslintrc": "^3", - "@types/luxon": "^3.4.2", + "@react-router/dev": "^7.5.3", + "@tailwindcss/vite": "^4.1.6", "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "eslint": "^9", - "eslint-config-next": "15.1.5", - "postcss": "^8", - "tailwindcss": "^3.4.1", - "typescript": "^5" + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "tailwindcss": "^4.1.6", + "typescript": "^5.8.3", + "vite": "^6.3.3", + "vite-tsconfig-paths": "^5.1.4" } } diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5dbdfcddcb14182535f6d32d1c900681321b1aa3 GIT binary patch literal 15086 zcmeI33v3ic7{|AFEmuJ-;v>ep_G*NPi6KM`qNryCe1PIJ8siIN1WZ(7qVa)RVtmC% z)Ch?tN+afMKm;5@rvorJk zcXnoOc4q51HBQnQH_jn!cAg&XI1?PlX>Kl^k8qq0;zkha`kY$Fxt#=KNJAE9CMdpW zqr4#g8`nTw191(+H4xW8Tmyru2I^3=J1G3emPxkPXA=3{vvuvse_WWSshqaqls^-m zgB7q8&Vk*aYRe?sn$n53dGH#%3y%^vxv{pL*-h0Z4bmb_(k6{FL7HWIz(V*HT#IcS z-wE{)+0x1U!RUPt3gB97%p}@oHxF4|6S*+Yw=_tLtxZ~`S=z6J?O^AfU>7qOX`JNBbV&8+bO0%@fhQitKIJ^O^ zpgIa__qD_y07t@DFlBJ)8SP_#^j{6jpaXt{U%=dx!qu=4u7^21lWEYHPPY5U3TcoQ zX_7W+lvZi>TapNk_X>k-KO%MC9iZp>1E`N34gHKd9tK&){jq2~7OsJ>!G0FzxQFw6G zm&Vb(2#-T|rM|n3>uAsG_hnbvUKFf3#ay@u4uTzia~NY%XgCHfx4^To4BDU@)HlV? z@EN=g^ymETa1sQK{kRwyE4Ax8?wT&GvaG@ASO}{&a17&^v`y z!oPdiSiia^oov(Z)QhG2&|FgE{M9_4hJROGbnj>#$~ZF$-G^|zPj*QApltKe?;u;uKHJ~-V!=VLkg7Kgct)l7u39f@%VG8e3f$N-B zAu3a4%ZGf)r+jPAYCSLt73m_J3}p>}6Tx0j(wg4vvKhP!DzgiWANiE;Ppvp}P2W@m z-VbYn+NXFF?6ngef5CfY6ZwKnWvNV4z6s^~yMXw2i5mv}jC$6$46g?G|CPAu{W5qF zDobS=zb2ILX9D827g*NtGe5w;>frjanY{f)hrBP_2ehBt1?`~ypvg_Ot4x1V+43P@Ve8>qd)9NX_jWdLo`Zfy zoeam9)@Dpym{4m@+LNxXBPjPKA7{3a&H+~xQvr>C_A;7=JrfK~$M2pCh>|xLz>W6SCs4qC|#V`)# z)0C|?$o>jzh<|-cpf

K7osU{Xp5PG4-K+L2G=)c3f&}H&M3wo7TlO_UJjQ-Oq&_ zjAc9=nNIYz{c3zxOiS5UfcE1}8#iI4@uy;$Q7>}u`j+OU0N<*Ezx$k{x_27+{s2Eg z`^=rhtIzCm!_UcJ?Db~Lh-=_))PT3{Q0{Mwdq;0>ZL%l3+;B&4!&xm#%HYAK|;b456Iv&&f$VQHf` z>$*K9w8T+paVwc7fLfMlhQ4)*zL_SG{~v4QR;IuX-(oRtYAhWOlh`NLoX0k$RUYMi z2Y!bqpdN}wz8q`-%>&Le@q|jFw92ErW-hma-le?S z-@OZt2EEUm4wLsuEMkt4zlyy29_3S50JAcQHTtgTC{P~%-mvCTzrjXOc|{}N`Cz`W zSj7CrXfa7lcsU0J(0uSX6G`54t^7}+OLM0n(|g4waOQ}bd3%!XLh?NX9|8G_|06Ie zD5F1)w5I~!et7lA{G^;uf7aqT`KE&2qx9|~O;s6t!gb`+zVLJyT2T)l*8l(j literal 0 HcmV?d00001 diff --git a/frontend/react-router.config.ts b/frontend/react-router.config.ts new file mode 100644 index 0000000..6ff16f9 --- /dev/null +++ b/frontend/react-router.config.ts @@ -0,0 +1,7 @@ +import type { Config } from "@react-router/dev/config"; + +export default { + // Config options... + // Server-side render by default, to enable SPA mode set this to `false` + ssr: true, +} satisfies Config; diff --git a/frontend/src/components/layout/Card.tsx b/frontend/src/components/layout/Card.tsx deleted file mode 100644 index 055a4b2..0000000 --- a/frontend/src/components/layout/Card.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, {FC} from "react"; -import classNames from "classnames"; - -interface Props { - children: React.ReactNode; - className?: string; -} - -const Card: FC = ({ children, className }) => { - const styles = classNames( - "w-full border-2 border-secondary p-4 my-2 rounded-lg flex bg-gray-700", - className - ); - - return ( -

- {children} -
- ); -} - -export default Card; \ No newline at end of file diff --git a/frontend/src/components/ui/Alert.tsx b/frontend/src/components/ui/Alert.tsx deleted file mode 100644 index 33bec26..0000000 --- a/frontend/src/components/ui/Alert.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, {FC} from "react"; -import classNames from "classnames"; - -interface Props { - children: React.ReactNode; - className?: string; - type: 'error' | 'warning' | 'info' | 'success'; -} - -const Alert: FC = ({ children, className, type } ) => { - const styles = classNames( - "px-4 py-3 rounded-lg border-2", - { - 'bg-danger-50 border-danger text-danger-800': type === 'error', - 'bg-warning-50 border-warning text-warning-800': type === 'warning', - 'bg-accent-50 border-accent text-accent-800': type === 'info', - 'bg-success-50 border-success text-success-800': type === 'success', - }, - className - ); - - return ( -
- {children} -
- ); -} - -export default Alert; \ No newline at end of file diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx deleted file mode 100644 index 915756c..0000000 --- a/frontend/src/components/ui/Button.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import Link from "next/link"; -import React, { FC, ReactElement, ReactNode } from "react"; -import classNames from "classnames"; - -interface ButtonProps { - appearance?: 'solid' | 'outline' | 'text'; - children: ReactNode; - className?: string; - href?: string; - icon?: ReactNode; - onClick?: () => void; - disabled?: boolean; - size?: 'small' | 'medium' | 'large'; - type?: 'button' | 'submit' | 'reset'; - variant?: 'primary' | 'secondary' | 'accent' | 'danger'; -} - -const Button: FC = ({ - appearance = 'solid', - children, - className, - disabled, - href, - icon, - onClick, - size = 'medium', - type = 'button', - variant = 'primary' -}) => { - const baseStyles = "inline-flex items-center justify-center gap-2 rounded font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; - - // Size styles - const sizeStyles = classNames({ - "text-sm py-1.5 px-3": size === "small", - "text-base py-2 px-4": size === "medium", - "text-lg py-3 px-6": size === "large", - }); - - // Variant + Appearance styles - const variantStyles = classNames({ - // Primary Solid - 'bg-primary text-white border-2 border-primary hover:bg-primary-600 focus:ring-primary-500': - variant === 'primary' && appearance === 'solid', - // Primary Outline - 'bg-transparent text-primary border-2 border-primary hover:bg-primary-50 focus:ring-primary-500': - variant === 'primary' && appearance === 'outline', - // Primary Text - 'bg-transparent text-primary border-2 border-transparent hover:bg-primary-50 focus:ring-primary-500': - variant === 'primary' && appearance === 'text', - - // Secondary Solid - 'bg-secondary text-white border-2 border-secondary hover:bg-secondary-600 focus:ring-secondary-500': - variant === 'secondary' && appearance === 'solid', - // Secondary Outline - 'bg-transparent text-secondary border-2 border-secondary hover:bg-secondary-50 focus:ring-secondary-500': - variant === 'secondary' && appearance === 'outline', - // Secondary Text - 'bg-transparent text-secondary border-2 border-transparent hover:bg-secondary-50 focus:ring-secondary-500': - variant === 'secondary' && appearance === 'text', - - // Accent Solid - 'bg-accent text-white border-2 border-accent hover:bg-accent-600 focus:ring-accent-500': - variant === 'accent' && appearance === 'solid', - // Accent Outline - 'bg-transparent text-accent border-2 border-accent hover:bg-accent-50 focus:ring-accent-500': - variant === 'accent' && appearance === 'outline', - // Accent Text - 'bg-transparent text-accent border-2 border-transparent hover:bg-accent-50 focus:ring-accent-500': - variant === 'accent' && appearance === 'text', - - // Danger Solid - 'bg-danger text-white border-2 border-danger hover:bg-danger-600 focus:ring-danger-500': - variant === 'danger' && appearance === 'solid', - // Danger Outline - 'bg-transparent text-danger border-2 border-danger hover:bg-danger-50 focus:ring-danger-500': - variant === 'danger' && appearance === 'outline', - // Danger Text - 'bg-transparent text-danger border-2 border-transparent hover:bg-danger-50 focus:ring-danger-500': - variant === 'danger' && appearance === 'text', - }); - - const styles = classNames(baseStyles, sizeStyles, variantStyles, className); - - const iconClassNames = classNames({ - "h-4 w-4": size === "small", - "h-5 w-5": size === "medium", - "h-6 w-6": size === "large", - }); - - const iconElement = icon && React.isValidElement(icon) - ? React.cloneElement(icon as ReactElement<{ className?: string }>, { - className: iconClassNames, - }) - : null; - - if (href !== undefined) { - return ( - - {iconElement} - {children} - - ); - } - - return ( - - ); -} - -export default Button; \ No newline at end of file diff --git a/frontend/src/components/ui/Checkbox.tsx b/frontend/src/components/ui/Checkbox.tsx deleted file mode 100644 index 6db787c..0000000 --- a/frontend/src/components/ui/Checkbox.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { FC, InputHTMLAttributes } from "react"; -import classNames from "classnames"; - -interface CheckboxProps extends Omit, 'type'> { - label?: string; - error?: string; - helperText?: string; -} - -const Checkbox: FC = ({ - label, - error, - helperText, - className, - id, - ...props -}) => { - const checkboxId = id || label?.toLowerCase().replace(/\s+/g, '-'); - - const checkboxStyles = classNames( - "h-5 w-5 rounded border-2 transition-colors cursor-pointer", - "focus:outline-none focus:ring-2 focus:ring-offset-1", - { - "border-secondary text-primary focus:ring-primary-500": !error, - "border-danger text-danger focus:ring-danger-500": error, - "opacity-50 cursor-not-allowed": props.disabled, - }, - className - ); - - const labelStyles = classNames( - "ml-2 text-sm font-medium cursor-pointer", - { - "text-secondary": !error, - "text-danger": error, - } - ); - - return ( -
-
- - {label && ( - - )} -
- {error && ( -

{error}

- )} - {helperText && !error && ( -

{helperText}

- )} -
- ); -}; - -export default Checkbox; diff --git a/frontend/src/components/ui/Input.tsx b/frontend/src/components/ui/Input.tsx deleted file mode 100644 index f23ee4d..0000000 --- a/frontend/src/components/ui/Input.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { FC, InputHTMLAttributes } from "react"; -import classNames from "classnames"; - -interface InputProps extends Omit, 'size'> { - label?: string; - error?: string; - helperText?: string; - fullWidth?: boolean; -} - -const Input: FC = ({ - label, - error, - helperText, - fullWidth = true, - className, - id, - required, - ...props -}) => { - const inputId = id || label?.toLowerCase().replace(/\s+/g, '-'); - - const inputStyles = classNames( - "px-3 py-2 rounded border-2 transition-colors", - "bg-gray-600 text-secondary", - "focus:outline-none focus:ring-2 focus:ring-offset-1", - { - "border-secondary focus:border-primary focus:ring-primary-500": !error, - "border-danger focus:border-danger focus:ring-danger-500": error, - "w-full": fullWidth, - "opacity-50 cursor-not-allowed": props.disabled, - }, - className - ); - - const labelStyles = classNames( - "block text-sm font-medium mb-1", - { - "text-secondary": !error, - "text-danger": error, - } - ); - - return ( -
- {label && ( - - )} - - {error && ( -

{error}

- )} - {helperText && !error && ( -

{helperText}

- )} -
- ); -}; - -export default Input; diff --git a/frontend/src/components/ui/Select.tsx b/frontend/src/components/ui/Select.tsx deleted file mode 100644 index 3b04546..0000000 --- a/frontend/src/components/ui/Select.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { FC, SelectHTMLAttributes } from "react"; -import classNames from "classnames"; - -interface SelectOption { - value: string | number; - label: string; -} - -interface SelectProps extends Omit, 'size'> { - label?: string; - error?: string; - helperText?: string; - fullWidth?: boolean; - options: SelectOption[]; -} - -const Select: FC = ({ - label, - error, - helperText, - fullWidth = true, - options, - className, - id, - required, - ...props -}) => { - const selectId = id || label?.toLowerCase().replace(/\s+/g, '-'); - - const selectStyles = classNames( - "px-3 py-2 rounded border-2 transition-colors", - "bg-gray-600 text-secondary", - "focus:outline-none focus:ring-2 focus:ring-offset-1", - "cursor-pointer", - { - "border-secondary focus:border-primary focus:ring-primary-500": !error, - "border-danger focus:border-danger focus:ring-danger-500": error, - "w-full": fullWidth, - "opacity-50 cursor-not-allowed": props.disabled, - }, - className - ); - - const labelStyles = classNames( - "block text-sm font-medium mb-1", - { - "text-secondary": !error, - "text-danger": error, - } - ); - - return ( -
- {label && ( - - )} - - {error && ( -

{error}

- )} - {helperText && !error && ( -

{helperText}

- )} -
- ); -}; - -export default Select; diff --git a/frontend/src/components/ui/Toggle.tsx b/frontend/src/components/ui/Toggle.tsx deleted file mode 100644 index 7b36a7a..0000000 --- a/frontend/src/components/ui/Toggle.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import {FC} from "react"; -import classNames from "classnames"; - -interface ToggleProps { - checked: boolean; - onChange: (checked: boolean) => void; - label?: string; - disabled?: boolean; - helperText?: string; -} - -const Toggle: FC = ({ checked, onChange, label, disabled = false, helperText }) => { - const handleChange = () => { - if (!disabled) { - onChange(!checked); - } - }; - - const containerStyles = classNames( - "flex items-center gap-3 cursor-pointer select-none", - { - "opacity-50 cursor-not-allowed": disabled, - } - ); - - const toggleStyles = classNames( - "relative transition-colors duration-200 w-14 h-7 rounded-full", - { - "bg-success": checked && !disabled, - "bg-gray-400": !checked && !disabled, - "bg-gray-500": disabled, - } - ); - - const sliderStyles = classNames( - "absolute top-0.5 left-0.5 w-6 h-6 rounded-full transform transition-transform duration-200 bg-white shadow-md", - { - "translate-x-7": checked, - "translate-x-0": !checked, - } - ); - - return ( -
- - {helperText && ( -

{helperText}

- )} -
- ); -}; - -export default Toggle; \ No newline at end of file diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts deleted file mode 100644 index 9ee2147..0000000 --- a/frontend/tailwind.config.ts +++ /dev/null @@ -1,148 +0,0 @@ -import type { Config } from "tailwindcss"; - -export default { - content: [ - "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", - "./src/components/**/*.{js,ts,jsx,tsx,mdx}", - "./src/app/**/*.{js,ts,jsx,tsx,mdx}", - ], - theme: { - extend: { - colors: { - // Primary colors (Rose) - primary: { - DEFAULT: "var(--color-primary)", - 50: "var(--color-rose-50)", - 100: "var(--color-primary-100)", - 200: "var(--color-primary-200)", - 300: "var(--color-primary-300)", - 400: "var(--color-primary-400)", - 500: "var(--color-primary-500)", - 600: "var(--color-primary-600)", - 700: "var(--color-primary-700)", - 800: "var(--color-primary-800)", - 900: "var(--color-primary-900)", - 950: "var(--color-rose-950)", - }, - // Secondary colors (Deluge) - secondary: { - DEFAULT: "var(--color-secondary)", - 50: "var(--color-deluge-50)", - 100: "var(--color-secondary-100)", - 200: "var(--color-secondary-200)", - 300: "var(--color-secondary-300)", - 400: "var(--color-secondary-400)", - 500: "var(--color-secondary-500)", - 600: "var(--color-secondary-600)", - 700: "var(--color-secondary-700)", - 800: "var(--color-secondary-800)", - 900: "var(--color-secondary-900)", - 950: "var(--color-deluge-950)", - }, - // Accent Blue (Malibu) - accent: { - DEFAULT: "var(--color-accent-blue)", - 50: "var(--color-malibu-50)", - 100: "var(--color-accent-blue-100)", - 200: "var(--color-accent-blue-200)", - 300: "var(--color-accent-blue-300)", - 400: "var(--color-accent-blue-400)", - 500: "var(--color-accent-blue-500)", - 600: "var(--color-accent-blue-600)", - 700: "var(--color-accent-blue-700)", - 800: "var(--color-accent-blue-800)", - 900: "var(--color-accent-blue-900)", - 950: "var(--color-malibu-950)", - }, - // Accent Yellow (Gamboge) - yellow: { - DEFAULT: "var(--color-accent-yellow)", - 50: "var(--color-accent-yellow-50)", - 100: "var(--color-accent-yellow-100)", - 200: "var(--color-accent-yellow-200)", - 300: "var(--color-accent-yellow-300)", - 400: "var(--color-accent-yellow-400)", - 500: "var(--color-accent-yellow-500)", - 600: "var(--color-accent-yellow-600)", - 700: "var(--color-accent-yellow-700)", - 800: "var(--color-accent-yellow-800)", - 900: "var(--color-accent-yellow-900)", - 950: "var(--color-accent-yellow-950)", - }, - // Grays (Ebony Clay) - gray: { - DEFAULT: "var(--color-gray-500)", - 100: "var(--color-gray-100)", - 200: "var(--color-gray-200)", - 300: "var(--color-gray-300)", - 400: "var(--color-gray-400)", - 500: "var(--color-gray-500)", - 600: "var(--color-gray-600)", - 700: "var(--color-gray-700)", - 800: "var(--color-gray-800)", - 900: "var(--color-gray-900)", - 950: "var(--color-ebony-clay-950)", - }, - // Semantic colors - danger: { - DEFAULT: "var(--color-danger)", - 50: "var(--color-danger-50)", - 100: "var(--color-danger-100)", - 200: "var(--color-danger-200)", - 300: "var(--color-danger-300)", - 400: "var(--color-danger-400)", - 500: "var(--color-danger-500)", - 600: "var(--color-danger-600)", - 700: "var(--color-danger-700)", - 800: "var(--color-danger-800)", - 900: "var(--color-danger-900)", - 950: "var(--color-danger-950)", - }, - success: { - DEFAULT: "var(--color-success)", - 50: "var(--color-success-50)", - 100: "var(--color-success-100)", - 200: "var(--color-success-200)", - 300: "var(--color-success-300)", - 400: "var(--color-success-400)", - 500: "var(--color-success-500)", - 600: "var(--color-success-600)", - 700: "var(--color-success-700)", - 800: "var(--color-success-800)", - 900: "var(--color-success-900)", - 950: "var(--color-success-950)", - }, - warning: { - DEFAULT: "var(--color-warning)", - 50: "var(--color-warning-50)", - 100: "var(--color-warning-100)", - 200: "var(--color-warning-200)", - 300: "var(--color-warning-300)", - 400: "var(--color-warning-400)", - 500: "var(--color-warning-500)", - 600: "var(--color-warning-600)", - 700: "var(--color-warning-700)", - 800: "var(--color-warning-800)", - 900: "var(--color-warning-900)", - 950: "var(--color-warning-950)", - }, - // Legacy support - background: "var(--color-background)", - foreground: "var(--color-foreground)", - }, - borderRadius: { - DEFAULT: "4px", - sm: "2px", - md: "4px", - lg: "8px", - xl: "12px", - "2xl": "16px", - }, - fontFamily: { - default: ['"Anta"', 'serif'], - sans: ['Inter', 'system-ui', 'sans-serif'], - }, - }, - }, - plugins: [], -} satisfies Config; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c133409..cfe4682 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,27 +1,31 @@ { + "include": [ + "**/*", + "**/.server/**/*", + "**/.client/**/*", + ".react-router/types/**/*" + ], "compilerOptions": { - "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["node", "vite/client"], + "target": "ES2022", + "module": "ES2022", "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], + "jsx": "react-jsx", + "rootDirs": [".", "./.react-router/types"], + "baseUrl": ".", "paths": { - "@/*": ["./src/*"] - } + "~/*": ["./app/*"], + "@/*": ["./app/*"] + }, + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "optimizeDeps": { + "include": ["react", "react-dom"] + } } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..4a88d58 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,8 @@ +import { reactRouter } from "@react-router/dev/vite"; +import tailwindcss from "@tailwindcss/vite"; +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; + +export default defineConfig({ + plugins: [tailwindcss(), reactRouter(), tsconfigPaths()], +});