feat(content+admin): home-events CRUD + comments moderation
Build backend images / build content-svc (push) Failing after 45s
Build backend images / build file-svc (push) Failing after 1m3s
Build backend images / build gateway (push) Failing after 0s
Build backend images / build identity-svc (push) Failing after 0s
Build backend images / build notification-svc (push) Failing after 1s
Build backend images / build render-svc (push) Failing after 1s
Build backend images / build studio-svc (push) Failing after 0s
Build backend images / build content-svc (push) Failing after 45s
Build backend images / build file-svc (push) Failing after 1m3s
Build backend images / build gateway (push) Failing after 0s
Build backend images / build identity-svc (push) Failing after 0s
Build backend images / build notification-svc (push) Failing after 1s
Build backend images / build render-svc (push) Failing after 1s
Build backend images / build studio-svc (push) Failing after 0s
- content-svc: home-events gains Create/Update/Delete + includeInactive list (POST/PUT/DELETE /v1/home-events, admin-gated; dates coerced to UTC) - admin /admin/home-events: full CRUD for homepage hero event banners - admin /admin/comments: list + approve/unapprove + delete (moderation) - AdminResource: optional listQuery to fetch inactive rows for admin views Fills the remaining legacy-admin gaps (home events, comments). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { AdminResource } from "@/components/admin/AdminResource";
|
||||
import { commentsConfig } from "@/components/admin/admin-resources";
|
||||
|
||||
export default function Page() {
|
||||
return <AdminResource config={commentsConfig} />;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { AdminResource } from "@/components/admin/AdminResource";
|
||||
import { homeEventsConfig } from "@/components/admin/admin-resources";
|
||||
|
||||
export default function Page() {
|
||||
return <AdminResource config={homeEventsConfig} />;
|
||||
}
|
||||
@@ -25,6 +25,8 @@ export default async function AdminLayout({
|
||||
{ href: "/admin/music", label: t("music") },
|
||||
{ href: "/admin/blogs", label: t("blogs") },
|
||||
{ href: "/admin/slides", label: t("slides") },
|
||||
{ href: "/admin/home-events", label: t("homeEvents") },
|
||||
{ href: "/admin/comments", label: t("comments") },
|
||||
{ href: "/admin/files", label: t("media") },
|
||||
{ href: "/admin/ai", label: t("aiContent") },
|
||||
{ href: "/admin/messaging", label: t("messaging") },
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface ResourceConfig {
|
||||
basePath: string; // e.g. "categories"
|
||||
idKey?: string; // default "id"
|
||||
listKey?: string; // wrap key, e.g. "items"; omit if response is a bare array
|
||||
listQuery?: string; // extra query string appended to the list fetch, e.g. "includeInactive=true"
|
||||
columns: ColumnDef[];
|
||||
fields?: FieldDef[];
|
||||
canCreate?: boolean;
|
||||
@@ -55,7 +56,7 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await fetch(url(), { cache: "no-store" });
|
||||
const res = await fetch(url(config.listQuery ? `?${config.listQuery}` : ""), { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data?.error ?? "Failed to load");
|
||||
const list = config.listKey ? data?.[config.listKey] : data;
|
||||
@@ -66,7 +67,7 @@ export function AdminResource({ config }: { config: ResourceConfig }) {
|
||||
setLoading(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [config.basePath, config.listKey]);
|
||||
}, [config.basePath, config.listKey, config.listQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
reload();
|
||||
|
||||
@@ -176,6 +176,65 @@ export const slidesConfig: ResourceConfig = {
|
||||
],
|
||||
};
|
||||
|
||||
export const homeEventsConfig: ResourceConfig = {
|
||||
title: "Home Events",
|
||||
description: "Promotional event banners on the homepage hero.",
|
||||
basePath: "home-events",
|
||||
listQuery: "includeInactive=true",
|
||||
canCreate: true,
|
||||
canEdit: true,
|
||||
canDelete: true,
|
||||
columns: [
|
||||
{ key: "title", label: "Title" },
|
||||
{ key: "badge", label: "Badge" },
|
||||
{ key: "sort", label: "Sort" },
|
||||
{ key: "is_active", label: "Active", render: (r) => badge(!!r.is_active, "active", "hidden") },
|
||||
],
|
||||
fields: [
|
||||
{ key: "title", label: "Title", required: true },
|
||||
{ key: "subtitle", label: "Subtitle" },
|
||||
{ key: "description", label: "Description", type: "textarea" },
|
||||
{ key: "badge", label: "Badge text" },
|
||||
{ key: "image", label: "Image", type: "image" },
|
||||
{ key: "button_text", label: "Button text" },
|
||||
{ key: "button_url", label: "Button URL" },
|
||||
{ key: "color", label: "Text color (hex)" },
|
||||
{ key: "background_color", label: "Background color (hex)" },
|
||||
{ key: "sort", label: "Sort", type: "number" },
|
||||
{ key: "is_active", label: "Active", type: "checkbox" },
|
||||
],
|
||||
};
|
||||
|
||||
export const commentsConfig: ResourceConfig = {
|
||||
title: "Comments",
|
||||
description: "Moderate user comments on blogs and templates.",
|
||||
basePath: "comments",
|
||||
listKey: "data",
|
||||
canCreate: false,
|
||||
canEdit: false,
|
||||
canDelete: true,
|
||||
columns: [
|
||||
{ key: "author_name", label: "Author" },
|
||||
{ key: "content", label: "Comment" },
|
||||
{ key: "is_approved", label: "Approved", render: (r) => badge(!!r.is_approved, "approved", "pending") },
|
||||
],
|
||||
rowActions: (row, reload) => {
|
||||
const id = String(row.id);
|
||||
const approve = async (val: boolean) => {
|
||||
await fetch(`/api/admin/resource/comments/${id}/approve?approve=${val}`, { method: "PATCH" });
|
||||
reload?.();
|
||||
};
|
||||
return (
|
||||
<button
|
||||
className="rounded-lg border border-[#262b40] px-3 py-1.5 text-xs text-gray-300 hover:bg-[#161a2e]"
|
||||
onClick={() => approve(!row.is_approved)}
|
||||
>
|
||||
{row.is_approved ? "لغو تأیید" : "تأیید"}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const usersConfig: ResourceConfig = {
|
||||
title: "Users",
|
||||
description: "Accounts in this tenant. Ban or unban below.",
|
||||
|
||||
Reference in New Issue
Block a user