From c800f87ffe74c7812ea59d09346b9e171323439d Mon Sep 17 00:00:00 2001 From: Nam Doan Date: Tue, 30 Dec 2025 09:47:20 +0700 Subject: [PATCH 1/3] feat; implement brand --- config/nam.3bear.design.json | 16 ++++ config/nam.drsquatch.design.json | 16 ++++ public/brand-logo.png | Bin 0 -> 20757 bytes .../home/components/vt-brand/index.tsx | 72 ++++++++++++++++++ src/vibentec/component-map.tsx | 2 + src/vibentec/configloader.ts | 3 +- 6 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 public/brand-logo.png create mode 100644 src/modules/home/components/vt-brand/index.tsx diff --git a/config/nam.3bear.design.json b/config/nam.3bear.design.json index 75406bc..eb2c9d3 100644 --- a/config/nam.3bear.design.json +++ b/config/nam.3bear.design.json @@ -474,6 +474,22 @@ } } }, + { + "VtBrand": { + "config": { + "className": "w-full py-12 bg-[#CFECD9]", + "innerClassName": "content-container flex flex-col items-center", + "title": "Trusted By", + "titleClassName": "text-[#003F31] text-[20px] font-semibold mb-8", + "brandsClassName": "flex w-full items-center justify-between gap-12", + "items": [ + { "label": "Men'sHealth", "containerClassName": "", "className": "text-[#003F31] text-[36px] font-semibold italic" }, + { "label": "GQ", "containerClassName": "", "className": "text-[#003F31] text-[44px] font-black tracking-tight" }, + { "label": "BIRCHBOX", "containerClassName": "", "className": "text-[#003F31] text-[36px] font-semibold tracking-[0.2em]" } + ] + } + } + }, { "FreeShippingPriceNudge": { "config": { diff --git a/config/nam.drsquatch.design.json b/config/nam.drsquatch.design.json index 1b01132..84a0dda 100644 --- a/config/nam.drsquatch.design.json +++ b/config/nam.drsquatch.design.json @@ -348,6 +348,22 @@ } } }, + { + "VtBrand": { + "config": { + "className": "w-full py-12 bg-[#CFECD9]", + "innerClassName": "content-container flex flex-col items-center", + "title": "", + "titleClassName": "text-[#1f3521] text-[20px] font-bold mb-8", + "brandsClassName": "flex w-full items-center justify-between gap-12", + "items": [ + { "imageSrc": "/brand-logo.png", "alt": "Men's Health", "containerClassName": "", "imageClassName": "h-[40px] object-contain" }, + { "imageSrc": "/brand-logo.png", "alt": "GQ", "containerClassName": "", "imageClassName": "h-[40px] object-contain" }, + { "imageSrc": "/brand-logo.png", "alt": "Birchbox", "containerClassName": "", "imageClassName": "h-[40px] object-contain" } + ] + } + } + }, { "CartMismatchBanner": { "config": { "show": true } } }, { "FreeShippingPriceNudge": { "config": { "variant": "popup" } } } ], diff --git a/public/brand-logo.png b/public/brand-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f71b9451bef610872ff165e047081d1274ecdb0b GIT binary patch literal 20757 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�#kZPDgldJz$%?%l$*>+eB{@xQkU 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 { + const content = brand.imageSrc ? ( + {brand.alt + ) : ( + + {brand.label ?? ""} + + ) + + return brand.href ? ( + + {content} + + ) : ( +
+ {content} +
+ ) + } + + return ( +
+
+ {title &&
{title}
} +
+ {items.map((brand: any, idx: number) => renderItem(brand, idx))} +
+
+
+ ) +} diff --git a/src/vibentec/component-map.tsx b/src/vibentec/component-map.tsx index 97bdf9c..5d1e679 100644 --- a/src/vibentec/component-map.tsx +++ b/src/vibentec/component-map.tsx @@ -27,6 +27,7 @@ import { VtCarousel } from "@modules/layout/templates/vt-carousel" 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" type ComponentConfig = Record @@ -103,6 +104,7 @@ export const componentMap: Record = { ImageDisplayer: nodesContextRenderer(VtCarousel), VtFeaturedProducts: nodesContextRenderer(VtFeaturedProducts), VtCategoryHighlight: nodesContextRenderer(VtCategoryHighlight), + VtBrand: nodesContextRenderer(VtBrand), } export type ComponentName = keyof typeof componentMap diff --git a/src/vibentec/configloader.ts b/src/vibentec/configloader.ts index 1759cee..7b07466 100644 --- a/src/vibentec/configloader.ts +++ b/src/vibentec/configloader.ts @@ -2,7 +2,7 @@ import fs from "fs" import path from "path" import { jsonFileNames } from "./devJsonFileNames" -const fileName = jsonFileNames.nam3Bear +const fileName = jsonFileNames.namDrsquatch async function readDesignFile() { const filePath = path.join(process.cwd(), "config", fileName) @@ -10,6 +10,7 @@ async function readDesignFile() { return JSON.parse(fileData) } + export async function loadLayoutConfig() { const config = await readDesignFile() if (Array.isArray(config)) return config From 1716ef2cf4e05befb9dedd70afdb3973b9fe4132 Mon Sep 17 00:00:00 2001 From: Nam Doan Date: Mon, 5 Jan 2026 09:49:35 +0700 Subject: [PATCH 2/3] feat: implement vt feedback component --- config/nam.drsquatch.design.json | 37 ++++++++ .../home/components/vt-feedback/index.tsx | 91 +++++++++++++++++++ src/vibentec/component-map.tsx | 2 + 3 files changed, 130 insertions(+) create mode 100644 src/modules/home/components/vt-feedback/index.tsx diff --git a/config/nam.drsquatch.design.json b/config/nam.drsquatch.design.json index 84a0dda..72c3150 100644 --- a/config/nam.drsquatch.design.json +++ b/config/nam.drsquatch.design.json @@ -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" } } } ], diff --git a/src/modules/home/components/vt-feedback/index.tsx b/src/modules/home/components/vt-feedback/index.tsx new file mode 100644 index 0000000..f3731c0 --- /dev/null +++ b/src/modules/home/components/vt-feedback/index.tsx @@ -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 ( +
+ {title &&

{title}

} +
+
+ {items.map((it: any, idx: number) => ( +
+
+
{renderStars(it.rating)}
+ {it.title &&
{it.title}
} + {it.text &&
{it.text}
} + {it.author &&
{it.author}
} +
+
+ ))} +
+ {showControls && ( +
+
+ +
+
+ +
+
+ )} +
+
+ ) +} diff --git a/src/vibentec/component-map.tsx b/src/vibentec/component-map.tsx index 5d1e679..b2c56a8 100644 --- a/src/vibentec/component-map.tsx +++ b/src/vibentec/component-map.tsx @@ -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 @@ -105,6 +106,7 @@ export const componentMap: Record = { VtFeaturedProducts: nodesContextRenderer(VtFeaturedProducts), VtCategoryHighlight: nodesContextRenderer(VtCategoryHighlight), VtBrand: nodesContextRenderer(VtBrand), + VtFeedback: nodesContextRenderer(VtFeedback), } export type ComponentName = keyof typeof componentMap From ad1e7827131c049fcd258698e7a4763d59f4b42d Mon Sep 17 00:00:00 2001 From: Nam Doan Date: Tue, 6 Jan 2026 11:06:01 +0700 Subject: [PATCH 3/3] feat: implmement feedback card and subcription section component --- config/nam.3bear.design.json | 91 ++++++- config/nam.drsquatch.design.json | 227 +++++++++++++++--- .../components/vt-feedback-card/index.tsx | 74 ++++++ .../home/components/vt-subcription/index.tsx | 193 +++++++++++++++ src/vibentec/component-map.tsx | 4 + 5 files changed, 551 insertions(+), 38 deletions(-) create mode 100644 src/modules/home/components/vt-feedback-card/index.tsx create mode 100644 src/modules/home/components/vt-subcription/index.tsx diff --git a/config/nam.3bear.design.json b/config/nam.3bear.design.json index eb2c9d3..fd3edde 100644 --- a/config/nam.3bear.design.json +++ b/config/nam.3bear.design.json @@ -483,13 +483,98 @@ "titleClassName": "text-[#003F31] text-[20px] font-semibold mb-8", "brandsClassName": "flex w-full items-center justify-between gap-12", "items": [ - { "label": "Men'sHealth", "containerClassName": "", "className": "text-[#003F31] text-[36px] font-semibold italic" }, - { "label": "GQ", "containerClassName": "", "className": "text-[#003F31] text-[44px] font-black tracking-tight" }, - { "label": "BIRCHBOX", "containerClassName": "", "className": "text-[#003F31] text-[36px] font-semibold tracking-[0.2em]" } + { + "label": "Men'sHealth", + "containerClassName": "", + "className": "text-[#003F31] text-[36px] font-semibold italic" + }, + { + "label": "GQ", + "containerClassName": "", + "className": "text-[#003F31] text-[44px] font-black tracking-tight" + }, + { + "label": "BIRCHBOX", + "containerClassName": "", + "className": "text-[#003F31] text-[36px] font-semibold tracking-[0.2em]" + } ] } } }, + { + "VtFeedbackCard": { + "config": { + "className": "content-container py-16 bg-[#CFECD9] mt-16", + "title": "Der Hafer-Hype ist real. Finden nicht nur 100.000+ zufriedene 3Bears Fans.", + "gridClassName": "grid grid-cols-1 small:grid-cols-2 xl:grid-cols-4 gap-6", + "cardClassName": "rounded-2xl overflow-hidden", + "imageClassName": "w-full h-[260px] object-cover", + "contentClassName": "p-6", + "nameClassName": "text-[#003F31] text-[20px] font-bold", + "subtitleClassName": "mt-1 text-[#003f31b3] text-[14px]", + "quoteClassName": "mt-4 text-[#003F31] text-[16px]", + "ctaClassName": "mt-6 inline-flex items-center justify-center bg-[#FCEE56] text-[#0D382E] px-6 py-2 rounded-full font-bold", + "items": [ + { + "imageSrc": "/overnight-oat.webp", + "name": "Harry Kane", + "subtitle": "Profißballer, Kapitän engl. Nationalmannschaft, Stürmer FC Bayern", + "quote": "Als Sportler ist das Frühstück die wichtigste Mahlzeit für mich, und natürlich achte ich sehr darauf, was ich esse. Als ich 3Bears entdeckt habe, hat mich nachhaltig beeindruckt, dass die Haferflocken auf ein neues Level heben.", + "cta": { + "label": "Mehr erfahren", + "href": "/" + } + }, + { + "imageSrc": "/overnight-oat.webp", + "name": "Sally Özcan", + "subtitle": "Foodcreatorin & Unternehmerin", + "quote": "Ich liebe Frühstück, weil es für mich der Start in einen guten Tag ist, mit meiner Familie, meinem Team oder unterwegs. Ich mag Produkte, die einfach einen Sinn ergeben, natürlich, lecker und ohne Schnickschnack. Genau das ist 3Bears für mich." + }, + { + "imageSrc": "/overnight-oat.webp", + "name": "Sarah Harrison", + "subtitle": "Unternehmerin & Influencerin", + "quote": "3Bears teilt meine Leidenschaft für hochwertige Lebensmittel, die nicht nur mega lecker, sondern auch vollwertig sind. Deswegen war ich so begeistert von der Idee, gemeinsam ein Granola zu entwickeln.", + "cta": { + "label": "Mehr erfahren", + "href": "/" + } + }, + { + "imageSrc": "/overnight-oat.webp", + "name": "Hendrik Pfeiffer", + "subtitle": "Profi-Läufer & German Champion", + "quote": "Als Profisportler spielt meine bewusste Ernährung eine absolute Schlüsselrolle, um vorne mitmischen zu können. Die Produkte von 3Bears passen dabei wie die Faust aufs Auge.", + "cta": { + "label": "Mehr erfahren", + "href": "/" + } + } + ] + } + } + }, + { + "VtSubcription": { + "config": { + "className": "content-container py-12 flex justify-center", + "cardClassName": "rounded-2xl overflow-hidden bg-[#CFECD9] w-[800px] p-10", + "title": "10% für dich!", + "titleClassName": "text-[#003F31] text-[28px] font-bold text-center", + "description": true, + "descriptionPrefix": "Melde dich jetzt zum 3Bears Newsletter an und sichere dir", + "descriptionHighlight": "10% Rabatt auf deinen nächsten Einkauf!", + "descriptionSuffix": "", + "subtext": "Deinen Rabattcode bekommst du von uns per Mail.", + "firstName": { "placeholder": "Vorname" }, + "email": { "placeholder": "E-Mail-Adresse" }, + "policyLabel": "Ich habe die DSGVO gelesen und akzeptiere sie.", + "cta": { "label": "Anmelden" } + } + } + }, { "FreeShippingPriceNudge": { "config": { diff --git a/config/nam.drsquatch.design.json b/config/nam.drsquatch.design.json index 72c3150..0d558f3 100644 --- a/config/nam.drsquatch.design.json +++ b/config/nam.drsquatch.design.json @@ -131,7 +131,9 @@ ] } }, - { "PropsChildren": {} }, + { + "PropsChildren": {} + }, { "Footer": { "config": { @@ -147,12 +149,30 @@ "className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white gap-8", "itemClassName": "text-[14px] font-[400] mt-3", "items": [ - { "text": "FAQ", "href": "/" }, - { "text": "Track my order", "href": "/categories/shoes" }, - { "text": "Placeholder", "href": "/categories/accessories" }, - { "text": "Placeholder", "href": "/categories/accessories" }, - { "text": "Placeholder", "href": "/categories/accessories" }, - { "text": "Placeholder", "href": "/categories/accessories" } + { + "text": "FAQ", + "href": "/" + }, + { + "text": "Track my order", + "href": "/categories/shoes" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + } ] } } @@ -164,13 +184,34 @@ "className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white", "itemClassName": "text-[14px] font-[400] flex items-center mt-3", "items": [ - { "text": "Twitter", "href": "/" }, - { "text": "Facebook", "href": "/categories/shoes" }, - { "text": "Pinterest", "href": "/categories/accessories" }, - { "text": "Placeholder", "href": "/categories/accessories" }, - { "text": "Placeholder", "href": "/categories/accessories" }, - { "text": "Placeholder", "href": "/categories/accessories" }, - { "text": "Placeholder", "href": "/categories/accessories" } + { + "text": "Twitter", + "href": "/" + }, + { + "text": "Facebook", + "href": "/categories/shoes" + }, + { + "text": "Pinterest", + "href": "/categories/accessories" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + }, + { + "text": "Placeholder", + "href": "/categories/accessories" + } ] } } @@ -182,9 +223,18 @@ "className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white", "itemClassName": "text-[14px] font-[400] w-[200px] mt-3", "items": [ - { "text": "The Squatch Difference", "href": "/" }, - { "text": "Why Natural Products", "href": "/categories/shoes" }, - { "text": "No Harmful Ingredients", "href": "/categories/accessories" } + { + "text": "The Squatch Difference", + "href": "/" + }, + { + "text": "Why Natural Products", + "href": "/categories/shoes" + }, + { + "text": "No Harmful Ingredients", + "href": "/categories/accessories" + } ] } } @@ -214,10 +264,26 @@ "buttonClassName": "bg-[#C4622C] w-[90px]", "socialsClassName": "mt-4 gap-8", "socials": [ - { "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" }, - { "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" }, - { "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" }, - { "icon": "Twitter", "href": "/", "className": "w-5 h-5 text-white" } + { + "icon": "Twitter", + "href": "/", + "className": "w-5 h-5 text-white" + }, + { + "icon": "Twitter", + "href": "/", + "className": "w-5 h-5 text-white" + }, + { + "icon": "Twitter", + "href": "/", + "className": "w-5 h-5 text-white" + }, + { + "icon": "Twitter", + "href": "/", + "className": "w-5 h-5 text-white" + } ] } } @@ -233,9 +299,18 @@ "text": "©2025 Vibentec IT. All rights reserved", "linksClassName": "flex items-center text-orange-500 mt-2 pl-2", "links": [ - { "label": "Privacy Policy", "href": "/" }, - { "label": "Terms of Service", "href": "/categories/shoes" }, - { "label": "Cookie Policy", "href": "/categories/accessories" } + { + "label": "Privacy Policy", + "href": "/" + }, + { + "label": "Terms of Service", + "href": "/categories/shoes" + }, + { + "label": "Cookie Policy", + "href": "/categories/accessories" + } ] } } @@ -250,8 +325,12 @@ "ImageDisplayer": { "config": { "duration": 0, - "images": ["./drsquatch-banner.jpg"], - "links": ["/account"] + "images": [ + "./drsquatch-banner.jpg" + ], + "links": [ + "/account" + ] } }, "left": [ @@ -297,7 +376,10 @@ "container": "absolute z-[1] top-0 left-0 pt-4", "text": "uppercase px-4 py-2 bg-[#3B6F47] text-white" }, - "thumbnail": { "className": "rounded-none h-[300px] shadow-none", "size": "full" }, + "thumbnail": { + "className": "rounded-none h-[300px] shadow-none", + "size": "full" + }, "content": " flex flex-col flex-1", "title": "mt-2 text-[#1f3521] text-[22px] font-bold", "price": "mt-2 text-[#3B6F47] font-bold text-[20px] flex gap-3 flex-row-reverse justify-end", @@ -357,13 +439,62 @@ "titleClassName": "text-[#1f3521] text-[20px] font-bold mb-8", "brandsClassName": "flex w-full items-center justify-between gap-12", "items": [ - { "imageSrc": "/brand-logo.png", "alt": "Men's Health", "containerClassName": "", "imageClassName": "h-[40px] object-contain" }, - { "imageSrc": "/brand-logo.png", "alt": "GQ", "containerClassName": "", "imageClassName": "h-[40px] object-contain" }, - { "imageSrc": "/brand-logo.png", "alt": "Birchbox", "containerClassName": "", "imageClassName": "h-[40px] object-contain" } + { + "imageSrc": "/brand-logo.png", + "alt": "Men's Health", + "containerClassName": "", + "imageClassName": "h-[40px] object-contain" + }, + { + "imageSrc": "/brand-logo.png", + "alt": "GQ", + "containerClassName": "", + "imageClassName": "h-[40px] object-contain" + }, + { + "imageSrc": "/brand-logo.png", + "alt": "Birchbox", + "containerClassName": "", + "imageClassName": "h-[40px] object-contain" + } ] } } }, + { + "VtSubcription": { + "config": { + "className": "w-full", + "cardClassName": "overflow-hidden bg-[#F3EDE3] p-10", + "title": "SUBSCRIBE & SAVE", + "titleClassName": "text-[#003F31] text-[28px] font-bold text-center", + "description": true, + "policyLabel": "Ich habe die DSGVO gelesen und akzeptiere sie.", + "benefits": [ + { + "icon": "🗓", + "title": "Ships Every 3 Months", + "description": "Customize your picks and scents, upgrade anytime, or hit snooze if you want. You're in control." + }, + { + "icon": "🚚", + "title": "Free Delivery", + "description": "Subscribe once and relax. All your shipping expenses are covered by Squatch." + }, + { + "icon": "⭐", + "title": "Exclusive Benefits", + "description": "Gain exclusive, subscriber-only benefits. Enjoy early access to new products and limited edition releases!" + } + ], + "formClassName": "flex justify-center", + "cta": { + "label": "SUBSCRIBE & SAVE", + "className": "w-fit mt-12 px-[30px] h-[56px] rounded-full bg-orange-500 text-white font-bold" + } + } + } + }, { "VtFeedback": { "config": { @@ -371,7 +502,9 @@ "className": "content-container py-16", "titleClassName": "text-[#1f3521] text-[28px] font-bold text-center mb-10", "duration": 5, - "options": { "loop": true }, + "options": { + "loop": true + }, "itemClassName": "min-w-full px-6", "starsClassName": "text-[#C4622C] text-xl leading-none", "reviewTitleClassName": "text-[#1f3521] font-bold", @@ -401,14 +534,38 @@ } } }, - { "CartMismatchBanner": { "config": { "show": true } } }, - { "FreeShippingPriceNudge": { "config": { "variant": "popup" } } } + { + "CartMismatchBanner": { + "config": { + "show": true + } + } + }, + { + "FreeShippingPriceNudge": { + "config": { + "variant": "popup" + } + } + } ], "Product": [ - { "VtFeaturedProducts": { "config": { "title": "drsquatch-best-seller" } } } + { + "VtFeaturedProducts": { + "config": { + "title": "drsquatch-best-seller" + } + } + } ], "StorePage": [ - { "VtFeaturedProducts": { "config": { "title": "drsquatch-best-seller" } } } + { + "VtFeaturedProducts": { + "config": { + "title": "drsquatch-best-seller" + } + } + } ] } } diff --git a/src/modules/home/components/vt-feedback-card/index.tsx b/src/modules/home/components/vt-feedback-card/index.tsx new file mode 100644 index 0000000..9520ce4 --- /dev/null +++ b/src/modules/home/components/vt-feedback-card/index.tsx @@ -0,0 +1,74 @@ +import { clx } from "@medusajs/ui" +import LocalizedClientLink from "@modules/common/components/localized-client-link" +import { + LayoutComponentDefinition, + LayoutContext, +} from "@vibentec/component-map" + +export default async function VtFeedbackCard({ + nodes, + context, +}: { + nodes: LayoutComponentDefinition + context: LayoutContext +}) { + const props = nodes.config ?? {} + + const title: string = props.title ?? "" + const items = props.items ?? [] + + const classes = { + container: props.className ?? "", + title: props.titleClassName ?? "text-[#003F31] text-[28px] font-semibold mb-10", + grid: props.gridClassName ?? "grid grid-cols-1 small:grid-cols-2 xl:grid-cols-4 gap-6", + card: props.cardClassName ?? "rounded-2xl overflow-hidden bg-[#CFECD9]", + image: props.imageClassName ?? "w-full h-[260px] object-cover", + content: props.contentClassName ?? "p-6", + name: props.nameClassName ?? "text-[#003F31] text-[20px] font-bold", + subtitle: props.subtitleClassName ?? "mt-1 text-[#003f31b3] text-[14px]", + quote: props.quoteClassName ?? "mt-4 text-[#003F31] text-[16px]", + cta: props.ctaClassName ?? "mt-6 inline-flex items-center justify-center bg-[#FCEE56] text-[#0D382E] px-6 py-2 rounded-full font-bold", + } + + if (!items || items.length === 0) return null + + const renderCard = (entry: any, idx: number) => { + const imageEl = entry.imageSrc ? ( + {entry.imageAlt + ) : null + + const ctaEl = entry.cta?.href ? ( + + {entry.cta.label ?? "Mehr erfahren"} + + ) : entry.cta?.label ? ( + + ) : null + + return ( +
+ {imageEl} +
+ {entry.name &&
{entry.name}
} + {entry.subtitle && ( +
{entry.subtitle}
+ )} + {entry.quote &&
{entry.quote}
} + {ctaEl} +
+
+ ) + } + + return ( +
+ {title &&

{title}

} +
{items.map((it: any, idx: number) => renderCard(it, idx))}
+
+ ) +} + diff --git a/src/modules/home/components/vt-subcription/index.tsx b/src/modules/home/components/vt-subcription/index.tsx new file mode 100644 index 0000000..f9bcf39 --- /dev/null +++ b/src/modules/home/components/vt-subcription/index.tsx @@ -0,0 +1,193 @@ +"use client" +import { Button } from "@medusajs/ui" +import { clx } from "@medusajs/ui" +import { + LayoutComponentDefinition, + LayoutContext, +} from "@vibentec/component-map" +import React, { useState } from "react" + +interface BenefitItem { + icon?: string + imgSrc?: string + title?: string + description?: string + className?: string + iconClassName?: string + titleClassName?: string + descriptionClassName?: string +} + +export default function VtSubcription({ + nodes, + context, +}: { + nodes: LayoutComponentDefinition + context: LayoutContext +}) { + const props = nodes.config ?? {} + + const [firstName, setFirstName] = useState("") + const [email, setEmail] = useState("") + const [accepted, setAccepted] = useState(false) + const [submitted, setSubmitted] = useState(false) + + const classes = { + container: props.className ?? "content-container", + card: props.cardClassName ?? "rounded-2xl bg-[#CFECD9] p-8 small:p-12", + title: + props.titleClassName ?? + "text-[#003F31] text-[28px] font-bold text-center", + description: + props.descriptionClassName ?? "mt-2 text-center text-[#003F31]", + highlight: props.highlightClassName ?? "font-bold", + form: props.formClassName ?? "mt-8 flex flex-col gap-6", + fields: props.fieldsClassName ?? "grid grid-cols-1 small:grid-cols-2 gap-4", + input: + props.inputClassName ?? + "h-[52px] rounded-md border border-[#003F31]/40 px-4 bg-transparent text-[#003F31]", + checkboxRow: props.checkboxRowClassName ?? "flex items-center gap-3", + checkbox: + props.checkboxClassName ?? + "w-5 h-5 rounded-md border border-[#003F31]/60", + checkboxLabel: props.checkboxLabelClassName ?? "text-[#003F31] text-[16px]", + subtextClass: props.subtextClassName ?? "text-[#003F31]", + submit: + props.submitClassName ?? "", + success: props.successClassName ?? "mt-4 text-center text-[#003F31]", + benefits: + props.benefitsClassName ?? + "mt-8 grid grid-cols-1 small:grid-cols-3 gap-8", + benefitItem: + props.benefitItemClassName ?? + "flex flex-col items-center text-center gap-3", + benefitIcon: + props.benefitIconClassName ?? + "w-12 h-12 rounded-full bg-[#003F31] text-white flex items-center justify-center", + benefitTitle: props.benefitTitleClassName ?? "text-[#003F31] font-semibold", + benefitDesc: props.benefitDescClassName ?? "text-[#003F31] opacity-80", + } + + const submitConfig = props.cta ?? {} + const policyLabel: string = + props.policyLabel ?? "Ich habe die DSGVO gelesen und akzeptiere sie." + const firstNameField = props.firstName ?? null + const emailField = props.email ?? null + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault() + if (!accepted) return + setSubmitted(true) + console.log("subscription_submit", { firstName, email, accepted }) + } + + return ( +
+
+ {props.title &&

{props.title}

} + {props.description && ( +

+ {props.descriptionPrefix}{" "} + + {props.descriptionHighlight} + {" "} + {props.descriptionSuffix} +

+ )} + {Array.isArray(props.benefits) && props.benefits.length > 0 && ( +
+ {props.benefits.map((b: BenefitItem, i: number) => ( +
+ {b.imgSrc ? ( + {b.title + ) : ( + + )} + {b.title && ( +
+ {b.title} +
+ )} + {b.description && ( +
+ {b.description} +
+ )} +
+ ))} +
+ )} + {props.subtext && ( +

+ {props.subtext} +

+ )} +
+
+ {firstNameField && ( + setFirstName(e.target.value)} + className={clx(classes.input, firstNameField.className)} + /> + )} + {emailField && ( + setEmail(e.target.value)} + className={clx(classes.input, emailField.className)} + required + /> + )} +
+ {props.newCheckboxRowClassName && ( + + )} + + +
+ {submitted && ( +
+ {props.successMessage ?? + "Danke! Prüfe deine E-Mails für den Rabattcode."} +
+ )} +
+
+ ) +} diff --git a/src/vibentec/component-map.tsx b/src/vibentec/component-map.tsx index b2c56a8..4337607 100644 --- a/src/vibentec/component-map.tsx +++ b/src/vibentec/component-map.tsx @@ -29,6 +29,8 @@ 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" +import VtFeedbackCard from "@modules/home/components/vt-feedback-card" +import VtSubcription from "@modules/home/components/vt-subcription" type ComponentConfig = Record @@ -107,6 +109,8 @@ export const componentMap: Record = { VtCategoryHighlight: nodesContextRenderer(VtCategoryHighlight), VtBrand: nodesContextRenderer(VtBrand), VtFeedback: nodesContextRenderer(VtFeedback), + VtFeedbackCard: nodesContextRenderer(VtFeedbackCard), + VtSubcription: nodesContextRenderer(VtSubcription), } export type ComponentName = keyof typeof componentMap