feat(admin): auto-slug from name + "add project" on Projects page
- slug fields auto-fill from the name (slugify keeps Persian + latin letters, spaces → "-") until the slug is edited by hand; applies to all data-driven forms (categories/tags/blogs/…) and the Templates form - Projects page (/admin/projects) gains "+ پروژه جدید": pick a template (container) + name/aspect/resolution/size/duration/fps/mode → POST /v1/projects. Previously a project could only be added while editing a template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,17 @@ const inputCls = "w-full rounded-lg border border-[#262b40] bg-[#0c0e1a] px-3 py
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
/** URL-safe slug; keeps unicode letters (incl. Persian) + digits, spaces → "-". */
|
||||
export function slugify(s: string): string {
|
||||
return s
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, "-")
|
||||
.replace(/[^\p{L}\p{N}-]+/gu, "")
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
const idKey = config.idKey ?? "id";
|
||||
const [rows, setRows] = useState<Record<string, unknown>[]>([]);
|
||||
@@ -55,6 +66,7 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
const [form, setForm] = useState<Record<string, unknown>>({});
|
||||
const [query, setQuery] = useState("");
|
||||
const [page, setPage] = useState(1);
|
||||
const [slugTouched, setSlugTouched] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const url = (suffix = "") => `/api/admin/resource/${config.basePath}${suffix}`;
|
||||
@@ -80,10 +92,23 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
reload();
|
||||
}, [reload]);
|
||||
|
||||
const hasSlug = !!config.fields?.some((f) => f.key === "slug");
|
||||
|
||||
// Update a field; auto-fill slug from name until the slug is edited by hand.
|
||||
const setField = (key: string, value: unknown) => {
|
||||
setForm((prev) => {
|
||||
const next = { ...prev, [key]: value };
|
||||
if (key === "name" && hasSlug && !slugTouched) next.slug = slugify(String(value ?? ""));
|
||||
return next;
|
||||
});
|
||||
if (key === "slug") setSlugTouched(true);
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
const init: Record<string, unknown> = {};
|
||||
config.fields?.forEach((f) => (init[f.key] = f.defaultValue ?? (f.type === "checkbox" ? false : "")));
|
||||
setForm(init);
|
||||
setSlugTouched(false); // new record → keep syncing slug from name
|
||||
setCreating(true);
|
||||
setEditing(null);
|
||||
};
|
||||
@@ -92,6 +117,7 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
const init: Record<string, unknown> = {};
|
||||
config.fields?.forEach((f) => (init[f.key] = row[f.key] ?? (f.type === "checkbox" ? false : "")));
|
||||
setForm(init);
|
||||
setSlugTouched(true); // existing record → never auto-rewrite its slug
|
||||
setEditing(row);
|
||||
setCreating(false);
|
||||
};
|
||||
@@ -266,7 +292,7 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
) : (
|
||||
<input type={f.type === "number" ? "number" : "text"} className={inputCls} placeholder={f.placeholder}
|
||||
value={String(form[f.key] ?? "")}
|
||||
onChange={(e) => setForm({ ...form, [f.key]: f.type === "number" ? Number(e.target.value) : e.target.value })} />
|
||||
onChange={(e) => setField(f.key, f.type === "number" ? Number(e.target.value) : e.target.value)} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user