From 3207264fc13c275d6dcfbd2628cc6b3974ceeaed Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Mon, 7 Apr 2025 01:03:26 +0100 Subject: feat: Allow editing bookmark details --- .../dashboard/bookmarks/EditBookmarkDialog.tsx | 371 +++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 apps/web/components/dashboard/bookmarks/EditBookmarkDialog.tsx (limited to 'apps/web/components/dashboard/bookmarks/EditBookmarkDialog.tsx') diff --git a/apps/web/components/dashboard/bookmarks/EditBookmarkDialog.tsx b/apps/web/components/dashboard/bookmarks/EditBookmarkDialog.tsx new file mode 100644 index 00000000..2d47102b --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/EditBookmarkDialog.tsx @@ -0,0 +1,371 @@ +import * as React from "react"; +import { ActionButton } from "@/components/ui/action-button"; +import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { Textarea } from "@/components/ui/textarea"; +import { toast } from "@/components/ui/use-toast"; +import { useTranslation } from "@/lib/i18n/client"; +import { cn } from "@/lib/utils"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { format } from "date-fns"; +import { CalendarIcon } from "lucide-react"; +import { useForm } from "react-hook-form"; + +import { useUpdateBookmark } from "@hoarder/shared-react/hooks/bookmarks"; +import { + BookmarkTypes, + ZBookmark, + ZUpdateBookmarksRequest, + zUpdateBookmarksRequestSchema, +} from "@hoarder/shared/types/bookmarks"; + +import { BookmarkTagsEditor } from "./BookmarkTagsEditor"; + +const formSchema = zUpdateBookmarksRequestSchema; + +export function EditBookmarkDialog({ + open, + setOpen, + bookmark, + children, +}: { + bookmark: ZBookmark; + children?: React.ReactNode; + open: boolean; + setOpen: (v: boolean) => void; +}) { + const { t } = useTranslation(); + const bookmarkToDefault = (bookmark: ZBookmark) => ({ + bookmarkId: bookmark.id, + summary: bookmark.summary, + title: bookmark.title + ? bookmark.title + : bookmark.content.type === BookmarkTypes.LINK + ? bookmark.content.title + : undefined, + createdAt: bookmark.createdAt ?? new Date(), + // Link specific defaults (only if bookmark is a link) + url: + bookmark.content.type === BookmarkTypes.LINK + ? bookmark.content.url + : undefined, + description: + bookmark.content.type === BookmarkTypes.LINK + ? (bookmark.content.description ?? "") + : undefined, + author: + bookmark.content.type === BookmarkTypes.LINK + ? (bookmark.content.author ?? "") + : undefined, + publisher: + bookmark.content.type === BookmarkTypes.LINK + ? (bookmark.content.publisher ?? "") + : undefined, + datePublished: + bookmark.content.type === BookmarkTypes.LINK + ? bookmark.content.datePublished + : undefined, + }); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: bookmarkToDefault(bookmark), + }); + + const { mutate: updateBookmarkMutate, isPending: isUpdatingBookmark } = + useUpdateBookmark({ + onSuccess: (updatedBookmark) => { + toast({ description: "Bookmark details updated successfully!" }); + // Close the dialog after successful detail update + setOpen(false); + // Reset form with potentially updated data + form.reset(bookmarkToDefault(updatedBookmark)); + }, + onError: (error) => { + toast({ + variant: "destructive", + title: "Failed to update bookmark", + description: error.message, + }); + }, + }); + + function onSubmit(values: ZUpdateBookmarksRequest) { + // Ensure optional fields that are empty strings are sent as null/undefined if appropriate + const payload = { + ...values, + title: values.title ?? null, + }; + updateBookmarkMutate(payload); + } + + // Reset form when bookmark data changes externally or dialog reopens + React.useEffect(() => { + if (open) { + form.reset(bookmarkToDefault(bookmark)); + } + }, [bookmark, form, open]); + + const isLink = bookmark.content.type === BookmarkTypes.LINK; + + return ( + + {children && {children}} + + + {t("bookmark_editor.title")} + {t("bookmark_editor.subtitle")} + +
+ + ( + + {t("common.title")} + + + + + + )} + /> + + {isLink && ( + ( + + {t("common.url")} + + + + + + )} + /> + )} + + {isLink && ( + ( + + {t("common.description")} + +