From 348b2ecc80931b247e7db9e4378ba456a3f7faf7 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 10:55:27 -0500 Subject: [PATCH 1/2] Dismiss admin banner when times are no longer recent --- src/admin/AdminNav.tsx | 4 ++-- src/admin/AdminNavClient.tsx | 30 +++++++++++++++++++++--------- src/admin/AdminPhotoMenuClient.tsx | 4 ++-- src/admin/DeleteButton.tsx | 6 +++--- src/state/AppState.ts | 4 ++-- src/state/AppStateProvider.tsx | 10 +++++----- 6 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx index ecc00562..2185390f 100644 --- a/src/admin/AdminNav.tsx +++ b/src/admin/AdminNav.tsx @@ -16,7 +16,7 @@ export default async function AdminNav() { countPhotos, countUploads, countTags, - mostRecentUpdate, + mostRecentPhotoUpdateTime, ] = await Promise.all([ getPhotosCountIncludingHiddenCached().catch(() => 0), getStorageUploadUrlsNoStore() @@ -53,6 +53,6 @@ export default async function AdminNav() { if (countTags > 0) { items.push(navItemTags); } return ( - + ); } diff --git a/src/admin/AdminNavClient.tsx b/src/admin/AdminNavClient.tsx index 58348a5e..69709277 100644 --- a/src/admin/AdminNavClient.tsx +++ b/src/admin/AdminNavClient.tsx @@ -12,31 +12,43 @@ import { clsx } from 'clsx/lite'; import { differenceInMinutes } from 'date-fns'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { BiCog } from 'react-icons/bi'; import { FaRegClock } from 'react-icons/fa'; -const RECENCY_THRESHOLD = 5; +const areTimesRecent = (dates: Date[]) => dates + .some(date => differenceInMinutes(new Date(), date) < 5); export default function AdminNavClient({ items, - mostRecentUpdate, + mostRecentPhotoUpdateTime, }: { items: { label: string, href: string, count: number, }[] - mostRecentUpdate?: Date + mostRecentPhotoUpdateTime?: Date }) { const pathname = usePathname(); - const { adminUpdates = [] } = useAppState(); + const { adminUpdateTimes = [] } = useAppState(); - const shouldShowBanner = useMemo(() => - ((mostRecentUpdate ? [mostRecentUpdate] : []).concat(adminUpdates)) - .some(date => differenceInMinutes(new Date(), date) < RECENCY_THRESHOLD) - , [mostRecentUpdate, adminUpdates]); + const updateTimes = useMemo(() => + (mostRecentPhotoUpdateTime ? [mostRecentPhotoUpdateTime] : []) + .concat(adminUpdateTimes) + , [mostRecentPhotoUpdateTime, adminUpdateTimes]); + + const [shouldShowBanner, setShouldShowBanner] = + useState(areTimesRecent(updateTimes)); + + useEffect(() => { + // Check every 10 seconds if update times are recent + const timeout = setTimeout(() => + setShouldShowBanner(areTimesRecent(updateTimes)) + , 10_000); + return () => clearTimeout(timeout); + }, [updateTimes]); return ( { revalidatePhoto?.(photo.id, true); - addAdminUpdate?.(); + registerAdminUpdate?.(); }); } }, diff --git a/src/admin/DeleteButton.tsx b/src/admin/DeleteButton.tsx index 7128c24f..7c570903 100644 --- a/src/admin/DeleteButton.tsx +++ b/src/admin/DeleteButton.tsx @@ -17,15 +17,15 @@ export default function DeleteButton ( ...rest } = props; - const { invalidateSwr, addAdminUpdate } = useAppState(); + const { invalidateSwr, registerAdminUpdate } = useAppState(); const onFormSubmit = useCallback(() => { onFormSubmitProps?.(); if (clearLocalState) { invalidateSwr?.(); - addAdminUpdate?.(); + registerAdminUpdate?.(); } - }, [onFormSubmitProps, clearLocalState, invalidateSwr, addAdminUpdate]); + }, [onFormSubmitProps, clearLocalState, invalidateSwr, registerAdminUpdate]); return > isCommandKOpen?: boolean setIsCommandKOpen?: Dispatch> - adminUpdates?: Date[] - addAdminUpdate?: () => void + adminUpdateTimes?: Date[] + registerAdminUpdate?: () => void shouldShowBaselineGrid?: boolean setShouldShowBaselineGrid?: Dispatch> clearNextPhotoAnimation?: () => void diff --git a/src/state/AppStateProvider.tsx b/src/state/AppStateProvider.tsx index becd004b..ac4ecf5b 100644 --- a/src/state/AppStateProvider.tsx +++ b/src/state/AppStateProvider.tsx @@ -26,7 +26,7 @@ export default function AppStateProvider({ useState(true); const [isCommandKOpen, setIsCommandKOpen] = useState(false); - const [adminUpdates, setAdminUpdates] = useState([]); + const [adminUpdateTimes, setAdminUpdateTimes] = useState([]); const [shouldShowBaselineGrid, setShouldShowBaselineGrid] = useState(false); @@ -35,8 +35,8 @@ export default function AppStateProvider({ const { data } = useSWR('getCurrentUser', getCurrentUser); useEffect(() => setUserEmail(data?.email ?? undefined), [data]); - const addAdminUpdate = useCallback(() => - setAdminUpdates(updates => [...updates, new Date()]) + const registerAdminUpdate = useCallback(() => + setAdminUpdateTimes(updates => [...updates, new Date()]) , []); useEffect(() => { @@ -60,8 +60,8 @@ export default function AppStateProvider({ setShouldRespondToKeyboardCommands, isCommandKOpen, setIsCommandKOpen, - adminUpdates, - addAdminUpdate, + adminUpdateTimes, + registerAdminUpdate, shouldShowBaselineGrid, setShouldShowBaselineGrid, clearNextPhotoAnimation: () => setNextPhotoAnimation?.(undefined), From dc99c473daa0eaa587fd0525161593d31ca0679a Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sun, 28 Apr 2024 11:02:54 -0500 Subject: [PATCH 2/2] Add FAQ question about static photo updates --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index be4000dc..21a00dac 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,9 @@ FAQ #### Why are my thumbnails square? > Absent configuration, the default grid aspect ratio is `1`. It can be set to any number (for instance `1.5` for 3:2 images) via `NEXT_PUBLIC_GRID_ASPECT_RATIO` or ignored entirely by setting to `0`. +#### Why don't my photo changes show up immediately? +> This template statically optimizes core views like `/` and `/grid` to minimize visitor load times. Consequently, when photos are added, edited, or removed, it might take several minutes for those changes to propagate. If it seems like a change is not taking effect, try navigating to `/admin/configuration` and clicking "Clear Cache." + #### My images/content have fallen out of sync with my database and/or my production site no longer matches local development. What do I do? > Navigate to `/admin/configuration` and click "Clear Cache."