feat(cms): improve catalog, editorial recovery and operations
This commit is contained in:
1 parent
2fead134e6
commit
c389c3893d
122 files changed
+8137
-703
No files matched your search
@@ -4,18 +4,24 @@ import { useRouter } from "next/navigation";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useMemo, useRef, useState, useTransition } from "react";
|
||||
import { useUnsavedChanges } from "@/hooks/use-unsaved-changes";
|
||||
import type { ArticleDraft } from "@/lib/article-draft";
|
||||
import type { ArticleSaveResult } from "@/lib/article-input";
|
||||
import { slugify } from "@/lib/format";
|
||||
import { ArticlePreview, type ArticlePreviewData } from "./article-preview";
|
||||
import { useArticleRecovery } from "./article-recovery";
|
||||
import { MediaPicker } from "./media-picker";
|
||||
import { RichText } from "./rich-text";
|
||||
|
||||
export function ArticleForm({
|
||||
action,
|
||||
defaultValues,
|
||||
articleKey = "new",
|
||||
baseToken = "",
|
||||
}: {
|
||||
action: (formData: FormData) => Promise<ArticleSaveResult | undefined>;
|
||||
edit?: boolean;
|
||||
articleKey?: string;
|
||||
baseToken?: string;
|
||||
defaultValues?: {
|
||||
title?: string;
|
||||
slug?: string;
|
||||
@@ -55,6 +61,31 @@ export function ArticleForm({
|
||||
}, [defaultValues?.publishAt]);
|
||||
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const [token, setToken] = useState(baseToken);
|
||||
const [summary, setSummary] = useState(defaultValues?.shortStory ?? "");
|
||||
const [body, setBody] = useState(defaultValues?.fullStory ?? "");
|
||||
const [bodyVersion, setBodyVersion] = useState(0);
|
||||
function restoreDraft(draft: ArticleDraft, revision: boolean) {
|
||||
setTitle(draft.title);
|
||||
setSlug(draft.slug);
|
||||
setSlugTouched(true);
|
||||
setImage(draft.image);
|
||||
setImageError(false);
|
||||
setSummary(draft.shortStory);
|
||||
setBody(draft.fullStory);
|
||||
setBodyVersion((v) => v + 1);
|
||||
setStatus("draft");
|
||||
setPublishAt("");
|
||||
setToken(revision ? baseToken : draft.baseToken);
|
||||
markDirty();
|
||||
}
|
||||
const recovery = useArticleRecovery(
|
||||
articleKey,
|
||||
formRef,
|
||||
saving,
|
||||
dirty,
|
||||
restoreDraft,
|
||||
);
|
||||
const [preview, setPreview] = useState<ArticlePreviewData | null>(null);
|
||||
const suggestedSlug = useMemo(() => slugify(title), [title]);
|
||||
|
||||
@@ -76,12 +107,14 @@ export function ArticleForm({
|
||||
setSaveError(null);
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await recovery.wait();
|
||||
const result = await action(data);
|
||||
if (result && !result.ok) {
|
||||
setSaveError(result.error);
|
||||
return;
|
||||
}
|
||||
if (editVersion.current === submittedVersion) setDirty(false);
|
||||
if (result?.ok) await recovery.clear().catch(() => {});
|
||||
if (result?.ok && result.data?.redirectTo)
|
||||
router.push(result.data.redirectTo);
|
||||
} catch (error) {
|
||||
@@ -104,6 +137,8 @@ export function ArticleForm({
|
||||
style={{ display: "grid", gap: "1rem" }}
|
||||
>
|
||||
<fieldset disabled={pending} className="contents">
|
||||
<input type="hidden" name="baseToken" value={token} />
|
||||
{recovery.panel}
|
||||
{saveError && (
|
||||
<p role="alert" className="text-sm">
|
||||
{saveError}
|
||||
@@ -193,7 +228,8 @@ export function ArticleForm({
|
||||
<textarea
|
||||
name="shortStory"
|
||||
maxLength={255}
|
||||
defaultValue={defaultValues?.shortStory ?? ""}
|
||||
value={summary}
|
||||
onChange={(event) => setSummary(event.target.value)}
|
||||
placeholder={t("bodyPlaceholder")}
|
||||
rows={2}
|
||||
className="input input-bordered mt-1 w-full resize-y"
|
||||
@@ -205,9 +241,10 @@ export function ArticleForm({
|
||||
{t("body")}
|
||||
</span>
|
||||
<RichText
|
||||
key={bodyVersion}
|
||||
onChange={markDirty}
|
||||
name="fullStory"
|
||||
defaultValue={defaultValues?.fullStory ?? ""}
|
||||
defaultValue={body}
|
||||
placeholder={t("bodyPlaceholder")}
|
||||
rows={12}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user