diff --git a/app/admin/recipes/[recipe]/edit/page.tsx b/app/admin/recipes/[recipe]/edit/page.tsx
new file mode 100644
index 00000000..5cdfc425
--- /dev/null
+++ b/app/admin/recipes/[recipe]/edit/page.tsx
@@ -0,0 +1,48 @@
+import AdminChildPage from '@/components/AdminChildPage';
+import { redirect } from 'next/navigation';
+import { getPhotosCached } from '@/photo/cache';
+import TagForm from '@/tag/TagForm';
+import { PATH_ADMIN, PATH_ADMIN_TAGS, pathForTag } from '@/app/paths';
+import PhotoLightbox from '@/photo/PhotoLightbox';
+import { getPhotosMeta } from '@/photo/db/query';
+import AdminTagBadge from '@/admin/AdminTagBadge';
+
+const MAX_PHOTO_TO_SHOW = 6;
+
+interface Props {
+ params: Promise<{ tag: string }>
+}
+
+export default async function PhotoPageEdit({
+ params,
+}: Props) {
+ const { tag: tagFromParams } = await params;
+
+ const tag = decodeURIComponent(tagFromParams);
+
+ const [
+ { count },
+ photos,
+ ] = await Promise.all([
+ getPhotosMeta({ tag }),
+ getPhotosCached({ tag, limit: MAX_PHOTO_TO_SHOW }),
+ ]);
+
+ if (count === 0) { redirect(PATH_ADMIN); }
+
+ return (
+ }
+ >
+
+
+
+
+ );
+};
diff --git a/app/admin/recipes/page.tsx b/app/admin/recipes/page.tsx
new file mode 100644
index 00000000..c70d03e7
--- /dev/null
+++ b/app/admin/recipes/page.tsx
@@ -0,0 +1,18 @@
+import AdminTagTable from '@/admin/AdminTagTable';
+import SiteGrid from '@/components/SiteGrid';
+import { getUniqueTagsHiddenCached } from '@/photo/cache';
+
+export default async function AdminTagsPage() {
+ const tags = await getUniqueTagsHiddenCached().catch(() => []);
+
+ return (
+
+
+ }
+ />
+ );
+}
diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx
index 6f64b0cd..6d4880e8 100644
--- a/src/admin/AdminAppMenu.tsx
+++ b/src/admin/AdminAppMenu.tsx
@@ -5,6 +5,7 @@ import {
PATH_ADMIN_CONFIGURATION,
PATH_ADMIN_INSIGHTS,
PATH_ADMIN_PHOTOS,
+ PATH_ADMIN_RECIPES,
PATH_ADMIN_TAGS,
PATH_ADMIN_UPLOADS,
PATH_GRID_INFERRED,
@@ -13,7 +14,7 @@ import { useAppState } from '@/state/AppState';
import { ImCheckboxUnchecked } from 'react-icons/im';
import { IoArrowDown, IoArrowUp, IoCloseSharp } from 'react-icons/io5';
import { clsx } from 'clsx/lite';
-import { TbPhoto } from 'react-icons/tb';
+import { TbChecklist, TbPhoto } from 'react-icons/tb';
import { FiTag } from 'react-icons/fi';
import { BiLockAlt } from 'react-icons/bi';
import AdminAppInfoIcon from './AdminAppInfoIcon';
@@ -37,6 +38,7 @@ export default function AdminAppMenu({
photosCountTotal = 0,
uploadsCount = 0,
tagsCount = 0,
+ recipesCount = 0,
selectedPhotoIds,
startUpload,
setSelectedPhotoIds,
@@ -103,6 +105,18 @@ export default function AdminAppMenu({
});
}
+ if (recipesCount) {
+ items.push({
+ label: 'Manage Recipes',
+ annotation: `${recipesCount}`,
+ icon: ,
+ href: PATH_ADMIN_RECIPES,
+ });
+ }
+
if (photosCountTotal) {
items.push({
label: isSelecting
diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx
index eecda0aa..671b1fd6 100644
--- a/src/admin/AdminNav.tsx
+++ b/src/admin/AdminNav.tsx
@@ -2,10 +2,12 @@ import { getStorageUploadUrlsNoStore } from '@/platforms/storage/cache';
import {
getPhotosMetaCached,
getPhotosMostRecentUpdateCached,
+ getUniqueRecipesCached,
getUniqueTagsCached,
} from '@/photo/cache';
import {
PATH_ADMIN_PHOTOS,
+ PATH_ADMIN_RECIPES,
PATH_ADMIN_TAGS,
PATH_ADMIN_UPLOADS,
} from '@/app/paths';
@@ -14,21 +16,24 @@ import AdminNavClient from './AdminNavClient';
export default async function AdminNav() {
const [
countPhotos,
- countTags,
countUploads,
+ countTags,
+ countRecipes,
mostRecentPhotoUpdateTime,
] = await Promise.all([
getPhotosMetaCached({ hidden: 'include' })
.then(({ count }) => count)
.catch(() => 0),
- getUniqueTagsCached().then(tags => tags.length)
- .catch(() => 0),
getStorageUploadUrlsNoStore()
.then(urls => urls.length)
.catch(e => {
console.error(`Error getting blob upload urls: ${e}`);
return 0;
}),
+ getUniqueTagsCached().then(tags => tags.length)
+ .catch(() => 0),
+ getUniqueRecipesCached().then(recipes => recipes.length)
+ .catch(() => 0),
getPhotosMostRecentUpdateCached().catch(() => undefined),
]);
@@ -55,6 +60,13 @@ export default async function AdminNav() {
count: countTags,
}); }
+ // Recipes
+ if (countRecipes > 0) { items.push({
+ label: 'Recipes',
+ href: PATH_ADMIN_RECIPES,
+ count: countRecipes,
+ }); }
+
return (
>;
@@ -17,8 +21,9 @@ export const getAdminDataAction = async () =>
const [
photosCount,
photosCountHidden,
- tagsCount,
uploadsCount,
+ tagsCount,
+ recipesCount,
insightsIndicatorStatus,
] = await Promise.all([
getPhotosMeta()
@@ -27,15 +32,18 @@ export const getAdminDataAction = async () =>
getPhotosMeta({ hidden: 'only' })
.then(({ count }) => count)
.catch(() => 0),
- getUniqueTags()
- .then(tags => tags.length)
- .catch(() => 0),
getStorageUploadUrlsNoStore()
.then(urls => urls.length)
.catch(e => {
console.error(`Error getting blob upload urls: ${e}`);
return 0;
}),
+ getUniqueTags()
+ .then(tags => tags.length)
+ .catch(() => 0),
+ getUniqueRecipes()
+ .then(recipes => recipes.length)
+ .catch(() => 0),
getInsightsIndicatorStatus(),
]);
@@ -50,8 +58,9 @@ export const getAdminDataAction = async () =>
photosCount,
photosCountHidden,
photosCountTotal,
- tagsCount,
uploadsCount,
+ tagsCount,
+ recipesCount,
insightsIndicatorStatus,
};
});
diff --git a/src/app/paths.ts b/src/app/paths.ts
index 8490f53b..77939527 100644
--- a/src/app/paths.ts
+++ b/src/app/paths.ts
@@ -41,6 +41,7 @@ export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
export const PATH_ADMIN_OUTDATED = `${PATH_ADMIN}/outdated`;
export const PATH_ADMIN_UPLOADS = `${PATH_ADMIN}/uploads`;
export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`;
+export const PATH_ADMIN_RECIPES = `${PATH_ADMIN}/recipes`;
export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;
export const PATH_ADMIN_INSIGHTS = `${PATH_ADMIN}/insights`;
export const PATH_ADMIN_BASELINE = `${PATH_ADMIN}/baseline`;