Wire featured categories to a static-image slot

Add a featured-categories list slot (published via /meshkee/static-image-slots
alongside the existing banner slots) so the homepage category strip can be
driven by admin-uploaded images instead of the category API, which has no
image field. Falls back to the current category list whenever the slot has
no images configured or the fetch fails.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
amirhosein.ashourloo
2026-08-23 08:57:24 +03:30
co-authored by Claude Sonnet 5
parent 60d7d55caa
commit 83809815c1
3 changed files with 61 additions and 4 deletions
+2 -1
View File
@@ -46,13 +46,14 @@ export default async function Home() {
(slot) => slot.key === STATIC_IMAGE_KEYS.threeBannerRow,
);
const heroImages = imagesForSlot(staticImageSlots, STATIC_IMAGE_KEYS.homeHero);
const featuredCategoryImages = imagesForSlot(staticImageSlots, STATIC_IMAGE_KEYS.featuredCategories);
return (
<>
<HeroSlider images={heroImages} />
<SpecialSaleCarousel products={specialSaleProducts} contactPhone={contactPhone} />
<FeatureBar />
<FeaturedCategories categories={categories} />
<FeaturedCategories categories={categories} images={featuredCategoryImages} />
<TwoBanners
images={imagesForSlot(staticImageSlots, STATIC_IMAGE_KEYS.twoBanner)}
aspectRatio={twoBannerSlot?.aspectRatio}
+49 -3
View File
@@ -7,9 +7,33 @@ import "swiper/css/free-mode";
import { Placeholder } from "@/components/ui/Placeholder";
import { SectionTitle } from "@/components/ui/SectionTitle";
import { EmptyState } from "@/components/ui/EmptyState";
import type { Category } from "@/lib/meshkee";
import type { Category, StaticImageWithUrl } from "@/lib/meshkee";
export function FeaturedCategories({ categories }: { categories: Category[] }) {
function CategorySlotCard({ image }: { image: StaticImageWithUrl }) {
const label = image.titleFa ?? image.titleEn ?? "";
const content = (
<div className="group flex h-full flex-col items-center rounded-xl border border-border bg-surface p-3 transition-all hover:border-red hover:shadow-md">
<div className="mb-3 aspect-square w-[72px] overflow-hidden rounded-full border border-border">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={image.url} alt={label} className="h-full w-full object-cover" />
</div>
{label && (
<span className="w-full truncate text-center text-xs font-medium leading-relaxed md:text-sm">
{label}
</span>
)}
</div>
);
return image.linkUrl ? <a href={image.linkUrl}>{content}</a> : content;
}
export function FeaturedCategories({
categories,
images = [],
}: {
categories: Category[];
images?: StaticImageWithUrl[];
}) {
return (
<section className="py-10 md:py-14">
<div className="mx-auto max-w-7xl px-4">
@@ -18,7 +42,29 @@ export function FeaturedCategories({ categories }: { categories: Category[] }) {
enTitle="Featured Categories"
action="همه دسته‌ها"
/>
{categories.length === 0 ? (
{images.length > 0 ? (
<Swiper
modules={[FreeMode]}
dir="rtl"
slidesPerView={2.3}
spaceBetween={12}
breakpoints={{
640: { slidesPerView: 4.3, spaceBetween: 12 },
1024: { slidesPerView: 8, spaceBetween: 12 },
}}
freeMode={{ enabled: true, momentum: true, sticky: true }}
grabCursor
className="!px-1 !py-2"
>
{images.map((image, i) => (
<SwiperSlide key={image.id}>
<div className="animate-slide-in h-full" style={{ animationDelay: `${i * 60}ms` }}>
<CategorySlotCard image={image} />
</div>
</SwiperSlide>
))}
</Swiper>
) : categories.length === 0 ? (
<EmptyState message="هنوز دسته‌بندی‌ای ثبت نشده است." />
) : (
<Swiper
+10
View File
@@ -484,6 +484,7 @@ export const STATIC_IMAGE_KEYS = {
homeHero: "home-hero",
twoBanner: "two-banner",
threeBannerRow: "three-banner-row",
featuredCategories: "featured-categories",
} as const;
export const STATIC_IMAGE_SLOT_CATALOG = [
@@ -514,6 +515,15 @@ export const STATIC_IMAGE_SLOT_CATALOG = [
itemCount: 3,
recommendedWidth: 1200,
},
{
key: STATIC_IMAGE_KEYS.featuredCategories,
label: "Featured categories",
kind: "list" as const,
pageKey: "home",
aspectRatio: "1:1",
itemCount: null,
recommendedWidth: 300,
},
];
export async function getStaticImageSlots(): Promise<StaticImageSlot[]> {