Refine states for batch upload actions
This commit is contained in:
parent
021e107cad
commit
0070e0d03e
@ -1,14 +1,12 @@
|
||||
import { BiImageAdd } from 'react-icons/bi';
|
||||
import PathLoaderButton from '@/components/primitives/PathLoaderButton';
|
||||
|
||||
export default function AddButton({
|
||||
path,
|
||||
}: {
|
||||
path: string,
|
||||
}) {
|
||||
import { ComponentProps } from 'react';
|
||||
export default function AddButton(
|
||||
props: ComponentProps<typeof PathLoaderButton>,
|
||||
) {
|
||||
return (
|
||||
<PathLoaderButton
|
||||
path={path}
|
||||
{...props}
|
||||
icon={<BiImageAdd size={18} className="translate-x-[1px]" />}
|
||||
>
|
||||
Add
|
||||
|
||||
@ -135,7 +135,7 @@ export default function AdminAppConfigurationClient({
|
||||
renderSubStatus(
|
||||
type,
|
||||
renderEnvVars([variable]),
|
||||
'translate-y-[4.5px]',
|
||||
'translate-y-[7px]',
|
||||
);
|
||||
|
||||
const renderError = ({
|
||||
@ -534,30 +534,32 @@ export default function AdminAppConfigurationClient({
|
||||
status={hasCategoryVisibility}
|
||||
optional
|
||||
>
|
||||
{categoryVisibility.map((category, index) =>
|
||||
<Fragment key={category}>
|
||||
{renderSubStatus(
|
||||
'checked',
|
||||
<>
|
||||
{index + 1}
|
||||
{'.'}
|
||||
{capitalize(category)}
|
||||
</>,
|
||||
)}
|
||||
</Fragment>)}
|
||||
{getHiddenDefaultCategories(categoryVisibility)
|
||||
.map((category, index) =>
|
||||
<div className="my-1">
|
||||
{categoryVisibility.map((category, index) =>
|
||||
<Fragment key={category}>
|
||||
{renderSubStatus(
|
||||
'optional',
|
||||
<span className="text-dim">
|
||||
{categoryVisibility.length + index + 1}
|
||||
'checked',
|
||||
<>
|
||||
{index + 1}
|
||||
{'.'}
|
||||
{capitalize(category)}
|
||||
</span>,
|
||||
</>,
|
||||
)}
|
||||
</Fragment>)}
|
||||
Configure photo category visibility and order
|
||||
{getHiddenDefaultCategories(categoryVisibility)
|
||||
.map((category, index) =>
|
||||
<Fragment key={category}>
|
||||
{renderSubStatus(
|
||||
'optional',
|
||||
<span className="text-dim">
|
||||
{categoryVisibility.length + index + 1}
|
||||
{'.'}
|
||||
{capitalize(category)}
|
||||
</span>,
|
||||
)}
|
||||
</Fragment>)}
|
||||
</div>
|
||||
Configure order and visibility of categories
|
||||
(seen in grid sidebar and CMD-K results)
|
||||
by storing comma-separated values
|
||||
(default: {`"${DEFAULT_CATEGORY_KEYS.join(',')}"`}):
|
||||
|
||||
@ -24,18 +24,22 @@ import { useAppState } from '@/state/AppState';
|
||||
|
||||
const UPLOAD_BATCH_SIZE = 4;
|
||||
|
||||
export default function AdminAddAllUploads({
|
||||
export default function AdminBatchUploadActions({
|
||||
storageUrls,
|
||||
uniqueTags,
|
||||
isAdding,
|
||||
setIsAdding,
|
||||
setUrlAddStatuses,
|
||||
isDeleting,
|
||||
setIsDeleting,
|
||||
}: {
|
||||
storageUrls: string[]
|
||||
uniqueTags?: Tags
|
||||
isAdding: boolean
|
||||
setIsAdding: (isAdding: boolean) => void
|
||||
setIsAdding: Dispatch<SetStateAction<boolean>>
|
||||
setUrlAddStatuses: Dispatch<SetStateAction<UrlAddStatus[]>>
|
||||
isDeleting: boolean
|
||||
setIsDeleting: Dispatch<SetStateAction<boolean>>
|
||||
}) {
|
||||
const { updateAdminData } = useAppState();
|
||||
|
||||
@ -141,7 +145,11 @@ export default function AdminAddAllUploads({
|
||||
className="w-full justify-center"
|
||||
progress={addingProgress}
|
||||
isLoading={isAdding}
|
||||
disabled={Boolean(tagErrorMessage) || isAddingComplete}
|
||||
disabled={
|
||||
Boolean(tagErrorMessage) ||
|
||||
isAddingComplete ||
|
||||
isDeleting
|
||||
}
|
||||
icon={isAddingComplete
|
||||
? <BiCheckCircle size={18} className="translate-x-[1px]" />
|
||||
: <BiImageAdd
|
||||
@ -187,13 +195,19 @@ export default function AdminAddAllUploads({
|
||||
</ProgressButton>
|
||||
<DeleteUploadButton
|
||||
urls={storageUrls}
|
||||
onDelete={() => {
|
||||
updateAdminData?.({ uploadsCount: 0 });
|
||||
router.push(PATH_ADMIN_PHOTOS);
|
||||
onDeleteStart={() => setIsDeleting(true)}
|
||||
onDelete={didFail => {
|
||||
if (!didFail) {
|
||||
updateAdminData?.({ uploadsCount: 0 });
|
||||
router.push(PATH_ADMIN_PHOTOS);
|
||||
} else {
|
||||
setIsDeleting(false);
|
||||
}
|
||||
}}
|
||||
className="w-full flex justify-center"
|
||||
shouldRedirectToAdminPhotos
|
||||
hideTextOnMobile={false}
|
||||
disabled={isAdding}
|
||||
>
|
||||
Delete All Uploads
|
||||
</DeleteUploadButton>
|
||||
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { StorageListResponse } from '@/platforms/storage';
|
||||
import AdminAddAllUploads from './AdminAddAllUploads';
|
||||
import AdminBatchUploadActions from './AdminBatchUploadActions';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Tags } from '@/tag';
|
||||
import AdminUploadsTable from './AdminUploadsTable';
|
||||
@ -23,20 +23,28 @@ export default function AdminUploadsClient({
|
||||
}) {
|
||||
const [isAdding, setIsAdding] = useState(false);
|
||||
const [urlAddStatuses, setUrlAddStatuses] = useState<UrlAddStatus[]>(urls);
|
||||
|
||||
const storageUrls = useMemo(() => urls.map(({ url }) => url), [urls]);
|
||||
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{(urls.length > 1 || isAdding) &&
|
||||
<AdminAddAllUploads {...{
|
||||
<AdminBatchUploadActions {...{
|
||||
storageUrls,
|
||||
uniqueTags,
|
||||
isAdding,
|
||||
setIsAdding,
|
||||
setUrlAddStatuses,
|
||||
isDeleting,
|
||||
setIsDeleting,
|
||||
}} />}
|
||||
<AdminUploadsTable {...{ isAdding, urlAddStatuses, setUrlAddStatuses }} />
|
||||
<AdminUploadsTable {...{
|
||||
isAdding,
|
||||
urlAddStatuses,
|
||||
setUrlAddStatuses,
|
||||
isDeleting,
|
||||
}} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -15,10 +15,12 @@ export default function AdminUploadsTable({
|
||||
isAdding,
|
||||
urlAddStatuses,
|
||||
setUrlAddStatuses,
|
||||
isDeleting,
|
||||
}: {
|
||||
isAdding?: boolean
|
||||
urlAddStatuses: UrlAddStatus[]
|
||||
setUrlAddStatuses?: (urlAddStatuses: UrlAddStatus[]) => void
|
||||
isDeleting?: boolean
|
||||
}) {
|
||||
const isComplete = urlAddStatuses.every(({ status }) => status === 'added');
|
||||
|
||||
@ -85,13 +87,17 @@ export default function AdminUploadsTable({
|
||||
</span>}
|
||||
</>
|
||||
: <>
|
||||
<AddButton path={pathForAdminUploadUrl(url)} />
|
||||
<AddButton
|
||||
path={pathForAdminUploadUrl(url)}
|
||||
disabled={isDeleting}
|
||||
/>
|
||||
<DeleteBlobButton
|
||||
urls={[url]}
|
||||
shouldRedirectToAdminPhotos={urlAddStatuses.length <= 1}
|
||||
onDelete={() => setUrlAddStatuses?.(urlAddStatuses.filter(
|
||||
({ url: urlToRemove }) => urlToRemove !== url,
|
||||
))}
|
||||
isLoading={isDeleting}
|
||||
/>
|
||||
</>}
|
||||
</span>
|
||||
|
||||
@ -16,6 +16,7 @@ export default function DeleteButton({
|
||||
className={clsx(
|
||||
'text-red-500! dark:text-red-500!',
|
||||
'active:bg-red-100/50! dark:active:bg-red-950/50!',
|
||||
'disabled:text-red-500/60! dark:disabled:text-red-500/60!',
|
||||
'disabled:bg-red-100/50! dark:disabled:bg-red-950/50!',
|
||||
'border-red-200! hover:border-red-300!',
|
||||
'dark:border-red-900/75! dark:hover:border-red-900!',
|
||||
|
||||
@ -4,34 +4,36 @@ import { deleteUploadsAction } from '@/photo/actions';
|
||||
import DeleteButton from './DeleteButton';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PATH_ADMIN_PHOTOS } from '@/app/paths';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { ComponentProps, useState } from 'react';
|
||||
import LoaderButton from '@/components/primitives/LoaderButton';
|
||||
|
||||
export default function DeleteUploadButton({
|
||||
urls,
|
||||
shouldRedirectToAdminPhotos,
|
||||
onDeleteStart,
|
||||
onDelete,
|
||||
hideTextOnMobile,
|
||||
children,
|
||||
className,
|
||||
isLoading,
|
||||
...props
|
||||
}: {
|
||||
urls: string[]
|
||||
shouldRedirectToAdminPhotos?: boolean
|
||||
onDelete?: () => void
|
||||
hideTextOnMobile?: boolean
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
}) {
|
||||
onDeleteStart?: () => void
|
||||
onDelete?: (didFail?: boolean) => void
|
||||
} & ComponentProps<typeof LoaderButton>) {
|
||||
const router = useRouter();
|
||||
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
return (
|
||||
<DeleteButton
|
||||
className={className}
|
||||
{...props}
|
||||
confirmText={urls.length === 1
|
||||
? 'Are you sure you want to delete this upload?'
|
||||
: `Are you sure you want to delete all ${urls.length} uploads?`}
|
||||
onClick={() => {
|
||||
onDeleteStart?.();
|
||||
setIsDeleting(true);
|
||||
deleteUploadsAction(urls)
|
||||
.then(() => {
|
||||
@ -42,9 +44,12 @@ export default function DeleteUploadButton({
|
||||
setIsDeleting(false);
|
||||
}
|
||||
})
|
||||
.catch(() => setIsDeleting(false));
|
||||
.catch(() => {
|
||||
setIsDeleting(false);
|
||||
onDelete?.(true);
|
||||
});
|
||||
}}
|
||||
isLoading={isDeleting}
|
||||
isLoading={isLoading ?? isDeleting}
|
||||
hideTextOnMobile={hideTextOnMobile}
|
||||
>
|
||||
{children}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user