Files
flatrender/src/components/sections/Testimonials.tsx
T
soroush.asadi b3637cf839 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>
2026-06-12 01:21:44 +03:30

49 lines
1.6 KiB
TypeScript

"use client";
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<string, string>;
}
const TESTIMONIAL_INDICES = [0, 1, 2, 3, 4, 5] as const;
export function Testimonials({ className, config }: TestimonialsProps) {
const t = useTranslations("testimonials");
const locale = useLocale();
const testimonials = TESTIMONIAL_INDICES.map((i) => ({
id: `item${i}`,
name: t(`item${i}Name` as Parameters<typeof t>[0]),
role: t(`item${i}Role` as Parameters<typeof t>[0]),
company: t(`item${i}Company` as Parameters<typeof t>[0]),
quote: t(`item${i}Quote` as Parameters<typeof t>[0]),
initials: t(`item${i}Initials` as Parameters<typeof t>[0]),
}));
return (
<section className={cn("w-full bg-neutral-50 py-20 sm:py-28", className)}>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<SectionReveal>
<h2 className="text-center font-heading text-3xl font-bold tracking-tight text-neutral-900 sm:text-4xl">
{cfgVal(config, "heading", locale) ?? t("heading")}
</h2>
</SectionReveal>
<SectionReveal className="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{testimonials.map((testimonial) => (
<TestimonialCard key={testimonial.id} testimonial={testimonial} />
))}
</SectionReveal>
</div>
</section>
);
}