diff --git a/src/admin/AdminGrid.tsx b/src/admin/AdminGrid.tsx
new file mode 100644
index 00000000..16cf493c
--- /dev/null
+++ b/src/admin/AdminGrid.tsx
@@ -0,0 +1,26 @@
+import { cc } from '@/utility/css';
+import { ReactNode } from 'react';
+
+export default function AdminGrid ({
+ title,
+ children,
+}: {
+ title?: string,
+ children: ReactNode,
+}) {
+ return
+ {title &&
+
+ {title}
+
}
+
+
;
+}
diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx
index be8c544d..250f1897 100644
--- a/src/admin/AdminNav.tsx
+++ b/src/admin/AdminNav.tsx
@@ -1,5 +1,6 @@
'use client';
+import SiteGrid from '@/components/SiteGrid';
import { cc } from '@/utility/css';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
@@ -16,25 +17,29 @@ export default function AdminNav({
const pathname = usePathname();
return (
-
-
- {items.map(({ label, href, count }) =>
-
- {label}
- ({count})
- )}
-
-
+
+
+ {items.map(({ label, href, count }) =>
+
+ {label}
+ ({count})
+ )}
+
+
+ }
+ />
);
}
diff --git a/src/admin/DeleteButton.tsx b/src/admin/DeleteButton.tsx
new file mode 100644
index 00000000..4709a58e
--- /dev/null
+++ b/src/admin/DeleteButton.tsx
@@ -0,0 +1,10 @@
+import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
+
+export default function DeleteButton () {
+ return ×}
+ >
+ Delete
+ ;
+}
diff --git a/src/admin/EditButton.tsx b/src/admin/EditButton.tsx
new file mode 100644
index 00000000..fc9e1967
--- /dev/null
+++ b/src/admin/EditButton.tsx
@@ -0,0 +1,23 @@
+import Link from 'next/link';
+import { FaRegEdit } from 'react-icons/fa';
+
+export default function EditButton ({
+ href,
+ label = 'Edit',
+}: {
+ href: string,
+ label?: string,
+}) {
+ return (
+
+
+
+ {label}
+
+
+ );
+}
diff --git a/src/app/(auth-state)/admin/photos/page.tsx b/src/app/(auth-state)/admin/photos/page.tsx
index 3555ec3e..91a935b8 100644
--- a/src/app/(auth-state)/admin/photos/page.tsx
+++ b/src/app/(auth-state)/admin/photos/page.tsx
@@ -1,4 +1,4 @@
-import { Fragment, ReactNode } from 'react';
+import { Fragment } from 'react';
import PhotoUploadInput from '@/photo/PhotoUploadInput';
import Link from 'next/link';
import PhotoTiny from '@/photo/PhotoTiny';
@@ -11,7 +11,6 @@ import {
deleteBlobPhotoAction,
syncCacheAction,
} from '@/photo/actions';
-import { FaRegEdit } from 'react-icons/fa';
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
import { pathForBlobUrl } from '@/services/blob';
import {
@@ -33,12 +32,17 @@ import {
PaginationParams,
getPaginationForSearchParams,
} from '@/site/pagination';
+import AdminGrid from '@/admin/AdminGrid';
+import DeleteButton from '@/admin/DeleteButton';
+import EditButton from '@/admin/EditButton';
export const runtime = 'edge';
const DEBUG_PHOTO_BLOBS = false;
-export default async function AdminPage({ searchParams }: PaginationParams) {
+export default async function AdminTagsPage({
+ searchParams,
+}: PaginationParams) {
const { offset, limit } = getPaginationForSearchParams(searchParams);
const [
@@ -85,7 +89,7 @@ export default async function AdminPage({ searchParams }: PaginationParams) {
label={`Photos Files (${blobPhotoUrls.length})`}
/>}
-
+
{photos.map(photo =>
-
- {title}
-
-
- ;
-}
-
-function EditButton ({
- href,
- label = 'Edit',
-}: {
- href: string,
- label?: string,
-}) {
- return (
-
-
-
- {label}
-
-
- );
-}
-
-function DeleteButton () {
- return ×}
- >
- Delete
- ;
-}
-
function BlobUrls ({
blobUrls,
label,
diff --git a/src/app/(auth-state)/admin/tags/page.tsx b/src/app/(auth-state)/admin/tags/page.tsx
new file mode 100644
index 00000000..d1cbeff4
--- /dev/null
+++ b/src/app/(auth-state)/admin/tags/page.tsx
@@ -0,0 +1,42 @@
+import FormWithConfirm from '@/components/FormWithConfirm';
+import SiteGrid from '@/components/SiteGrid';
+import { deletePhotoTagGloballyAction } from '@/photo/actions';
+import { getUniqueTagsCached } from '@/cache';
+import AdminGrid from '@/admin/AdminGrid';
+import { Fragment } from 'react';
+import DeleteButton from '@/admin/DeleteButton';
+
+export const runtime = 'edge';
+
+export default async function AdminPhotosPage() {
+ const tags = await getUniqueTagsCached();
+
+ return (
+
+
+
+ {tags.map(tag =>
+
+
+ {tag}
+
+
+
+
+
+
+
+ )}
+
+
+ }
+ />
+ );
+}