Refine outdated photo view

This commit is contained in:
Sam Becker 2024-06-16 15:18:16 -05:00
parent 141b7e3ed8
commit 48b1751b12
7 changed files with 137 additions and 75 deletions

View File

@ -4,15 +4,22 @@ import PhotoUpload from '@/photo/PhotoUpload';
import { PATH_ADMIN_PHOTOS } from '@/site/paths';
import { useAppState } from '@/state/AppState';
import Link from 'next/link';
import { useState } from 'react';
import { FaArrowRight } from 'react-icons/fa';
export default function AdminCTA() {
const { isUserSignedIn } = useAppState();
const [isUploading, setIsUploading] = useState(false);
return (
<div className="flex justify-center pt-4">
{isUserSignedIn
? <PhotoUpload showUploadStatus={false} />
? <PhotoUpload
showUploadStatus={false}
isUploading={isUploading}
setIsUploading={setIsUploading}
/>
: <Link
href={PATH_ADMIN_PHOTOS}
className="button primary"

View File

@ -0,0 +1,86 @@
'use client';
import PhotoUpload from '@/photo/PhotoUpload';
import { clsx } from 'clsx/lite';
import SiteGrid from '@/components/SiteGrid';
import AdminUploadsTable from '@/admin/AdminUploadsTable';
import { AI_TEXT_GENERATION_ENABLED, PRO_MODE_ENABLED } from '@/site/config';
import AdminPhotosTable from '@/admin/AdminPhotosTable';
import AdminPhotosTableInfinite from '@/admin/AdminPhotosTableInfinite';
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
import { PATH_ADMIN_OUTDATED } from '@/site/paths';
import { Photo } from '@/photo';
import { StorageListResponse } from '@/services/storage';
import { useState } from 'react';
import { FaRegClock } from 'react-icons/fa6';
export default function AdminPhotosClient({
photos,
photosCount,
photosCountOutdated,
onLastPhotoUpload,
blobPhotoUrls,
infiniteScrollInitial,
infiniteScrollMultiple,
}: {
photos: Photo[]
photosCount: number
photosCountOutdated: number
onLastPhotoUpload: () => Promise<void>
blobPhotoUrls: StorageListResponse
infiniteScrollInitial: number
infiniteScrollMultiple: number
}) {
const [isUploading, setIsUploading] = useState(false);
return (
<SiteGrid
contentMain={
<div className="space-y-4">
<div className="flex">
<div className="grow">
<PhotoUpload
shouldResize={!PRO_MODE_ENABLED}
isUploading={isUploading}
setIsUploading={setIsUploading}
onLastUpload={onLastPhotoUpload}
/>
</div>
{photosCountOutdated > 0 && <PathLoaderButton
path={PATH_ADMIN_OUTDATED}
icon={<FaRegClock size={15} className="translate-y-[1px]" />}
title={`${photosCountOutdated} Outdated Photos`}
className={clsx(
isUploading && 'hidden md:inline-flex',
)}
hideTextOnMobile={false}
>
{photosCountOutdated}
</PathLoaderButton>}
</div>
{!isUploading && blobPhotoUrls.length > 0 &&
<div className={clsx(
'border-b pb-6',
'border-gray-200 dark:border-gray-700',
)}>
<AdminUploadsTable
title={`Photo Blobs (${blobPhotoUrls.length})`}
urls={blobPhotoUrls}
/>
</div>}
<div className="space-y-4">
<AdminPhotosTable
photos={photos}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
/>
{photosCount > photos.length &&
<AdminPhotosTableInfinite
initialOffset={infiniteScrollInitial}
itemsPerPage={infiniteScrollMultiple}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
/>}
</div>
</div>}
/>
);
}

View File

@ -25,11 +25,15 @@ export default function AdminPhotosTable({
onLastPhotoVisible,
revalidatePhoto,
hasAiTextGeneration,
canEdit = true,
canDelete = true,
}: {
photos: Photo[],
onLastPhotoVisible?: () => void
revalidatePhoto?: RevalidatePhoto
hasAiTextGeneration?: boolean
canEdit?: boolean
canDelete?: boolean
}) {
const { invalidateSwr } = useAppState();
@ -82,7 +86,8 @@ export default function AdminPhotosTable({
'flex flex-nowrap',
'gap-2 sm:gap-3 items-center',
)}>
<EditButton path={pathForAdminPhotoEdit(photo)} />
{canEdit &&
<EditButton path={pathForAdminPhotoEdit(photo)} />}
<PhotoSyncButton
action={syncPhotoAction}
photoTitle={titleForPhoto(photo)}
@ -92,15 +97,16 @@ export default function AdminPhotosTable({
shouldConfirm
shouldToast
/>
<FormWithConfirm
action={deletePhotoFormAction}
confirmText={deleteConfirmationTextForPhoto(photo)}
onSubmit={() => revalidatePhoto?.(photo.id, true)}
>
<input type="hidden" name="id" value={photo.id} />
<input type="hidden" name="url" value={photo.url} />
<DeleteButton clearLocalState />
</FormWithConfirm>
{canDelete &&
<FormWithConfirm
action={deletePhotoFormAction}
confirmText={deleteConfirmationTextForPhoto(photo)}
onSubmit={() => revalidatePhoto?.(photo.id, true)}
>
<input type="hidden" name="id" value={photo.id} />
<input type="hidden" name="url" value={photo.url} />
<DeleteButton clearLocalState />
</FormWithConfirm>}
</div>
</Fragment>)}
</AdminTable>

View File

@ -3,16 +3,18 @@
import { PATH_ADMIN_PHOTOS } from '@/site/paths';
import InfinitePhotoScroll from '../photo/InfinitePhotoScroll';
import AdminPhotosTable from './AdminPhotosTable';
import { ComponentProps } from 'react';
export default function AdminPhotosTableInfinite({
initialOffset,
itemsPerPage,
hasAiTextGeneration,
canEdit,
canDelete,
}: {
initialOffset: number
itemsPerPage: number
hasAiTextGeneration?: boolean
}) {
} & Omit<ComponentProps<typeof AdminPhotosTable>, 'photos'>) {
return (
<InfinitePhotoScroll
cacheKey={`page-${PATH_ADMIN_PHOTOS}`}
@ -27,6 +29,8 @@ export default function AdminPhotosTableInfinite({
onLastPhotoVisible={onLastPhotoVisible}
revalidatePhoto={revalidatePhoto}
hasAiTextGeneration={hasAiTextGeneration}
canEdit={canEdit}
canDelete={canDelete}
/>}
</InfinitePhotoScroll>
);

View File

@ -54,6 +54,8 @@ export default async function AdminPhotosPage() {
<AdminPhotosTable
photos={photos}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
canEdit={false}
canDelete={false}
/>
</div>
</div>

View File

@ -1,19 +1,9 @@
import PhotoUpload from '@/photo/PhotoUpload';
import { clsx } from 'clsx/lite';
import SiteGrid from '@/components/SiteGrid';
import AdminUploadsTable from '@/admin/AdminUploadsTable';
import { AI_TEXT_GENERATION_ENABLED, PRO_MODE_ENABLED } from '@/site/config';
import { getStoragePhotoUrlsNoStore } from '@/services/storage/cache';
import { getPhotos } from '@/photo/db/query';
import { revalidatePath } from 'next/cache';
import AdminPhotosTable from '@/admin/AdminPhotosTable';
import AdminPhotosTableInfinite from
'@/admin/AdminPhotosTableInfinite';
import { getPhotosMetaCached } from '@/photo/cache';
import { IoInformationCircleOutline } from 'react-icons/io5';
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
import { PATH_ADMIN_OUTDATED } from '@/site/paths';
import { OUTDATED_THRESHOLD } from '@/photo';
import AdminPhotosClient from '@/admin/AdminPhotosClient';
import { revalidatePath } from 'next/cache';
const DEBUG_PHOTO_BLOBS = false;
@ -47,54 +37,18 @@ export default async function AdminPhotosPage() {
]);
return (
<SiteGrid
contentMain={
<div className="space-y-4">
<div className="flex">
<div className="grow">
<PhotoUpload
shouldResize={!PRO_MODE_ENABLED}
onLastUpload={async () => {
'use server';
// Update upload count in admin nav
revalidatePath('/admin', 'layout');
}}
/>
</div>
{photosCountOutdated > 0 && <PathLoaderButton
path={PATH_ADMIN_OUTDATED}
icon={<IoInformationCircleOutline
size={18}
/>}
title={`${photosCountOutdated} Outdated Photos`}
hideTextOnMobile={false}
>
{photosCountOutdated}
</PathLoaderButton>}
</div>
{blobPhotoUrls.length > 0 &&
<div className={clsx(
'border-b pb-6',
'border-gray-200 dark:border-gray-700',
)}>
<AdminUploadsTable
title={`Photo Blobs (${blobPhotoUrls.length})`}
urls={blobPhotoUrls}
/>
</div>}
<div className="space-y-4">
<AdminPhotosTable
photos={photos}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
/>
{photosCount > photos.length &&
<AdminPhotosTableInfinite
initialOffset={INFINITE_SCROLL_INITIAL_ADMIN_PHOTOS}
itemsPerPage={INFINITE_SCROLL_MULTIPLE_ADMIN_PHOTOS}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
/>}
</div>
</div>}
/>
<AdminPhotosClient {...{
photos,
photosCount,
photosCountOutdated,
onLastPhotoUpload: async () => {
'use server';
// Update upload count in admin nav
revalidatePath('/admin', 'layout');
},
blobPhotoUrls,
infiniteScrollInitial: INFINITE_SCROLL_INITIAL_ADMIN_PHOTOS,
infiniteScrollMultiple: INFINITE_SCROLL_MULTIPLE_ADMIN_PHOTOS,
}} />
);
}

View File

@ -10,15 +10,18 @@ import { clsx } from 'clsx/lite';
export default function PhotoUpload({
shouldResize,
onLastUpload,
isUploading,
setIsUploading,
showUploadStatus,
debug,
}: {
shouldResize?: boolean
onLastUpload?: () => Promise<void>
isUploading: boolean
setIsUploading: (isUploading: boolean) => void
showUploadStatus?: boolean
debug?: boolean
}) {
const [isUploading, setIsUploading] = useState(false);
const [uploadError, setUploadError] = useState<string>();
const [debugDownload, setDebugDownload] = useState<{
href: string