From 646f32e642bb876f8105664f8eb372d16cf51907 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Tue, 15 Jul 2025 22:43:36 -0500 Subject: [PATCH] Rich sort controls (#283) * Generalize app switcher menus * Organize sort module * Build configuration for nav sort control * Refine sort menu styles * Upgrade next.js * Reset custom sort when clicking grid/full a second time * Light up sort button when overridden --- README.md | 8 +- __tests__/path.test.ts | 2 +- app/admin/photos/[photoId]/edit/page.tsx | 2 +- app/admin/recipes/[recipe]/edit/page.tsx | 2 +- app/admin/tags/[tag]/edit/page.tsx | 2 +- app/admin/uploads/[uploadPath]/page.tsx | 2 +- app/admin/uploads/page.tsx | 2 +- app/film/[film]/[photoId]/page.tsx | 2 +- app/film/[film]/page.tsx | 2 +- app/focal/[focal]/[photoId]/page.tsx | 2 +- app/focal/[focal]/page.tsx | 2 +- app/full/[sortType]/[sortOrder]/page.tsx | 4 +- app/grid/[sortType]/[sortOrder]/page.tsx | 4 +- app/layout.tsx | 2 +- app/lens/[make]/[model]/[photoId]/page.tsx | 2 +- app/p/[photoId]/page.tsx | 2 +- app/recents/[photoId]/page.tsx | 2 +- app/recents/page.tsx | 2 +- app/recipe/[recipe]/[photoId]/page.tsx | 2 +- app/recipe/[recipe]/page.tsx | 2 +- app/shot-on/[make]/[model]/[photoId]/page.tsx | 2 +- app/sign-in/page.tsx | 2 +- app/sitemap.ts | 2 +- app/tag/[tag]/[photoId]/page.tsx | 2 +- app/tag/[tag]/page.tsx | 2 +- app/tag/private/[photoId]/page.tsx | 2 +- app/tag/private/page.tsx | 2 +- app/year/[year]/[photoId]/page.tsx | 2 +- app/year/[year]/page.tsx | 2 +- middleware.ts | 2 +- package.json | 18 +- pnpm-lock.yaml | 573 +++++++++--------- src/admin/AddUploadButton.tsx | 2 +- src/admin/AdminAppMenu.tsx | 78 +-- src/admin/AdminBatchEditPanelClient.tsx | 2 +- src/admin/AdminBatchUploadActions.tsx | 2 +- src/admin/AdminInfoNav.tsx | 2 +- src/admin/AdminNav.tsx | 2 +- src/admin/AdminNavClient.tsx | 2 +- src/admin/AdminPhotoMenu.tsx | 58 +- src/admin/AdminPhotosClient.tsx | 2 +- src/admin/AdminPhotosSyncClient.tsx | 2 +- src/admin/AdminPhotosTable.tsx | 2 +- src/admin/AdminPhotosTableInfinite.tsx | 2 +- src/admin/AdminRecipeForm.tsx | 2 +- src/admin/AdminRecipeTable.tsx | 2 +- src/admin/AdminTagForm.tsx | 2 +- src/admin/AdminTagTable.tsx | 2 +- src/admin/AdminUploadsTableRow.tsx | 2 +- src/admin/DeleteUploadButton.tsx | 2 +- .../config/AdminAppConfigurationClient.tsx | 19 +- src/admin/insights/AdminAppInsightsClient.tsx | 2 +- src/app/AppStateProvider.tsx | 2 +- src/app/AppViewSwitcher.tsx | 94 ++- src/app/Footer.tsx | 2 +- src/app/Nav.tsx | 2 +- src/app/ThemeSwitcher.tsx | 4 +- src/app/config.ts | 9 +- src/app/{paths.ts => path.ts} | 8 +- src/auth/SignInForm.tsx | 2 +- src/auth/server.ts | 2 +- src/camera/CameraOGTile.tsx | 2 +- src/camera/CameraShareModal.tsx | 2 +- src/camera/PhotoCamera.tsx | 2 +- src/camera/meta.ts | 2 +- src/cmdk/CommandKClient.tsx | 2 +- src/components/HttpStatusPage.tsx | 2 +- src/components/Modal.tsx | 2 +- src/components/more/MoreMenu.tsx | 23 +- src/components/{ => switcher}/Switcher.tsx | 0 .../{ => switcher}/SwitcherItem.tsx | 16 +- src/components/switcher/SwitcherItemMenu.tsx | 36 ++ src/feed/index.ts | 2 +- src/feed/json.ts | 2 +- src/feed/rss.ts | 2 +- src/film/FilmOGTile.tsx | 2 +- src/film/FilmShareModal.tsx | 2 +- src/film/PhotoFilm.tsx | 2 +- src/film/index.tsx | 2 +- src/focal/FocalLengthOGTile.tsx | 2 +- src/focal/FocalLengthShareModal.tsx | 2 +- src/focal/PhotoFocalLength.tsx | 2 +- src/focal/index.ts | 2 +- src/lens/LensOGTile.tsx | 2 +- src/lens/LensShareModal.tsx | 2 +- src/lens/PhotoLens.tsx | 2 +- src/lens/index.ts | 2 +- src/lens/meta.ts | 2 +- src/photo/InfinitePhotoScroll.tsx | 2 +- src/photo/PhotoEditPageClient.tsx | 2 +- src/photo/PhotoEscapeHandler.tsx | 2 +- src/photo/PhotoFullPage.tsx | 2 +- src/photo/PhotoGridContainer.tsx | 2 +- src/photo/PhotoGridInfinite.tsx | 2 +- src/photo/PhotoGridPageClient.tsx | 4 +- src/photo/PhotoLarge.tsx | 2 +- src/photo/PhotoLink.tsx | 2 +- src/photo/PhotoMedium.tsx | 2 +- src/photo/PhotoOGTile.tsx | 2 +- src/photo/PhotoPrevNextActions.tsx | 2 +- src/photo/PhotoShareModal.tsx | 2 +- src/photo/PhotoSmall.tsx | 2 +- src/photo/PhotoUploadWithStatus.tsx | 2 +- src/photo/PhotosEmptyState.tsx | 2 +- src/photo/PhotosLargeInfinite.tsx | 4 +- src/photo/StaggeredOgPhotosInfinite.tsx | 2 +- src/photo/UploadPageClient.tsx | 2 +- src/photo/actions.ts | 2 +- src/photo/cache.ts | 2 +- src/photo/db/index.ts | 2 +- src/photo/form/PhotoForm.tsx | 2 +- src/photo/index.ts | 2 +- src/photo/sort/SortMenu.tsx | 66 ++ src/photo/{db/sort.ts => sort/index.ts} | 15 + src/photo/{db/sort-path.ts => sort/path.ts} | 102 +++- src/platforms/storage/index.ts | 2 +- src/platforms/storage/vercel-blob.ts | 2 +- src/recents/PhotoRecents.tsx | 2 +- src/recents/RecentsOGTile.tsx | 2 +- src/recents/RecentsShareModal.tsx | 2 +- src/recents/meta.ts | 2 +- src/recipe/PhotoRecipe.tsx | 2 +- src/recipe/RecipeOGTile.tsx | 2 +- src/recipe/RecipeShareModal.tsx | 2 +- src/recipe/index.ts | 2 +- src/share/index.ts | 2 +- src/tag/PhotoFavs.tsx | 2 +- src/tag/PhotoPrivate.tsx | 2 +- src/tag/PhotoTag.tsx | 2 +- src/tag/TagOGTile.tsx | 2 +- src/tag/TagShareModal.tsx | 2 +- src/tag/index.ts | 2 +- src/years/PhotoYear.tsx | 2 +- src/years/YearOGTile.tsx | 2 +- src/years/YearShareModal.tsx | 2 +- src/years/meta.ts | 2 +- 136 files changed, 777 insertions(+), 596 deletions(-) rename src/app/{paths.ts => path.ts} (98%) rename src/components/{ => switcher}/Switcher.tsx (100%) rename src/components/{ => switcher}/SwitcherItem.tsx (81%) create mode 100644 src/components/switcher/SwitcherItemMenu.tsx create mode 100644 src/photo/sort/SortMenu.tsx rename src/photo/{db/sort.ts => sort/index.ts} (72%) rename src/photo/{db/sort-path.ts => sort/path.ts} (56%) diff --git a/README.md b/README.md index d67d9b47..f29b7939 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,13 @@ Application behavior can be changed by configuring the following environment var - `uploaded-at` - `uploaded-at-oldest-first` - `NEXT_PUBLIC_PRIORITY_BASED_SORTING = 1` takes priority field into account when sorting photos (⚠️ enabling may have performance consequences) -- `NEXT_PUBLIC_SHOW_SORT_CONTROL = 1` shows sort control in desktop nav on grid/full homepages +- `NEXT_PUBLIC_NAV_SORT_CONTROL` + - Controls sort UI on grid/full homepages + - Accepted values: + - `none` + - `toggle` (default) + - `menu` + #### Display - `NEXT_PUBLIC_HIDE_KEYBOARD_SHORTCUT_TOOLTIPS = 1` hides keyboard shortcut hints in areas like the main nav, and previous/next photo links diff --git a/__tests__/path.test.ts b/__tests__/path.test.ts index e336ad22..de22a4f2 100644 --- a/__tests__/path.test.ts +++ b/__tests__/path.test.ts @@ -11,7 +11,7 @@ import { isPathProtected, isPathTag, isPathTagPhoto, -} from '@/app/paths'; +} from '@/app/path'; import { TAG_PRIVATE } from '@/tag'; const PHOTO_ID = 'UsKSGcbt'; diff --git a/app/admin/photos/[photoId]/edit/page.tsx b/app/admin/photos/[photoId]/edit/page.tsx index 38dda275..75b27714 100644 --- a/app/admin/photos/[photoId]/edit/page.tsx +++ b/app/admin/photos/[photoId]/edit/page.tsx @@ -5,7 +5,7 @@ import { getUniqueRecipesCached, getUniqueTagsCached, } from '@/photo/cache'; -import { PATH_ADMIN } from '@/app/paths'; +import { PATH_ADMIN } from '@/app/path'; import PhotoEditPageClient from '@/photo/PhotoEditPageClient'; import { AI_TEXT_GENERATION_ENABLED, diff --git a/app/admin/recipes/[recipe]/edit/page.tsx b/app/admin/recipes/[recipe]/edit/page.tsx index e2ee5934..e4215249 100644 --- a/app/admin/recipes/[recipe]/edit/page.tsx +++ b/app/admin/recipes/[recipe]/edit/page.tsx @@ -1,7 +1,7 @@ import AdminChildPage from '@/components/AdminChildPage'; import { redirect } from 'next/navigation'; import { getPhotosCached, getPhotosMetaCached } from '@/photo/cache'; -import { PATH_ADMIN, PATH_ADMIN_RECIPES, pathForRecipe } from '@/app/paths'; +import { PATH_ADMIN, PATH_ADMIN_RECIPES, pathForRecipe } from '@/app/path'; import PhotoLightbox from '@/photo/PhotoLightbox'; import AdminRecipeBadge from '@/admin/AdminRecipeBadge'; import AdminRecipeForm from '@/admin/AdminRecipeForm'; diff --git a/app/admin/tags/[tag]/edit/page.tsx b/app/admin/tags/[tag]/edit/page.tsx index b07ce24b..a741fdd8 100644 --- a/app/admin/tags/[tag]/edit/page.tsx +++ b/app/admin/tags/[tag]/edit/page.tsx @@ -2,7 +2,7 @@ import AdminChildPage from '@/components/AdminChildPage'; import { redirect } from 'next/navigation'; import { getPhotosCached, getPhotosMetaCached } from '@/photo/cache'; import AdminTagForm from '@/admin/AdminTagForm'; -import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/app/paths'; +import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/app/path'; import PhotoLightbox from '@/photo/PhotoLightbox'; import AdminTagBadge from '@/admin/AdminTagBadge'; diff --git a/app/admin/uploads/[uploadPath]/page.tsx b/app/admin/uploads/[uploadPath]/page.tsx index 5a25deea..ce2d60ad 100644 --- a/app/admin/uploads/[uploadPath]/page.tsx +++ b/app/admin/uploads/[uploadPath]/page.tsx @@ -1,4 +1,4 @@ -import { PARAM_UPLOAD_TITLE, PATH_ADMIN } from '@/app/paths'; +import { PARAM_UPLOAD_TITLE, PATH_ADMIN } from '@/app/path'; import { extractImageDataFromBlobPath } from '@/photo/server'; import { redirect } from 'next/navigation'; import { diff --git a/app/admin/uploads/page.tsx b/app/admin/uploads/page.tsx index d5c05d0e..09472b50 100644 --- a/app/admin/uploads/page.tsx +++ b/app/admin/uploads/page.tsx @@ -3,7 +3,7 @@ import AppGrid from '@/components/AppGrid'; import { getUniqueTagsCached } from '@/photo/cache'; import AdminUploadsClient from '@/admin/AdminUploadsClient'; import { redirect } from 'next/navigation'; -import { PATH_ADMIN_PHOTOS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS } from '@/app/path'; export const maxDuration = 60; diff --git a/app/film/[film]/[photoId]/page.tsx b/app/film/[film]/[photoId]/page.tsx index b6f00cf4..cd6e2f47 100644 --- a/app/film/[film]/[photoId]/page.tsx +++ b/app/film/[film]/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosMetaCached, diff --git a/app/film/[film]/page.tsx b/app/film/[film]/page.tsx index b230dcd2..94a51666 100644 --- a/app/film/[film]/page.tsx +++ b/app/film/[film]/page.tsx @@ -5,7 +5,7 @@ import FilmOverview from '@/film/FilmOverview'; import { getPhotosFilmDataCached } from '@/film/data'; import { Metadata } from 'next/types'; import { cache } from 'react'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import { redirect } from 'next/navigation'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; import { getAppText } from '@/i18n/state/server'; diff --git a/app/focal/[focal]/[photoId]/page.tsx b/app/focal/[focal]/[photoId]/page.tsx index 6bb9d457..292ba4ef 100644 --- a/app/focal/[focal]/[photoId]/page.tsx +++ b/app/focal/[focal]/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosNearIdCached, getPhotosMetaCached } from '@/photo/cache'; import { cache } from 'react'; diff --git a/app/focal/[focal]/page.tsx b/app/focal/[focal]/page.tsx index 044087b6..3cd36f90 100644 --- a/app/focal/[focal]/page.tsx +++ b/app/focal/[focal]/page.tsx @@ -3,7 +3,7 @@ import FocalLengthOverview from '@/focal/FocalLengthOverview'; import { getPhotosFocalLengthDataCached } from '@/focal/data'; import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { getUniqueFocalLengths } from '@/photo/db/query'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { cache } from 'react'; diff --git a/app/full/[sortType]/[sortOrder]/page.tsx b/app/full/[sortType]/[sortOrder]/page.tsx index 1f74f185..9204760e 100644 --- a/app/full/[sortType]/[sortOrder]/page.tsx +++ b/app/full/[sortType]/[sortOrder]/page.tsx @@ -5,8 +5,8 @@ import { cache } from 'react'; import { getPhotos } from '@/photo/db/query'; import PhotoFullPage from '@/photo/PhotoFullPage'; import { getPhotosMetaCached } from '@/photo/cache'; -import { SortProps } from '@/photo/db/sort'; -import { getSortOptionsFromParams } from '@/photo/db/sort-path'; +import { SortProps } from '@/photo/sort'; +import { getSortOptionsFromParams } from '@/photo/sort/path'; import { PhotoQueryOptions } from '@/photo/db'; import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed'; diff --git a/app/grid/[sortType]/[sortOrder]/page.tsx b/app/grid/[sortType]/[sortOrder]/page.tsx index 0a4f4963..e688b3ea 100644 --- a/app/grid/[sortType]/[sortOrder]/page.tsx +++ b/app/grid/[sortType]/[sortOrder]/page.tsx @@ -6,8 +6,8 @@ import { cache } from 'react'; import PhotoGridPage from '@/photo/PhotoGridPage'; import { getDataForCategoriesCached } from '@/category/cache'; import { getPhotosMetaCached } from '@/photo/cache'; -import { SortProps } from '@/photo/db/sort'; -import { getSortOptionsFromParams } from '@/photo/db/sort-path'; +import { SortProps } from '@/photo/sort'; +import { getSortOptionsFromParams } from '@/photo/sort/path'; import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed'; import { PhotoQueryOptions } from '@/photo/db'; diff --git a/app/layout.tsx b/app/layout.tsx index 62b8a0cd..79c0c9ab 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -30,7 +30,7 @@ import RecipeModal from '@/recipe/RecipeModal'; import ThemeColors from '@/app/ThemeColors'; import AppTextProvider from '@/i18n/state/AppTextProvider'; import SharedHoverProvider from '@/components/shared-hover/SharedHoverProvider'; -import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/paths'; +import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/path'; import '../tailwind.css'; diff --git a/app/lens/[make]/[model]/[photoId]/page.tsx b/app/lens/[make]/[model]/[photoId]/page.tsx index 226a8ccc..1047b4a3 100644 --- a/app/lens/[make]/[model]/[photoId]/page.tsx +++ b/app/lens/[make]/[model]/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosMetaCached, diff --git a/app/p/[photoId]/page.tsx b/app/p/[photoId]/page.tsx index 57a7310b..f2cce863 100644 --- a/app/p/[photoId]/page.tsx +++ b/app/p/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotoCached, getPhotosNearIdCached } from '@/photo/cache'; import { cache } from 'react'; diff --git a/app/recents/[photoId]/page.tsx b/app/recents/[photoId]/page.tsx index 1fad819e..f9b81c21 100644 --- a/app/recents/[photoId]/page.tsx +++ b/app/recents/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosMetaCached, diff --git a/app/recents/page.tsx b/app/recents/page.tsx index 94c35940..ef169046 100644 --- a/app/recents/page.tsx +++ b/app/recents/page.tsx @@ -4,7 +4,7 @@ import RecentsOverview from '@/recents/RecentsOverview'; import { getPhotosRecentsDataCached } from '@/recents/data'; import { Metadata } from 'next/types'; import { cache } from 'react'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import { redirect } from 'next/navigation'; import { getAppText } from '@/i18n/state/server'; diff --git a/app/recipe/[recipe]/[photoId]/page.tsx b/app/recipe/[recipe]/[photoId]/page.tsx index b6c1eff6..5050872d 100644 --- a/app/recipe/[recipe]/[photoId]/page.tsx +++ b/app/recipe/[recipe]/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosMetaCached, getPhotosNearIdCached } from '@/photo/cache'; import { cache } from 'react'; diff --git a/app/recipe/[recipe]/page.tsx b/app/recipe/[recipe]/page.tsx index 18c2b08d..1e3d9cca 100644 --- a/app/recipe/[recipe]/page.tsx +++ b/app/recipe/[recipe]/page.tsx @@ -1,6 +1,6 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { getUniqueRecipes } from '@/photo/db/query'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; import { cache } from 'react'; diff --git a/app/shot-on/[make]/[model]/[photoId]/page.tsx b/app/shot-on/[make]/[model]/[photoId]/page.tsx index c2917a55..de5df0de 100644 --- a/app/shot-on/[make]/[model]/[photoId]/page.tsx +++ b/app/shot-on/[make]/[model]/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosMetaCached, diff --git a/app/sign-in/page.tsx b/app/sign-in/page.tsx index 64320772..e5832754 100644 --- a/app/sign-in/page.tsx +++ b/app/sign-in/page.tsx @@ -1,6 +1,6 @@ import { auth } from '@/auth/server'; import SignInForm from '@/auth/SignInForm'; -import { PATH_ADMIN, PATH_ROOT } from '@/app/paths'; +import { PATH_ADMIN, PATH_ROOT } from '@/app/path'; import { clsx } from 'clsx/lite'; import { redirect } from 'next/navigation'; import LinkWithStatus from '@/components/LinkWithStatus'; diff --git a/app/sitemap.ts b/app/sitemap.ts index 1ed45743..cfccc1ca 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -12,7 +12,7 @@ import { absolutePathForRecipe, absolutePathForTag, absolutePathForYear, -} from '@/app/paths'; +} from '@/app/path'; import { isTagFavs } from '@/tag'; import { BASE_URL, GRID_HOMEPAGE_ENABLED } from '@/app/config'; import { getPhotoIdsAndUpdatedAt } from '@/photo/db/query'; diff --git a/app/tag/[tag]/[photoId]/page.tsx b/app/tag/[tag]/[photoId]/page.tsx index 86055cad..36cbb364 100644 --- a/app/tag/[tag]/[photoId]/page.tsx +++ b/app/tag/[tag]/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosMetaCached, getPhotosNearIdCached } from '@/photo/cache'; import { cache } from 'react'; diff --git a/app/tag/[tag]/page.tsx b/app/tag/[tag]/page.tsx index 58925e7a..07b4f0aa 100644 --- a/app/tag/[tag]/page.tsx +++ b/app/tag/[tag]/page.tsx @@ -1,6 +1,6 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { getUniqueTags } from '@/photo/db/query'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import { generateMetaForTag } from '@/tag'; import TagOverview from '@/tag/TagOverview'; import { getPhotosTagDataCached } from '@/tag/data'; diff --git a/app/tag/private/[photoId]/page.tsx b/app/tag/private/[photoId]/page.tsx index 9bf59fcd..5880788d 100644 --- a/app/tag/private/[photoId]/page.tsx +++ b/app/tag/private/[photoId]/page.tsx @@ -8,7 +8,7 @@ import { getPhotosMetaCached, getPhotosNearIdCached, } from '@/photo/cache'; -import { PATH_ROOT, absolutePathForPhoto } from '@/app/paths'; +import { PATH_ROOT, absolutePathForPhoto } from '@/app/path'; import { TAG_PRIVATE } from '@/tag'; import { Metadata } from 'next'; import { redirect } from 'next/navigation'; diff --git a/app/tag/private/page.tsx b/app/tag/private/page.tsx index 8c078006..a0f6698d 100644 --- a/app/tag/private/page.tsx +++ b/app/tag/private/page.tsx @@ -3,7 +3,7 @@ import Note from '@/components/Note'; import AppGrid from '@/components/AppGrid'; import PhotoGrid from '@/photo/PhotoGrid'; import { getPhotosMetaCached, getPhotosNoStore } from '@/photo/cache'; -import { absolutePathForTag } from '@/app/paths'; +import { absolutePathForTag } from '@/app/path'; import { TAG_PRIVATE, descriptionForTaggedPhotos, titleForTag } from '@/tag'; import PrivateHeader from '@/tag/PrivateHeader'; import { Metadata } from 'next'; diff --git a/app/year/[year]/[photoId]/page.tsx b/app/year/[year]/[photoId]/page.tsx index a01b6972..4c328863 100644 --- a/app/year/[year]/[photoId]/page.tsx +++ b/app/year/[year]/[photoId]/page.tsx @@ -9,7 +9,7 @@ import { PATH_ROOT, absolutePathForPhoto, absolutePathForPhotoImage, -} from '@/app/paths'; +} from '@/app/path'; import PhotoDetailPage from '@/photo/PhotoDetailPage'; import { getPhotosMetaCached, diff --git a/app/year/[year]/page.tsx b/app/year/[year]/page.tsx index 7fd48038..8a3871d4 100644 --- a/app/year/[year]/page.tsx +++ b/app/year/[year]/page.tsx @@ -5,7 +5,7 @@ import YearOverview from '@/years/YearOverview'; import { getPhotosYearDataCached } from '@/years/data'; import { Metadata } from 'next/types'; import { cache } from 'react'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import { redirect } from 'next/navigation'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; import { getAppText } from '@/i18n/state/server'; diff --git a/middleware.ts b/middleware.ts index 56ff71ae..61e22b46 100644 --- a/middleware.ts +++ b/middleware.ts @@ -8,7 +8,7 @@ import { PATH_OG_SAMPLE, PREFIX_PHOTO, PREFIX_TAG, -} from './src/app/paths'; +} from './src/app/path'; export default function middleware(req: NextRequest, res:NextResponse) { const pathname = req.nextUrl.pathname; diff --git a/package.json b/package.json index 16a6e94e..c1d3c1a9 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@ai-sdk/openai": "^1.3.23", - "@aws-sdk/client-s3": "3.844.0", - "@aws-sdk/s3-request-presigner": "3.844.0", + "@aws-sdk/client-s3": "3.846.0", + "@aws-sdk/s3-request-presigner": "3.846.0", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", "@radix-ui/react-tooltip": "^1.2.7", @@ -21,16 +21,16 @@ "@vercel/analytics": "^1.5.0", "@vercel/blob": "^1.1.1", "@vercel/speed-insights": "^1.2.0", - "ai": "^4.3.17", + "ai": "^4.3.19", "camelcase-keys": "^9.1.3", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", "date-fns-tz": "^3.2.0", "fast-deep-equal": "^3.1.3", - "framer-motion": "^12.23.3", + "framer-motion": "^12.23.6", "nanoid": "^5.1.5", - "next": "15.3.5", + "next": "15.4.1", "next-auth": "5.0.0-beta.29", "next-themes": "^0.4.6", "pg": "^8.16.3", @@ -47,8 +47,8 @@ }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", - "@next/bundle-analyzer": "15.3.5", - "@next/eslint-plugin-next": "^15.3.5", + "@next/bundle-analyzer": "15.4.1", + "@next/eslint-plugin-next": "^15.4.1", "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/postcss": "^4.1.11", @@ -56,14 +56,14 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@types/jest": "^30.0.0", - "@types/node": "^24.0.13", + "@types/node": "^24.0.14", "@types/pg": "^8.15.4", "@types/react": "19.1.8", "@types/react-dom": "19.1.6", "@types/sanitize-html": "^2.16.0", "cross-fetch": "^4.1.0", "eslint": "9.31.0", - "eslint-config-next": "15.3.5", + "eslint-config-next": "15.4.1", "eslint-plugin-react-hooks": "^5.2.0", "jest": "^30.0.4", "jest-environment-jsdom": "^30.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be243870..84ce6c6b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,11 +12,11 @@ importers: specifier: ^1.3.23 version: 1.3.23(zod@3.24.2) '@aws-sdk/client-s3': - specifier: 3.844.0 - version: 3.844.0 + specifier: 3.846.0 + version: 3.846.0 '@aws-sdk/s3-request-presigner': - specifier: 3.844.0 - version: 3.844.0 + specifier: 3.846.0 + version: 3.846.0 '@radix-ui/react-dialog': specifier: ^1.1.14 version: 1.1.14(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -37,16 +37,16 @@ importers: version: 1.35.1 '@vercel/analytics': specifier: ^1.5.0 - version: 1.5.0(next@15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 1.5.0(next@15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) '@vercel/blob': specifier: ^1.1.1 version: 1.1.1 '@vercel/speed-insights': specifier: ^1.2.0 - version: 1.2.0(next@15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 1.2.0(next@15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) ai: - specifier: ^4.3.17 - version: 4.3.17(react@19.1.0)(zod@3.24.2) + specifier: ^4.3.19 + version: 4.3.19(react@19.1.0)(zod@3.24.2) camelcase-keys: specifier: ^9.1.3 version: 9.1.3 @@ -66,17 +66,17 @@ importers: specifier: ^3.1.3 version: 3.1.3 framer-motion: - specifier: ^12.23.3 - version: 12.23.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: ^12.23.6 + version: 12.23.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) nanoid: specifier: ^5.1.5 version: 5.1.5 next: - specifier: 15.3.5 - version: 15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 15.4.1 + version: 15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-auth: specifier: 5.0.0-beta.29 - version: 5.0.0-beta.29(next@15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 5.0.0-beta.29(next@15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -118,11 +118,11 @@ importers: specifier: ^3.3.1 version: 3.3.1 '@next/bundle-analyzer': - specifier: 15.3.5 - version: 15.3.5 + specifier: 15.4.1 + version: 15.4.1 '@next/eslint-plugin-next': - specifier: ^15.3.5 - version: 15.3.5 + specifier: ^15.4.1 + version: 15.4.1 '@tailwindcss/container-queries': specifier: ^0.1.1 version: 0.1.1(tailwindcss@4.1.11) @@ -145,8 +145,8 @@ importers: specifier: ^30.0.0 version: 30.0.0 '@types/node': - specifier: ^24.0.13 - version: 24.0.13 + specifier: ^24.0.14 + version: 24.0.14 '@types/pg': specifier: ^8.15.4 version: 8.15.4 @@ -166,14 +166,14 @@ importers: specifier: 9.31.0 version: 9.31.0(jiti@2.4.2) eslint-config-next: - specifier: 15.3.5 - version: 15.3.5(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) + specifier: 15.4.1 + version: 15.4.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) eslint-plugin-react-hooks: specifier: ^5.2.0 version: 5.2.0(eslint@9.31.0(jiti@2.4.2)) jest: specifier: ^30.0.4 - version: 30.0.4(@types/node@24.0.13)(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)) + version: 30.0.4(@types/node@24.0.14)(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)) jest-environment-jsdom: specifier: ^30.0.4 version: 30.0.4 @@ -185,7 +185,7 @@ importers: version: 4.1.11 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@24.0.13)(typescript@5.8.3) + version: 10.9.2(@types/node@24.0.14)(typescript@5.8.3) typescript: specifier: 5.8.3 version: 5.8.3 @@ -275,44 +275,44 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.844.0': - resolution: {integrity: sha512-Yhp8+U4KFVQqL6phZ5yrHF5PdCvKWbYtLSS+egAfAW+N5w78amhbZcctervj59uqOZHMGDWXuDBklN+7eVfasg==} + '@aws-sdk/client-s3@3.846.0': + resolution: {integrity: sha512-+C9qRJ7SFN+Bi2DJqfJ73Aj4ORpic9Jk5boosiOZj+TZi6qYHW6TCUqxheiC6JT/0xtE5C7VFIhW/UP/CAh0Tw==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.844.0': - resolution: {integrity: sha512-FktodSx+pfUfIqMjoNwZ6t1xqq/G3cfT7I4JJ0HKHoIIZdoCHQB52x0OzKDtHDJAnEQPInasdPS8PorZBZtHmg==} + '@aws-sdk/client-sso@3.846.0': + resolution: {integrity: sha512-7MgMl3nlwf2ixad5Xe8pFHtcwFchkx37MEvGuB00tn5jyBp3AQQ4dK3iHtj2HjhXcXD0G67zVPvH4/QNOL7/gw==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.844.0': - resolution: {integrity: sha512-pfpI54bG5Xf2NkqrDBC2REStXlDXNCw/whORhkEs+Tp5exU872D5QKguzjPA6hH+8Pvbq1qgt5zXMbduISTHJw==} + '@aws-sdk/core@3.846.0': + resolution: {integrity: sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.844.0': - resolution: {integrity: sha512-WB94Ox86MqcZ4CnRjKgopzaSuZH4hMP0GqdOxG4s1it1lRWOIPOHOC1dPiM0Zbj1uqITIhbXUQVXyP/uaJeNkw==} + '@aws-sdk/credential-provider-env@3.846.0': + resolution: {integrity: sha512-QuCQZET9enja7AWVISY+mpFrEIeHzvkx/JEEbHYzHhUkxcnC2Kq2c0bB7hDihGD0AZd3Xsm653hk1O97qu69zg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.844.0': - resolution: {integrity: sha512-e+efVqfkhpM8zxYeiLNgTUlX+tmtXzVm3bw1A02U9Z9cWBHyQNb8pi90M7QniLoqRURY1B0C2JqkOE61gd4KNg==} + '@aws-sdk/credential-provider-http@3.846.0': + resolution: {integrity: sha512-Jh1iKUuepdmtreMYozV2ePsPcOF5W9p3U4tWhi3v6nDvz0GsBjzjAROW+BW8XMz9vAD3I9R+8VC3/aq63p5nlw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.844.0': - resolution: {integrity: sha512-jc5ArGz2HfAx5QPXD+Ep36+QWyCKzl2TG6Vtl87/vljfLhVD0gEHv8fRsqWEp3Rc6hVfKnCjLW5ayR2HYcow9w==} + '@aws-sdk/credential-provider-ini@3.846.0': + resolution: {integrity: sha512-GUxaBBKsYx1kOlRbcs77l6BVyG9K70zekJX+5hdwTEgJq7AoHl/XYoWiDxPf6zQ7J4euixPJoyRhpNbJjAXdFw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-node@3.844.0': - resolution: {integrity: sha512-pUqB0StTNyW0R03XjTA3wrQZcie/7FJKSXlYHue921ZXuhLOZpzyDkLNfdRsZTcEoYYWVPSmyS+Eu/g5yVsBNA==} + '@aws-sdk/credential-provider-node@3.846.0': + resolution: {integrity: sha512-du2DsXYRfQ8VIt/gXGThhT8KdUEt2j9W91W87Bl9IA5DINt4nSZv+gzh8LqHBYsTSqoUpKb+qIfP1RjZM/8r0A==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-process@3.844.0': - resolution: {integrity: sha512-VCI8XvIDt2WBfk5Gi/wXKPcWTS3OkAbovB66oKcNQalllH8ESDg4SfLNhchdnN8A5sDGj6tIBJ19nk+dQ6GaqQ==} + '@aws-sdk/credential-provider-process@3.846.0': + resolution: {integrity: sha512-mEpwDYarJSH+CIXnnHN0QOe0MXI+HuPStD6gsv3z/7Q6ESl8KRWon3weFZCDnqpiJMUVavlDR0PPlAFg2MQoPg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-sso@3.844.0': - resolution: {integrity: sha512-UNp/uWufGlb5nWa4dpc6uQnDOB/9ysJJFG95ACowNVL9XWfi1LJO7teKrqNkVhq0CzSJS1tCt3FvX4UfM+aN1g==} + '@aws-sdk/credential-provider-sso@3.846.0': + resolution: {integrity: sha512-Dxz9dpdjfxUsSfW92SAldu9wy8wgEbskn4BNWBFHslQHTmqurmR0ci4P1SMxJJKd498AUEoIAzZOtjGOC38irQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-web-identity@3.844.0': - resolution: {integrity: sha512-iDmX4pPmatjttIScdspZRagaFnCjpHZIEEwTyKdXxUaU0iAOSXF8ecrCEvutETvImPOC86xdrq+MPacJOnMzUA==} + '@aws-sdk/credential-provider-web-identity@3.846.0': + resolution: {integrity: sha512-j6zOd+kynPQJzmVwSKSUTpsLXAf7vKkr7hCPbQyqC8ZqkIuExsRqu2vRQjX2iH/MKhwZ+qEWMxPMhfDoyv7Gag==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-bucket-endpoint@3.840.0': @@ -323,8 +323,8 @@ packages: resolution: {integrity: sha512-iJg2r6FKsKKvdiU4oCOuCf7Ro/YE0Q2BT/QyEZN3/Rt8Nr4SAZiQOlcBXOCpGvuIKOEAhvDOUnW3aDHL01PdVw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.844.0': - resolution: {integrity: sha512-LCImZd1hpM0cegfdpgZyK6x4on4Ky+c9XCFURfE4wil1J9HXf6OP4KsfHQwt1yIkMEbFqvd/ab2I5fmp7S7aFA==} + '@aws-sdk/middleware-flexible-checksums@3.846.0': + resolution: {integrity: sha512-CdkeVfkwt3+bDLhmOwBxvkUf6oY9iUhvosaUnqkoPsOqIiUEN54yTGOnO8A0wLz6mMsZ6aBlfFrQhFnxt3c+yw==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-host-header@3.840.0': @@ -343,36 +343,36 @@ packages: resolution: {integrity: sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.844.0': - resolution: {integrity: sha512-vOD5reqZszXBWMbZFN3EUar203o2i8gcoTdrymY4GMsAPDsh0k8yd3VJRNPuxT/017tP6G+rQepOGzna4umung==} + '@aws-sdk/middleware-sdk-s3@3.846.0': + resolution: {integrity: sha512-jP9x+2Q87J5l8FOP+jlAd7vGLn0cC6G9QGmf386e5OslBPqxXKcl3RjqGLIOKKos2mVItY3ApP5xdXQx7jGTVA==} engines: {node: '>=18.0.0'} '@aws-sdk/middleware-ssec@3.840.0': resolution: {integrity: sha512-CBZP9t1QbjDFGOrtnUEHL1oAvmnCUUm7p0aPNbIdSzNtH42TNKjPRN3TuEIJDGjkrqpL3MXyDSmNayDcw/XW7Q==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.844.0': - resolution: {integrity: sha512-SIbDNUL6ZYXPj5Tk0qEz05sW9kNS1Gl3/wNWEmH+AuUACipkyIeKKWzD6z5433MllETh73vtka/JQF3g7AuZww==} + '@aws-sdk/middleware-user-agent@3.846.0': + resolution: {integrity: sha512-85/oUc2jMXqQWo+HHH7WwrdqqArzhMmTmBCpXZwklBHG+ZMzTS5Wug2B0HhGDVWo9aYRMeikSq4lsrpHFVd2MQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.844.0': - resolution: {integrity: sha512-p2XILWc7AcevUSpBg2VtQrk79eWQC4q2JsCSY7HxKpFLZB4mMOfmiTyYkR1gEA6AttK/wpCOtfz+hi1/+z2V1A==} + '@aws-sdk/nested-clients@3.846.0': + resolution: {integrity: sha512-LCXPVtNQnkTuE8inPCtpfWN2raE/ndFBKf5OIbuHnC/0XYGOUl5q7VsJz471zJuN9FX3WMfopaFwmNc7cQNMpQ==} engines: {node: '>=18.0.0'} '@aws-sdk/region-config-resolver@3.840.0': resolution: {integrity: sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==} engines: {node: '>=18.0.0'} - '@aws-sdk/s3-request-presigner@3.844.0': - resolution: {integrity: sha512-i953TKW1rXbd9G2xEgWoJZDoF0Z1ONRlrXkOKDGOrY/uQhAIPNDz5k6tFcXG5oIaLWW197ShENv3CeEJnhfh3g==} + '@aws-sdk/s3-request-presigner@3.846.0': + resolution: {integrity: sha512-R2I3bdYSvncieucQiGGfSo3Xu8kV424uyu5tZZQk3zEH7ZNshJlE8YcYAGkN9caqlnYZ4V+CK6+71ufTc8I/XQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.844.0': - resolution: {integrity: sha512-QC8nocQcZ3Bj7vTnuL47iNhcuUjMC46E2L85mU+sPQo3LN2qBVGSOTF+xSWGvmSFDpkN4ZXUMVeA0cJoJFEDFA==} + '@aws-sdk/signature-v4-multi-region@3.846.0': + resolution: {integrity: sha512-ZMfIMxUljqZzPJGOcraC6erwq/z1puNMU35cO1a/WdhB+LdYknMn1lr7SJuH754QwNzzIlZbEgg4hoHw50+DpQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.844.0': - resolution: {integrity: sha512-Kh728FEny0fil+LeH8U1offPJCTd/EDh8liBAvLtViLHt2WoX2xC8rk98D38Q5p79aIUhHb3Pf4n9IZfTu/Kog==} + '@aws-sdk/token-providers@3.846.0': + resolution: {integrity: sha512-sGNk3xclK7xx+rIJZDJC4FNFqaSSqN0nSr+AdVdQ+/iKQKaUA6hixRbXaQ7I7M5mhqS6fMW1AsqVRywQq2BSMw==} engines: {node: '>=18.0.0'} '@aws-sdk/types@3.840.0': @@ -383,8 +383,8 @@ packages: resolution: {integrity: sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-endpoints@3.844.0': - resolution: {integrity: sha512-1DHh0WTUmxlysz3EereHKtKoxVUG9UC5BsfAw6Bm4/6qDlJiqtY3oa2vebkYN23yltKdfsCK65cwnBRU59mWVg==} + '@aws-sdk/util-endpoints@3.845.0': + resolution: {integrity: sha512-MBmOf0Pb4q6xs9V7jXT1+qciW2965yvaoZUlUUnxUEoX6zxWROeIu/gttASc4vSjOHr/+64hmFkxjeBUF37FJA==} engines: {node: '>=18.0.0'} '@aws-sdk/util-format-url@3.840.0': @@ -398,8 +398,8 @@ packages: '@aws-sdk/util-user-agent-browser@3.840.0': resolution: {integrity: sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==} - '@aws-sdk/util-user-agent-node@3.844.0': - resolution: {integrity: sha512-0eTpURp9Gxbyyeqr78ogARZMSWS5KUMZuN+XMHxNpQLmn2S+J3g+MAyoklCcwhKXlbdQq2aMULEiy0mqIWytuw==} + '@aws-sdk/util-user-agent-node@3.846.0': + resolution: {integrity: sha512-MXYXCplw76xe8A9ejVaIru6Carum/2LQbVtNHsIa4h0TlafLdfulywsoMWL1F53Y9XxQSeOKyyqDKLNOgRVimw==} engines: {node: '>=18.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -991,59 +991,59 @@ packages: '@napi-rs/wasm-runtime@0.2.11': resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} - '@next/bundle-analyzer@15.3.5': - resolution: {integrity: sha512-r1tlg7N4IUWpdqdy8/6bf7pvo2yeN9Oc6OHEiMsMfIooJ5k37Odi9HC1qBS4soULNE7FiQ18JP/TdmQKiaIkoA==} + '@next/bundle-analyzer@15.4.1': + resolution: {integrity: sha512-O5R3iPLR3/oQWFIXl+Mnd02IyhvWBterTlXcceIGw29QHWL/gjvyO0eIVEvrJPS7zzE6/NSu1TiSVgi8mxotlw==} - '@next/env@15.3.5': - resolution: {integrity: sha512-7g06v8BUVtN2njAX/r8gheoVffhiKFVt4nx74Tt6G4Hqw9HCLYQVx/GkH2qHvPtAHZaUNZ0VXAa0pQP6v1wk7g==} + '@next/env@15.4.1': + resolution: {integrity: sha512-DXQwFGAE2VH+f2TJsKepRXpODPU+scf5fDbKOME8MMyeyswe4XwgRdiiIYmBfkXU+2ssliLYznajTrOQdnLR5A==} - '@next/eslint-plugin-next@15.3.5': - resolution: {integrity: sha512-BZwWPGfp9po/rAnJcwUBaM+yT/+yTWIkWdyDwc74G9jcfTrNrmsHe+hXHljV066YNdVs8cxROxX5IgMQGX190w==} + '@next/eslint-plugin-next@15.4.1': + resolution: {integrity: sha512-lQnHUxN7mMksK7IxgKDIXNMWFOBmksVrjamMEURXiYfo7zgsc30lnU8u4y/MJktSh+nB80ktTQeQbWdQO6c8Ow==} - '@next/swc-darwin-arm64@15.3.5': - resolution: {integrity: sha512-lM/8tilIsqBq+2nq9kbTW19vfwFve0NR7MxfkuSUbRSgXlMQoJYg+31+++XwKVSXk4uT23G2eF/7BRIKdn8t8w==} + '@next/swc-darwin-arm64@15.4.1': + resolution: {integrity: sha512-L+81yMsiHq82VRXS2RVq6OgDwjvA4kDksGU8hfiDHEXP+ncKIUhUsadAVB+MRIp2FErs/5hpXR0u2eluWPAhig==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.3.5': - resolution: {integrity: sha512-WhwegPQJ5IfoUNZUVsI9TRAlKpjGVK0tpJTL6KeiC4cux9774NYE9Wu/iCfIkL/5J8rPAkqZpG7n+EfiAfidXA==} + '@next/swc-darwin-x64@15.4.1': + resolution: {integrity: sha512-jfz1RXu6SzL14lFl05/MNkcN35lTLMJWPbqt7Xaj35+ZWAX342aePIJrN6xBdGeKl6jPXJm0Yqo3Xvh3Gpo3Uw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.3.5': - resolution: {integrity: sha512-LVD6uMOZ7XePg3KWYdGuzuvVboxujGjbcuP2jsPAN3MnLdLoZUXKRc6ixxfs03RH7qBdEHCZjyLP/jBdCJVRJQ==} + '@next/swc-linux-arm64-gnu@15.4.1': + resolution: {integrity: sha512-k0tOFn3dsnkaGfs6iQz8Ms6f1CyQe4GacXF979sL8PNQxjYS1swx9VsOyUQYaPoGV8nAZ7OX8cYaeiXGq9ahPQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.3.5': - resolution: {integrity: sha512-k8aVScYZ++BnS2P69ClK7v4nOu702jcF9AIHKu6llhHEtBSmM2zkPGl9yoqbSU/657IIIb0QHpdxEr0iW9z53A==} + '@next/swc-linux-arm64-musl@15.4.1': + resolution: {integrity: sha512-4ogGQ/3qDzbbK3IwV88ltihHFbQVq6Qr+uEapzXHXBH1KsVBZOB50sn6BWHPcFjwSoMX2Tj9eH/fZvQnSIgc3g==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.3.5': - resolution: {integrity: sha512-2xYU0DI9DGN/bAHzVwADid22ba5d/xrbrQlr2U+/Q5WkFUzeL0TDR963BdrtLS/4bMmKZGptLeg6282H/S2i8A==} + '@next/swc-linux-x64-gnu@15.4.1': + resolution: {integrity: sha512-Jj0Rfw3wIgp+eahMz/tOGwlcYYEFjlBPKU7NqoOkTX0LY45i5W0WcDpgiDWSLrN8KFQq/LW7fZq46gxGCiOYlQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.3.5': - resolution: {integrity: sha512-TRYIqAGf1KCbuAB0gjhdn5Ytd8fV+wJSM2Nh2is/xEqR8PZHxfQuaiNhoF50XfY90sNpaRMaGhF6E+qjV1b9Tg==} + '@next/swc-linux-x64-musl@15.4.1': + resolution: {integrity: sha512-9WlEZfnw1vFqkWsTMzZDgNL7AUI1aiBHi0S2m8jvycPyCq/fbZjtE/nDkhJRYbSjXbtRHYLDBlmP95kpjEmJbw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.3.5': - resolution: {integrity: sha512-h04/7iMEUSMY6fDGCvdanKqlO1qYvzNxntZlCzfE8i5P0uqzVQWQquU1TIhlz0VqGQGXLrFDuTJVONpqGqjGKQ==} + '@next/swc-win32-arm64-msvc@15.4.1': + resolution: {integrity: sha512-WodRbZ9g6CQLRZsG3gtrA9w7Qfa9BwDzhFVdlI6sV0OCPq9JrOrJSp9/ioLsezbV8w9RCJ8v55uzJuJ5RgWLZg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.3.5': - resolution: {integrity: sha512-5fhH6fccXxnX2KhllnGhkYMndhOiLOLEiVGYjP2nizqeGWkN10sA9taATlXwake2E2XMvYZjjz0Uj7T0y+z1yw==} + '@next/swc-win32-x64-msvc@15.4.1': + resolution: {integrity: sha512-y+wTBxelk2xiNofmDOVU7O5WxTHcvOoL3srOM0kxTzKDjQ57kPU0tpnPJ/BWrRnsOwXEv0+3QSbGR7hY4n9LkQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1531,12 +1531,12 @@ packages: resolution: {integrity: sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.1.14': - resolution: {integrity: sha512-+BGLpK5D93gCcSEceaaYhUD/+OCGXM1IDaq/jKUQ+ujB0PTWlWN85noodKw/IPFZhIKFCNEe19PGd/reUMeLSQ==} + '@smithy/middleware-endpoint@4.1.15': + resolution: {integrity: sha512-L2M0oz+r6Wv0KZ90MgClXmWkV7G72519Hd5/+K5i3gQMu4WNQykh7ERr58WT3q60dd9NqHSMc3/bAK0FsFg3Fw==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.1.15': - resolution: {integrity: sha512-iKYUJpiyTQ33U2KlOZeUb0GwtzWR3C0soYcKuCnTmJrvt6XwTPQZhMfsjJZNw7PpQ3TU4Ati1qLSrkSJxnnSMQ==} + '@smithy/middleware-retry@4.1.16': + resolution: {integrity: sha512-PpPhMpC6U1fLW0evKnC8gJtmobBYn0oi4RrIKGhN1a86t6XgVEK+Vb9C8dh5PPXb3YDr8lE6aYKh1hd3OikmWw==} engines: {node: '>=18.0.0'} '@smithy/middleware-serde@4.0.8': @@ -1583,8 +1583,8 @@ packages: resolution: {integrity: sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.4.6': - resolution: {integrity: sha512-3wfhywdzB/CFszP6moa5L3lf5/zSfQoH0kvVSdkyK2az5qZet0sn2PAHjcTDiq296Y4RP5yxF7B6S6+3oeBUCQ==} + '@smithy/smithy-client@4.4.7': + resolution: {integrity: sha512-x+MxBNOcG7rY9i5QsbdgvvRJngKKvUJrbU5R5bT66PTH3e6htSupJ4Q+kJ3E7t6q854jyl57acjpPi6qG1OY5g==} engines: {node: '>=18.0.0'} '@smithy/types@4.3.1': @@ -1619,12 +1619,12 @@ packages: resolution: {integrity: sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.0.22': - resolution: {integrity: sha512-hjElSW18Wq3fUAWVk6nbk7pGrV7ZT14DL1IUobmqhV3lxcsIenr5FUsDe2jlTVaS8OYBI3x+Og9URv5YcKb5QA==} + '@smithy/util-defaults-mode-browser@4.0.23': + resolution: {integrity: sha512-NqRi6VvEIwpJ+KSdqI85+HH46H7uVoNqVTs2QO7p1YKnS7k8VZnunJj8R5KdmmVnTojkaL1OMPyZC8uR5F7fSg==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.0.22': - resolution: {integrity: sha512-7B8mfQBtwwr2aNRRmU39k/bsRtv9B6/1mTMrGmmdJFKmLAH+KgIiOuhaqfKOBGh9sZ/VkZxbvm94rI4MMYpFjQ==} + '@smithy/util-defaults-mode-node@4.0.23': + resolution: {integrity: sha512-NE9NtEVigFa+HHJ5bBeQT7KF3KiltW880CLN9TnWWL55akeou3ziRAHO22QSUPgPZ/nqMfPXi/LGMQ6xQvXPNQ==} engines: {node: '>=18.0.0'} '@smithy/util-endpoints@3.0.6': @@ -1663,9 +1663,6 @@ packages: resolution: {integrity: sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg==} engines: {node: '>=18.0.0'} - '@swc/counter@0.1.3': - resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1847,8 +1844,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@24.0.13': - resolution: {integrity: sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==} + '@types/node@24.0.14': + resolution: {integrity: sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==} '@types/pg@8.15.4': resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} @@ -2112,8 +2109,8 @@ packages: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} - ai@4.3.17: - resolution: {integrity: sha512-uWqIQ94Nb1GTYtYElGHegJMOzv3r2mCKNFlKrqkft9xrfvIahTI5OdcnD5U9612RFGuUNGmSDTO1/YRNFXobaQ==} + ai@4.3.19: + resolution: {integrity: sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc @@ -2279,10 +2276,6 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2609,8 +2602,8 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@15.3.5: - resolution: {integrity: sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg==} + eslint-config-next@15.4.1: + resolution: {integrity: sha512-XIIN+lq8XuSwXUrcv+0uHMDFGJFPxLAw04/a4muFZYygSvStvVa15nY7kh4Il6yOVJyxdMUyVdQ9ApGedaeupw==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -2820,8 +2813,8 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - framer-motion@12.23.3: - resolution: {integrity: sha512-llmLkf44zuIZOPSrE4bl4J0UTg6bav+rlKEfMRKgvDMXqBrUtMg6cspoQeRVK3nqRLxTmAJhfGXk39UDdZD7Kw==} + framer-motion@12.23.6: + resolution: {integrity: sha512-dsJ389QImVE3lQvM8Mnk99/j8tiZDM/7706PCqvkQ8sSCnpmWxsgX+g0lj7r5OBVL0U36pIecCTBoIWcM2RuKw==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -3584,11 +3577,11 @@ packages: engines: {node: '>=10'} hasBin: true - motion-dom@12.23.2: - resolution: {integrity: sha512-73j6xDHX/NvVh5L5oS1ouAVnshsvmApOq4F3VZo5MkYSD/YVsVLal4Qp9wvVgJM9uU2bLZyc0Sn8B9c/MMKk4g==} + motion-dom@12.23.6: + resolution: {integrity: sha512-G2w6Nw7ZOVSzcQmsdLc0doMe64O/Sbuc2bVAbgMz6oP/6/pRStKRiVRV4bQfHp5AHYAKEGhEdVHTM+R3FDgi5w==} - motion-utils@12.23.2: - resolution: {integrity: sha512-cIEXlBlXAOUyiAtR0S+QPQUM9L3Diz23Bo+zM420NvSd/oPQJwg6U+rT+WRTpp0rizMsBGQOsAwhWIfglUcZfA==} + motion-utils@12.23.6: + resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} @@ -3637,13 +3630,13 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@15.3.5: - resolution: {integrity: sha512-RkazLBMMDJSJ4XZQ81kolSpwiCt907l0xcgcpF4xC2Vml6QVcPNXW0NQRwQ80FFtSn7UM52XN0anaw8TEJXaiw==} + next@15.4.1: + resolution: {integrity: sha512-eNKB1q8C7o9zXF8+jgJs2CzSLIU3T6bQtX6DcTnCq1sIR1CJ0GlSyRs1BubQi3/JgCnr9Vr+rS5mOMI38FFyQw==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 + '@playwright/test': ^1.51.1 babel-plugin-react-compiler: '*' react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 @@ -4158,10 +4151,6 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -4706,29 +4695,29 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.844.0': + '@aws-sdk/client-s3@3.846.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.844.0 - '@aws-sdk/credential-provider-node': 3.844.0 + '@aws-sdk/core': 3.846.0 + '@aws-sdk/credential-provider-node': 3.846.0 '@aws-sdk/middleware-bucket-endpoint': 3.840.0 '@aws-sdk/middleware-expect-continue': 3.840.0 - '@aws-sdk/middleware-flexible-checksums': 3.844.0 + '@aws-sdk/middleware-flexible-checksums': 3.846.0 '@aws-sdk/middleware-host-header': 3.840.0 '@aws-sdk/middleware-location-constraint': 3.840.0 '@aws-sdk/middleware-logger': 3.840.0 '@aws-sdk/middleware-recursion-detection': 3.840.0 - '@aws-sdk/middleware-sdk-s3': 3.844.0 + '@aws-sdk/middleware-sdk-s3': 3.846.0 '@aws-sdk/middleware-ssec': 3.840.0 - '@aws-sdk/middleware-user-agent': 3.844.0 + '@aws-sdk/middleware-user-agent': 3.846.0 '@aws-sdk/region-config-resolver': 3.840.0 - '@aws-sdk/signature-v4-multi-region': 3.844.0 + '@aws-sdk/signature-v4-multi-region': 3.846.0 '@aws-sdk/types': 3.840.0 - '@aws-sdk/util-endpoints': 3.844.0 + '@aws-sdk/util-endpoints': 3.845.0 '@aws-sdk/util-user-agent-browser': 3.840.0 - '@aws-sdk/util-user-agent-node': 3.844.0 + '@aws-sdk/util-user-agent-node': 3.846.0 '@aws-sdk/xml-builder': 3.821.0 '@smithy/config-resolver': 4.1.4 '@smithy/core': 3.7.0 @@ -4742,21 +4731,21 @@ snapshots: '@smithy/invalid-dependency': 4.0.4 '@smithy/md5-js': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.1.14 - '@smithy/middleware-retry': 4.1.15 + '@smithy/middleware-endpoint': 4.1.15 + '@smithy/middleware-retry': 4.1.16 '@smithy/middleware-serde': 4.0.8 '@smithy/middleware-stack': 4.0.4 '@smithy/node-config-provider': 4.1.3 '@smithy/node-http-handler': 4.1.0 '@smithy/protocol-http': 5.1.2 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 '@smithy/url-parser': 4.0.4 '@smithy/util-base64': 4.0.0 '@smithy/util-body-length-browser': 4.0.0 '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.22 - '@smithy/util-defaults-mode-node': 4.0.22 + '@smithy/util-defaults-mode-browser': 4.0.23 + '@smithy/util-defaults-mode-node': 4.0.23 '@smithy/util-endpoints': 3.0.6 '@smithy/util-middleware': 4.0.4 '@smithy/util-retry': 4.0.6 @@ -4769,41 +4758,41 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.844.0': + '@aws-sdk/client-sso@3.846.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/middleware-host-header': 3.840.0 '@aws-sdk/middleware-logger': 3.840.0 '@aws-sdk/middleware-recursion-detection': 3.840.0 - '@aws-sdk/middleware-user-agent': 3.844.0 + '@aws-sdk/middleware-user-agent': 3.846.0 '@aws-sdk/region-config-resolver': 3.840.0 '@aws-sdk/types': 3.840.0 - '@aws-sdk/util-endpoints': 3.844.0 + '@aws-sdk/util-endpoints': 3.845.0 '@aws-sdk/util-user-agent-browser': 3.840.0 - '@aws-sdk/util-user-agent-node': 3.844.0 + '@aws-sdk/util-user-agent-node': 3.846.0 '@smithy/config-resolver': 4.1.4 '@smithy/core': 3.7.0 '@smithy/fetch-http-handler': 5.1.0 '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.1.14 - '@smithy/middleware-retry': 4.1.15 + '@smithy/middleware-endpoint': 4.1.15 + '@smithy/middleware-retry': 4.1.16 '@smithy/middleware-serde': 4.0.8 '@smithy/middleware-stack': 4.0.4 '@smithy/node-config-provider': 4.1.3 '@smithy/node-http-handler': 4.1.0 '@smithy/protocol-http': 5.1.2 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 '@smithy/url-parser': 4.0.4 '@smithy/util-base64': 4.0.0 '@smithy/util-body-length-browser': 4.0.0 '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.22 - '@smithy/util-defaults-mode-node': 4.0.22 + '@smithy/util-defaults-mode-browser': 4.0.23 + '@smithy/util-defaults-mode-node': 4.0.23 '@smithy/util-endpoints': 3.0.6 '@smithy/util-middleware': 4.0.4 '@smithy/util-retry': 4.0.6 @@ -4812,7 +4801,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.844.0': + '@aws-sdk/core@3.846.0': dependencies: '@aws-sdk/types': 3.840.0 '@aws-sdk/xml-builder': 3.821.0 @@ -4821,7 +4810,7 @@ snapshots: '@smithy/property-provider': 4.0.4 '@smithy/protocol-http': 5.1.2 '@smithy/signature-v4': 5.1.2 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 '@smithy/util-base64': 4.0.0 '@smithy/util-body-length-browser': 4.0.0 @@ -4830,36 +4819,36 @@ snapshots: fast-xml-parser: 5.2.5 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.844.0': + '@aws-sdk/credential-provider-env@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/property-provider': 4.0.4 '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.844.0': + '@aws-sdk/credential-provider-http@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/fetch-http-handler': 5.1.0 '@smithy/node-http-handler': 4.1.0 '@smithy/property-provider': 4.0.4 '@smithy/protocol-http': 5.1.2 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 '@smithy/util-stream': 4.2.3 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.844.0': + '@aws-sdk/credential-provider-ini@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 - '@aws-sdk/credential-provider-env': 3.844.0 - '@aws-sdk/credential-provider-http': 3.844.0 - '@aws-sdk/credential-provider-process': 3.844.0 - '@aws-sdk/credential-provider-sso': 3.844.0 - '@aws-sdk/credential-provider-web-identity': 3.844.0 - '@aws-sdk/nested-clients': 3.844.0 + '@aws-sdk/core': 3.846.0 + '@aws-sdk/credential-provider-env': 3.846.0 + '@aws-sdk/credential-provider-http': 3.846.0 + '@aws-sdk/credential-provider-process': 3.846.0 + '@aws-sdk/credential-provider-sso': 3.846.0 + '@aws-sdk/credential-provider-web-identity': 3.846.0 + '@aws-sdk/nested-clients': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/credential-provider-imds': 4.0.6 '@smithy/property-provider': 4.0.4 @@ -4869,14 +4858,14 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.844.0': + '@aws-sdk/credential-provider-node@3.846.0': dependencies: - '@aws-sdk/credential-provider-env': 3.844.0 - '@aws-sdk/credential-provider-http': 3.844.0 - '@aws-sdk/credential-provider-ini': 3.844.0 - '@aws-sdk/credential-provider-process': 3.844.0 - '@aws-sdk/credential-provider-sso': 3.844.0 - '@aws-sdk/credential-provider-web-identity': 3.844.0 + '@aws-sdk/credential-provider-env': 3.846.0 + '@aws-sdk/credential-provider-http': 3.846.0 + '@aws-sdk/credential-provider-ini': 3.846.0 + '@aws-sdk/credential-provider-process': 3.846.0 + '@aws-sdk/credential-provider-sso': 3.846.0 + '@aws-sdk/credential-provider-web-identity': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/credential-provider-imds': 4.0.6 '@smithy/property-provider': 4.0.4 @@ -4886,20 +4875,20 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.844.0': + '@aws-sdk/credential-provider-process@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/property-provider': 4.0.4 '@smithy/shared-ini-file-loader': 4.0.4 '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.844.0': + '@aws-sdk/credential-provider-sso@3.846.0': dependencies: - '@aws-sdk/client-sso': 3.844.0 - '@aws-sdk/core': 3.844.0 - '@aws-sdk/token-providers': 3.844.0 + '@aws-sdk/client-sso': 3.846.0 + '@aws-sdk/core': 3.846.0 + '@aws-sdk/token-providers': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/property-provider': 4.0.4 '@smithy/shared-ini-file-loader': 4.0.4 @@ -4908,10 +4897,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.844.0': + '@aws-sdk/credential-provider-web-identity@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 - '@aws-sdk/nested-clients': 3.844.0 + '@aws-sdk/core': 3.846.0 + '@aws-sdk/nested-clients': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/property-provider': 4.0.4 '@smithy/types': 4.3.1 @@ -4936,12 +4925,12 @@ snapshots: '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.844.0': + '@aws-sdk/middleware-flexible-checksums@3.846.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/is-array-buffer': 4.0.0 '@smithy/node-config-provider': 4.1.3 @@ -4978,16 +4967,16 @@ snapshots: '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/middleware-sdk-s3@3.844.0': + '@aws-sdk/middleware-sdk-s3@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/types': 3.840.0 '@aws-sdk/util-arn-parser': 3.804.0 '@smithy/core': 3.7.0 '@smithy/node-config-provider': 4.1.3 '@smithy/protocol-http': 5.1.2 '@smithy/signature-v4': 5.1.2 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 '@smithy/util-config-provider': 4.0.0 '@smithy/util-middleware': 4.0.4 @@ -5001,51 +4990,51 @@ snapshots: '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.844.0': + '@aws-sdk/middleware-user-agent@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/types': 3.840.0 - '@aws-sdk/util-endpoints': 3.844.0 + '@aws-sdk/util-endpoints': 3.845.0 '@smithy/core': 3.7.0 '@smithy/protocol-http': 5.1.2 '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.844.0': + '@aws-sdk/nested-clients@3.846.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.844.0 + '@aws-sdk/core': 3.846.0 '@aws-sdk/middleware-host-header': 3.840.0 '@aws-sdk/middleware-logger': 3.840.0 '@aws-sdk/middleware-recursion-detection': 3.840.0 - '@aws-sdk/middleware-user-agent': 3.844.0 + '@aws-sdk/middleware-user-agent': 3.846.0 '@aws-sdk/region-config-resolver': 3.840.0 '@aws-sdk/types': 3.840.0 - '@aws-sdk/util-endpoints': 3.844.0 + '@aws-sdk/util-endpoints': 3.845.0 '@aws-sdk/util-user-agent-browser': 3.840.0 - '@aws-sdk/util-user-agent-node': 3.844.0 + '@aws-sdk/util-user-agent-node': 3.846.0 '@smithy/config-resolver': 4.1.4 '@smithy/core': 3.7.0 '@smithy/fetch-http-handler': 5.1.0 '@smithy/hash-node': 4.0.4 '@smithy/invalid-dependency': 4.0.4 '@smithy/middleware-content-length': 4.0.4 - '@smithy/middleware-endpoint': 4.1.14 - '@smithy/middleware-retry': 4.1.15 + '@smithy/middleware-endpoint': 4.1.15 + '@smithy/middleware-retry': 4.1.16 '@smithy/middleware-serde': 4.0.8 '@smithy/middleware-stack': 4.0.4 '@smithy/node-config-provider': 4.1.3 '@smithy/node-http-handler': 4.1.0 '@smithy/protocol-http': 5.1.2 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 '@smithy/url-parser': 4.0.4 '@smithy/util-base64': 4.0.0 '@smithy/util-body-length-browser': 4.0.0 '@smithy/util-body-length-node': 4.0.0 - '@smithy/util-defaults-mode-browser': 4.0.22 - '@smithy/util-defaults-mode-node': 4.0.22 + '@smithy/util-defaults-mode-browser': 4.0.23 + '@smithy/util-defaults-mode-node': 4.0.23 '@smithy/util-endpoints': 3.0.6 '@smithy/util-middleware': 4.0.4 '@smithy/util-retry': 4.0.6 @@ -5063,30 +5052,30 @@ snapshots: '@smithy/util-middleware': 4.0.4 tslib: 2.8.1 - '@aws-sdk/s3-request-presigner@3.844.0': + '@aws-sdk/s3-request-presigner@3.846.0': dependencies: - '@aws-sdk/signature-v4-multi-region': 3.844.0 + '@aws-sdk/signature-v4-multi-region': 3.846.0 '@aws-sdk/types': 3.840.0 '@aws-sdk/util-format-url': 3.840.0 - '@smithy/middleware-endpoint': 4.1.14 + '@smithy/middleware-endpoint': 4.1.15 '@smithy/protocol-http': 5.1.2 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/signature-v4-multi-region@3.844.0': + '@aws-sdk/signature-v4-multi-region@3.846.0': dependencies: - '@aws-sdk/middleware-sdk-s3': 3.844.0 + '@aws-sdk/middleware-sdk-s3': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/protocol-http': 5.1.2 '@smithy/signature-v4': 5.1.2 '@smithy/types': 4.3.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.844.0': + '@aws-sdk/token-providers@3.846.0': dependencies: - '@aws-sdk/core': 3.844.0 - '@aws-sdk/nested-clients': 3.844.0 + '@aws-sdk/core': 3.846.0 + '@aws-sdk/nested-clients': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/property-provider': 4.0.4 '@smithy/shared-ini-file-loader': 4.0.4 @@ -5104,7 +5093,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.844.0': + '@aws-sdk/util-endpoints@3.845.0': dependencies: '@aws-sdk/types': 3.840.0 '@smithy/types': 4.3.1 @@ -5130,9 +5119,9 @@ snapshots: bowser: 2.11.0 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.844.0': + '@aws-sdk/util-user-agent-node@3.846.0': dependencies: - '@aws-sdk/middleware-user-agent': 3.844.0 + '@aws-sdk/middleware-user-agent': 3.846.0 '@aws-sdk/types': 3.840.0 '@smithy/node-config-provider': 4.1.3 '@smithy/types': 4.3.1 @@ -5581,13 +5570,13 @@ snapshots: '@jest/console@30.0.4': dependencies: '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 chalk: 4.1.2 jest-message-util: 30.0.2 jest-util: 30.0.2 slash: 3.0.0 - '@jest/core@30.0.4(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3))': + '@jest/core@30.0.4(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3))': dependencies: '@jest/console': 30.0.4 '@jest/pattern': 30.0.1 @@ -5595,14 +5584,14 @@ snapshots: '@jest/test-result': 30.0.4 '@jest/transform': 30.0.4 '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.2.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.0.2 - jest-config: 30.0.4(@types/node@24.0.13)(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)) + jest-config: 30.0.4(@types/node@24.0.14)(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)) jest-haste-map: 30.0.2 jest-message-util: 30.0.2 jest-regex-util: 30.0.1 @@ -5633,7 +5622,7 @@ snapshots: '@jest/fake-timers': 30.0.4 '@jest/types': 30.0.1 '@types/jsdom': 21.1.7 - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-mock: 30.0.2 jest-util: 30.0.2 jsdom: 26.1.0 @@ -5642,7 +5631,7 @@ snapshots: dependencies: '@jest/fake-timers': 30.0.4 '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-mock: 30.0.2 '@jest/expect-utils@30.0.0': @@ -5664,7 +5653,7 @@ snapshots: dependencies: '@jest/types': 30.0.1 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-message-util: 30.0.2 jest-mock: 30.0.2 jest-util: 30.0.2 @@ -5684,12 +5673,12 @@ snapshots: '@jest/pattern@30.0.0': dependencies: - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-regex-util: 30.0.0 '@jest/pattern@30.0.1': dependencies: - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-regex-util: 30.0.1 '@jest/reporters@30.0.4': @@ -5700,7 +5689,7 @@ snapshots: '@jest/transform': 30.0.4 '@jest/types': 30.0.1 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 24.0.13 + '@types/node': 24.0.14 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit-x: 0.2.2 @@ -5781,7 +5770,7 @@ snapshots: '@jest/schemas': 30.0.0 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.0.13 + '@types/node': 24.0.14 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -5791,7 +5780,7 @@ snapshots: '@jest/schemas': 30.0.1 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.0.13 + '@types/node': 24.0.14 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -5824,41 +5813,41 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@next/bundle-analyzer@15.3.5': + '@next/bundle-analyzer@15.4.1': dependencies: webpack-bundle-analyzer: 4.10.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@next/env@15.3.5': {} + '@next/env@15.4.1': {} - '@next/eslint-plugin-next@15.3.5': + '@next/eslint-plugin-next@15.4.1': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.3.5': + '@next/swc-darwin-arm64@15.4.1': optional: true - '@next/swc-darwin-x64@15.3.5': + '@next/swc-darwin-x64@15.4.1': optional: true - '@next/swc-linux-arm64-gnu@15.3.5': + '@next/swc-linux-arm64-gnu@15.4.1': optional: true - '@next/swc-linux-arm64-musl@15.3.5': + '@next/swc-linux-arm64-musl@15.4.1': optional: true - '@next/swc-linux-x64-gnu@15.3.5': + '@next/swc-linux-x64-gnu@15.4.1': optional: true - '@next/swc-linux-x64-musl@15.3.5': + '@next/swc-linux-x64-musl@15.4.1': optional: true - '@next/swc-win32-arm64-msvc@15.3.5': + '@next/swc-win32-arm64-msvc@15.4.1': optional: true - '@next/swc-win32-x64-msvc@15.3.5': + '@next/swc-win32-x64-msvc@15.4.1': optional: true '@nodelib/fs.scandir@2.1.5': @@ -6351,7 +6340,7 @@ snapshots: '@smithy/types': 4.3.1 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.1.14': + '@smithy/middleware-endpoint@4.1.15': dependencies: '@smithy/core': 3.7.0 '@smithy/middleware-serde': 4.0.8 @@ -6362,12 +6351,12 @@ snapshots: '@smithy/util-middleware': 4.0.4 tslib: 2.8.1 - '@smithy/middleware-retry@4.1.15': + '@smithy/middleware-retry@4.1.16': dependencies: '@smithy/node-config-provider': 4.1.3 '@smithy/protocol-http': 5.1.2 '@smithy/service-error-classification': 4.0.6 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 '@smithy/util-middleware': 4.0.4 '@smithy/util-retry': 4.0.6 @@ -6441,10 +6430,10 @@ snapshots: '@smithy/util-utf8': 4.0.0 tslib: 2.8.1 - '@smithy/smithy-client@4.4.6': + '@smithy/smithy-client@4.4.7': dependencies: '@smithy/core': 3.7.0 - '@smithy/middleware-endpoint': 4.1.14 + '@smithy/middleware-endpoint': 4.1.15 '@smithy/middleware-stack': 4.0.4 '@smithy/protocol-http': 5.1.2 '@smithy/types': 4.3.1 @@ -6489,21 +6478,21 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.0.22': + '@smithy/util-defaults-mode-browser@4.0.23': dependencies: '@smithy/property-provider': 4.0.4 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 bowser: 2.11.0 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.0.22': + '@smithy/util-defaults-mode-node@4.0.23': dependencies: '@smithy/config-resolver': 4.1.4 '@smithy/credential-provider-imds': 4.0.6 '@smithy/node-config-provider': 4.1.3 '@smithy/property-provider': 4.0.4 - '@smithy/smithy-client': 4.4.6 + '@smithy/smithy-client': 4.4.7 '@smithy/types': 4.3.1 tslib: 2.8.1 @@ -6559,8 +6548,6 @@ snapshots: '@smithy/types': 4.3.1 tslib: 2.8.1 - '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -6734,7 +6721,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 24.0.13 + '@types/node': 24.0.14 '@types/tough-cookie': 4.0.5 parse5: 7.2.1 @@ -6742,13 +6729,13 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@24.0.13': + '@types/node@24.0.14': dependencies: undici-types: 7.8.0 '@types/pg@8.15.4': dependencies: - '@types/node': 24.0.13 + '@types/node': 24.0.14 pg-protocol: 1.10.0 pg-types: 2.2.0 @@ -6927,9 +6914,9 @@ snapshots: dependencies: uncrypto: 0.1.3 - '@vercel/analytics@1.5.0(next@15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + '@vercel/analytics@1.5.0(next@15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': optionalDependencies: - next: 15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 '@vercel/blob@1.1.1': @@ -6940,9 +6927,9 @@ snapshots: throttleit: 2.1.0 undici: 5.28.5 - '@vercel/speed-insights@1.2.0(next@15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': + '@vercel/speed-insights@1.2.0(next@15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)': optionalDependencies: - next: 15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 acorn-jsx@5.3.2(acorn@8.14.0): @@ -6963,7 +6950,7 @@ snapshots: agent-base@7.1.3: {} - ai@4.3.17(react@19.1.0)(zod@3.24.2): + ai@4.3.19(react@19.1.0)(zod@3.24.2): dependencies: '@ai-sdk/provider': 1.1.3 '@ai-sdk/provider-utils': 2.2.8(zod@3.24.2) @@ -7185,10 +7172,6 @@ snapshots: buffer-from@1.1.2: {} - busboy@1.6.0: - dependencies: - streamsearch: 1.1.0 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -7546,9 +7529,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@15.3.5(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3): + eslint-config-next@15.4.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.3.5 + '@next/eslint-plugin-next': 15.4.1 '@rushstack/eslint-patch': 1.10.5 '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/parser': 8.24.1(eslint@9.31.0(jiti@2.4.2))(typescript@5.8.3) @@ -7857,10 +7840,10 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - framer-motion@12.23.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + framer-motion@12.23.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - motion-dom: 12.23.2 - motion-utils: 12.23.2 + motion-dom: 12.23.6 + motion-utils: 12.23.6 tslib: 2.8.1 optionalDependencies: react: 19.1.0 @@ -8240,7 +8223,7 @@ snapshots: '@jest/expect': 30.0.4 '@jest/test-result': 30.0.4 '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0 @@ -8260,15 +8243,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.0.4(@types/node@24.0.13)(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)): + jest-cli@30.0.4(@types/node@24.0.14)(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)): dependencies: - '@jest/core': 30.0.4(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)) + '@jest/core': 30.0.4(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)) '@jest/test-result': 30.0.4 '@jest/types': 30.0.1 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.0.4(@types/node@24.0.13)(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)) + jest-config: 30.0.4(@types/node@24.0.14)(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)) jest-util: 30.0.2 jest-validate: 30.0.2 yargs: 17.7.2 @@ -8279,7 +8262,7 @@ snapshots: - supports-color - ts-node - jest-config@30.0.4(@types/node@24.0.13)(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)): + jest-config@30.0.4(@types/node@24.0.14)(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)): dependencies: '@babel/core': 7.27.4 '@jest/get-type': 30.0.1 @@ -8306,8 +8289,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 24.0.13 - ts-node: 10.9.2(@types/node@24.0.13)(typescript@5.8.3) + '@types/node': 24.0.14 + ts-node: 10.9.2(@types/node@24.0.14)(typescript@5.8.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -8343,7 +8326,7 @@ snapshots: '@jest/environment': 30.0.4 '@jest/environment-jsdom-abstract': 30.0.4(jsdom@26.1.0) '@types/jsdom': 21.1.7 - '@types/node': 24.0.13 + '@types/node': 24.0.14 jsdom: 26.1.0 transitivePeerDependencies: - bufferutil @@ -8355,7 +8338,7 @@ snapshots: '@jest/environment': 30.0.4 '@jest/fake-timers': 30.0.4 '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-mock: 30.0.2 jest-util: 30.0.2 jest-validate: 30.0.2 @@ -8363,7 +8346,7 @@ snapshots: jest-haste-map@30.0.2: dependencies: '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -8421,13 +8404,13 @@ snapshots: jest-mock@30.0.0: dependencies: '@jest/types': 30.0.0 - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-util: 30.0.0 jest-mock@30.0.2: dependencies: '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 jest-util: 30.0.2 jest-pnp-resolver@1.2.3(jest-resolve@30.0.2): @@ -8463,7 +8446,7 @@ snapshots: '@jest/test-result': 30.0.4 '@jest/transform': 30.0.4 '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -8492,7 +8475,7 @@ snapshots: '@jest/test-result': 30.0.4 '@jest/transform': 30.0.4 '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 chalk: 4.1.2 cjs-module-lexer: 2.1.0 collect-v8-coverage: 1.0.2 @@ -8539,7 +8522,7 @@ snapshots: jest-util@30.0.0: dependencies: '@jest/types': 30.0.0 - '@types/node': 24.0.13 + '@types/node': 24.0.14 chalk: 4.1.2 ci-info: 4.2.0 graceful-fs: 4.2.11 @@ -8548,7 +8531,7 @@ snapshots: jest-util@30.0.2: dependencies: '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 chalk: 4.1.2 ci-info: 4.2.0 graceful-fs: 4.2.11 @@ -8567,7 +8550,7 @@ snapshots: dependencies: '@jest/test-result': 30.0.4 '@jest/types': 30.0.1 - '@types/node': 24.0.13 + '@types/node': 24.0.14 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -8576,18 +8559,18 @@ snapshots: jest-worker@30.0.2: dependencies: - '@types/node': 24.0.13 + '@types/node': 24.0.14 '@ungap/structured-clone': 1.3.0 jest-util: 30.0.2 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.0.4(@types/node@24.0.13)(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)): + jest@30.0.4(@types/node@24.0.14)(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)): dependencies: - '@jest/core': 30.0.4(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)) + '@jest/core': 30.0.4(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)) '@jest/types': 30.0.1 import-local: 3.2.0 - jest-cli: 30.0.4(@types/node@24.0.13)(ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3)) + jest-cli: 30.0.4(@types/node@24.0.14)(ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -8807,11 +8790,11 @@ snapshots: mkdirp@3.0.1: {} - motion-dom@12.23.2: + motion-dom@12.23.6: dependencies: - motion-utils: 12.23.2 + motion-utils: 12.23.6 - motion-utils@12.23.2: {} + motion-utils@12.23.6: {} mrmime@2.0.1: {} @@ -8825,10 +8808,10 @@ snapshots: natural-compare@1.4.0: {} - next-auth@5.0.0-beta.29(next@15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): + next-auth@5.0.0-beta.29(next@15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): dependencies: '@auth/core': 0.40.0 - next: 15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react: 19.1.0 next-themes@0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0): @@ -8836,26 +8819,24 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) - next@15.3.5(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.4.1(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@next/env': 15.3.5 - '@swc/counter': 0.1.3 + '@next/env': 15.4.1 '@swc/helpers': 0.5.15 - busboy: 1.6.0 caniuse-lite: 1.0.30001700 postcss: 8.4.31 react: 19.1.0 react-dom: 19.1.0(react@19.1.0) styled-jsx: 5.1.6(@babel/core@7.27.4)(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.3.5 - '@next/swc-darwin-x64': 15.3.5 - '@next/swc-linux-arm64-gnu': 15.3.5 - '@next/swc-linux-arm64-musl': 15.3.5 - '@next/swc-linux-x64-gnu': 15.3.5 - '@next/swc-linux-x64-musl': 15.3.5 - '@next/swc-win32-arm64-msvc': 15.3.5 - '@next/swc-win32-x64-msvc': 15.3.5 + '@next/swc-darwin-arm64': 15.4.1 + '@next/swc-darwin-x64': 15.4.1 + '@next/swc-linux-arm64-gnu': 15.4.1 + '@next/swc-linux-arm64-musl': 15.4.1 + '@next/swc-linux-x64-gnu': 15.4.1 + '@next/swc-linux-x64-musl': 15.4.1 + '@next/swc-win32-arm64-msvc': 15.4.1 + '@next/swc-win32-x64-msvc': 15.4.1 '@opentelemetry/api': 1.9.0 sharp: 0.34.3 transitivePeerDependencies: @@ -9383,8 +9364,6 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 - streamsearch@1.1.0: {} - string-length@4.0.2: dependencies: char-regex: 1.0.2 @@ -9561,14 +9540,14 @@ snapshots: dependencies: sax: 1.2.4 - ts-node@10.9.2(@types/node@24.0.13)(typescript@5.8.3): + ts-node@10.9.2(@types/node@24.0.14)(typescript@5.8.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 24.0.13 + '@types/node': 24.0.14 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 diff --git a/src/admin/AddUploadButton.tsx b/src/admin/AddUploadButton.tsx index 6590fd1f..297ceaca 100644 --- a/src/admin/AddUploadButton.tsx +++ b/src/admin/AddUploadButton.tsx @@ -4,7 +4,7 @@ import { generateLocalNaivePostgresString, generateLocalPostgresString, } from '@/utility/date'; -import { pathForAdminUploadUrl } from '@/app/paths'; +import { pathForAdminUploadUrl } from '@/app/path'; import { useRouter } from 'next/navigation'; import { ComponentProps, useState } from 'react'; import IconAddUpload from '@/components/icons/IconAddUpload'; diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx index 2e4d4bce..a0130339 100644 --- a/src/admin/AdminAppMenu.tsx +++ b/src/admin/AdminAppMenu.tsx @@ -1,6 +1,5 @@ 'use client'; -import MoreMenu from '@/components/more/MoreMenu'; import { PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS, @@ -10,7 +9,7 @@ import { PATH_ADMIN_TAGS, PATH_ADMIN_UPLOADS, PATH_GRID_INFERRED, -} from '@/app/paths'; +} from '@/app/path'; import { useAppState } from '@/app/AppState'; import { IoArrowDown, IoArrowUp, IoCloseSharp } from 'react-icons/io5'; import { clsx } from 'clsx/lite'; @@ -30,19 +29,15 @@ import InsightsIndicatorDot from './insights/InsightsIndicatorDot'; import MoreMenuItem from '@/components/more/MoreMenuItem'; import Spinner from '@/components/Spinner'; import { useAppText } from '@/i18n/state/client'; +import SwitcherItemMenu from '@/components/switcher/SwitcherItemMenu'; +import { MoreMenuSection } from '@/components/more/MoreMenu'; export default function AdminAppMenu({ - active, - animateMenuClose, isOpen, setIsOpen, - className, }: { - active?: boolean - animateMenuClose?: boolean isOpen?: boolean setIsOpen?: (isOpen: boolean) => void - className?: string }) { const { photosCountTotal = 0, @@ -66,19 +61,18 @@ export default function AdminAppMenu({ const showAppInsightsLink = photosCountTotal > 0 && !isAltPressed; - const sectionUpload: ComponentProps[] = - useMemo(() => ([{ - label: appText.admin.uploadPhotos, - icon: , - annotation: isLoadingAdminData && - , - action: startUpload, - }]), [appText, isLoadingAdminData, startUpload]); + const sectionUpload: MoreMenuSection = useMemo(() => ({ items: [{ + label: appText.admin.uploadPhotos, + icon: , + annotation: isLoadingAdminData && + , + action: startUpload, + }]}), [appText, isLoadingAdminData, startUpload]); - const sectionMain: ComponentProps[] = useMemo(() => { + const sectionMain: MoreMenuSection = useMemo(() => { const items: ComponentProps[] = []; if (uploadsCount) { @@ -188,7 +182,7 @@ export default function AdminAppMenu({ : PATH_ADMIN_CONFIGURATION, }); - return items; + return { items }; }, [ appText, isSelecting, @@ -201,28 +195,24 @@ export default function AdminAppMenu({ uploadsCount, ]); - const sectionSignOut: ComponentProps[] = - useMemo(() => ([{ + const sectionSignOut: MoreMenuSection = useMemo(() => ({ + items: [{ label: appText.auth.signOut, icon: , action: () => signOutAction().then(clearAuthStateAndRedirectIfNecessary), - }]), [appText.auth.signOut, clearAuthStateAndRedirectIfNecessary]); + }], + }), [appText.auth.signOut, clearAuthStateAndRedirectIfNecessary]); const sections = useMemo(() => [sectionUpload, sectionMain, sectionSignOut] , [sectionUpload, sectionMain, sectionSignOut]); return ( - + icon={
@@ -233,28 +223,12 @@ export default function AdminAppMenu({ sideOffset={12} alignOffset={-84} onOpen={refreshAdminData} - className={clsx( - 'outline-medium', - className, - )} - classNameButton={clsx( - 'p-0!', - 'w-full h-full', - 'flex items-center justify-center', - 'hover:bg-transparent dark:hover:bg-transparent', - 'active:bg-transparent dark:active:bg-transparent', - 'rounded-none focus:outline-none', - active - ? 'text-black dark:text-white' - : 'text-gray-400 dark:text-gray-600', - )} - classNameButtonOpen={clsx( - 'bg-dim text-main!', - '[&>*>*]:translate-y-[6px]', - !animateMenuClose && '[&>*>*]:duration-300', - )} sections={sections} ariaLabel="Admin Menu" + classNameButtonOpen={clsx( + '[&>*>*]:translate-y-[6px]', + '[&>*>*]:duration-300', + )} /> ); } diff --git a/src/admin/AdminBatchEditPanelClient.tsx b/src/admin/AdminBatchEditPanelClient.tsx index ca771963..c8894049 100644 --- a/src/admin/AdminBatchEditPanelClient.tsx +++ b/src/admin/AdminBatchEditPanelClient.tsx @@ -9,7 +9,7 @@ import { IoCloseSharp } from 'react-icons/io5'; import { useEffect, useRef, useState } from 'react'; import { TAG_FAVS, Tags } from '@/tag'; import { usePathname } from 'next/navigation'; -import { PATH_GRID_INFERRED } from '@/app/paths'; +import { PATH_GRID_INFERRED } from '@/app/path'; import PhotoTagFieldset from './PhotoTagFieldset'; import { tagMultiplePhotosAction } from '@/photo/actions'; import { toastSuccess } from '@/toast'; diff --git a/src/admin/AdminBatchUploadActions.tsx b/src/admin/AdminBatchUploadActions.tsx index f6dbacb3..3f80a718 100644 --- a/src/admin/AdminBatchUploadActions.tsx +++ b/src/admin/AdminBatchUploadActions.tsx @@ -4,7 +4,7 @@ import ErrorNote from '@/components/ErrorNote'; import FieldsetWithStatus from '@/components/FieldsetWithStatus'; import Container from '@/components/Container'; import { addUploadsAction } from '@/photo/actions'; -import { PATH_ADMIN_PHOTOS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS } from '@/app/path'; import { Tags } from '@/tag'; import { generateLocalNaivePostgresString, diff --git a/src/admin/AdminInfoNav.tsx b/src/admin/AdminInfoNav.tsx index 4318507a..d02e807b 100644 --- a/src/admin/AdminInfoNav.tsx +++ b/src/admin/AdminInfoNav.tsx @@ -1,6 +1,6 @@ 'use client'; -import { PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS } from '@/app/paths'; +import { PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS } from '@/app/path'; import ResponsiveText from '@/components/primitives/ResponsiveText'; import clsx from 'clsx/lite'; import ClearCacheButton from '@/admin/ClearCacheButton'; diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx index e7268a9c..f51bd725 100644 --- a/src/admin/AdminNav.tsx +++ b/src/admin/AdminNav.tsx @@ -10,7 +10,7 @@ import { PATH_ADMIN_RECIPES, PATH_ADMIN_TAGS, PATH_ADMIN_UPLOADS, -} from '@/app/paths'; +} from '@/app/path'; import AdminNavClient from './AdminNavClient'; import { getAppText } from '@/i18n/state/server'; diff --git a/src/admin/AdminNavClient.tsx b/src/admin/AdminNavClient.tsx index 19b21e72..e10d6081 100644 --- a/src/admin/AdminNavClient.tsx +++ b/src/admin/AdminNavClient.tsx @@ -10,7 +10,7 @@ import { checkPathPrefix, isPathAdminInfo, isPathTopLevelAdmin, -} from '@/app/paths'; +} from '@/app/path'; import { useAppState } from '@/app/AppState'; import { clsx } from 'clsx/lite'; import { differenceInMinutes } from 'date-fns'; diff --git a/src/admin/AdminPhotoMenu.tsx b/src/admin/AdminPhotoMenu.tsx index 4f6ec13e..a291945c 100644 --- a/src/admin/AdminPhotoMenu.tsx +++ b/src/admin/AdminPhotoMenu.tsx @@ -6,7 +6,7 @@ import { PATH_ROOT, pathForAdminPhotoEdit, pathForTag, -} from '@/app/paths'; +} from '@/app/path'; import { deletePhotoAction, syncPhotoAction, @@ -21,7 +21,7 @@ import { import { isPathFavs, isPhotoFav, TAG_PRIVATE } from '@/tag'; import { usePathname } from 'next/navigation'; import { BiTrash } from 'react-icons/bi'; -import MoreMenu from '@/components/more/MoreMenu'; +import MoreMenu, { MoreMenuSection } from '@/components/more/MoreMenu'; import { useAppState } from '@/app/AppState'; import { RevalidatePhoto } from '@/photo/InfinitePhotoScroll'; import { MdOutlineFileDownload } from 'react-icons/md'; @@ -139,7 +139,7 @@ export default function AdminPhotoMenu({ ...showKeyCommands && { keyCommand: KEY_COMMANDS.sync }, }); - return items; + return { items }; }, [ appText, photo, @@ -151,31 +151,33 @@ export default function AdminPhotoMenu({ revalidatePhoto, ]); - const sectionDelete: ComponentProps[] = useMemo(() => [{ - label: appText.admin.delete, - icon: , - className: 'text-error *:hover:text-error', - color: 'red', - action: () => { - if (confirm(deleteConfirmationTextForPhoto(photo, appText))) { - return deletePhotoAction( - photo.id, - photo.url, - shouldRedirectDelete, - ).then(() => { - revalidatePhoto?.(photo.id, true); - registerAdminUpdate?.(); - }); - } - }, - ...showKeyCommands && { - keyCommandModifier: KEY_COMMANDS.delete[0], - keyCommand: KEY_COMMANDS.delete[1], - }, - }], [ + const sectionDelete: MoreMenuSection = useMemo(() => ({ + items: [{ + label: appText.admin.delete, + icon: , + className: 'text-error *:hover:text-error', + color: 'red', + action: () => { + if (confirm(deleteConfirmationTextForPhoto(photo, appText))) { + return deletePhotoAction( + photo.id, + photo.url, + shouldRedirectDelete, + ).then(() => { + revalidatePhoto?.(photo.id, true); + registerAdminUpdate?.(); + }); + } + }, + ...showKeyCommands && { + keyCommandModifier: KEY_COMMANDS.delete[0], + keyCommand: KEY_COMMANDS.delete[1], + }, + }], + }), [ appText, photo, showKeyCommands, diff --git a/src/admin/AdminPhotosClient.tsx b/src/admin/AdminPhotosClient.tsx index fb707812..a631c1fa 100644 --- a/src/admin/AdminPhotosClient.tsx +++ b/src/admin/AdminPhotosClient.tsx @@ -5,7 +5,7 @@ import AppGrid from '@/components/AppGrid'; import AdminPhotosTable from '@/admin/AdminPhotosTable'; import AdminPhotosTableInfinite from '@/admin/AdminPhotosTableInfinite'; import PathLoaderButton from '@/components/primitives/PathLoaderButton'; -import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/path'; import { Photo } from '@/photo'; import { StorageListResponse } from '@/platforms/storage'; import AdminUploadsTable from './AdminUploadsTable'; diff --git a/src/admin/AdminPhotosSyncClient.tsx b/src/admin/AdminPhotosSyncClient.tsx index 5f37eb82..248a188e 100644 --- a/src/admin/AdminPhotosSyncClient.tsx +++ b/src/admin/AdminPhotosSyncClient.tsx @@ -5,7 +5,7 @@ import AdminPhotosTable from '@/admin/AdminPhotosTable'; import IconGrSync from '@/components/icons/IconGrSync'; import Note from '@/components/Note'; import AdminChildPage from '@/components/AdminChildPage'; -import { PATH_ADMIN_PHOTOS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS } from '@/app/path'; import { useMemo, useRef, useState } from 'react'; import { syncPhotosAction } from '@/photo/actions'; import { useRouter } from 'next/navigation'; diff --git a/src/admin/AdminPhotosTable.tsx b/src/admin/AdminPhotosTable.tsx index da305585..820cc213 100644 --- a/src/admin/AdminPhotosTable.tsx +++ b/src/admin/AdminPhotosTable.tsx @@ -5,7 +5,7 @@ import AdminTable from './AdminTable'; import { Fragment } from 'react'; import PhotoSmall from '@/photo/PhotoSmall'; import { clsx } from 'clsx/lite'; -import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/paths'; +import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/path'; import Link from 'next/link'; import PhotoDate from '@/photo/PhotoDate'; import EditButton from './EditButton'; diff --git a/src/admin/AdminPhotosTableInfinite.tsx b/src/admin/AdminPhotosTableInfinite.tsx index c4ae655c..581fa67c 100644 --- a/src/admin/AdminPhotosTableInfinite.tsx +++ b/src/admin/AdminPhotosTableInfinite.tsx @@ -1,6 +1,6 @@ 'use client'; -import { PATH_ADMIN_PHOTOS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS } from '@/app/path'; import InfinitePhotoScroll from '../photo/InfinitePhotoScroll'; import AdminPhotosTable from './AdminPhotosTable'; import { ComponentProps } from 'react'; diff --git a/src/admin/AdminRecipeForm.tsx b/src/admin/AdminRecipeForm.tsx index 36a894fc..f5d797ed 100644 --- a/src/admin/AdminRecipeForm.tsx +++ b/src/admin/AdminRecipeForm.tsx @@ -2,7 +2,7 @@ import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import Link from 'next/link'; -import { PATH_ADMIN_RECIPES } from '@/app/paths'; +import { PATH_ADMIN_RECIPES } from '@/app/path'; import FieldsetWithStatus from '@/components/FieldsetWithStatus'; import { ReactNode, useMemo, useState } from 'react'; import { renamePhotoRecipeGloballyAction } from '@/photo/actions'; diff --git a/src/admin/AdminRecipeTable.tsx b/src/admin/AdminRecipeTable.tsx index d0e6de2d..31814301 100644 --- a/src/admin/AdminRecipeTable.tsx +++ b/src/admin/AdminRecipeTable.tsx @@ -5,7 +5,7 @@ import { Fragment } from 'react'; import DeleteFormButton from '@/admin/DeleteFormButton'; import { photoQuantityText } from '@/photo'; import EditButton from '@/admin/EditButton'; -import { pathForAdminRecipeEdit } from '@/app/paths'; +import { pathForAdminRecipeEdit } from '@/app/path'; import { clsx } from 'clsx/lite'; import { formatRecipe, Recipes, sortRecipes } from '@/recipe'; import AdminRecipeBadge from './AdminRecipeBadge'; diff --git a/src/admin/AdminTagForm.tsx b/src/admin/AdminTagForm.tsx index 3f067e8a..b62f5e78 100644 --- a/src/admin/AdminTagForm.tsx +++ b/src/admin/AdminTagForm.tsx @@ -2,7 +2,7 @@ import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import Link from 'next/link'; -import { PATH_ADMIN_TAGS } from '@/app/paths'; +import { PATH_ADMIN_TAGS } from '@/app/path'; import FieldsetWithStatus from '@/components/FieldsetWithStatus'; import { ReactNode, useMemo, useState } from 'react'; import { renamePhotoTagGloballyAction } from '@/photo/actions'; diff --git a/src/admin/AdminTagTable.tsx b/src/admin/AdminTagTable.tsx index 75b4734f..260a7676 100644 --- a/src/admin/AdminTagTable.tsx +++ b/src/admin/AdminTagTable.tsx @@ -6,7 +6,7 @@ import DeleteFormButton from '@/admin/DeleteFormButton'; import { photoQuantityText } from '@/photo'; import { Tags, formatTag, sortTags } from '@/tag'; import EditButton from '@/admin/EditButton'; -import { pathForAdminTagEdit } from '@/app/paths'; +import { pathForAdminTagEdit } from '@/app/path'; import { clsx } from 'clsx/lite'; import AdminTagBadge from './AdminTagBadge'; import { getAppText } from '@/i18n/state/server'; diff --git a/src/admin/AdminUploadsTableRow.tsx b/src/admin/AdminUploadsTableRow.tsx index bf2dda94..090432e8 100644 --- a/src/admin/AdminUploadsTableRow.tsx +++ b/src/admin/AdminUploadsTableRow.tsx @@ -8,7 +8,7 @@ import clsx from 'clsx/lite'; import ResponsiveDate from '@/components/ResponsiveDate'; import Spinner from '@/components/Spinner'; import { FaRegCircleCheck } from 'react-icons/fa6'; -import { pathForAdminUploadUrl } from '@/app/paths'; +import { pathForAdminUploadUrl } from '@/app/path'; import DeleteUploadButton from './DeleteUploadButton'; import { Dispatch, SetStateAction, useEffect, useRef } from 'react'; import { isElementEntirelyInViewport } from '@/utility/dom'; diff --git a/src/admin/DeleteUploadButton.tsx b/src/admin/DeleteUploadButton.tsx index 80115375..f56b6b94 100644 --- a/src/admin/DeleteUploadButton.tsx +++ b/src/admin/DeleteUploadButton.tsx @@ -3,7 +3,7 @@ import { deleteUploadsAction } from '@/photo/actions'; import DeleteButton from './DeleteButton'; import { useRouter } from 'next/navigation'; -import { PATH_ADMIN_PHOTOS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS } from '@/app/path'; import { ComponentProps, useState } from 'react'; import LoaderButton from '@/components/primitives/LoaderButton'; diff --git a/src/admin/config/AdminAppConfigurationClient.tsx b/src/admin/config/AdminAppConfigurationClient.tsx index b82c0c94..a4e54b68 100644 --- a/src/admin/config/AdminAppConfigurationClient.tsx +++ b/src/admin/config/AdminAppConfigurationClient.tsx @@ -23,8 +23,8 @@ import { DEFAULT_CATEGORY_KEYS, getHiddenCategories } from '@/category'; import { AI_AUTO_GENERATED_FIELDS_ALL } from '@/photo/ai'; import clsx from 'clsx/lite'; import Link from 'next/link'; -import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/paths'; -import { APP_DEFAULT_SORT_BY, SORT_BY_OPTIONS } from '@/photo/db/sort'; +import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/path'; +import { APP_DEFAULT_SORT_BY, SORT_BY_OPTIONS } from '@/photo/sort'; import { AdminConfigSection, ConfigSectionKey, @@ -86,7 +86,8 @@ export default function AdminAppConfigurationClient({ hasDefaultSortBy, defaultSortBy, isSortWithPriority, - showSortControl, + hasNavSortControl, + navSortControl, // Display showKeyboardShortcutTooltips, showExifInfo, @@ -605,7 +606,7 @@ export default function AdminAppConfigurationClient({ case 'Sorting': return <> @@ -634,13 +635,13 @@ export default function AdminAppConfigurationClient({ {renderEnvVars(['NEXT_PUBLIC_PRIORITY_BASED_SORTING'])} - Set environment variable to {'"1"'} to - show sort control in desktop nav on grid/full homepages: - {renderEnvVars(['NEXT_PUBLIC_SHOW_SORT_CONTROL'])} + Set environment variable to {'"none"'}, {'"toggle"'} (default), + or {'"menu"'}, to control sort UI on grid/full homepages: + {renderEnvVars(['NEXT_PUBLIC_NAV_SORT_CONTROL'])} ; case 'Display': diff --git a/src/admin/insights/AdminAppInsightsClient.tsx b/src/admin/insights/AdminAppInsightsClient.tsx index c1db3885..27ef8f9e 100644 --- a/src/admin/insights/AdminAppInsightsClient.tsx +++ b/src/admin/insights/AdminAppInsightsClient.tsx @@ -27,7 +27,7 @@ import { import EnvVar from '@/components/EnvVar'; import { IoSyncCircle } from 'react-icons/io5'; import clsx from 'clsx/lite'; -import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS_UPDATES } from '@/app/path'; import { LiaBroomSolid } from 'react-icons/lia'; import { IoMdGrid } from 'react-icons/io'; import { RiSpeedMiniLine } from 'react-icons/ri'; diff --git a/src/app/AppStateProvider.tsx b/src/app/AppStateProvider.tsx index bbdacce1..16955aaa 100644 --- a/src/app/AppStateProvider.tsx +++ b/src/app/AppStateProvider.tsx @@ -27,7 +27,7 @@ import { getAuthEmailCookie, } from '@/auth'; import { useRouter, usePathname } from 'next/navigation'; -import { isPathProtected, PATH_ROOT } from '@/app/paths'; +import { isPathProtected, PATH_ROOT } from '@/app/path'; import { INITIAL_UPLOAD_STATE, UploadState } from '@/admin/upload'; import { RecipeProps } from '@/recipe'; import { nanoid } from 'nanoid'; diff --git a/src/app/AppViewSwitcher.tsx b/src/app/AppViewSwitcher.tsx index 5c22f966..1efc52c8 100644 --- a/src/app/AppViewSwitcher.tsx +++ b/src/app/AppViewSwitcher.tsx @@ -1,34 +1,37 @@ -import Switcher from '@/components/Switcher'; -import SwitcherItem from '@/components/SwitcherItem'; +import Switcher from '@/components/switcher/Switcher'; +import SwitcherItem from '@/components/switcher/SwitcherItem'; import IconFull from '@/components/icons/IconFull'; import IconGrid from '@/components/icons/IconGrid'; import { doesPathOfferSort, PATH_FULL_INFERRED, PATH_GRID_INFERRED, -} from '@/app/paths'; +} from '@/app/path'; import IconSearch from '../components/icons/IconSearch'; import { useAppState } from '@/app/AppState'; import { GRID_HOMEPAGE_ENABLED, SHOW_KEYBOARD_SHORTCUT_TOOLTIPS, - SHOW_SORT_CONTROL, + NAV_SORT_CONTROL, } from './config'; import AdminAppMenu from '@/admin/AdminAppMenu'; import Spinner from '@/components/Spinner'; import clsx from 'clsx/lite'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import useKeydownHandler from '@/utility/useKeydownHandler'; import { usePathname } from 'next/navigation'; import { KEY_COMMANDS } from '@/photo/key-commands'; import { useAppText } from '@/i18n/state/client'; import IconSort from '@/components/icons/IconSort'; -import { getSortConfigFromPath } from '@/photo/db/sort-path'; +import { getSortConfigFromPath } from '@/photo/sort/path'; import { motion } from 'framer-motion'; +import SortMenu from '@/photo/sort/SortMenu'; +import { SWR_KEYS } from '@/swr'; export type SwitcherSelection = 'full' | 'grid' | 'admin'; -const GAP_CLASS = 'mr-1.5 sm:mr-2'; +const GAP_CLASS_RIGHT = 'mr-1.5 sm:mr-2'; +const GAP_CLASS_LEFT = 'ml-0.5 sm:ml-1'; export default function AppViewSwitcher({ currentSelection, @@ -50,20 +53,26 @@ export default function AppViewSwitcher({ invalidateSwr, } = useAppState(); - const showSortControl = SHOW_SORT_CONTROL && doesPathOfferSort(pathname); + const sortConfig = useMemo(() => getSortConfigFromPath(pathname), [pathname]); + const { sortBy, + isSortedByDefault, isAscending, pathGrid, pathFull, - pathSort, - } = getSortConfigFromPath(pathname); + pathSortToggle, + } = sortConfig; + + const showSortControl = + NAV_SORT_CONTROL !== 'none' && + doesPathOfferSort(pathname); const hasLoadedRef = useRef(false); useEffect(() => { if (hasLoadedRef.current) { // After initial load, invalidate cache every time sort changes - invalidateSwr?.('INFINITE_PHOTO_SCROLL'); + invalidateSwr?.(SWR_KEYS.INFINITE_PHOTO_SCROLL); } hasLoadedRef.current = true; }, [invalidateSwr, sortBy]); @@ -88,6 +97,7 @@ export default function AppViewSwitcher({ }, [pathname, isUserSignedIn]); useKeydownHandler({ onKeyDown }); + const [isSortMenuOpen, setIsSortMenuOpen] = useState(false); const [isAdminMenuOpen, setIsAdminMenuOpen] = useState(false); const renderItemFull = @@ -120,7 +130,7 @@ export default function AppViewSwitcher({
{ + setIsAdminMenuOpen(isOpen); + if (isOpen) { setIsSortMenuOpen(false); } + }} />} tooltip={{ ...!isAdminMenuOpen && SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && { @@ -162,19 +175,49 @@ export default function AppViewSwitcher({ exit={{ opacity: 0, scale: 0.5 }} transition={{ duration: 0.2, ease: 'easeInOut' }} > - - + {NAV_SORT_CONTROL === 'menu' + ? { + setIsSortMenuOpen(isOpen); + if (isOpen) { setIsAdminMenuOpen(false); } + }} + />} + tooltip={{ + ...!isSortMenuOpen && SHOW_KEYBOARD_SHORTCUT_TOOLTIPS && { + content: 'Sort', + }, + }} + width="narrow" + noPadding + /> + : } + tooltip={{ + content: isAscending + ? appText.sort.newest + : appText.sort.oldest, + }} + width="narrow" + noPadding />} - tooltip={{ - content: isAscending - ? appText.sort.newest - : appText.sort.oldest, - }} - /> } diff --git a/src/app/Footer.tsx b/src/app/Footer.tsx index 41b4161f..bc18a0c6 100644 --- a/src/app/Footer.tsx +++ b/src/app/Footer.tsx @@ -7,7 +7,7 @@ import Link from 'next/link'; import { SHOW_REPO_LINK } from '@/app/config'; import RepoLink from '../components/RepoLink'; import { usePathname } from 'next/navigation'; -import { PATH_ADMIN_PHOTOS, isPathAdmin, isPathSignIn } from './paths'; +import { PATH_ADMIN_PHOTOS, isPathAdmin, isPathSignIn } from './path'; import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import { signOutAction } from '@/auth/actions'; import AnimateItems from '@/components/AnimateItems'; diff --git a/src/app/Nav.tsx b/src/app/Nav.tsx index ca019be1..82ea75c1 100644 --- a/src/app/Nav.tsx +++ b/src/app/Nav.tsx @@ -12,7 +12,7 @@ import { isPathGrid, isPathProtected, isPathSignIn, -} from '@/app/paths'; +} from '@/app/path'; import AnimateItems from '../components/AnimateItems'; import { GRID_HOMEPAGE_ENABLED, diff --git a/src/app/ThemeSwitcher.tsx b/src/app/ThemeSwitcher.tsx index 777318a8..b1869b62 100644 --- a/src/app/ThemeSwitcher.tsx +++ b/src/app/ThemeSwitcher.tsx @@ -2,8 +2,8 @@ import { useState, useEffect } from 'react'; import { useTheme } from 'next-themes'; -import Switcher from '@/components/Switcher'; -import SwitcherItem from '@/components/SwitcherItem'; +import Switcher from '@/components/switcher/Switcher'; +import SwitcherItem from '@/components/switcher/SwitcherItem'; import { BiDesktop, BiMoon, BiSun } from 'react-icons/bi'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/app/config.ts b/src/app/config.ts index c3d7a6ba..ec8e56aa 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -8,7 +8,7 @@ import { makeUrlAbsolute, shortenUrl, } from '@/utility/url'; -import { getSortByFromString } from '@/photo/db/sort'; +import { getNavSortControlFromString, getSortByFromString } from '@/photo/sort'; // HARD-CODED GLOBAL CONFIGURATION @@ -280,8 +280,8 @@ export const USER_DEFAULT_SORT_OPTIONS = { sortBy: USER_DEFAULT_SORT_BY, sortWithPriority: USER_DEFAULT_SORT_WITH_PRIORITY, }; -export const SHOW_SORT_CONTROL = - process.env.NEXT_PUBLIC_SHOW_SORT_CONTROL === '1'; +export const NAV_SORT_CONTROL = + getNavSortControlFromString(process.env.NEXT_PUBLIC_NAV_SORT_CONTROL); // DISPLAY @@ -422,7 +422,8 @@ export const APP_CONFIGURATION = { hasDefaultSortBy: Boolean(process.env.NEXT_PUBLIC_DEFAULT_SORT), defaultSortBy: USER_DEFAULT_SORT_BY, isSortWithPriority: USER_DEFAULT_SORT_WITH_PRIORITY, - showSortControl: SHOW_SORT_CONTROL, + hasNavSortControl: Boolean(process.env.NEXT_PUBLIC_NAV_SORT_CONTROL), + navSortControl: NAV_SORT_CONTROL, // Display showKeyboardShortcutTooltips: SHOW_KEYBOARD_SHORTCUT_TOOLTIPS, showExifInfo: SHOW_EXIF_DATA, diff --git a/src/app/paths.ts b/src/app/path.ts similarity index 98% rename from src/app/paths.ts rename to src/app/path.ts index fe84d527..57c7379d 100644 --- a/src/app/paths.ts +++ b/src/app/path.ts @@ -24,10 +24,10 @@ export const PATH_FULL_INFERRED = GRID_HOMEPAGE_ENABLED : PATH_ROOT; // Sort -export const PARAM_SORT_TYPE_TAKEN_AT = 'taken-at'; -export const PARAM_SORT_TYPE_UPLOADED_AT = 'uploaded-at'; -export const PARAM_SORT_ORDER_NEWEST = 'newest-first'; -export const PARAM_SORT_ORDER_OLDEST = 'oldest-first'; +export const PARAM_SORT_TYPE_TAKEN_AT = 'taken-at'; +export const PARAM_SORT_TYPE_UPLOADED_AT = 'uploaded-at'; +export const PARAM_SORT_ORDER_NEWEST = 'newest-first'; +export const PARAM_SORT_ORDER_OLDEST = 'oldest-first'; export const doesPathOfferSort = (pathname: string) => pathname === PATH_ROOT || pathname.startsWith(PATH_GRID) || diff --git a/src/auth/SignInForm.tsx b/src/auth/SignInForm.tsx index 7f137f6f..14538f99 100644 --- a/src/auth/SignInForm.tsx +++ b/src/auth/SignInForm.tsx @@ -19,7 +19,7 @@ import { import { useSearchParams } from 'next/navigation'; import { useAppState } from '@/app/AppState'; import { clsx } from 'clsx/lite'; -import { PATH_ADMIN_PHOTOS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS } from '@/app/path'; import IconLock from '@/components/icons/IconLock'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/auth/server.ts b/src/auth/server.ts index 4a8b9a1b..33758db2 100644 --- a/src/auth/server.ts +++ b/src/auth/server.ts @@ -1,4 +1,4 @@ -import { isPathProtected } from '@/app/paths'; +import { isPathProtected } from '@/app/path'; import NextAuth, { User } from 'next-auth'; import Credentials from 'next-auth/providers/credentials'; diff --git a/src/camera/CameraOGTile.tsx b/src/camera/CameraOGTile.tsx index b4a2d0f5..239b2117 100644 --- a/src/camera/CameraOGTile.tsx +++ b/src/camera/CameraOGTile.tsx @@ -1,7 +1,7 @@ 'use client'; import { Photo, PhotoDateRange } from '@/photo'; -import { pathForCamera, pathForCameraImage } from '@/app/paths'; +import { pathForCamera, pathForCameraImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { Camera } from '.'; import { descriptionForCameraPhotos, titleForCamera } from './meta'; diff --git a/src/camera/CameraShareModal.tsx b/src/camera/CameraShareModal.tsx index 8c252c3e..d3d095b2 100644 --- a/src/camera/CameraShareModal.tsx +++ b/src/camera/CameraShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForCamera } from '@/app/paths'; +import { absolutePathForCamera } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import CameraOGTile from './CameraOGTile'; diff --git a/src/camera/PhotoCamera.tsx b/src/camera/PhotoCamera.tsx index 60bc82d0..8a6ec2bc 100644 --- a/src/camera/PhotoCamera.tsx +++ b/src/camera/PhotoCamera.tsx @@ -1,7 +1,7 @@ 'use client'; import { AiFillApple } from 'react-icons/ai'; -import { pathForCamera } from '@/app/paths'; +import { pathForCamera } from '@/app/path'; import { Camera, formatCameraText } from '.'; import EntityLink, { EntityLinkExternalProps, diff --git a/src/camera/meta.ts b/src/camera/meta.ts index aff2a116..27cc9e56 100644 --- a/src/camera/meta.ts +++ b/src/camera/meta.ts @@ -8,7 +8,7 @@ import { Camera, cameraFromPhoto, formatCameraText } from '.'; import { absolutePathForCamera, absolutePathForCameraImage, -} from '@/app/paths'; +} from '@/app/path'; import { AppTextState } from '@/i18n/state'; // Meta functions moved to separate file to avoid diff --git a/src/cmdk/CommandKClient.tsx b/src/cmdk/CommandKClient.tsx index e6c467a9..3d608dcd 100644 --- a/src/cmdk/CommandKClient.tsx +++ b/src/cmdk/CommandKClient.tsx @@ -32,7 +32,7 @@ import { pathForTag, pathForYear, PREFIX_RECENTS, -} from '../app/paths'; +} from '../app/path'; import Modal from '../components/Modal'; import { clsx } from 'clsx/lite'; import { useDebounce } from 'use-debounce'; diff --git a/src/components/HttpStatusPage.tsx b/src/components/HttpStatusPage.tsx index 1101c1e9..f66477d6 100644 --- a/src/components/HttpStatusPage.tsx +++ b/src/components/HttpStatusPage.tsx @@ -1,7 +1,7 @@ import { ReactNode } from 'react'; import AppGrid from './AppGrid'; import { clsx } from 'clsx/lite'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import Link from 'next/link'; export default function HttpStatusPage({ diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 1cefa710..0f8b3b1b 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -6,7 +6,7 @@ import { clsx } from 'clsx/lite'; import useClickInsideOutside from '@/utility/useClickInsideOutside'; import { useRouter } from 'next/navigation'; import AnimateItems from './AnimateItems'; -import { PATH_ROOT } from '@/app/paths'; +import { PATH_ROOT } from '@/app/path'; import usePrefersReducedMotion from '@/utility/usePrefersReducedMotion'; import useEscapeHandler from '@/utility/useEscapeHandler'; import { useTheme } from 'next-themes'; diff --git a/src/components/more/MoreMenu.tsx b/src/components/more/MoreMenu.tsx index de182c82..a7fc334c 100644 --- a/src/components/more/MoreMenu.tsx +++ b/src/components/more/MoreMenu.tsx @@ -10,6 +10,11 @@ import { clsx } from 'clsx/lite'; import { FiMoreHorizontal } from 'react-icons/fi'; import MoreMenuItem from './MoreMenuItem'; +export type MoreMenuSection = { + label?: string + items: ComponentProps[] +} + export default function MoreMenu({ sections, icon, @@ -26,7 +31,7 @@ export default function MoreMenu({ onOpen, ...props }: { - sections: ComponentProps[][] + sections: MoreMenuSection[] icon?: ReactNode header?: ReactNode className?: string @@ -84,7 +89,7 @@ export default function MoreMenu({ 'min-w-[8rem]', 'component-surface', 'py-1', - 'shadow-lg shadow-gray-900/10 dark:shadow-900', + 'not-dark:shadow-lg not-dark:shadow-gray-900/10', 'data-[side=top]:dark:shadow-[0_0px_40px_rgba(0,0,0,0.6)]', 'data-[side=bottom]:dark:shadow-[0_10px_40px_rgba(0,0,0,0.6)]', 'data-[side=top]:animate-fade-in-from-bottom', @@ -99,7 +104,7 @@ export default function MoreMenu({ {header}
}
- {sections.map((section, index) => + {sections.map(({ label, items }, index) =>
- {section.map(props => -
+ {label &&
+ {label} +
} + {items.map(item => +
, )} diff --git a/src/components/Switcher.tsx b/src/components/switcher/Switcher.tsx similarity index 100% rename from src/components/Switcher.tsx rename to src/components/switcher/Switcher.tsx diff --git a/src/components/SwitcherItem.tsx b/src/components/switcher/SwitcherItem.tsx similarity index 81% rename from src/components/SwitcherItem.tsx rename to src/components/switcher/SwitcherItem.tsx index 850bdff8..92c94aa0 100644 --- a/src/components/SwitcherItem.tsx +++ b/src/components/switcher/SwitcherItem.tsx @@ -1,11 +1,12 @@ import { clsx } from 'clsx/lite'; import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config'; import { ComponentProps, ReactNode, RefObject } from 'react'; -import Spinner from './Spinner'; -import LinkWithIconLoader from './LinkWithIconLoader'; -import Tooltip from './Tooltip'; +import Spinner from '../Spinner'; +import LinkWithIconLoader from '../LinkWithIconLoader'; +import Tooltip from '../Tooltip'; -const WIDTH_CLASS = 'w-[42px]'; +const WIDTH_CLASS = 'w-[42px]'; +const WIDTH_CLASS_NARROW = 'w-[36px]'; export default function SwitcherItem({ icon, @@ -19,6 +20,7 @@ export default function SwitcherItem({ noPadding, prefetch = SHOULD_PREFETCH_ALL_LINKS, tooltip, + width = 'normal', }: { icon: ReactNode title?: string @@ -31,10 +33,12 @@ export default function SwitcherItem({ noPadding?: boolean prefetch?: boolean tooltip?: ComponentProps + width?: 'narrow' | 'normal' }) { + const widthClass = width === 'narrow' ? WIDTH_CLASS_NARROW : WIDTH_CLASS; const className = clsx( 'flex items-center justify-center', - `${WIDTH_CLASS} h-[28px]`, + `${widthClass} h-[28px]`, isInteractive && 'cursor-pointer', isInteractive && 'hover:bg-gray-100/60 active:bg-gray-100', isInteractive && 'dark:hover:bg-gray-900/75 dark:active:bg-gray-900', @@ -75,7 +79,7 @@ export default function SwitcherItem({ tooltip ? {content} diff --git a/src/components/switcher/SwitcherItemMenu.tsx b/src/components/switcher/SwitcherItemMenu.tsx new file mode 100644 index 00000000..da532edf --- /dev/null +++ b/src/components/switcher/SwitcherItemMenu.tsx @@ -0,0 +1,36 @@ +'use client'; + +import MoreMenu from '@/components/more/MoreMenu'; +import { clsx } from 'clsx/lite'; +import { ComponentProps } from 'react'; + +export default function SwitcherItemMenu({ + className, + classNameButton, + classNameButtonOpen, + ...props +}: ComponentProps) { + return ( + + ); +} diff --git a/src/feed/index.ts b/src/feed/index.ts index 88fea6d7..06ab0ee2 100644 --- a/src/feed/index.ts +++ b/src/feed/index.ts @@ -4,7 +4,7 @@ import { INFINITE_SCROLL_FULL_INITIAL, INFINITE_SCROLL_GRID_INITIAL, } from '../photo'; -import { SortBy } from '../photo/db/sort'; +import { SortBy } from '../photo/sort'; import { FEED_PHOTO_REQUEST_LIMIT } from './programmatic'; const FEED_BASE_QUERY_OPTIONS: PhotoQueryOptions = { diff --git a/src/feed/json.ts b/src/feed/json.ts index 0b29cab9..ef78521c 100644 --- a/src/feed/json.ts +++ b/src/feed/json.ts @@ -1,4 +1,4 @@ -import { absolutePathForPhoto } from '@/app/paths'; +import { absolutePathForPhoto } from '@/app/path'; import { FEED_PHOTO_WIDTH_LARGE, FEED_PHOTO_WIDTH_MEDIUM, diff --git a/src/feed/rss.ts b/src/feed/rss.ts index cbd4882a..cecdb5b1 100644 --- a/src/feed/rss.ts +++ b/src/feed/rss.ts @@ -6,7 +6,7 @@ import { generateFeedMedia, getCoreFeedFields, } from './programmatic'; -import { ABSOLUTE_PATH_RSS_XML, absolutePathForPhoto } from '@/app/paths'; +import { ABSOLUTE_PATH_RSS_XML, absolutePathForPhoto } from '@/app/path'; import { formatDate } from '@/utility/date'; import { formatStringForXml } from '@/utility/string'; import { BASE_URL, META_DESCRIPTION, META_TITLE } from '@/app/config'; diff --git a/src/film/FilmOGTile.tsx b/src/film/FilmOGTile.tsx index ddd74f2d..0fde8c37 100644 --- a/src/film/FilmOGTile.tsx +++ b/src/film/FilmOGTile.tsx @@ -4,7 +4,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import { pathForFilm, pathForFilmImage, -} from '@/app/paths'; +} from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { descriptionForFilmPhotos, titleForFilm } from '.'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/film/FilmShareModal.tsx b/src/film/FilmShareModal.tsx index f3ef7820..cbed8ef5 100644 --- a/src/film/FilmShareModal.tsx +++ b/src/film/FilmShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForFilm } from '@/app/paths'; +import { absolutePathForFilm } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import FilmOGTile from './FilmOGTile'; diff --git a/src/film/PhotoFilm.tsx b/src/film/PhotoFilm.tsx index 0c58fc32..f2588fea 100644 --- a/src/film/PhotoFilm.tsx +++ b/src/film/PhotoFilm.tsx @@ -1,7 +1,7 @@ 'use client'; import PhotoFilmIcon from './PhotoFilmIcon'; -import { pathForFilm } from '@/app/paths'; +import { pathForFilm } from '@/app/path'; import EntityLink, { EntityLinkExternalProps, } from '@/components/entity/EntityLink'; diff --git a/src/film/index.tsx b/src/film/index.tsx index 9771fd78..2821b967 100644 --- a/src/film/index.tsx +++ b/src/film/index.tsx @@ -7,7 +7,7 @@ import { import { absolutePathForFilm, absolutePathForFilmImage, -} from '@/app/paths'; +} from '@/app/path'; import { FUJIFILM_SIMULATION_FORM_INPUT_OPTIONS, labelForFujifilmSimulation, diff --git a/src/focal/FocalLengthOGTile.tsx b/src/focal/FocalLengthOGTile.tsx index ee552b56..a18db5b4 100644 --- a/src/focal/FocalLengthOGTile.tsx +++ b/src/focal/FocalLengthOGTile.tsx @@ -4,7 +4,7 @@ import { Photo, PhotoDateRange } from '@/photo'; import { pathForFocalLength, pathForFocalLengthImage, -} from '@/app/paths'; +} from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { descriptionForFocalLengthPhotos, titleForFocalLength } from '.'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/focal/FocalLengthShareModal.tsx b/src/focal/FocalLengthShareModal.tsx index 2948c423..5afd7e46 100644 --- a/src/focal/FocalLengthShareModal.tsx +++ b/src/focal/FocalLengthShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForFocalLength } from '@/app/paths'; +import { absolutePathForFocalLength } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import FocalLengthOGTile from './FocalLengthOGTile'; diff --git a/src/focal/PhotoFocalLength.tsx b/src/focal/PhotoFocalLength.tsx index f4776800..7367ccd0 100644 --- a/src/focal/PhotoFocalLength.tsx +++ b/src/focal/PhotoFocalLength.tsx @@ -1,6 +1,6 @@ 'use client'; -import { pathForFocalLength } from '@/app/paths'; +import { pathForFocalLength } from '@/app/path'; import EntityLink, { EntityLinkExternalProps, } from '@/components/entity/EntityLink'; diff --git a/src/focal/index.ts b/src/focal/index.ts index 64cdd13e..db30f1d6 100644 --- a/src/focal/index.ts +++ b/src/focal/index.ts @@ -7,7 +7,7 @@ import { import { absolutePathForFocalLength, absolutePathForFocalLengthImage, -} from '@/app/paths'; +} from '@/app/path'; import { AppTextState } from '@/i18n/state'; import { CategoryQueryMeta } from '@/category'; diff --git a/src/lens/LensOGTile.tsx b/src/lens/LensOGTile.tsx index a43c5b0d..f34b449d 100644 --- a/src/lens/LensOGTile.tsx +++ b/src/lens/LensOGTile.tsx @@ -1,5 +1,5 @@ import { Photo, PhotoDateRange } from '@/photo'; -import { pathForLens, pathForLensImage } from '@/app/paths'; +import { pathForLens, pathForLensImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { Lens } from '.'; import { titleForLens, descriptionForLensPhotos } from './meta'; diff --git a/src/lens/LensShareModal.tsx b/src/lens/LensShareModal.tsx index c4b5ddd1..9cff2c33 100644 --- a/src/lens/LensShareModal.tsx +++ b/src/lens/LensShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForLens } from '@/app/paths'; +import { absolutePathForLens } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import { formatLensText, Lens } from '.'; diff --git a/src/lens/PhotoLens.tsx b/src/lens/PhotoLens.tsx index 786acaab..4e897d4f 100644 --- a/src/lens/PhotoLens.tsx +++ b/src/lens/PhotoLens.tsx @@ -1,6 +1,6 @@ 'use client'; -import { pathForLens } from '@/app/paths'; +import { pathForLens } from '@/app/path'; import { Lens, formatLensText } from '.'; import EntityLink, { EntityLinkExternalProps, diff --git a/src/lens/index.ts b/src/lens/index.ts index a274b6b0..bc6398d8 100644 --- a/src/lens/index.ts +++ b/src/lens/index.ts @@ -1,7 +1,7 @@ import { Photo } from '@/photo'; import { parameterize } from '@/utility/string'; import { formatAppleLensText, isLensApple } from '../platforms/apple'; -import { MISSING_FIELD } from '@/app/paths'; +import { MISSING_FIELD } from '@/app/path'; import { formatGoogleLensText, isLensGoogle } from '../platforms/google'; import { CategoryQueryMeta } from '@/category'; diff --git a/src/lens/meta.ts b/src/lens/meta.ts index 04d5070a..6fb6b318 100644 --- a/src/lens/meta.ts +++ b/src/lens/meta.ts @@ -8,7 +8,7 @@ import { Lens, lensFromPhoto, formatLensText } from '.'; import { absolutePathForLens, absolutePathForLensImage, -} from '@/app/paths'; +} from '@/app/path'; import { AppTextState } from '@/i18n/state'; // Meta functions moved to separate file to avoid diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx index f3365248..12d34237 100644 --- a/src/photo/InfinitePhotoScroll.tsx +++ b/src/photo/InfinitePhotoScroll.tsx @@ -17,7 +17,7 @@ import { clsx } from 'clsx/lite'; import { useAppState } from '@/app/AppState'; import useVisible from '@/utility/useVisible'; import { ADMIN_DB_OPTIMIZE_ENABLED } from '@/app/config'; -import { SortBy } from './db/sort'; +import { SortBy } from './sort'; import { SWR_KEYS } from '@/swr'; const SIZE_KEY_SEPARATOR = '__'; diff --git a/src/photo/PhotoEditPageClient.tsx b/src/photo/PhotoEditPageClient.tsx index bfc3fe84..5336440a 100644 --- a/src/photo/PhotoEditPageClient.tsx +++ b/src/photo/PhotoEditPageClient.tsx @@ -2,7 +2,7 @@ import AdminChildPage from '@/components/AdminChildPage'; import { Photo } from '.'; -import { PATH_ADMIN_PHOTOS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS } from '@/app/path'; import { PhotoFormData, convertPhotoToFormData } from './form'; import PhotoForm from './form/PhotoForm'; import { Tags } from '@/tag'; diff --git a/src/photo/PhotoEscapeHandler.tsx b/src/photo/PhotoEscapeHandler.tsx index 89047a76..56cbaa4f 100644 --- a/src/photo/PhotoEscapeHandler.tsx +++ b/src/photo/PhotoEscapeHandler.tsx @@ -1,6 +1,6 @@ 'use client'; -import { getEscapePath } from '@/app/paths'; +import { getEscapePath } from '@/app/path'; import { useRouter, usePathname } from 'next/navigation'; import { useCallback } from 'react'; import useEscapeHandler from '../utility/useEscapeHandler'; diff --git a/src/photo/PhotoFullPage.tsx b/src/photo/PhotoFullPage.tsx index 52ebfa9b..70ea9cd3 100644 --- a/src/photo/PhotoFullPage.tsx +++ b/src/photo/PhotoFullPage.tsx @@ -4,7 +4,7 @@ import { } from '.'; import PhotosLarge from './PhotosLarge'; import PhotosLargeInfinite from './PhotosLargeInfinite'; -import { SortBy } from './db/sort'; +import { SortBy } from './sort'; export default function PhotoFullPage({ photos, diff --git a/src/photo/PhotoGridContainer.tsx b/src/photo/PhotoGridContainer.tsx index 8087d41d..15c69fea 100644 --- a/src/photo/PhotoGridContainer.tsx +++ b/src/photo/PhotoGridContainer.tsx @@ -7,7 +7,7 @@ import { clsx } from 'clsx/lite'; import AnimateItems from '@/components/AnimateItems'; import { ComponentProps, useCallback, useState, ReactNode } from 'react'; import { GRID_SPACE_CLASSNAME } from '@/components'; -import { SortBy } from './db/sort'; +import { SortBy } from './sort'; export default function PhotoGridContainer({ cacheKey, diff --git a/src/photo/PhotoGridInfinite.tsx b/src/photo/PhotoGridInfinite.tsx index c1592d1d..cbc083dc 100644 --- a/src/photo/PhotoGridInfinite.tsx +++ b/src/photo/PhotoGridInfinite.tsx @@ -4,7 +4,7 @@ import { INFINITE_SCROLL_GRID_MULTIPLE } from '.'; import InfinitePhotoScroll from './InfinitePhotoScroll'; import PhotoGrid from './PhotoGrid'; import { ComponentProps } from 'react'; -import { SortBy } from './db/sort'; +import { SortBy } from './sort'; export default function PhotoGridInfinite({ cacheKey, diff --git a/src/photo/PhotoGridPageClient.tsx b/src/photo/PhotoGridPageClient.tsx index 7d9cc4dd..01c58f32 100644 --- a/src/photo/PhotoGridPageClient.tsx +++ b/src/photo/PhotoGridPageClient.tsx @@ -1,7 +1,7 @@ 'use client'; import { Photo } from '.'; -import { PATH_GRID_INFERRED } from '@/app/paths'; +import { PATH_GRID_INFERRED } from '@/app/path'; import PhotoGridSidebar from './PhotoGridSidebar'; import PhotoGridContainer from './PhotoGridContainer'; import { ComponentProps, useEffect, useRef } from 'react'; @@ -10,7 +10,7 @@ import clsx from 'clsx/lite'; import useElementHeight from '@/utility/useElementHeight'; import MaskedScroll from '@/components/MaskedScroll'; import { IS_RECENTS_FIRST } from '@/app/config'; -import { SortBy } from './db/sort'; +import { SortBy } from './sort'; export default function PhotoGridPageClient({ photos, diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx index 7e22aa1d..5e2a8e76 100644 --- a/src/photo/PhotoLarge.tsx +++ b/src/photo/PhotoLarge.tsx @@ -15,7 +15,7 @@ import AppGrid from '@/components/AppGrid'; import ImageLarge from '@/components/image/ImageLarge'; import { clsx } from 'clsx/lite'; import Link from 'next/link'; -import { pathForFocalLength, pathForPhoto } from '@/app/paths'; +import { pathForFocalLength, pathForPhoto } from '@/app/path'; import PhotoTags from '@/tag/PhotoTags'; import ShareButton from '@/share/ShareButton'; import DownloadButton from '@/components/DownloadButton'; diff --git a/src/photo/PhotoLink.tsx b/src/photo/PhotoLink.tsx index 8d6670ae..7b43a9bd 100644 --- a/src/photo/PhotoLink.tsx +++ b/src/photo/PhotoLink.tsx @@ -5,7 +5,7 @@ import { Photo, titleForPhoto } from '@/photo'; import { PhotoSetCategory } from '@/category'; import { AnimationConfig } from '../components/AnimateItems'; import { useAppState } from '@/app/AppState'; -import { pathForPhoto } from '@/app/paths'; +import { pathForPhoto } from '@/app/path'; import { clsx } from 'clsx/lite'; import LinkWithStatus from '@/components/LinkWithStatus'; import Spinner from '@/components/Spinner'; diff --git a/src/photo/PhotoMedium.tsx b/src/photo/PhotoMedium.tsx index a6e0c1a8..10686d80 100644 --- a/src/photo/PhotoMedium.tsx +++ b/src/photo/PhotoMedium.tsx @@ -8,7 +8,7 @@ import { import { PhotoSetCategory } from '../category'; import ImageMedium from '@/components/image/ImageMedium'; import { clsx } from 'clsx/lite'; -import { pathForPhoto } from '@/app/paths'; +import { pathForPhoto } from '@/app/path'; import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config'; import { useRef } from 'react'; import useVisible from '@/utility/useVisible'; diff --git a/src/photo/PhotoOGTile.tsx b/src/photo/PhotoOGTile.tsx index 628decd5..cc09dd83 100644 --- a/src/photo/PhotoOGTile.tsx +++ b/src/photo/PhotoOGTile.tsx @@ -6,7 +6,7 @@ import { titleForPhoto, } from '@/photo'; import { PhotoSetCategory } from '../category'; -import { pathForPhoto, pathForPhotoImage } from '@/app/paths'; +import { pathForPhoto, pathForPhotoImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; export default function PhotoOGTile({ diff --git a/src/photo/PhotoPrevNextActions.tsx b/src/photo/PhotoPrevNextActions.tsx index 994e9e06..a2e7c105 100644 --- a/src/photo/PhotoPrevNextActions.tsx +++ b/src/photo/PhotoPrevNextActions.tsx @@ -9,7 +9,7 @@ import { } from '@/photo'; import { PhotoSetCategory } from '../category'; import PhotoLink from './PhotoLink'; -import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/paths'; +import { pathForAdminPhotoEdit, pathForPhoto } from '@/app/path'; import { useAppState } from '@/app/AppState'; import { AnimationConfig } from '@/components/AnimateItems'; import { clsx } from 'clsx/lite'; diff --git a/src/photo/PhotoShareModal.tsx b/src/photo/PhotoShareModal.tsx index c2a84ca8..31d18456 100644 --- a/src/photo/PhotoShareModal.tsx +++ b/src/photo/PhotoShareModal.tsx @@ -1,5 +1,5 @@ import PhotoOGTile from '@/photo/PhotoOGTile'; -import { absolutePathForPhoto } from '@/app/paths'; +import { absolutePathForPhoto } from '@/app/path'; import { Photo, titleForPhoto } from '.'; import { PhotoSetCategory } from '../category'; import ShareModal from '@/share/ShareModal'; diff --git a/src/photo/PhotoSmall.tsx b/src/photo/PhotoSmall.tsx index e6884b83..57f0552f 100644 --- a/src/photo/PhotoSmall.tsx +++ b/src/photo/PhotoSmall.tsx @@ -7,7 +7,7 @@ import { PhotoSetCategory } from '../category'; import ImageSmall from '@/components/image/ImageSmall'; import Link from 'next/link'; import { clsx } from 'clsx/lite'; -import { pathForPhoto } from '@/app/paths'; +import { pathForPhoto } from '@/app/path'; import { SHOULD_PREFETCH_ALL_LINKS } from '@/app/config'; import { useRef } from 'react'; import useVisible from '@/utility/useVisible'; diff --git a/src/photo/PhotoUploadWithStatus.tsx b/src/photo/PhotoUploadWithStatus.tsx index bac358fd..7d687d39 100644 --- a/src/photo/PhotoUploadWithStatus.tsx +++ b/src/photo/PhotoUploadWithStatus.tsx @@ -2,7 +2,7 @@ import { uploadPhotoFromClient } from '@/platforms/storage'; import { usePathname, useRouter } from 'next/navigation'; -import { PATH_ADMIN_UPLOADS, pathForAdminUploadUrl } from '@/app/paths'; +import { PATH_ADMIN_UPLOADS, pathForAdminUploadUrl } from '@/app/path'; import ImageInput from '../components/ImageInput'; import { clsx } from 'clsx/lite'; import { useAppState } from '@/app/AppState'; diff --git a/src/photo/PhotosEmptyState.tsx b/src/photo/PhotosEmptyState.tsx index 98675dfa..3325fba6 100644 --- a/src/photo/PhotosEmptyState.tsx +++ b/src/photo/PhotosEmptyState.tsx @@ -10,7 +10,7 @@ import { HiOutlinePhotograph } from 'react-icons/hi'; import { revalidatePath } from 'next/cache'; import SignInOrUploadClient from '@/admin/SignInOrUploadClient'; import Link from 'next/link'; -import { PATH_ADMIN_CONFIGURATION } from '@/app/paths'; +import { PATH_ADMIN_CONFIGURATION } from '@/app/path'; import AnimateItems from '@/components/AnimateItems'; import { getAppText } from '@/i18n/state/server'; diff --git a/src/photo/PhotosLargeInfinite.tsx b/src/photo/PhotosLargeInfinite.tsx index be2b3595..5cd0bb63 100644 --- a/src/photo/PhotosLargeInfinite.tsx +++ b/src/photo/PhotosLargeInfinite.tsx @@ -1,9 +1,9 @@ 'use client'; -import { PATH_FULL_INFERRED } from '@/app/paths'; +import { PATH_FULL_INFERRED } from '@/app/path'; import InfinitePhotoScroll from './InfinitePhotoScroll'; import PhotosLarge from './PhotosLarge'; -import { SortBy } from './db/sort'; +import { SortBy } from './sort'; export default function PhotosLargeInfinite({ initialOffset, diff --git a/src/photo/StaggeredOgPhotosInfinite.tsx b/src/photo/StaggeredOgPhotosInfinite.tsx index feed5967..26a4be75 100644 --- a/src/photo/StaggeredOgPhotosInfinite.tsx +++ b/src/photo/StaggeredOgPhotosInfinite.tsx @@ -1,6 +1,6 @@ 'use client'; -import { PATH_OG } from '@/app/paths'; +import { PATH_OG } from '@/app/path'; import InfinitePhotoScroll from './InfinitePhotoScroll'; import StaggeredOgPhotos from './StaggeredOgPhotos'; diff --git a/src/photo/UploadPageClient.tsx b/src/photo/UploadPageClient.tsx index 5eebd787..f17de457 100644 --- a/src/photo/UploadPageClient.tsx +++ b/src/photo/UploadPageClient.tsx @@ -1,7 +1,7 @@ 'use client'; import AdminChildPage from '@/components/AdminChildPage'; -import { PATH_ADMIN_UPLOADS } from '@/app/paths'; +import { PATH_ADMIN_UPLOADS } from '@/app/path'; import { PhotoFormData, generateTakenAtFields } from './form'; import PhotoForm from './form/PhotoForm'; import { Tags } from '@/tag'; diff --git a/src/photo/actions.ts b/src/photo/actions.ts index 5f43f56c..f05255d2 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -37,7 +37,7 @@ import { PATH_ADMIN_TAGS, PATH_ROOT, pathForPhoto, -} from '@/app/paths'; +} from '@/app/path'; import { blurImageFromUrl, convertFormDataToPhotoDbInsertAndLookupRecipeTitle, diff --git a/src/photo/cache.ts b/src/photo/cache.ts index 84f26a66..5991a920 100644 --- a/src/photo/cache.ts +++ b/src/photo/cache.ts @@ -36,7 +36,7 @@ import { PREFIX_TAG, pathForPhoto, PREFIX_YEAR, -} from '@/app/paths'; +} from '@/app/path'; import { createLensKey } from '@/lens'; // Table key diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts index fca80201..f160d076 100644 --- a/src/photo/db/index.ts +++ b/src/photo/db/index.ts @@ -2,7 +2,7 @@ import { parameterize } from '@/utility/string'; import { PhotoSetCategory } from '../../category'; import { Camera } from '@/camera'; import { Lens } from '@/lens'; -import { APP_DEFAULT_SORT_BY, SortBy } from './sort'; +import { APP_DEFAULT_SORT_BY, SortBy } from '../sort'; export const GENERATE_STATIC_PARAMS_LIMIT = 1000; export const PHOTO_DEFAULT_LIMIT = 100; diff --git a/src/photo/form/PhotoForm.tsx b/src/photo/form/PhotoForm.tsx index 24dc25e1..f7e1879e 100644 --- a/src/photo/form/PhotoForm.tsx +++ b/src/photo/form/PhotoForm.tsx @@ -24,7 +24,7 @@ import { createPhotoAction, updatePhotoAction } from '../actions'; import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import Link from 'next/link'; import { clsx } from 'clsx/lite'; -import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/app/paths'; +import { PATH_ADMIN_PHOTOS, PATH_ADMIN_UPLOADS } from '@/app/path'; import { toastSuccess, toastWarning } from '@/toast'; import { getDimensionsFromSize } from '@/utility/size'; import ImageWithFallback from '@/components/image/ImageWithFallback'; diff --git a/src/photo/index.ts b/src/photo/index.ts index e732eef1..3d439019 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -8,7 +8,7 @@ import { SHOW_LENSES, SHOW_RECIPES, } from '@/app/config'; -import { ABSOLUTE_PATH_HOME_IMAGE } from '@/app/paths'; +import { ABSOLUTE_PATH_HOME_IMAGE } from '@/app/path'; import { formatDate, formatDateFromPostgresString } from '@/utility/date'; import { formatAperture, diff --git a/src/photo/sort/SortMenu.tsx b/src/photo/sort/SortMenu.tsx new file mode 100644 index 00000000..ecb64853 --- /dev/null +++ b/src/photo/sort/SortMenu.tsx @@ -0,0 +1,66 @@ +import IconSort from '@/components/icons/IconSort'; +import SwitcherItemMenu from '@/components/switcher/SwitcherItemMenu'; +import { getSortConfigFromPath } from './path'; +import IconCheck from '@/components/icons/IconCheck'; +import { clsx } from 'clsx/lite'; + +export default function SortMenu({ + isOpen, + setIsOpen, + isAscending, + isTakenAt, + isUploadedAt, + pathNewest, + pathOldest, + pathTakenAt, + pathUploadedAt, +}: { + isOpen?: boolean + setIsOpen?: (isOpen: boolean) => void +} & ReturnType) { + const renderIcon = (isChecked: boolean) => isChecked + ? + : ; + + const renderLabel = (label: string, isSelected: boolean) => ({ + label, + labelComplex: + {label} + , + }); + + return ( + } + sections={[{ + items: [{ + ...renderLabel('Newest', !isAscending), + icon: renderIcon(!isAscending), + href: pathNewest, + }, { + ...renderLabel('Oldest', isAscending), + icon: renderIcon(isAscending), + href: pathOldest, + }], + }, { + items: [{ + ...renderLabel('Taken at', isTakenAt), + icon: renderIcon(isTakenAt), + href: pathTakenAt, + }, { + ...renderLabel('Uploaded', isUploadedAt), + icon: renderIcon(isUploadedAt), + href: pathUploadedAt, + }], + }]} + align="start" + side="top" + sideOffset={12} + ariaLabel="Sort Menu" + /> + ); +} diff --git a/src/photo/db/sort.ts b/src/photo/sort/index.ts similarity index 72% rename from src/photo/db/sort.ts rename to src/photo/sort/index.ts index a61f00f5..4babbf9a 100644 --- a/src/photo/db/sort.ts +++ b/src/photo/sort/index.ts @@ -1,3 +1,18 @@ +export type NavSortControl = 'none' | 'toggle' | 'menu'; + +export const NAV_SORT_CONTROL_DEFAULT: NavSortControl = 'toggle'; + +export const getNavSortControlFromString = ( + navSortControl = '', +): NavSortControl => { + switch (navSortControl.toLocaleLowerCase()) { + case 'none': return 'none'; + case 'toggle': return 'toggle'; + case 'menu': return 'menu'; + default: return NAV_SORT_CONTROL_DEFAULT; + } +}; + export const SORT_BY_OPTIONS = [{ sortBy: 'takenAt', string: 'taken-at', diff --git a/src/photo/db/sort-path.ts b/src/photo/sort/path.ts similarity index 56% rename from src/photo/db/sort-path.ts rename to src/photo/sort/path.ts index 5b0d02a9..e1e8eefb 100644 --- a/src/photo/db/sort-path.ts +++ b/src/photo/sort/path.ts @@ -1,4 +1,4 @@ -// 'sort-path.ts' separate from 'sort.ts' +// 'sort/path.ts' separate from 'sort/index.ts' // to avoid circular dependencies import { @@ -6,12 +6,10 @@ import { PARAM_SORT_ORDER_OLDEST, PARAM_SORT_TYPE_TAKEN_AT, PARAM_SORT_TYPE_UPLOADED_AT, - PATH_FULL, PATH_FULL_INFERRED, - PATH_GRID, PATH_GRID_INFERRED, -} from '@/app/paths'; -import { SortBy, SortParams } from './sort'; +} from '@/app/path'; +import { SortBy, SortParams } from '.'; import { USER_DEFAULT_SORT_BY, GRID_HOMEPAGE_ENABLED, @@ -83,8 +81,9 @@ export const getSortOptionsFromParams = async ( return _getSortOptionsFromParams(sortType, sortOrder); }; -export const getPathSortComponents = (pathname: string) => { +const getPathSortComponents = (pathname: string) => { const [_, gridOrFull, sortType, sortOrder] = pathname.split('/'); + const { sortBy } = _getSortOptionsFromParams(sortType, sortOrder); return { gridOrFull: gridOrFull || (GRID_HOMEPAGE_ENABLED ? 'grid' @@ -92,37 +91,80 @@ export const getPathSortComponents = (pathname: string) => { ), sortType: sortType || DEFAULT_SORT_TYPE, sortOrder: sortOrder || DEFAULT_SORT_ORDER, + sortBy, }; }; -const getReversedSortOrder = (sortOrder: string): string => - sortOrder === PARAM_SORT_ORDER_OLDEST - ? PARAM_SORT_ORDER_NEWEST - : PARAM_SORT_ORDER_OLDEST; - export const getSortConfigFromPath = (pathname: string) => { - const { gridOrFull, sortType, sortOrder } = getPathSortComponents(pathname); - const { sortBy } = _getSortOptionsFromParams(sortType, sortOrder); - const isSortedByDefault = sortBy === USER_DEFAULT_SORT_BY; - const reversedSortOrder = getReversedSortOrder(sortOrder); - const isAscending = sortOrder === PARAM_SORT_ORDER_OLDEST; - const doesReverseSortMatchDefault = _getSortOptionsFromParams( + const { + gridOrFull: _gridOrFull, sortType, - reversedSortOrder, - ).sortBy === USER_DEFAULT_SORT_BY; + sortOrder, + sortBy, + } = getPathSortComponents(pathname); + + const isSortedByDefault = sortBy === USER_DEFAULT_SORT_BY; + const sortOrderReversed = sortOrder === PARAM_SORT_ORDER_OLDEST + ? PARAM_SORT_ORDER_NEWEST + : PARAM_SORT_ORDER_OLDEST; + const isAscending = sortOrder === PARAM_SORT_ORDER_OLDEST; + const isTakenAt = sortType === PARAM_SORT_TYPE_TAKEN_AT; + const isUploadedAt = sortType === PARAM_SORT_TYPE_UPLOADED_AT; + + const getPath = ({ + gridOrFull = _gridOrFull, + sortType, + sortOrder, + }: { + gridOrFull?: string + sortType: string + sortOrder: string + }) => { + const { sortBy } = _getSortOptionsFromParams(sortType, sortOrder); + if (sortBy === USER_DEFAULT_SORT_BY) { + return gridOrFull === 'grid' + ? PATH_GRID_INFERRED + : PATH_FULL_INFERRED; + } else { + return `/${gridOrFull}/${sortType}/${sortOrder}`; + } + }; + + // Core paths + // (reset custom sort when clicking grid/full a second time) + const pathGrid = _gridOrFull === 'grid' && sortBy !== USER_DEFAULT_SORT_BY + ? PATH_GRID_INFERRED + : getPath({ gridOrFull: 'grid', sortType, sortOrder }); + const pathFull = _gridOrFull === 'full' && sortBy !== USER_DEFAULT_SORT_BY + ? PATH_FULL_INFERRED + : getPath({ gridOrFull: 'full', sortType, sortOrder }); + + // Sort toggle path + const pathSortToggle = + getPath({ sortType, sortOrder: sortOrderReversed }); + + // Sort menu paths + const pathNewest = + getPath({ sortType, sortOrder: PARAM_SORT_ORDER_NEWEST }); + const pathOldest = + getPath({ sortType, sortOrder: PARAM_SORT_ORDER_OLDEST }); + const pathTakenAt = + getPath({ sortType: PARAM_SORT_TYPE_TAKEN_AT, sortOrder }); + const pathUploadedAt = + getPath({ sortType: PARAM_SORT_TYPE_UPLOADED_AT, sortOrder }); + return { sortBy, + isSortedByDefault, isAscending, - pathGrid: isSortedByDefault - ? PATH_GRID_INFERRED - : `${PATH_GRID}/${sortType}/${sortOrder}`, - pathFull: isSortedByDefault - ? PATH_FULL_INFERRED - : `${PATH_FULL}/${sortType}/${sortOrder}`, - pathSort: doesReverseSortMatchDefault - ? gridOrFull === 'grid' - ? PATH_GRID_INFERRED - : PATH_FULL_INFERRED - : `/${gridOrFull}/${sortType}/${reversedSortOrder}`, + isTakenAt, + isUploadedAt, + pathGrid, + pathFull, + pathNewest, + pathOldest, + pathTakenAt, + pathUploadedAt, + pathSortToggle, }; }; diff --git a/src/platforms/storage/index.ts b/src/platforms/storage/index.ts index f581ceba..e7be288a 100644 --- a/src/platforms/storage/index.ts +++ b/src/platforms/storage/index.ts @@ -29,7 +29,7 @@ import { cloudflareR2Put, isUrlFromCloudflareR2, } from './cloudflare-r2'; -import { PATH_API_PRESIGNED_URL } from '@/app/paths'; +import { PATH_API_PRESIGNED_URL } from '@/app/path'; export const generateStorageId = () => generateNanoid(16); diff --git a/src/platforms/storage/vercel-blob.ts b/src/platforms/storage/vercel-blob.ts index 66812dd1..698f856e 100644 --- a/src/platforms/storage/vercel-blob.ts +++ b/src/platforms/storage/vercel-blob.ts @@ -1,4 +1,4 @@ -import { PATH_API_VERCEL_BLOB_UPLOAD } from '@/app/paths'; +import { PATH_API_VERCEL_BLOB_UPLOAD } from '@/app/path'; import { copy, del, list, put } from '@vercel/blob'; import { upload } from '@vercel/blob/client'; import { fileNameForStorageUrl, StorageListResponse } from '.'; diff --git a/src/recents/PhotoRecents.tsx b/src/recents/PhotoRecents.tsx index 9adc05a2..26010d17 100644 --- a/src/recents/PhotoRecents.tsx +++ b/src/recents/PhotoRecents.tsx @@ -1,4 +1,4 @@ -import { PREFIX_RECENTS } from '@/app/paths'; +import { PREFIX_RECENTS } from '@/app/path'; import EntityLink, { EntityLinkExternalProps } from '@/components/entity/EntityLink'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/recents/RecentsOGTile.tsx b/src/recents/RecentsOGTile.tsx index d29b98b7..b2d3df50 100644 --- a/src/recents/RecentsOGTile.tsx +++ b/src/recents/RecentsOGTile.tsx @@ -1,7 +1,7 @@ 'use client'; import { Photo, PhotoDateRange, descriptionForPhotoSet } from '@/photo'; -import { PREFIX_RECENTS, pathForRecentsImage } from '@/app/paths'; +import { PREFIX_RECENTS, pathForRecentsImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/recents/RecentsShareModal.tsx b/src/recents/RecentsShareModal.tsx index 1e5738b7..d560650b 100644 --- a/src/recents/RecentsShareModal.tsx +++ b/src/recents/RecentsShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForRecents } from '@/app/paths'; +import { absolutePathForRecents } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import RecentsOGTile from './RecentsOGTile'; diff --git a/src/recents/meta.ts b/src/recents/meta.ts index a1b6e6fa..ee10460b 100644 --- a/src/recents/meta.ts +++ b/src/recents/meta.ts @@ -3,7 +3,7 @@ import { AppTextState } from '@/i18n/state'; import { absolutePathForRecents, absolutePathForRecentsImage, -} from '@/app/paths'; +} from '@/app/path'; export const generateMetaForRecents = ( photos: Photo[], diff --git a/src/recipe/PhotoRecipe.tsx b/src/recipe/PhotoRecipe.tsx index fdd7861c..ba1b9a22 100644 --- a/src/recipe/PhotoRecipe.tsx +++ b/src/recipe/PhotoRecipe.tsx @@ -1,6 +1,6 @@ 'use client'; -import { pathForRecipe } from '@/app/paths'; +import { pathForRecipe } from '@/app/path'; import EntityLink, { EntityLinkExternalProps, } from '@/components/entity/EntityLink'; diff --git a/src/recipe/RecipeOGTile.tsx b/src/recipe/RecipeOGTile.tsx index 9977d64a..123dc508 100644 --- a/src/recipe/RecipeOGTile.tsx +++ b/src/recipe/RecipeOGTile.tsx @@ -1,5 +1,5 @@ import { Photo, PhotoDateRange } from '@/photo'; -import { pathForRecipe, pathForRecipeImage } from '@/app/paths'; +import { pathForRecipe, pathForRecipeImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { descriptionForRecipePhotos, titleForRecipe } from '.'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/recipe/RecipeShareModal.tsx b/src/recipe/RecipeShareModal.tsx index 5de1a32c..698bc286 100644 --- a/src/recipe/RecipeShareModal.tsx +++ b/src/recipe/RecipeShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForRecipe } from '@/app/paths'; +import { absolutePathForRecipe } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import { diff --git a/src/recipe/index.ts b/src/recipe/index.ts index af02885a..500d7e16 100644 --- a/src/recipe/index.ts +++ b/src/recipe/index.ts @@ -1,4 +1,4 @@ -import { absolutePathForRecipe, absolutePathForRecipeImage } from '@/app/paths'; +import { absolutePathForRecipe, absolutePathForRecipeImage } from '@/app/path'; import { descriptionForPhotoSet, Photo, diff --git a/src/share/index.ts b/src/share/index.ts index dac477fb..5ed40823 100644 --- a/src/share/index.ts +++ b/src/share/index.ts @@ -9,7 +9,7 @@ import { absolutePathForRecipeImage, absolutePathForTagImage, absolutePathForYearImage, -} from '@/app/paths'; +} from '@/app/path'; export type ShareModalProps = Omit & { photo?: Photo diff --git a/src/tag/PhotoFavs.tsx b/src/tag/PhotoFavs.tsx index 4b47cb3f..a477ecbb 100644 --- a/src/tag/PhotoFavs.tsx +++ b/src/tag/PhotoFavs.tsx @@ -1,7 +1,7 @@ 'use client'; import { TAG_FAVS } from '.'; -import { pathForTag } from '@/app/paths'; +import { pathForTag } from '@/app/path'; import EntityLink, { EntityLinkExternalProps, } from '@/components/entity/EntityLink'; diff --git a/src/tag/PhotoPrivate.tsx b/src/tag/PhotoPrivate.tsx index c901e5e9..a4c8e557 100644 --- a/src/tag/PhotoPrivate.tsx +++ b/src/tag/PhotoPrivate.tsx @@ -1,5 +1,5 @@ import { TAG_PRIVATE } from '.'; -import { pathForTag } from '@/app/paths'; +import { pathForTag } from '@/app/path'; import EntityLink, { EntityLinkExternalProps, } from '@/components/entity/EntityLink'; diff --git a/src/tag/PhotoTag.tsx b/src/tag/PhotoTag.tsx index f821c9c6..cf74f3e3 100644 --- a/src/tag/PhotoTag.tsx +++ b/src/tag/PhotoTag.tsx @@ -1,6 +1,6 @@ 'use client'; -import { pathForTag } from '@/app/paths'; +import { pathForTag } from '@/app/path'; import { formatTag } from '.'; import EntityLink, { EntityLinkExternalProps, diff --git a/src/tag/TagOGTile.tsx b/src/tag/TagOGTile.tsx index 340e677c..d6a02ac3 100644 --- a/src/tag/TagOGTile.tsx +++ b/src/tag/TagOGTile.tsx @@ -1,7 +1,7 @@ 'use client'; import { Photo, PhotoDateRange } from '@/photo'; -import { pathForTag, pathForTagImage } from '@/app/paths'; +import { pathForTag, pathForTagImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { descriptionForTaggedPhotos, titleForTag } from '.'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/tag/TagShareModal.tsx b/src/tag/TagShareModal.tsx index b9734f68..9c3c42e1 100644 --- a/src/tag/TagShareModal.tsx +++ b/src/tag/TagShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForTag } from '@/app/paths'; +import { absolutePathForTag } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import TagOGTile from './TagOGTile'; diff --git a/src/tag/index.ts b/src/tag/index.ts index 89e299ed..1110125b 100644 --- a/src/tag/index.ts +++ b/src/tag/index.ts @@ -8,7 +8,7 @@ import { absolutePathForTag, absolutePathForTagImage, getPathComponents, -} from '@/app/paths'; +} from '@/app/path'; import { capitalizeWords, convertStringToArray, diff --git a/src/years/PhotoYear.tsx b/src/years/PhotoYear.tsx index 26288205..1b16ac3c 100644 --- a/src/years/PhotoYear.tsx +++ b/src/years/PhotoYear.tsx @@ -1,4 +1,4 @@ -import { pathForYear } from '@/app/paths'; +import { pathForYear } from '@/app/path'; import EntityLink, { EntityLinkExternalProps } from '@/components/entity/EntityLink'; import IconYear from '@/components/icons/IconYear'; diff --git a/src/years/YearOGTile.tsx b/src/years/YearOGTile.tsx index bad9b02e..ee1a6d3f 100644 --- a/src/years/YearOGTile.tsx +++ b/src/years/YearOGTile.tsx @@ -1,7 +1,7 @@ 'use client'; import { Photo, PhotoDateRange, descriptionForPhotoSet } from '@/photo'; -import { pathForYear, pathForYearImage } from '@/app/paths'; +import { pathForYear, pathForYearImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { useAppText } from '@/i18n/state/client'; diff --git a/src/years/YearShareModal.tsx b/src/years/YearShareModal.tsx index e307b0f5..6df5d41a 100644 --- a/src/years/YearShareModal.tsx +++ b/src/years/YearShareModal.tsx @@ -1,4 +1,4 @@ -import { absolutePathForYear } from '@/app/paths'; +import { absolutePathForYear } from '@/app/path'; import { PhotoSetAttributes } from '../category'; import ShareModal from '@/share/ShareModal'; import YearOGTile from './YearOGTile'; diff --git a/src/years/meta.ts b/src/years/meta.ts index ff98fa12..26c80f6f 100644 --- a/src/years/meta.ts +++ b/src/years/meta.ts @@ -1,6 +1,6 @@ import { descriptionForPhotoSet, Photo, PhotoDateRange } from '@/photo'; import { AppTextState } from '@/i18n/state'; -import { absolutePathForYear, absolutePathForYearImage } from '@/app/paths'; +import { absolutePathForYear, absolutePathForYearImage } from '@/app/path'; export const generateMetaForYear = ( year: string,