Add guidance around sync buttons

This commit is contained in:
Sam Becker 2025-03-07 08:39:40 -08:00
parent 333f1b99d7
commit 21ed815cba
5 changed files with 78 additions and 55 deletions

View File

@ -6,7 +6,10 @@ import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';
import { TIMEZONE_COOKIE_NAME } from '@/utility/timezone';
import { getOutdatedPhotosCount } from '@/photo/db/query';
import { PRESERVE_ORIGINAL_UPLOADS } from '@/app/config';
import {
AI_TEXT_GENERATION_ENABLED,
PRESERVE_ORIGINAL_UPLOADS,
} from '@/app/config';
export const maxDuration = 60;
@ -45,6 +48,7 @@ export default async function AdminPhotosPage() {
photosCount,
photosCountOutdated,
shouldResize: !PRESERVE_ORIGINAL_UPLOADS,
hasAiTextGeneration: AI_TEXT_GENERATION_ENABLED,
onLastUpload: async () => {
'use server';
// Update upload count in admin nav

View File

@ -2,9 +2,6 @@
import { clsx } from 'clsx/lite';
import SiteGrid from '@/components/SiteGrid';
import {
AI_TEXT_GENERATION_ENABLED,
} from '@/app/config';
import AdminPhotosTable from '@/admin/AdminPhotosTable';
import AdminPhotosTableInfinite from '@/admin/AdminPhotosTableInfinite';
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
@ -23,6 +20,7 @@ export default function AdminPhotosClient({
photosCountOutdated,
blobPhotoUrls,
shouldResize,
hasAiTextGeneration,
onLastUpload,
infiniteScrollInitial,
infiniteScrollMultiple,
@ -33,6 +31,7 @@ export default function AdminPhotosClient({
photosCountOutdated: number
blobPhotoUrls: StorageListResponse
shouldResize: boolean
hasAiTextGeneration: boolean
onLastUpload: () => Promise<void>
infiniteScrollInitial: number
infiniteScrollMultiple: number
@ -89,14 +88,14 @@ export default function AdminPhotosClient({
<div className="space-y-[6px] sm:space-y-[10px]">
<AdminPhotosTable
photos={photos}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
hasAiTextGeneration={hasAiTextGeneration}
timezone={timezone}
/>
{photosCount > photos.length &&
<AdminPhotosTableInfinite
initialOffset={infiniteScrollInitial}
itemsPerPage={infiniteScrollMultiple}
hasAiTextGeneration={AI_TEXT_GENERATION_ENABLED}
hasAiTextGeneration={hasAiTextGeneration}
timezone={timezone}
/>}
</div>

View File

@ -2,6 +2,7 @@
import LoaderButton from '@/components/primitives/LoaderButton';
import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus';
import Tooltip from '@/components/Tooltip';
import { getExifDataAction } from '@/photo/actions';
import { PhotoFormData } from '@/photo/form';
import { clsx } from 'clsx/lite';
@ -18,23 +19,27 @@ export default function ExifCaptureButton({
const [isLoading, setIsLoading] = useState(false);
return (
<LoaderButton
title="Update photo from original file"
isLoading={isLoading}
onClick={() => {
setIsLoading(true);
getExifDataAction(photoUrl)
.then(onSync)
.finally(() => setIsLoading(false));
}}
icon={<LuDatabaseBackup
size={16}
className={clsx(
'translate-y-[0.5px] translate-x-[0.5px]',
'sm:translate-x-[-0.5px]',
)} />}
<Tooltip
content="Refresh form with EXIF data from original file"
supportMobile={false}
>
EXIF
</LoaderButton>
<LoaderButton
isLoading={isLoading}
onClick={() => {
setIsLoading(true);
getExifDataAction(photoUrl)
.then(onSync)
.finally(() => setIsLoading(false));
}}
icon={<LuDatabaseBackup
size={16}
className={clsx(
'translate-y-[0.5px] translate-x-[0.5px]',
'sm:translate-x-[-0.5px]',
)} />}
>
EXIF
</LoaderButton>
</Tooltip>
);
}

View File

@ -3,6 +3,7 @@ import { syncPhotoAction } from '@/photo/actions';
import IconGrSync from '@/app/IconGrSync';
import { toastSuccess } from '@/toast';
import { ComponentProps, useState } from 'react';
import Tooltip from '@/components/Tooltip';
export default function PhotoSyncButton({
photoId,
@ -33,29 +34,33 @@ export default function PhotoSyncButton({
confirmText.push('This action cannot be undone.');
return (
<LoaderButton
title="Update photo from original file"
className={className}
icon={<IconGrSync
className="translate-y-[0.5px] translate-x-[0.5px]"
/>}
onClick={() => {
if (!shouldConfirm || window.confirm(confirmText.join(' '))) {
setIsSyncing(true);
syncPhotoAction(photoId)
.then(() => {
onSyncComplete?.();
if (shouldToast) {
toastSuccess(photoTitle
? `"${photoTitle}" data synced`
: 'Data synced');
}
})
.finally(() => setIsSyncing(false));
}
}}
isLoading={isSyncing || isSyncingExternal}
disabled={disabled}
/>
<Tooltip
content="Regenerate photo data"
supportMobile={false}
>
<LoaderButton
className={className}
icon={<IconGrSync
className="translate-y-[0.5px] translate-x-[0.5px]"
/>}
onClick={() => {
if (!shouldConfirm || window.confirm(confirmText.join(' '))) {
setIsSyncing(true);
syncPhotoAction(photoId)
.then(() => {
onSyncComplete?.();
if (shouldToast) {
toastSuccess(photoTitle
? `"${photoTitle}" data synced`
: 'Data synced');
}
})
.finally(() => setIsSyncing(false));
}
}}
isLoading={isSyncing || isSyncingExternal}
disabled={disabled}
/>
</Tooltip>
);
}

View File

@ -10,14 +10,16 @@ import useClickInsideOutside from '@/utility/useClickInsideOutside';
export default function TooltipPrimitive({
content,
className,
classNameTrigger,
classNameTrigger: classNameTriggerProp,
sideOffset = 10,
supportMobile = true,
children,
}: {
content?: ReactNode
className?: string
classNameTrigger?: string
sideOffset?: number
supportMobile?: boolean
children: ReactNode
}) {
const refTrigger = useRef<HTMLButtonElement>(null);
@ -27,6 +29,8 @@ export default function TooltipPrimitive({
const supportsHover = useSupportsHover();
const includeButton = supportMobile && !supportsHover;
useClickInsideOutside({
htmlElements: [refTrigger, refContent],
onClickOutside: () => {
@ -34,17 +38,23 @@ export default function TooltipPrimitive({
},
});
const classNameTrigger = clsx('link cursor-default', classNameTriggerProp);
return (
<Tooltip.Provider delayDuration={100}>
<Tooltip.Root open={!supportsHover ? isOpen : undefined}>
<Tooltip.Trigger asChild>
<button
ref={refTrigger}
onClick={!supportsHover ? () => setIsOpen(!isOpen) : undefined}
className={clsx('link cursor-default', classNameTrigger)}
>
{children}
</button>
{includeButton
? <button
ref={refTrigger}
onClick={() => setIsOpen(!isOpen)}
className={classNameTrigger}
>
{children}
</button>
: <span className={classNameTrigger}>
{children}
</span>}
</Tooltip.Trigger>
<Tooltip.Portal >
<Tooltip.Content