feat(cms): recover drafts and failed imports with verified UI workflows
This commit is contained in:
1 parent
410a1e466c
commit
88790d1a0d
84 files changed
+3952
-288
No files matched your search
@@ -3,6 +3,7 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useMemo, useRef, useState, useTransition } from "react";
|
||||
import { useFormRecovery } from "@/hooks/use-form-recovery";
|
||||
import { useUnsavedChanges } from "@/hooks/use-unsaved-changes";
|
||||
import type { ArticleDraft } from "@/lib/article-draft";
|
||||
import type { ArticleSaveResult } from "@/lib/article-input";
|
||||
@@ -17,11 +18,13 @@ import { RichText } from "./rich-text";
|
||||
|
||||
export function ArticleForm({
|
||||
action,
|
||||
userId,
|
||||
defaultValues,
|
||||
articleKey = "new",
|
||||
baseToken = "",
|
||||
}: {
|
||||
action: (formData: FormData) => Promise<ArticleSaveResult | undefined>;
|
||||
userId?: string;
|
||||
edit?: boolean;
|
||||
articleKey?: string;
|
||||
baseToken?: string;
|
||||
@@ -45,6 +48,7 @@ export function ArticleForm({
|
||||
const saving = useRef(false);
|
||||
const editVersion = useRef(0);
|
||||
function markDirty() {
|
||||
localRecovery.changed();
|
||||
editVersion.current++;
|
||||
setDirty(true);
|
||||
}
|
||||
@@ -90,6 +94,24 @@ export function ArticleForm({
|
||||
dirty,
|
||||
restoreDraft,
|
||||
);
|
||||
const localRecovery = useFormRecovery({
|
||||
serverDraft: recovery.draft,
|
||||
userId,
|
||||
kind: "article",
|
||||
record: articleKey,
|
||||
dirty,
|
||||
snapshot: () => ({
|
||||
title,
|
||||
slug: slugTouched ? slug : slugify(title),
|
||||
image,
|
||||
shortStory: summary,
|
||||
fullStory: body,
|
||||
status,
|
||||
publishAt,
|
||||
}),
|
||||
restore: (payload) =>
|
||||
restoreDraft({ ...payload, baseToken } as ArticleDraft, false),
|
||||
});
|
||||
useFormErrorFocus(formRef, fieldErrors, pending);
|
||||
const [preview, setPreview] = useState<ArticlePreviewData | null>(null);
|
||||
const suggestedSlug = useMemo(() => slugify(title), [title]);
|
||||
@@ -126,6 +148,7 @@ export function ArticleForm({
|
||||
if (!Number.isNaN(date.getTime()))
|
||||
data.set("publishAt", date.toISOString());
|
||||
}
|
||||
localRecovery.flush();
|
||||
const submittedVersion = editVersion.current;
|
||||
saving.current = true;
|
||||
setSaveError(null);
|
||||
@@ -139,7 +162,10 @@ export function ArticleForm({
|
||||
setFieldErrors(result.fieldErrors ?? {});
|
||||
return;
|
||||
}
|
||||
if (editVersion.current === submittedVersion) setDirty(false);
|
||||
if (result?.ok && editVersion.current === submittedVersion) {
|
||||
setDirty(false);
|
||||
localRecovery.clear();
|
||||
}
|
||||
if (result?.ok) await recovery.clear().catch(() => {});
|
||||
if (result?.ok && result.data?.redirectTo)
|
||||
router.push(result.data.redirectTo);
|
||||
@@ -164,6 +190,7 @@ export function ArticleForm({
|
||||
>
|
||||
<fieldset disabled={pending} className="contents">
|
||||
<input type="hidden" name="baseToken" value={token} />
|
||||
{localRecovery.panel}
|
||||
{recovery.panel}
|
||||
{saveError && (
|
||||
<p role="alert" className="text-sm">
|
||||
@@ -282,6 +309,7 @@ export function ArticleForm({
|
||||
{t("body")}
|
||||
</span>
|
||||
<RichText
|
||||
ariaLabel={t("body")}
|
||||
key={bodyVersion}
|
||||
onChange={() => {
|
||||
markDirty();
|
||||
|
||||
Reference in new issue
Block a user