feat(home): admin-managed homepage section manager (toggle/reorder/edit)
The homepage is now driven by a `home_layout` Website Setting (jsonb) instead of a hardcoded section stack — zero backend changes, no migration. - lib/home-layout.ts: section catalog + saved-layout merge + locale-aware config reader (`<field>_fa`/`<field>_en`) + public fetchHomeLayout() (falls back to defaults when unset/unreachable). - app/[locale]/page.tsx: renders ordered, enabled sections from the layout, passing per-section content overrides. - sections (Hero/Products/Templates/HowItWorks/Pricing/Testimonials/FAQ): accept an optional `config` prop overriding heading/subtitle/CTA, locale-aware, default-safe. - new HomeSlides + HomeEvents sections render the previously-orphaned admin Slides (/v1/slides) and Home Events (/v1/home-events) data. - admin: HomeSectionsManager (toggle on/off, ↑/↓ reorder, per-section FA/EN content editor) at /admin/home, saved via the existing /v1/settings upsert; nav item + i18n. Verified: a saved layout overrides Hero/Pricing headings and reorders sections; removing it reverts to the default homepage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { HomeSectionsManager } from "@/components/admin/HomeSectionsManager";
|
||||
|
||||
export default function Page() {
|
||||
return <HomeSectionsManager />;
|
||||
}
|
||||
@@ -25,6 +25,7 @@ export default async function AdminLayout({
|
||||
{
|
||||
title: "محتوا",
|
||||
items: [
|
||||
{ href: "/admin/home", label: t("homePage") },
|
||||
{ href: "/admin/categories", label: t("categories") },
|
||||
{ href: "/admin/templates", label: t("templates") },
|
||||
{ href: "/admin/projects", label: t("projects") },
|
||||
|
||||
+45
-13
@@ -1,15 +1,20 @@
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
import { FAQ } from "@/components/sections/FAQ";
|
||||
import { Hero } from "@/components/sections/Hero";
|
||||
import { HomeEvents } from "@/components/sections/HomeEvents";
|
||||
import { HomeSlides } from "@/components/sections/HomeSlides";
|
||||
import { HowItWorks } from "@/components/sections/HowItWorks";
|
||||
import { Pricing } from "@/components/sections/Pricing";
|
||||
import { ProductsShowcase } from "@/components/sections/ProductsShowcase";
|
||||
import { TemplateGallery } from "@/components/sections/TemplateGallery";
|
||||
import { FAQ } from "@/components/sections/FAQ";
|
||||
import { Testimonials } from "@/components/sections/Testimonials";
|
||||
import { createPageMetadata } from "@/lib/metadata";
|
||||
import { fetchProjects } from "@/lib/admin-api";
|
||||
import { fetchProjects, type AdminProject } from "@/lib/admin-api";
|
||||
import { fetchHomeLayout, type HomeSection } from "@/lib/home-layout";
|
||||
|
||||
export const revalidate = 30;
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const t = await getTranslations("auto.appPage");
|
||||
@@ -20,21 +25,48 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
});
|
||||
}
|
||||
|
||||
/** Maps a configured section key → its component, passing admin content overrides. */
|
||||
function renderSection(section: HomeSection, adminProjects: AdminProject[]) {
|
||||
const config = section.config ?? {};
|
||||
switch (section.key) {
|
||||
case "hero":
|
||||
return <Hero config={config} />;
|
||||
case "slides":
|
||||
return <HomeSlides />;
|
||||
case "products":
|
||||
return <ProductsShowcase config={config} />;
|
||||
case "templates":
|
||||
return <TemplateGallery adminItems={adminProjects} config={config} />;
|
||||
case "howItWorks":
|
||||
return <HowItWorks config={config} />;
|
||||
case "pricing":
|
||||
return <Pricing config={config} />;
|
||||
case "testimonials":
|
||||
return <Testimonials config={config} />;
|
||||
case "faq":
|
||||
return <FAQ config={config} />;
|
||||
case "events":
|
||||
return <HomeEvents />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Home() {
|
||||
// Fetch up to 8 published projects from the admin service.
|
||||
// Returns an empty array when ADMIN_API_URL is not set or the service
|
||||
// is unreachable — TemplateGallery falls back to hardcoded data.
|
||||
const { items: adminProjects } = await fetchProjects({ pageSize: 8 });
|
||||
// Layout (which sections, order, on/off, content overrides) is admin-managed via
|
||||
// the `home_layout` setting; falls back to sensible defaults when unset.
|
||||
const [sections, projects] = await Promise.all([
|
||||
fetchHomeLayout(),
|
||||
fetchProjects({ pageSize: 8 }),
|
||||
]);
|
||||
|
||||
return (
|
||||
<main>
|
||||
<Hero />
|
||||
<ProductsShowcase />
|
||||
<TemplateGallery adminItems={adminProjects} />
|
||||
<HowItWorks />
|
||||
<Pricing />
|
||||
<Testimonials />
|
||||
<FAQ />
|
||||
{sections
|
||||
.filter((s) => s.enabled)
|
||||
.map((s) => (
|
||||
<div key={s.key}>{renderSection(s, projects.items)}</div>
|
||||
))}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user