From 3039076e273ba948f097d200433374baf28dc35f Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 26 May 2024 22:40:02 -0500 Subject: [PATCH] Introduce multiple uploads component --- src/admin/AdminAddAllUploads.tsx | 124 ++++++++++++++++++++++++++ src/app/admin/uploads/page.tsx | 12 ++- src/components/ErrorNote.tsx | 5 +- src/components/FieldSetWithStatus.tsx | 68 ++++++++------ src/components/TagInput.tsx | 5 ++ src/photo/actions.ts | 44 ++++++++- src/photo/form/PhotoForm.tsx | 10 +-- src/photo/form/index.ts | 6 +- src/services/openai.ts | 120 ++++++++++++++++--------- src/tag/index.ts | 27 +++++- src/utility/date.ts | 2 +- 11 files changed, 330 insertions(+), 93 deletions(-) create mode 100644 src/admin/AdminAddAllUploads.tsx diff --git a/src/admin/AdminAddAllUploads.tsx b/src/admin/AdminAddAllUploads.tsx new file mode 100644 index 00000000..223e9e4f --- /dev/null +++ b/src/admin/AdminAddAllUploads.tsx @@ -0,0 +1,124 @@ +'use client'; + +import ErrorNote from '@/components/ErrorNote'; +import FieldSetWithStatus from '@/components/FieldSetWithStatus'; +import InfoBlock from '@/components/InfoBlock'; +import LoaderButton from '@/components/primitives/LoaderButton'; +import { addAllUploads } from '@/photo/actions'; +import { PATH_ADMIN_PHOTOS } from '@/site/paths'; +import { + TagsWithMeta, + convertTagsForForm, + getValidationMessageForTags, +} from '@/tag'; +import { + generateLocalNaivePostgresString, + generateLocalPostgresString, +} from '@/utility/date'; +import { convertStringToArray } from '@/utility/string'; +import clsx from 'clsx'; +import { useRouter } from 'next/navigation'; +import { useRef, useState } from 'react'; +import { BiImageAdd } from 'react-icons/bi'; + +export default function AdminAddAllUploads({ + storageUrlCount, + uniqueTags, +}: { + storageUrlCount: number + uniqueTags?: TagsWithMeta +}) { + const divRef = useRef(null); + + const [isLoading, setIsLoading] = useState(false); + const [showTags, setShowTags] = useState(false); + const [tags, setTags] = useState(''); + const [actionErrorMessage, setActionErrorMessage] = useState(''); + const [tagErrorMessage, setTagErrorMessage] = useState(''); + + const router = useRouter(); + + return ( + <> + {actionErrorMessage && + {actionErrorMessage}} + +
+
+
+ {showTags + ? tagErrorMessage || 'Add tags to all uploads' + : `Found ${storageUrlCount} uploads`} +
+ { + setShowTags(value === 'true'); + if (value === 'true') { + setTimeout(() => + divRef.current?.querySelectorAll('input')[0]?.focus() + , 100); + } + }} + /> +
+
+ { + setTags(tags); + setTagErrorMessage(getValidationMessageForTags(tags) ?? ''); + }} + error={tagErrorMessage} + required={false} + hideLabel + /> +
+
+ } + onClick={() => { + if (confirm( + `Are you sure you want to add all ${storageUrlCount} uploads?` + )) { + setIsLoading(true); + addAllUploads({ + tags: showTags && tags + ? convertStringToArray(tags) ?? [] + : [], + takenAtLocal: generateLocalPostgresString(), + takenAtNaiveLocal: generateLocalNaivePostgresString(), + }) + .then(() => + router.push(PATH_ADMIN_PHOTOS)) + .catch(e => { + setIsLoading(false); + setActionErrorMessage(e.message); + }); + } + }} + hideTextOnMobile={false} + > + Add all {storageUrlCount} uploads + +
+
+
+ + ); +} diff --git a/src/app/admin/uploads/page.tsx b/src/app/admin/uploads/page.tsx index e4fecd0f..d9a945d2 100644 --- a/src/app/admin/uploads/page.tsx +++ b/src/app/admin/uploads/page.tsx @@ -1,12 +1,22 @@ import AdminUploadsTable from '@/admin/AdminUploadsTable'; import { getStorageUploadUrlsNoStore } from '@/services/storage/cache'; import SiteGrid from '@/components/SiteGrid'; +import AdminAddAllUploads from '@/admin/AdminAddAllUploads'; +import { getUniqueTagsCached } from '@/photo/cache'; export default async function AdminUploadsPage() { const storageUrls = await getStorageUploadUrlsNoStore(); + const uniqueTags = await getUniqueTagsCached(); return ( } + contentMain={
+ {storageUrls.length > 1 && + } + +
} /> ); } diff --git a/src/components/ErrorNote.tsx b/src/components/ErrorNote.tsx index 06f618bf..4ac5f5dc 100644 --- a/src/components/ErrorNote.tsx +++ b/src/components/ErrorNote.tsx @@ -3,18 +3,21 @@ import { ReactNode } from 'react'; import { BiErrorAlt } from 'react-icons/bi'; export default function ErrorNote({ + className, children, }: { + className?: string children: ReactNode }) { return (
accessory?: React.ReactNode + hideLabel?: boolean }) { const { pending } = useFormStatus(); return ( -
- +
+ {!hideLabel && + }
{selectOptions ?