import type { Resume } from "@/features/resume/builder/draft"; import { i18n } from "@lingui/core"; import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; import { ClockCounterClockwiseIcon } from "@phosphor-icons/react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMemo, useState } from "react"; import { toast } from "sonner"; import { Button } from "@reactive-resume/ui/components/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@reactive-resume/ui/components/dropdown-menu"; import { useResumeStore } from "@/features/resume/builder/draft"; import { lockStylesheetStoreForRestore, replaceStylesheetStoreAfterRestore, unlockStylesheetStoreAfterRestore, } from "@/features/resume/stylesheet/store"; import { useConfirm } from "@/hooks/use-confirm"; import { getResumeErrorMessage } from "@/libs/error-message"; import { formatRelativeTime } from "@/libs/locale"; import { orpc } from "@/libs/orpc/client"; type BuilderVersionHistoryProps = { resumeId: string; }; export function BuilderVersionHistory({ resumeId }: BuilderVersionHistoryProps) { const [open, setOpen] = useState(false); const confirm = useConfirm(); const queryClient = useQueryClient(); const replaceResumeFromServer = useResumeStore((state) => state.replaceResumeFromServer); const relativeTimeFormatter = useMemo(() => new Intl.RelativeTimeFormat(i18n.locale, { numeric: "auto" }), []); const { data: versions, isLoading } = useQuery({ ...orpc.resume.listVersions.queryOptions({ input: { resumeId } }), enabled: open, }); const { mutateAsync: restoreVersion, isPending } = useMutation(orpc.resume.restoreVersion.mutationOptions()); const handleRestore = async (versionId: string) => { const confirmed = await confirm(t`Restore this version?`, { description: t`Earlier versions are kept; the builder's undo history is reset.`, }); if (!confirmed) return; const token = lockStylesheetStoreForRestore(resumeId); if (!token) return; try { const restored = await restoreVersion({ resumeId, versionId }); const applied = replaceStylesheetStoreAfterRestore({ resumeId, resumeData: restored.resume.data, initial: restored.stylesheetState, token, }); if (!applied) { unlockStylesheetStoreAfterRestore(token); return; } replaceResumeFromServer(restored.resume as Resume); queryClient.setQueryData(orpc.resume.getById.queryOptions({ input: { id: resumeId } }).queryKey, restored.resume); void queryClient.invalidateQueries({ queryKey: orpc.resume.listVersions.queryKey({ input: { resumeId } }) }); toast.success(t`Your resume has been restored to the selected version.`); } catch (error) { unlockStylesheetStoreAfterRestore(token); toast.error(getResumeErrorMessage(error)); } }; return ( } /> Version history {isLoading && (
Loading…
)} {!isLoading && (!versions || versions.length === 0) && (
No saved versions yet.
)} {versions?.map((version) => ( handleRestore(version.id)} > {version.label} {formatRelativeTime(version.createdAt, relativeTimeFormatter)} ))}
); }