refactor(admin): organize user actions and improve content editing UX
CI / check (push) Successful in 1m32s
CI / deploy (push) Successful in 1m11s
CI / e2e (push) Successful in 21s

This commit is contained in:
Simo committed 2026-09-05 18:56:56 +02:00
1 parent e17346ab9f
commit c6b919c01d
10 files changed
+688 -458

No files matched your search

+42 -5
View File
@@ -1,8 +1,10 @@
"use client";
import { useTranslations } from "next-intl";
import { useMemo, useState } from "react";
import { useMemo, useRef, useState } from "react";
import { useFormStatus } from "react-dom";
import { slugify } from "@/lib/format";
import { ArticlePreview, type ArticlePreviewData } from "./article-preview";
import { MediaPicker } from "./media-picker";
import { RichText } from "./rich-text";
@@ -30,10 +32,13 @@ export function ArticleForm({
const [imageError, setImageError] = useState(false);
const [status, setStatus] = useState(defaultValues?.status ?? "published");
const formRef = useRef<HTMLFormElement>(null);
const [preview, setPreview] = useState<ArticlePreviewData | null>(null);
const suggestedSlug = useMemo(() => slugify(title), [title]);
return (
<form
ref={formRef}
action={action}
className="admin-card"
style={{ display: "grid", gap: "1rem" }}
@@ -76,6 +81,7 @@ export function ArticleForm({
</span>
<div className="mt-1 flex gap-2 items-start">
<input
aria-label={t("image")}
name="image"
value={image}
onChange={(e) => {
@@ -83,7 +89,7 @@ export function ArticleForm({
setImageError(false);
}}
placeholder={t("imagePlaceholder")}
className="input input-bordered flex-1"
className="input input-bordered min-w-0 flex-1"
/>
<MediaPicker
onSelect={(url) => {
@@ -168,9 +174,40 @@ export function ArticleForm({
) : null}
</div>
<button type="submit" className="btn btn-primary justify-self-start">
{t("save")}
</button>
<div className="sticky bottom-0 flex flex-wrap gap-2 border-t border-[var(--admin-border)] bg-[var(--admin-surface)] py-3">
<button
type="button"
className="btn btn-outline"
onClick={() => {
if (!formRef.current) return;
const values = new FormData(formRef.current);
setPreview({
title: String(values.get("title") ?? ""),
image: String(values.get("image") ?? ""),
summary: String(values.get("shortStory") ?? ""),
body: String(values.get("fullStory") ?? ""),
});
}}
>
Preview
</button>
<ArticleSaveButton label={t("save")} />
</div>
<ArticlePreview data={preview} onClose={() => setPreview(null)} />
</form>
);
}
function ArticleSaveButton({ label }: { label: string }) {
const { pending } = useFormStatus();
return (
<button
type="submit"
className="btn btn-primary"
disabled={pending}
aria-busy={pending}
>
{pending ? "Saving…" : label}
</button>
);
}