diff --git a/src/components/sections/Testimonials.tsx b/src/components/sections/Testimonials.tsx
index 24c1cdf..6318a3d 100644
--- a/src/components/sections/Testimonials.tsx
+++ b/src/components/sections/Testimonials.tsx
@@ -1,20 +1,23 @@
"use client";
-import { useTranslations } from "next-intl";
+import { useLocale, useTranslations } from "next-intl";
import { cn } from "@/lib/utils";
+import { cfgVal } from "@/lib/home-layout";
import { SectionReveal } from "./SectionReveal";
import { TestimonialCard } from "./TestimonialCard";
export interface TestimonialsProps {
className?: string;
+ config?: Record
;
}
const TESTIMONIAL_INDICES = [0, 1, 2, 3, 4, 5] as const;
-export function Testimonials({ className }: TestimonialsProps) {
+export function Testimonials({ className, config }: TestimonialsProps) {
const t = useTranslations("testimonials");
+ const locale = useLocale();
const testimonials = TESTIMONIAL_INDICES.map((i) => ({
id: `item${i}`,
@@ -30,7 +33,7 @@ export function Testimonials({ className }: TestimonialsProps) {
- {t("heading")}
+ {cfgVal(config, "heading", locale) ?? t("heading")}
diff --git a/src/lib/home-extras.ts b/src/lib/home-extras.ts
new file mode 100644
index 0000000..7324d5c
--- /dev/null
+++ b/src/lib/home-extras.ts
@@ -0,0 +1,36 @@
+/** Public reads for the homepage Slides carousel + Home Events banner sections. */
+import { gatewayUrl } from "@/lib/api/gateway";
+
+export interface HomeSlide {
+ id: string;
+ title?: string | null;
+ image?: string | null;
+ parameter?: string | null; // link target
+ keyword?: string | null;
+}
+
+export interface HomeEvent {
+ id: string;
+ title?: string | null;
+ subtitle?: string | null;
+ badge?: string | null;
+ button_text?: string | null;
+ button_url?: string | null;
+ background_color?: string | null;
+ text_color?: string | null;
+ image?: string | null;
+}
+
+async function getList(path: string): Promise {
+ try {
+ const r = await fetch(gatewayUrl(path), { next: { revalidate: 30 }, headers: { Accept: "application/json" } });
+ if (!r.ok) return [];
+ const d = await r.json();
+ return Array.isArray(d) ? (d as T[]) : ((d?.items ?? []) as T[]);
+ } catch {
+ return [];
+ }
+}
+
+export const fetchSlides = () => getList("/v1/slides/");
+export const fetchHomeEvents = () => getList("/v1/home-events/");
diff --git a/src/lib/home-layout.ts b/src/lib/home-layout.ts
new file mode 100644
index 0000000..da9ea4c
--- /dev/null
+++ b/src/lib/home-layout.ts
@@ -0,0 +1,94 @@
+/**
+ * Homepage layout: which sections appear, in what order, toggled on/off, plus
+ * per-section content overrides. Stored as a single Website Setting (key
+ * `home_layout`, jsonb value) — read publicly via the gateway, edited in admin.
+ *
+ * The CODE is the source of truth for which section *types* exist (SECTION_CATALOG);
+ * the saved layout only overrides enabled / sort / config. New section types added
+ * in code therefore appear automatically (appended, default-enabled per catalog).
+ */
+
+import { gatewayUrl } from "@/lib/api/gateway";
+
+export const HOME_LAYOUT_KEY = "home_layout";
+
+export interface HomeSection {
+ key: string;
+ enabled: boolean;
+ sort: number;
+ /** Flat overrides; localized fields use `_fa` / `_en` keys. */
+ config?: Record;
+}
+
+export interface HomeLayout {
+ sections: HomeSection[];
+}
+
+/** Canonical catalog: declaration order = default order; defaultEnabled = initial on/off. */
+export const SECTION_CATALOG: { key: string; defaultEnabled: boolean }[] = [
+ { key: "hero", defaultEnabled: true },
+ { key: "slides", defaultEnabled: false },
+ { key: "products", defaultEnabled: true },
+ { key: "templates", defaultEnabled: true },
+ { key: "howItWorks", defaultEnabled: true },
+ { key: "pricing", defaultEnabled: true },
+ { key: "testimonials", defaultEnabled: true },
+ { key: "faq", defaultEnabled: true },
+ { key: "events", defaultEnabled: false },
+];
+
+export function defaultSections(): HomeSection[] {
+ return SECTION_CATALOG.map((s, i) => ({ key: s.key, enabled: s.defaultEnabled, sort: i, config: {} }));
+}
+
+/** Merge a saved layout with the catalog: saved enabled/sort/config win; catalog
+ * sections missing from the saved layout are appended after it. */
+export function mergeLayout(saved: HomeLayout | null | undefined): HomeSection[] {
+ const def = defaultSections();
+ if (!saved?.sections?.length) return def;
+ const byKey = new Map(saved.sections.map((s) => [s.key, s]));
+ let maxSort = Math.max(0, ...saved.sections.map((s) => Number(s.sort) || 0));
+ const merged: HomeSection[] = def.map((d) => {
+ const s = byKey.get(d.key);
+ return s
+ ? { key: d.key, enabled: s.enabled ?? d.enabled, sort: Number(s.sort) ?? d.sort, config: s.config ?? {} }
+ : { key: d.key, enabled: d.enabled, sort: ++maxSort, config: {} };
+ });
+ return merged.sort((a, b) => a.sort - b.sort);
+}
+
+/** Read a localized config override: `_` → `` → undefined. */
+export function cfgVal(
+ config: Record | undefined,
+ field: string,
+ locale: string,
+): string | undefined {
+ if (!config) return undefined;
+ const v = config[`${field}_${locale}`] ?? config[field];
+ return v && String(v).trim() !== "" ? v : undefined;
+}
+
+/** Server-side: fetch the ordered, catalog-merged homepage sections. Falls back to
+ * defaults when the gateway is unset/unreachable or the setting is absent. */
+export async function fetchHomeLayout(): Promise {
+ try {
+ const res = await fetch(gatewayUrl("/v1/settings/"), {
+ next: { revalidate: 30 },
+ headers: { Accept: "application/json" },
+ });
+ if (!res.ok) return mergeLayout(null);
+ const rows = (await res.json()) as Array<{ key: string; value: string }>;
+ const row = Array.isArray(rows) ? rows.find((r) => r.key === HOME_LAYOUT_KEY) : null;
+ if (!row?.value) return mergeLayout(null);
+ let parsed: HomeLayout | null = null;
+ try {
+ const v = typeof row.value === "string" ? JSON.parse(row.value) : row.value;
+ parsed = v && typeof v === "object" && Array.isArray(v.sections) ? (v as HomeLayout) : null;
+ } catch {
+ parsed = null;
+ }
+ return mergeLayout(parsed);
+ } catch {
+ return mergeLayout(null);
+ }
+}