Add favorites to admin photo menu
This commit is contained in:
parent
8151a4f1cd
commit
d860777604
@ -3,16 +3,38 @@
|
||||
import { ComponentProps } from 'react';
|
||||
import { pathForAdminPhotoEdit } from '@/site/paths';
|
||||
import MoreMenu from '../components/MoreMenu';
|
||||
import { toggleFavoritePhoto } from '@/photo/actions';
|
||||
import { FaRegEdit, FaStar } from 'react-icons/fa';
|
||||
import { Photo } from '@/photo';
|
||||
import { isPathFavs, isPhotoFav } from '@/tag';
|
||||
import clsx from 'clsx/lite';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export default function AdminPhotoMenuClient({
|
||||
photoId,
|
||||
photo,
|
||||
...props
|
||||
}: Omit<ComponentProps<typeof MoreMenu>, 'items'> & {
|
||||
photoId: string
|
||||
photo: Photo
|
||||
}) {
|
||||
const isFav = isPhotoFav(photo);
|
||||
const path = usePathname();
|
||||
const shouldRedirect = isPathFavs(path) && isFav;
|
||||
return (
|
||||
<MoreMenu {...{
|
||||
items: [{ href: pathForAdminPhotoEdit(photoId), label: 'Edit Photo' }],
|
||||
items: [
|
||||
{
|
||||
label: 'Edit Photo',
|
||||
icon: <FaRegEdit size={14} className="translate-y-[-0.5px]" />,
|
||||
href: pathForAdminPhotoEdit(photo.id),
|
||||
}, {
|
||||
label: isFav ? 'Unfavorite' : 'Favorite',
|
||||
icon: <FaStar stroke='text-amber-500' className={clsx(
|
||||
'translate-x-[-1px]',
|
||||
isFav && 'text-amber-500',
|
||||
)} />,
|
||||
action: () => toggleFavoritePhoto(photo.id, shouldRedirect),
|
||||
},
|
||||
],
|
||||
...props,
|
||||
}}/>
|
||||
);
|
||||
|
||||
@ -2,17 +2,45 @@ import { clsx} from 'clsx/lite';
|
||||
import Link from 'next/link';
|
||||
import { Menu } from '@headlessui/react';
|
||||
import { FiMoreHorizontal } from 'react-icons/fi';
|
||||
import { ReactNode } from 'react';
|
||||
import { Fragment, ReactNode, useState } from 'react';
|
||||
|
||||
export default function MoreMenu({
|
||||
items,
|
||||
className,
|
||||
buttonClassName,
|
||||
}: {
|
||||
items: { href: string, label: ReactNode }[]
|
||||
items: {
|
||||
label: ReactNode,
|
||||
icon?: ReactNode,
|
||||
href?: string,
|
||||
action?: () => Promise<void>,
|
||||
}[]
|
||||
className?: string
|
||||
buttonClassName?: string
|
||||
}) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const itemClass = clsx(
|
||||
'block w-full',
|
||||
'border-none min-h-0 bg-transparent',
|
||||
'text-left',
|
||||
'px-3 py-1.5 rounded-[3px]',
|
||||
'hover:text-main',
|
||||
'hover:bg-gray-50 active:bg-gray-100',
|
||||
'hover:dark:bg-gray-900/75 active:dark:bg-gray-900',
|
||||
'whitespace-nowrap',
|
||||
isLoading && 'cursor-not-allowed opacity-50',
|
||||
);
|
||||
|
||||
const renderItemContent = (
|
||||
label: ReactNode,
|
||||
icon?: ReactNode,
|
||||
) =>
|
||||
<div className="flex items-center">
|
||||
<span className="w-6">{icon}</span>
|
||||
<span>{label}</span>
|
||||
</div>;
|
||||
|
||||
return (
|
||||
<div className={clsx(
|
||||
className,
|
||||
@ -30,27 +58,38 @@ export default function MoreMenu({
|
||||
<Menu.Items className={clsx(
|
||||
'block outline-none h-auto',
|
||||
'absolute top-6',
|
||||
'text-left',
|
||||
'md:right-1',
|
||||
'text-sm',
|
||||
'p-1 rounded-md border',
|
||||
'bg-content',
|
||||
'shadow-lg dark:shadow-xl',
|
||||
)}>
|
||||
{items.map(({ href, label }) =>
|
||||
<Menu.Item key={href}>
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx(
|
||||
'block',
|
||||
'px-3 py-1.5 rounded-[3px]',
|
||||
'hover:text-main',
|
||||
'hover:bg-gray-50 active:bg-gray-100',
|
||||
'hover:dark:bg-gray-900/75 active:dark:bg-gray-900',
|
||||
'whitespace-nowrap',
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
{items.map(({ label, icon, href, action }) =>
|
||||
<Menu.Item
|
||||
key={`${label}`}
|
||||
disabled={isLoading}
|
||||
as={Fragment}
|
||||
>
|
||||
<>
|
||||
{href &&
|
||||
<Link
|
||||
href={href}
|
||||
className={itemClass}
|
||||
>
|
||||
{renderItemContent(label, icon)}
|
||||
</Link>}
|
||||
{action &&
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsLoading(true);
|
||||
action().finally(() => setIsLoading(false));
|
||||
}}
|
||||
className={itemClass}
|
||||
>
|
||||
{renderItemContent(label, icon)}
|
||||
</button>}
|
||||
</>
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu.Items>
|
||||
|
||||
@ -90,7 +90,7 @@ export default function PhotoLarge({
|
||||
</div>
|
||||
<Suspense>
|
||||
<div className="h-4 translate-y-[-3.5px] z-10">
|
||||
<AdminPhotoMenu photoId={photo.id} />
|
||||
<AdminPhotoMenu photo={photo} />
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@ -23,7 +23,7 @@ import {
|
||||
revalidateAllKeysAndPaths,
|
||||
revalidatePhotosKey,
|
||||
} from '@/photo/cache';
|
||||
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS } from '@/site/paths';
|
||||
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS, PATH_ROOT } from '@/site/paths';
|
||||
import { extractExifDataFromBlobPath } from './server';
|
||||
import { TAG_FAVS, isTagFavs } from '@/tag';
|
||||
import { convertPhotoToPhotoDbInsert } from '.';
|
||||
@ -52,7 +52,10 @@ export async function updatePhotoAction(formData: FormData) {
|
||||
redirect(PATH_ADMIN_PHOTOS);
|
||||
}
|
||||
|
||||
export async function toggleFavoritePhoto(photoId: string) {
|
||||
export async function toggleFavoritePhoto(
|
||||
photoId: string,
|
||||
shouldRedirect?: boolean,
|
||||
) {
|
||||
const photo = await getPhoto(photoId);
|
||||
if (photo) {
|
||||
const { tags } = photo;
|
||||
@ -61,6 +64,9 @@ export async function toggleFavoritePhoto(photoId: string) {
|
||||
: [...tags, TAG_FAVS];
|
||||
await sqlUpdatePhoto(convertPhotoToPhotoDbInsert(photo));
|
||||
revalidateAllKeysAndPaths();
|
||||
if (shouldRedirect) {
|
||||
redirect(PATH_ROOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,11 @@ import {
|
||||
descriptionForPhotoSet,
|
||||
photoQuantityText,
|
||||
} from '@/photo';
|
||||
import { absolutePathForTag, absolutePathForTagImage } from '@/site/paths';
|
||||
import {
|
||||
absolutePathForTag,
|
||||
absolutePathForTagImage,
|
||||
getPathComponents,
|
||||
} from '@/site/paths';
|
||||
import { capitalizeWords, convertStringToArray } from '@/utility/string';
|
||||
|
||||
export const TAG_FAVS = 'favs';
|
||||
@ -77,3 +81,8 @@ export const generateMetaForTag = (
|
||||
});
|
||||
|
||||
export const isTagFavs = (tag: string) => tag.toLowerCase() === TAG_FAVS;
|
||||
|
||||
export const isPhotoFav = ({ tags }: Photo) => tags.some(isTagFavs);
|
||||
|
||||
export const isPathFavs = (pathname?: string) =>
|
||||
getPathComponents(pathname).tag === TAG_FAVS;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user