namds/implement-highlight-and-subcription-section #35

Merged
namds29 merged 3 commits from namds/implement-highlight-and-subcription-section into main 2026-01-07 03:16:54 +00:00
3 changed files with 130 additions and 0 deletions
Showing only changes of commit 1716ef2cf4 - Show all commits

View File

@ -364,6 +364,43 @@
}
}
},
{
"VtFeedback": {
"config": {
"title": "100,000+ Reviews From Squatchers",
"className": "content-container py-16",
"titleClassName": "text-[#1f3521] text-[28px] font-bold text-center mb-10",
"duration": 5,
"options": { "loop": true },
"itemClassName": "min-w-full px-6",
"starsClassName": "text-[#C4622C] text-xl leading-none",
"reviewTitleClassName": "text-[#1f3521] font-bold",
"reviewTextClassName": "text-[#1f3521]",
"authorClassName": "italic text-[#1f3521]",
"controls": "mt-6 flex items-center justify-center gap-4",
"items": [
{
"rating": 5,
"title": "Ah-freaking-amazing!",
"text": "So I just had my first shower with Dr. Squatch Cool Fresh Aloe. Holy sh*t this stuff is Ah-freaking-amazing! Talk about a life hack!",
"author": "Stephen B."
},
{
"rating": 5,
"title": "Best damn soap ever…period.",
"text": "Best Damn Soap I EVER bought! Super smooth on the skin, smells awesome, makes you feel good showering, and yes…the wife approves.",
"author": "Chris H."
},
{
"rating": 5,
"title": "Hilarious…products awesome too",
"text": "Ok…the Dr. Squatch commercials are just freakin hilarious…plus the products are awesome too! So yes, buy it now and subscribe to it!",
"author": "Mike C."
}
]
}
}
},
{ "CartMismatchBanner": { "config": { "show": true } } },
{ "FreeShippingPriceNudge": { "config": { "variant": "popup" } } }
],

View File

@ -0,0 +1,91 @@
"use client"
import useEmblaCarousel from "embla-carousel-react"
import Autoplay from "embla-carousel-autoplay"
import { useMemo } from "react"
import { clx } from "@medusajs/ui"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import { NextButton, PrevButton, usePrevNextButtons } from "@modules/layout/templates/vt-carousel/carousel-arrow-button"
export default function VtFeedback({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
const title: string = props.title ?? ""
const items = props.items ?? []
const durationSeconds: number = props.duration ?? 5
const options = props.options ?? { loop: true }
const plugins = useMemo(() => {
if (!durationSeconds || durationSeconds <= 0) return []
return [
Autoplay({
delay: durationSeconds * 1000,
stopOnInteraction: false,
stopOnMouseEnter: true,
}),
]
}, [durationSeconds])
const [emblaRef, emblaApi] = useEmblaCarousel(options, plugins)
const { prevBtnDisabled, nextBtnDisabled, onPrevButtonClick, onNextButtonClick } = usePrevNextButtons(emblaApi)
const classes = {
container: props.className ?? "content-container py-16",
title: props.titleClassName ?? "text-[#1f3521] text-[28px] font-bold text-center mb-10",
viewport: "relative overflow-hidden",
containerInner: "flex",
slide: props.itemClassName ?? "min-w-full px-6",
slideInner: "flex flex-col items-center text-center gap-3",
stars: props.starsClassName ?? "text-[#C4622C] text-xl leading-none",
reviewTitle: props.reviewTitleClassName ?? "text-[#1f3521] font-bold",
reviewText: props.reviewTextClassName ?? "text-[#1f3521]",
author: props.authorClassName ?? "italic text-[#1f3521]",
controls: props.controls,
}
if (!items || items.length === 0) return null
const showControls = items.length > 1 && classes.controls
const renderStars = (rating?: number) => {
const count = Math.max(0, Math.min(5, Math.round(rating ?? 5)))
return "★★★★★".slice(0, count)
}
return (
<section className={classes.container}>
{title && <h2 className={classes.title}>{title}</h2>}
<div className={classes.viewport} ref={emblaRef}>
<div className={classes.containerInner}>
{items.map((it: any, idx: number) => (
<div className={classes.slide} key={`feedback-${idx}`}>
<div className={classes.slideInner}>
<div className={classes.stars}>{renderStars(it.rating)}</div>
{it.title && <div className={classes.reviewTitle}>{it.title}</div>}
{it.text && <div className={classes.reviewText}>{it.text}</div>}
{it.author && <div className={classes.author}>{it.author}</div>}
</div>
</div>
))}
</div>
{showControls && (
<div className="absolute top-1/2 -translate-y-1/2 left-0 right-0 flex items-center justify-between px-4">
<div className="pointer-events-auto">
<PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} />
</div>
<div className="pointer-events-auto">
<NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} />
</div>
</div>
)}
</div>
</section>
)
}

View File

@ -28,6 +28,7 @@ import { VtCtaBanner } from "@modules/layout/templates/vt-cta-banner"
import VtFeaturedProducts from "@modules/home/components/vt-featured-products"
import VtCategoryHighlight from "@modules/home/components/vt-category-highlight"
import VtBrand from "@modules/home/components/vt-brand"
import VtFeedback from "@modules/home/components/vt-feedback"
type ComponentConfig = Record<string, any>
@ -105,6 +106,7 @@ export const componentMap: Record<string, ComponentRenderer> = {
VtFeaturedProducts: nodesContextRenderer(VtFeaturedProducts),
VtCategoryHighlight: nodesContextRenderer(VtCategoryHighlight),
VtBrand: nodesContextRenderer(VtBrand),
VtFeedback: nodesContextRenderer(VtFeedback),
}
export type ComponentName = keyof typeof componentMap