1 Commits

Author SHA1 Message Date
Nam Doan e30230d338 feat: Implement header and footer json file of each pages and components following by structure of starter design
refactor: remove singular component and recreate component type cta and hero component
feat: Implement starter design with hero banner and vibentec banner
2025-12-15 14:49:03 +07:00
36 changed files with 1226 additions and 2530 deletions
+289 -445
View File
@@ -1,175 +1,302 @@
{
"layout": [
{
"Header": {
"config": {
"sticky": true,
"variant": "ticker"
[
{
"Header": {
"config": {
"sticky": true,
"variant": "ticker"
},
"children": [
{
"Banner": {
"config": {
"variant": "ticker",
"className": "h-12 bg-[#009b93] text-[#fff] gap-12",
"speed": 24,
"items": [
{
"Link": {
"config": {
"label": "NEU: Overnight Oats Sallys Nussecke 😍",
"href": "/"
}
}
},
{
"Link": {
"config": {
"label": "Versandkostenfrei ab 45 € 💛",
"href": "/"
}
}
},
{
"Link": {
"config": {
"label": "Gratis Geschenk ab 60 € Warenkorbwert 🎁",
"href": "/"
}
}
}
]
}
}
},
"children": [
{
"Nav": {
"config": {
"className": "h-24 bg-white text-[#003F31] gap-12",
"left": [
{
"Logo": {
"config": {
"src": "/3bear-logo.png",
"alt": "MyShop",
"className": "h-[150px] w-[180px]",
"objectFit": "contain"
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Shop",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
}
}
}
},
{
"Dropdown": {
"config": {
"trigger": {
"text": "Über Uns",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"text": "Unser Unternehmen",
"href": "/"
},
{
"text": "Loren ipsum",
"href": "/"
},
{
"text": "Not a Link"
}
]
}
}
},
{
"Dropdown": {
"config": {
"trigger": {
"text": "Über unsere Produkte",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"text": "Unser Unternehmen",
"href": "/"
},
{
"text": "Loren ipsum",
"href": "/"
},
{
"text": "Not a Link"
}
]
}
}
},
{
"Link": {
"config": {
"label": "Rezepte",
"href": "/",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]"
}
}
},
{
"Link": {
"config": {
"label": "Triff Harry Kane",
"href": "/",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center gap-1 hover:text-[#009b93]"
}
}
}
],
"right": [
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-[#11314E] flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": true,
"isDisplayFullname": true
}
}
}
},
{
"Button": {
"config": {
"icon": "MagnifyingGlass",
"className": "shadow-none"
}
}
},
{
"AccountButton": {
"config": {
"icon": "User",
"className": " flex items-center gap-1 shadow-none"
}
}
},
{
"VtCartButton": {
"config": {
"icon": "ShoppingBag",
"className": "shadow-none bg-transparent text-black w-[50px]"
}
}
}
]
}
}
}
]
}
},
{
"CartMismatchBanner": {
"config": {
"show": true
}
}
},
{
"FreeShippingPriceNudge": {
"config": {
"variant": "popup"
}
}
},
{
"PropsChildren": {}
},
{
"Footer": {
"config": {
"className": "content-container border-none flex w-full bg-[#003f31] text-white border justify-between pb-8 pt-14",
"leftClassName": "flex-col ml-3",
"centerClassName": "",
"rightClassName": "flex gap-[10rem] mr-[80px]",
"left": [
{
"Banner": {
"VtFooterHero": {
"config": {
"variant": "ticker",
"className": "h-12 bg-[#009b93] text-[#fff] gap-12",
"speed": 24,
"logoClassName": "h-[100px] w-[200px]",
"logoSrc": "/3bear-white-logo.avif",
"logoAlt": "3Bear",
"title": "Melde dich für unsere Oatnews an 💛",
"email": {
"emailInputClassName": "w-[300px] ml-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" }
],
"socialsClassName": "ml-8 mt-10",
"className": "",
"ctaClassName": "ml-8",
"titleClassName": "ml-8 text-white w-full",
"descriptionClassName": "ml-8"
}
}
}
],
"center": [],
"right": [
{
"VtMenuItem": {
"config": {
"title": "Information",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-white hover:text-white",
"itemClassName": "text-[1rem] font-[400] opacity-70 hover:text-white",
"items": [
{
"Link": {
"config": {
"label": "NEU: Overnight Oats Sallys Nussecke 😍",
"href": "/"
}
}
"text": "Über Uns",
"href": "/"
},
{
"Link": {
"config": {
"label": "Versandkostenfrei ab 45 € 💛",
"href": "/"
}
}
"text": "Placeholder",
"href": "/categories/shoes"
},
{
"Link": {
"config": {
"label": "Gratis Geschenk ab 60 € Warenkorbwert 🎁",
"href": "/"
}
}
"text": "Placeholder",
"href": "/categories/accessories"
}
]
}
}
},
{
"Nav": {
"VtMenuItem": {
"config": {
"className": "h-24 bg-white text-[#003F31] gap-12",
"left": [
"title": "Kundendienst",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-white hover:text-white",
"itemClassName": "text-[1rem] font-[400] flex items-center opacity-70 hover:text-white",
"items": [
{
"Logo": {
"config": {
"src": "/3bear-logo.png",
"alt": "MyShop",
"className": "h-[150px] w-[180px]",
"objectFit": "contain"
}
}
"text": "Twitter",
"href": "/"
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Shop",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
}
}
}
"text": "Facebook",
"href": "/categories/shoes"
},
{
"Dropdown": {
"config": {
"trigger": {
"text": "Über Uns",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"text": "Unser Unternehmen",
"href": "/"
},
{
"text": "Loren ipsum",
"href": "/"
},
{
"text": "Not a Link"
}
]
}
}
},
{
"Dropdown": {
"config": {
"trigger": {
"text": "Über unsere Produkte",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"text": "Unser Unternehmen",
"href": "/"
},
{
"text": "Loren ipsum",
"href": "/"
},
{
"text": "Not a Link"
}
]
}
}
},
{
"Link": {
"config": {
"label": "Rezepte",
"href": "/",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center mr-8 gap-1 hover:text-[#009b93]"
}
}
},
{
"Link": {
"config": {
"label": "Triff Harry Kane",
"href": "/",
"className": "font-bold text-[1rem] text-[#003F31] flex items-center gap-1 hover:text-[#009b93]"
}
}
"text": "Pinterest",
"href": "/categories/accessories"
}
],
"right": [
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Weiteres",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-white",
"itemClassName": "text-[1rem] font-[400] w-[150px] opacity-70 hover:text-white",
"items": [
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-[#11314E] flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": true,
"isDisplayFullname": true
}
}
}
"text": "Karriere",
"href": "/"
},
{
"Button": {
"config": {
"icon": "MagnifyingGlass",
"className": "shadow-none"
}
}
"text": "Unser Team",
"href": "/categories/shoes"
},
{
"AccountButton": {
"config": {
"icon": "User",
"className": " flex items-center gap-1 shadow-none"
}
}
"text": "B2B",
"href": "/categories/accessories"
},
{
"VtCartButton": {
"config": {
"icon": "ShoppingBag",
"className": "shadow-none bg-transparent text-black w-[50px]"
}
}
"text": "Presse",
"href": "/categories/accessories"
}
]
}
@@ -177,310 +304,27 @@
}
]
}
},
{
"PropsChildren": {}
},
{
"Footer": {
"config": {
"className": "content-container border-none flex w-full bg-[#003f31] text-white border justify-between pb-8 pt-14",
"leftClassName": "flex-col ml-3",
"centerClassName": "",
"rightClassName": "flex gap-[10rem] mr-[80px]",
"left": [
{
"VtFooterHero": {
"config": {
"logoClassName": "h-[100px] w-[200px]",
"logoSrc": "/3bear-white-logo.avif",
"logoAlt": "3Bear",
"title": "Melde dich für unsere Oatnews an 💛",
"email": {
"emailInputClassName": "w-[300px] ml-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"
}
],
"socialsClassName": "ml-8 mt-10",
"className": "",
"ctaClassName": "ml-8",
"titleClassName": "ml-8 text-white w-full",
"descriptionClassName": "ml-8"
}
}
},
{
"Footer": {
"config": {
"className": "content-container bg-[#003f31] w-full text text-[#11314E] flex items-center justify-between",
"leftClassName": "w-full",
"left": [
],
"center": [],
"right": [
{
"VtFooterBottom": {
"config": {
"className": " mr-[80px]",
"icons": ["Mastercard", "PayPal", "Visa", "Mastercard","Mastercard","Mastercard","Mastercard"]
}
}
],
"center": [],
"right": [
{
"VtMenuItem": {
"config": {
"title": "Information",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-white hover:text-white",
"itemClassName": "text-[1rem] font-[400] opacity-70 hover:text-white",
"items": [
{
"text": "Über Uns",
"href": "/"
},
{
"text": "Placeholder",
"href": "/categories/shoes"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
}
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Kundendienst",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-white hover:text-white",
"itemClassName": "text-[1rem] font-[400] flex items-center opacity-70 hover:text-white",
"items": [
{
"text": "Twitter",
"href": "/"
},
{
"text": "Facebook",
"href": "/categories/shoes"
},
{
"text": "Pinterest",
"href": "/categories/accessories"
}
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Weiteres",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-white",
"itemClassName": "text-[1rem] font-[400] w-[150px] opacity-70 hover:text-white",
"items": [
{
"text": "Karriere",
"href": "/"
},
{
"text": "Unser Team",
"href": "/categories/shoes"
},
{
"text": "B2B",
"href": "/categories/accessories"
},
{
"text": "Presse",
"href": "/categories/accessories"
}
]
}
}
}
]
}
}
},
{
"Footer": {
"config": {
"className": "content-container bg-[#003f31] w-full text text-[#11314E] flex items-center justify-between",
"leftClassName": "w-full",
"left": [],
"center": [],
"right": [
{
"VtFooterBottom": {
"config": {
"className": " mr-[80px]",
"icons": [
"Mastercard",
"PayPal",
"Visa",
"Mastercard",
"Mastercard",
"Mastercard",
"Mastercard"
]
}
}
}
]
}
}
]
}
}
],
"pages": {
"Home": [
{
"Hero": {
"config": {
"className": "h-[35rem]",
"ImageDisplayer": {
"config": {
"duration": 0,
"images": [
"./banner-hero.webp"
],
"links": [
"/account"
]
}
},
"left": [
{
"VtCtaBanner": {
"config": {
"className": "left-[120px] top-[80px] relative w-full p-12 flex flex-col items-start justify-center text-left bg-transperant border-none shadow-none",
"buttonTextClassName": "inline-flex items-center justify-center bg-[#FCEE56] hover:bg-[#FCEE56]/90 text-[#0D382E] px-8 py-3 rounded-full font-bold text-lg shadow-none border-none",
"tagTextClassName": "text-[#0D382E] text-lg font-medium mb-2 bg-transparent",
"tagText": "So einfach kann Frühstück sein mit unseren leckeren Overnight Oats.",
"titleText": "breakfast made easy.",
"buttonText": "Jetzt entdecken"
}
}
}
],
"center": [],
"right": []
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "best-seller",
"styles": {
"container": "content-container py-12 small:py-20",
"header": {
"container": "ml-16",
"title": "text-2xl mb-12",
"isShowViewAll": false
},
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
"productCard": {
"badgeText": "Bestseller",
"card": "relative flex flex-col items-center bg-transparent shadow-none border-none p-0",
"badge": {
"container": "absolute top-0 left-0 z-[1]",
"text": "px-3 py-1 rounded-full bg-[#009b93] text-white text-[12px] font-semibold"
},
"thumbnail": {
"className": "rounded-2xl bg-white h-[320px] object-contain shadow-none",
"size": "full"
},
"content": "flex flex-col items-center justify-start text-center p-0 mt-6",
"title": "text-[#003F31] text-[28px] font-semibold",
"description": "order-3 text-[#003f31b3]",
"price": "mt-2 text-[#0D382E] text-[24px] font-bold order-2 flex gap-2",
"reviews": {
"container": "mt-3 flex items-center gap-2 order-1",
"stars": "flex gap-1",
"star": "text-[#F59E0B] text-xl leading-none",
"emptyStar": "text-[#F59E0B] text-xl opacity-30 leading-none",
"text": "text-[#003F31] text-[14px]",
"rating": 4.5,
"count": 38
},
"button": {
"addToCart": "hidden",
"moreInfo": "hidden",
"isShowIcon": false
}
}
}
}
}
},
{
"CartMismatchBanner": {
"config": {
"show": true
}
}
},
{
"VtCategoryHighlight": {
"config": {
"title": "Oder lieber stöbern? Hier findest du sicher deine neuen Hafer-Favoriten.",
"className": "content-container py-12 small:py-20",
"gridClassName": "grid grid-cols-4 gap-6 w-full",
"labelClassName": "absolute left-4 bottom-4 text-[#003F31] text-[18px] font-semibold",
"items": [
{
"imageSrc": "/overnight-oat.webp",
"href": "/categories/overnight-oats",
"label": "Overnight Oats",
"className": "bg-[#CFECD9] col-start-1 col-end-3 row-start-1 row-end-3",
"imageClassName": "object-contain"
},
{
"imageSrc": "/overnight-oat.webp",
"href": "/categories/porridge",
"label": "Porridge",
"className": "bg-[#F9E0B0]",
"imageClassName": "object-contain"
},
{
"imageSrc": "/overnight-oat.webp",
"href": "/categories/cereals",
"label": "Cereals",
"className": "bg-[#F59E0B]",
"imageClassName": "object-contain"
},
{
"imageSrc": "/overnight-oat.webp",
"href": "/categories/granola",
"label": "Granola",
"className": "bg-[#A7D8F0]",
"imageClassName": "object-contain"
},
{
"imageSrc": "/overnight-oat.webp",
"href": "/categories/oat-bars",
"label": "Oat Bars",
"className": "bg-[#EED7F2]",
"imageClassName": "object-contain"
}
]
}
}
},
{
"FreeShippingPriceNudge": {
"config": {
"variant": "popup"
}
}
}
]
}
}
]
+275 -321
View File
@@ -1,361 +1,315 @@
{
"layout": [
{
"Header": {
"config": {
"sticky": true
[
{
"Header": {
"config": {
"sticky": true
},
"children": [
{
"Banner": {
"config": {
"variant": "nav",
"className": "h-12 bg-[#e6c981] text-black gap-12",
"center": [
{
"Link": {
"config": {
"label": "BLACK FRIDAY",
"href": "/",
"className": "font-bold text-[1rem] flex items-center gap-1"
}
}
},
{
"Link": {
"config": {
"label": "Up to 55% off Bundles",
"href": "/",
"className": "text-[1rem] flex items-center gap-1"
}
}
},
{
"Link": {
"config": {
"label": "SHOP NOW",
"href": "/",
"className": "font-bold text-[1rem] flex items-center gap-1 underline"
}
}
}
]
}
}
},
"children": [
{
"Nav": {
"config": {
"className": "h-24 bg-[#1f3521] text-white gap-12",
"left": [
{
"Logo": {
"config": {
"src": "/drsquatch-logo.webp",
"alt": "MyShop",
"className": "h-auto w-40 mr-24",
"objectFit": "contain"
}
}
},
{
"Link": {
"config": {
"label": "SUBCRIBE",
"href": "/",
"className": "font-bold text-[1rem] text-white flex items-center mr-8 gap-1 hover:underline hover:text-white"
}
}
},
{
"Link": {
"config": {
"label": "REWARD",
"href": "/",
"className": "font-bold text-[1rem] text-white flex items-center mr-8 gap-1 hover:underline hover:text-white"
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "SHOP",
"className": "font-bold text-[1rem] flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-white"
}
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "OUR STORY",
"className": "font-bold text-[1rem] text-white flex items-center hover:bg-transparent hover:underline hover:text-white"
}
}
}
}
],
"right": [
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-white flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": true
}
}
}
},
{
"Button": {
"config": {
"icon": "User",
"className": "shadow-none bg-transparent text-white hover:text-black"
}
}
},
{
"VtCartButton": {
"config": {
"icon": "ShoppingCart",
"className": "shadow-none bg-transparent text-black w-[50px]"
}
}
}
]
}
}
}
]
}
},
{
"CartMismatchBanner": {
"config": {
"show": true
}
}
},
{
"FreeShippingPriceNudge": {
"config": {
"variant": "popup"
}
}
},
{
"PropsChildren": {}
},
{
"Footer": {
"config": {
"className": "content-container border-none bg-[#1f3621] flex w-full border justify-between pb-8 gap-10 pt-16 px-[120px]",
"leftClassName": "flex ml-6 gap-x-24",
"centerClassName": "flex",
"rightClassName": "flex",
"left": [
{
"Banner": {
"VtMenuItem": {
"config": {
"variant": "nav",
"className": "h-12 bg-[#e6c981] text-black gap-12",
"center": [
"title": "Help",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white gap-8",
"itemClassName": "text-[14px] font-[400] mt-3",
"items": [
{
"Link": {
"config": {
"label": "BLACK FRIDAY",
"href": "/",
"className": "font-bold text-[1rem] flex items-center gap-1"
}
}
"text": "FAQ",
"href": "/"
},
{
"Link": {
"config": {
"label": "Up to 55% off Bundles",
"href": "/",
"className": "text-[1rem] flex items-center gap-1"
}
}
"text": "Track my order",
"href": "/categories/shoes"
},
{
"Link": {
"config": {
"label": "SHOP NOW",
"href": "/",
"className": "font-bold text-[1rem] flex items-center gap-1 underline"
}
}
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
}
]
}
}
},
{
"Nav": {
"VtMenuItem": {
"config": {
"className": "h-24 bg-[#1f3521] text-white gap-12",
"left": [
"title": "Shop",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white",
"itemClassName": "text-[14px] font-[400] flex items-center mt-3",
"items": [
{
"Logo": {
"config": {
"src": "/drsquatch-logo.webp",
"alt": "MyShop",
"className": "h-auto w-40 mr-24",
"objectFit": "contain"
}
}
"text": "Twitter",
"href": "/"
},
{
"Link": {
"config": {
"label": "SUBCRIBE",
"href": "/",
"className": "font-bold text-[1rem] text-white flex items-center mr-8 gap-1 hover:underline hover:text-white"
}
}
"text": "Facebook",
"href": "/categories/shoes"
},
{
"Link": {
"config": {
"label": "REWARD",
"href": "/",
"className": "font-bold text-[1rem] text-white flex items-center mr-8 gap-1 hover:underline hover:text-white"
}
}
"text": "Pinterest",
"href": "/categories/accessories"
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "SHOP",
"className": "font-bold text-[1rem] flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-white"
}
}
}
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "OUR STORY",
"className": "font-bold text-[1rem] text-white flex items-center hover:bg-transparent hover:underline hover:text-white"
}
}
}
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
}
],
"right": [
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Info",
"className": "flex flex-col gap-y-2 text-[16px] font-semibold text-white",
"itemClassName": "text-[14px] font-[400] w-[200px] mt-3",
"items": [
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-white flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": true
}
}
}
"text": "The Squatch Difference",
"href": "/"
},
{
"Button": {
"config": {
"icon": "User",
"className": "shadow-none bg-transparent text-white hover:text-black"
}
}
"text": "Why Natural Products",
"href": "/categories/shoes"
},
{
"VtCartButton": {
"config": {
"icon": "ShoppingCart",
"className": "shadow-none bg-transparent text-black w-[50px]"
}
}
"text": "No Harmful Ingredients",
"href": "/categories/accessories"
}
]
}
}
}
],
"center": [
{
"Logo": {
"config": {
"src": "/b-corp-logo.webp",
"alt": "MyShop",
"className": "h-auto w-[90px] mr-24",
"objectFit": "contain"
}
}
}
],
"right": [
{
"VtFooterSignUp": {
"config": {
"title": "Don't miss out on hot deals! Sign up and get up to 30% off.",
"className": "max-w-[760px]",
"titleClassName": "text-white text-[18px]",
"formClassName": "mt-2 w-full",
"inputClassName": "w-full",
"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" }
]
}
}
}
]
}
},
{ "PropsChildren": {} },
{
"Footer": {
"config": {
"className": "content-container border-none bg-[#1f3621] flex w-full border justify-between pb-8 gap-10 pt-16 px-[120px]",
"leftClassName": "flex ml-6 gap-x-24",
"centerClassName": "flex",
"rightClassName": "flex",
"left": [
{
"VtMenuItem": {
"config": {
"title": "Help",
"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" }
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Shop",
"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" }
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Info",
"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" }
]
}
}
}
],
"center": [
{
"Logo": {
"config": {
"src": "/b-corp-logo.webp",
"alt": "MyShop",
"className": "h-auto w-[90px] mr-24",
"objectFit": "contain"
}
}
}
],
"right": [
{
"VtFooterSignUp": {
"config": {
"title": "Don't miss out on hot deals! Sign up and get up to 30% off.",
"className": "max-w-[760px]",
"titleClassName": "text-white text-[18px]",
"formClassName": "mt-2 w-full",
"inputClassName": "w-full",
"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" }
]
}
}
}
]
}
}
},
{
"VtFooterBottom": {
"config": {
"className": "text-white text-[14px] font-[400] bg-[#1f3621] flex items-center justify-center",
"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" }
]
}
}
},
{
"VtFooterBottom": {
"config": {
"className": "text-white text-[14px] font-[400] bg-[#1f3621] flex items-center justify-center",
"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"
}
]
}
}
],
"pages": {
"Home": [
{
"Hero": {
"config": {
"className": "h-[35rem]",
"ImageDisplayer": {
"config": {
"duration": 0,
"images": ["./drsquatch-banner.jpg"],
"links": ["/account"]
}
},
"left": [
{
"VtCtaBanner": {
"config": {
"variant": "default",
"className": "left-[120px] top-[120px] bg-transparent border-none text-white text-center",
"tagTextClassName": "text-white bg-transparent",
"titleTextClassName": "text-white font-bold leading-normal text-[30px]",
"descriptionTextClassName": "text-white text-[1rem] w-[300px] ml-[6.6rem]",
"buttonTextClassName": "text-white bg-orange-500 w-[300px]",
"tagText": "ALL NEW!",
"titleText": "LUMBERJACK LODGE",
"descriptionText": "Step into the lodge with the warm, sweet scent of maple and vanilla.",
"buttonText": "SHOP NOW"
}
}
}
],
"center": [],
"right": []
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "drsquatch-best-seller",
"styles": {
"container": "content-container py-12 px-[100px] small:py-24",
"header": {
"container": "flex justify-center mb-8",
"title": "text-[28px] font-bold text-[#1f3521]",
"isShowViewAll": false
},
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
"productCard": {
"card": "shadow-none border-none",
"className": "relative overflow-hidden rounded-2xl bg-white shadow-elevation-card-rest h-full flex flex-col",
"badgeText": "LIMITED EDITION",
"badge": {
"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" },
"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",
"description": "mt-2",
"reviews": {
"container": "mt-3 flex items-center gap-2",
"stars": "flex gap-1",
"star": "text-[#C4622C] text-xl leading-none",
"emptyStar": "text-[#C4622C] text-xl opacity-30 leading-none",
"text": "text-[#1f3521]",
"rating": 3.6,
"count": 59
},
"button": {
"addToCart": "mt-6 w-full bg-[#C4622C] hover:bg-[#C4622C]/90 shadow-none text-white rounded-none h-fit font-bold",
"moreInfo": "hidden"
}
}
}
}
}
},
{
"VtCategoryHighlight": {
"config": {
"title": "Oder lieber stöbern? Hier findest du sicher deine neuen Hafer-Favoriten.",
"className": "content-container py-12 small:py-20",
"gridClassName": "grid grid-cols-2 gap-6 w-full",
"labelClassName": "absolute left-4 bottom-4 text-[#003F31] text-[18px] font-semibold",
"items": [
{
"imageSrc": "/overnight-oat.webp",
"href": "/categories/overnight-oats",
"label": "Overnight Oats",
"className": "bg-[#CFECD9] h-[250px]",
"imageClassName": "object-contain"
},
{
"headingLabel": "The Squatch Difference",
"descriptionLabel": "Learn why men everywhere are loving Dr. Squatch.",
"buttonLabel": "Learn more",
"className": "flex-col bg-[#F9E0B0] p-6 justify-center",
"headingClassName": "text-[#003F31] text-[28px] font-semibold",
"descriptionClassName": "text-[#003f31b3]",
"buttonClassName": "mt-4 text-[#003F31] text-[18px] font-semibold bg-orange-500 py-2 px-16 rounded text-white"
}
]
}
}
},
{ "CartMismatchBanner": { "config": { "show": true } } },
{ "FreeShippingPriceNudge": { "config": { "variant": "popup" } } }
],
"Product": [
{ "VtFeaturedProducts": { "config": { "title": "drsquatch-best-seller" } } }
],
"StorePage": [
{ "VtFeaturedProducts": { "config": { "title": "drsquatch-best-seller" } } }
]
}
}
]
+199 -235
View File
@@ -1,264 +1,228 @@
{
"layout": [
{
"Header": {
"config": {
"sticky": true
},
"children": [
{
"Banner": {
"config": {
"variant": "nav",
"className": "h-12 bg-[#E6EFFC] text-[#11314E] gap-12 pl-16",
"left": [
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1 cursor-pointer"
}
}
},
{
"Link": {
"config": {
"label": "Kontaktieren Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1"
}
[
{
"Header": {
"config": {
"sticky": true
},
"children": [
{
"Banner": {
"config": {
"variant": "nav",
"className": "h-12 bg-[#E6EFFC] text-[#11314E] gap-12 pl-16",
"left": [
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1 cursor-pointer"
}
}
],
"center": [
{
"Link": {
"config": {
"label": "Einsparung durch Digitalisierung in der Arztpraxis",
"href": "/",
"className": "text-[13px] flex items-center gap-1 "
}
}
},
{
"Button": {
"config": {
"label": "Mehr Info",
"href": "/",
"className": "text-[13px] flex items-center bg-[#112638] gap-1 "
}
},
{
"Link": {
"config": {
"label": "Kontaktieren Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1"
}
}
],
"right": [
{
"Dropdown": {
"config": {
"trigger": {
}
],
"center": [
{
"Link": {
"config": {
"label": "Einsparung durch Digitalisierung in der Arztpraxis",
"href": "/",
"className": "text-[13px] flex items-center gap-1 "
}
}
},
{
"Button": {
"config": {
"label": "Mehr Info",
"href": "/",
"className": "text-[13px] flex items-center bg-[#112638] gap-1 "
}
}
}
],
"right": [
{
"Dropdown": {
"config": {
"trigger": {
"text": "EURO",
"className": "font-bold text-[13px] text-[#11314E] flex items-center gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Flag_of_Germany.svg/1200px-Flag_of_Germany.svg.png",
"text": "EURO",
"className": "font-bold text-[13px] text-[#11314E] flex items-center gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Flag_of_Germany.svg/1200px-Flag_of_Germany.svg.png",
"text": "EURO",
"href": "/"
}
]
"href": "/"
}
]
}
}
},
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-[#11314E] flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": false
}
}
}
}
]
}
}
},
{
"Nav": {
"config": {
"left": [
{
"VtSideMenu": {}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Sale",
"className": "text-[13px] text-[#11314E] flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-[#009b93]"
}
}
}
}
],
"center": [
{
"HomeButton": {
"config": {
"label": "Medusa Store"
}
}
}
],
"right": [
{
"AccountButton": {
"config": {
"label": "Account",
"className": "hover:text-ui-fg-base"
}
}
},
{
"VtCartButton": {
"config": {
"variant": "link",
"className": "hover:text-ui-fg-base"
}
}
}
]
}
}
}
]
}
},
{
"Hero": {
"config": {
"variant": "default"
}
}
},
{
"CartMismatchBanner": {
"config": {
"show": true
}
}
},
{
"FreeShippingPriceNudge": {
"config": {
"variant": "popup"
}
}
},
{
"PropsChildren": {}
},
{
"Footer": {
"config": {
"className": "content-container flex w-full border h-[300px] justify-between",
"left": [
{
"VtMenuItem": {
"config": {
"title": "category",
"className": "flex flex-col gap-y-2",
"itemClassName": "text-ui-fg-subtle txt-small ml-3",
"items": [
{
"text": "Clothing",
"href": "/"
},
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-[#11314E] flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": false
}
}
}
"text": "Shoes",
"href": "/categories/shoes"
},
{
"text": "Accessories",
"href": "/categories/accessories"
}
]
}
}
},
}
],
"center": [
{
"Nav": {
"VtMenuItem": {
"config": {
"left": [
"title": "category",
"className": "flex flex-col gap-y-2",
"itemClassName": "text-ui-fg-subtle txt-small ml-3",
"items": [
{
"VtSideMenu": {}
"text": "Clothing",
"href": "/"
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Sale",
"className": "text-[13px] text-[#11314E] flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-[#009b93]"
}
}
}
}
],
"center": [
{
"HomeButton": {
"config": {
"label": "Medusa Store"
}
}
}
],
"right": [
{
"AccountButton": {
"config": {
"label": "Account",
"className": "hover:text-ui-fg-base"
}
}
"text": "Shoes",
"href": "/categories/shoes"
},
{
"VtCartButton": {
"config": {
"variant": "link",
"className": "hover:text-ui-fg-base"
}
}
"text": "Accessories",
"href": "/categories/accessories"
}
]
}
}
}
],
"right": [
{
"Text": {
"config": {
"label": "Medusa Check",
"className": "text-[13px] text-[#A6A6A6]"
}
}
}
]
}
},
{ "PropsChildren": {} },
{
"Footer": {
"config": {
"className": "content-container flex w-full border h-[300px] justify-between",
"left": [
{
"VtMenuItem": {
"config": {
"title": "category",
"className": "flex flex-col gap-y-2",
"itemClassName": "text-ui-fg-subtle txt-small ml-3",
"items": [
{ "text": "Clothing", "href": "/" },
{ "text": "Shoes", "href": "/categories/shoes" },
{ "text": "Accessories", "href": "/categories/accessories" }
]
}
}
}
],
"center": [
{
"VtMenuItem": {
"config": {
"title": "category",
"className": "flex flex-col gap-y-2",
"itemClassName": "text-ui-fg-subtle txt-small ml-3",
"items": [
{ "text": "Clothing", "href": "/" },
{ "text": "Shoes", "href": "/categories/shoes" },
{ "text": "Accessories", "href": "/categories/accessories" }
]
}
}
}
],
"right": [
{
"Text": {
"config": {
"label": "Medusa Check",
"className": "text-[13px] text-[#A6A6A6]"
}
}
}
]
}
}
}
],
"pages": {
"Home": [
{
"Hero": {
"config": {
"variant": "default",
"className": "bg-custom-gradient"
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "best-seller"
}
}
},
{
"VtCategoryHighlight": {
"config": {
"title": "Oder lieber stöbern? Hier findest du sicher deine neuen Hafer-Favoriten.",
"className": "content-container py-12 small:py-20",
"gridClassName": "grid grid-cols-2 gap-6 w-full",
"labelClassName": "absolute left-4 bottom-4 text-[#003F31] text-[18px] font-semibold",
"items": [
{
"imageSrc": "/overnight-oat.webp",
"href": "/categories/overnight-oats",
"label": "Overnight Oats",
"className": "bg-[#CFECD9] h-[250px]",
"imageClassName": "object-contain"
},
{
"headingLabel": "The Squatch Difference",
"descriptionLabel": "Learn why men everywhere are loving Dr. Squatch.",
"buttonLabel": "Learn more",
"className": "flex-col bg-[#F9E0B0] p-6 justify-center",
"headingClassName": "text-[#003F31] text-[28px] font-semibold",
"descriptionClassName": "text-[#003f31b3]",
"buttonClassName": "mt-4 text-[#003F31] text-[18px] font-semibold bg-orange-500 py-2 px-16 rounded text-white"
}
]
}
}
},
{
"CartMismatchBanner": {
"config": { "show": true }
}
},
{
"FreeShippingPriceNudge": {
"config": { "variant": "popup" }
}
}
],
"Product": [
{
"VtFeaturedProducts": {
"config": {
"title": "best-seller"
}
}
}
],
"StorePage": [
{
"VtFeaturedProducts": {
"config": {
"title": "best-seller"
}
}
}
]
}
}
]
+308 -351
View File
@@ -1,174 +1,324 @@
{
"layout": [
{
"Header": {
"config": {
"sticky": true
},
"children": [
{
"Banner": {
"config": {
"variant": "nav",
"className": "h-12 bg-[#E6EFFC] text-[#11314E] gap-12 pl-16",
"left": [
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1 cursor-pointer"
}
}
},
{
"Link": {
"config": {
"label": "Kontaktieren Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1"
}
[
{
"Header": {
"config": {
"sticky": true
},
"children": [
{
"Banner": {
"config": {
"variant": "nav",
"className": "h-12 bg-[#E6EFFC] text-[#11314E] gap-12 pl-16",
"left": [
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1 cursor-pointer"
}
}
],
"center": [
{
"Link": {
"config": {
"label": "Einsparung durch Digitalisierung in der Arztpraxis",
"href": "/",
"className": "text-[13px] flex items-center gap-1 "
}
}
},
{
"Link": {
"config": {
"label": "Mehr Info",
"href": "/",
"className": "text-[13px] rounded-md hover:text-white w-[80px] text-white text-center flex items-center bg-[#112638] flex justify-center h-[28px] "
}
},
{
"Link": {
"config": {
"label": "Kontaktieren Uns",
"href": "/",
"className": "text-[13px] flex items-center gap-1"
}
}
],
"right": [
{
"Dropdown": {
"config": {
"trigger": {
}
],
"center": [
{
"Link": {
"config": {
"label": "Einsparung durch Digitalisierung in der Arztpraxis",
"href": "/",
"className": "text-[13px] flex items-center gap-1 "
}
}
},
{
"Link": {
"config": {
"label": "Mehr Info",
"href": "/",
"className": "text-[13px] rounded-md hover:text-white w-[80px] text-white text-center flex items-center bg-[#112638] flex justify-center h-[28px] "
}
}
}
],
"right": [
{
"Dropdown": {
"config": {
"trigger": {
"text": "EURO",
"className": "font-bold text-[13px] text-[#11314E] flex items-center gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Flag_of_Germany.svg/1200px-Flag_of_Germany.svg.png",
"text": "EURO",
"className": "font-bold text-[13px] text-[#11314E] flex items-center gap-1 hover:text-[#009b93]",
"isShowArrow": true
},
"items": [
{
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Flag_of_Germany.svg/1200px-Flag_of_Germany.svg.png",
"text": "EURO",
"href": "/"
}
]
"href": "/"
}
]
}
}
},
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-[#11314E] flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": false
}
}
}
}
]
}
}
},
{
"Nav": {
"config": {
"className": "h-24 bg-[white] text-[#11314E] gap-12 pl-16",
"left": [
{
"Logo": {
"config": {
"src": "/VibentecIT-logo.svg",
"alt": "MyShop",
"className": "h-full w-[180px] mr-4",
"objectFit": "contain"
}
}
},
{
"Link": {
"config": {
"label": "Home",
"href": "/",
"className": "text-[13px] text-[#11314E] flex items-center mr-8 gap-1 hover:underline hover:text-[#009b93]"
}
}
},
{
"Link": {
"config": {
"label": "Shop",
"href": "/",
"className": "text-[13px] text-[#11314E] flex items-center mr-6 gap-1 hover:underline hover:text-[#009b93]"
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Sale",
"className": "text-[13px] text-[#11314E] flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-[#009b93]"
}
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "OUR STORY",
"className": "font-bold text-[1rem] text-white flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-white"
}
}
}
}
],
"right": [
{
"SearchInput": {
"config": {
"placeholder": "Search"
}
}
},
{
"AccountButton": {
"config": {
"icon": "User",
"className": " flex items-center gap-1 shadow-none"
}
}
},
{
"Button": {
"config": {
"icon": "Heart",
"className": " flex items-center gap-1 shadow-none w-[50px]"
}
}
},
{
"VtCartButton": {
"config": {
"icon": "ShoppingCart",
"className": "shadow-none bg-transparent text-black w-[50px]"
}
}
}
]
}
}
}
]
}
},
{
"Hero": {
"config": {
"variant": "vibentec",
"text": "alo",
"className": "bg-custom-gradient"
}
}
},
{
"CartMismatchBanner": {
"config": {
"show": true
}
}
},
{
"FreeShippingPriceNudge": {
"config": {
"variant": "popup"
}
}
},
{
"PropsChildren": {}
},
{
"Footer": {
"config": {
"className": "content-container flex w-full border justify-between pb-8",
"leftClassName": "flex-col ml-6",
"centerClassName": "flex mt-[130px] gap-24",
"rightClassName": "flex mt-[160px]",
"left": [
{
"VtFooterHero": {
"config": {
"logoClassName": "h-[100px] w-[255px]",
"logoSrc": "/VibentecIT-logo.svg",
"logoAlt": "Vibentec IT",
"title": "Der Wegbereiter für innovative IT-Lösungen",
"description": "Tauchen Sie ein in eine Welt modernster Technologien, zuverlässiger Support und proaktiver Innovation gemeinsam gestalten wir die digitale Zukunft Ihres Unternehmens.",
"cta": { "label": "Kontaktieren Sie uns", "href": "/" },
"className": "",
"ctaClassName": "ml-8",
"titleClassName": "ml-8",
"descriptionClassName": "ml-8 w-[320px]"
}
}
}
],
"center": [
{
"VtMenuItem": {
"config": {
"title": "Unternehmen",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400]",
"items": [
{
"text": "Über Uns",
"href": "/"
},
{
"VtCountryCodeSelect": {
"config": {
"trigger": {
"className": "w-auto font-bold text-[13px] text-[#11314E] flex justify-start items-center gap-1 hover:text-[#009b93] bg-transparent shadow-none hover:bg-transparent",
"isFlag": false
}
}
}
"text": "Placeholder",
"href": "/categories/shoes"
},
{
"text": "Placeholder",
"href": "/categories/accessories"
}
]
}
}
},
{
"Nav": {
"VtMenuItem": {
"config": {
"className": "h-24 bg-[white] text-[#11314E] gap-12 pl-16",
"left": [
"title": "Social Media",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400] flex items-center",
"items": [
{
"Logo": {
"config": {
"src": "/VibentecIT-logo.svg",
"alt": "MyShop",
"className": "h-full w-[180px] mr-4",
"objectFit": "contain"
}
}
"text": "Twitter",
"href": "/",
"icon": "X"
},
{
"Link": {
"config": {
"label": "Home",
"href": "/",
"className": "text-[13px] text-[#11314E] flex items-center mr-8 gap-1 hover:underline hover:text-[#009b93]"
}
}
"text": "Facebook",
"href": "/categories/shoes",
"icon": "X"
},
{
"Link": {
"config": {
"label": "Shop",
"href": "/",
"className": "text-[13px] text-[#11314E] flex items-center mr-6 gap-1 hover:underline hover:text-[#009b93]"
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "Sale",
"className": "text-[13px] text-[#11314E] flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-[#009b93]"
}
}
}
},
{
"VtMegaMenu": {
"config": {
"navLabel": {
"text": "OUR STORY",
"className": "font-bold text-[1rem] text-white flex items-center mr-8 gap-1 hover:bg-transparent hover:underline hover:text-white"
}
}
}
"text": "Pinterest",
"href": "/categories/accessories",
"icon": "X"
}
],
"right": [
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Addresse",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400] w-[150px]",
"items": [
{
"SearchInput": {
"config": {
"placeholder": "Search"
}
}
"text": "Hopfenstr. 10c76185 Karlsruhe Deutschland",
"href": "/"
},
{
"AccountButton": {
"config": {
"icon": "User",
"className": " flex items-center gap-1 shadow-none"
}
}
"text": "+497271 5970098",
"href": "/categories/shoes"
},
{
"Button": {
"config": {
"icon": "Heart",
"className": " flex items-center gap-1 shadow-none w-[50px]"
}
}
"text": "info@vibentec-it.io",
"href": "/categories/accessories"
}
]
}
}
}
],
"right": [
{
"VtMenuItem": {
"config": {
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400] w-[150px]",
"items": [
{
"text": "Datenschutz",
"href": "/"
},
{
"VtCartButton": {
"config": {
"icon": "ShoppingCart",
"className": "shadow-none bg-transparent text-black w-[50px]"
}
}
"text": "Impressum",
"href": "/categories/shoes"
},
{
"text": "Installation Info",
"href": "/categories/accessories"
}
]
}
@@ -176,217 +326,24 @@
}
]
}
},
{ "PropsChildren": {} },
{
"Footer": {
"config": {
"className": "content-container flex w-full border justify-between pb-8",
"leftClassName": "flex-col ml-6",
"centerClassName": "flex mt-[130px] gap-24",
"rightClassName": "flex mt-[160px]",
"left": [
{
"VtFooterHero": {
"config": {
"logoClassName": "h-[100px] w-[255px]",
"logoSrc": "/VibentecIT-logo.svg",
"logoAlt": "Vibentec IT",
"title": "Der Wegbereiter für innovative IT-Lösungen",
"description": "Tauchen Sie ein in eine Welt modernster Technologien, zuverlässiger Support und proaktiver Innovation gemeinsam gestalten wir die digitale Zukunft Ihres Unternehmens.",
"cta": {
"label": "Kontaktieren Sie uns",
"href": "/"
},
"className": "",
"ctaClassName": "ml-8",
"titleClassName": "ml-8",
"descriptionClassName": "ml-8 w-[320px]"
}
}
},
{
"Footer": {
"config": {
"className": "content-container h-[128px] w-full text text-[#11314E] flex items-center justify-between px-20 mt-2",
"leftClassName": "w-full",
"left": [
{
"VtFooterBottom": {
"config": {
"text": "©2025 Vibentec IT. All rights reserved",
"icons": ["Mastercard", "PayPal", "Visa"]
}
}
],
"center": [
{
"VtMenuItem": {
"config": {
"title": "Unternehmen",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400]",
"items": [
{ "text": "Über Uns", "href": "/" },
{ "text": "Placeholder", "href": "/categories/shoes" },
{ "text": "Placeholder", "href": "/categories/accessories" }
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Social Media",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400] flex items-center",
"items": [
{ "text": "Twitter", "href": "/", "icon": "X" },
{ "text": "Facebook", "href": "/categories/shoes", "icon": "X" },
{ "text": "Pinterest", "href": "/categories/accessories", "icon": "X" }
]
}
}
},
{
"VtMenuItem": {
"config": {
"title": "Addresse",
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400] w-[150px]",
"items": [
{ "text": "Hopfenstr. 10c76185 Karlsruhe Deutschland", "href": "/" },
{ "text": "+497271 5970098", "href": "/categories/shoes" },
{ "text": "info@vibentec-it.io", "href": "/categories/accessories" }
]
}
}
}
],
"right": [
{
"VtMenuItem": {
"config": {
"className": "flex flex-col gap-y-2 text-[24px] font-semibold text-[#11314E]",
"itemClassName": "text-[1rem] font-[400] w-[150px]",
"items": [
{ "text": "Datenschutz", "href": "/" },
{ "text": "Impressum", "href": "/categories/shoes" },
{ "text": "Installation Info", "href": "/categories/accessories" }
]
}
}
}
]
}
}
},
{
"Footer": {
"config": {
"className": "content-container h-[128px] w-full text text-[#11314E] flex items-center justify-between px-20 mt-2",
"leftClassName": "w-full",
"left": [
{
"VtFooterBottom": {
"config": {
"text": "©2025 Vibentec IT. All rights reserved",
"icons": ["Mastercard", "PayPal", "Visa"]
}
}
}
]
}
}
]
}
}
],
"pages": {
"Home": [
{
"Hero": {
"config": {
"className": "h-[35rem]",
"ImageDisplayer": {
"config": {
"duration": 5,
"images": ["./banner-hero.webp", "./banner-hero-1.webp", "./banner-hero-2.webp"],
"links": ["/", "/account", "/product"]
}
},
"left": [
{
"VtCtaBanner": {
"config": {
"variant": "default",
"className": "left-[120px] top-[80px]",
"titleTextClassName": "leading-normal",
"tagText": "Besonders Aktion",
"titleText": "Willkommen in Vibentec IT Shop",
"descriptionText": "Insert the accordion description here. It would look better as two lines of text or more.",
"buttonText": "Zum Einkaufen"
}
}
}
],
"center": [],
"right": []
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "best-seller",
"styles": {
"container": "content-container py-12 px-[100px] small:py-24",
"header": {
"container": "flex justify-between mb-8",
"title": "text-[56px] text-[#11314E]",
"isShowViewAll": false
},
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
"productCard": {
"card": "relative overflow-hidden rounded-2xl border border-[#285A86] bg-ui-bg-base shadow-elevation-card-rest h-full flex flex-col",
"badge": {
"container": "p-4",
"text": "z-20 px-3 py-1 border-[0.5px] rounded bg-[#c9e0f5] txt-compact-small-plus shadow-borders-base text-[#285A86]"
},
"thumbnail": { "className": "rounded-none h-[240px]", "size": "full" },
"subtitle": "text-ui-fg-subtle text-[14px]",
"content": "flex flex-col flex-1 justify-between p-4",
"title": "text-ui-fg-subtle text-[18px]",
"description": "mt-2 text-ui-fg-subtle text-[14px]",
"price": "flex items-center gap-x-1 text-[#285A86] font-bold border-b pb-4",
"button": {
"addToCart": "w-fit h-[40px] bg-black text-white rounded-md",
"moreInfo": "w-fit h-[40px] border border-[#285A86] text-[#285A86] rounded-md"
}
}
}
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "produkten",
"styles": {
"container": "content-container py-12 px-[100px] small:py-24",
"header": {
"container": "flex justify-between mb-8",
"title": "text-[56px] text-[#11314E]",
"isShowViewAll": false
},
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
"productCard": {
"card": "relative overflow-hidden rounded-2xl border border-[#285A86] bg-ui-bg-base shadow-elevation-card-rest h-full flex flex-col",
"badge": {
"container": "p-4",
"text": "z-20 px-3 py-1 border-[0.5px] rounded bg-[#c9e0f5] txt-compact-small-plus shadow-borders-base text-[#285A86]"
},
"thumbnail": { "className": "rounded-none h-[240px]", "size": "full" },
"subtitle": "text-ui-fg-subtle text-[14px]",
"content": "flex flex-col flex-1 justify-between p-4",
"title": "text-ui-fg-subtle text-[18px]",
"price": "flex items-center gap-x-1 text-[#285A86] font-bold border-b pb-4",
"button": {
"addToCart": "w-fit h-[40px] bg-black text-white rounded-md",
"moreInfo": "w-fit h-[40px] border border-[#285A86] text-[#285A86] rounded-md"
}
}
}
}
}
},
{ "CartMismatchBanner": { "config": { "show": true } } },
{ "FreeShippingPriceNudge": { "config": { "variant": "popup" } } }
]
}
}
]
-2
View File
@@ -21,8 +21,6 @@
"@radix-ui/react-accordion": "^1.2.1",
"@stripe/react-stripe-js": "^1.7.2",
"@stripe/stripe-js": "^1.29.0",
"embla-carousel-autoplay": "^8.6.0",
"embla-carousel-react": "^8.6.0",
"lodash": "^4.17.21",
"next": "^15.3.1",
"pg": "^8.11.3",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

+4 -14
View File
@@ -5,22 +5,14 @@ import { retrieveCustomer } from "@lib/data/customer"
import { getBaseURL } from "@lib/util/env"
import { StoreCartShippingOption } from "@medusajs/types"
import { DynamicLayoutRenderer } from "../../../vibentec/renderer"
import { LayoutContext, LayoutComponentNode } from "../../../vibentec/component-map"
import { loadLayoutConfig } from "vibentec/configloader"
import { getRegion } from "@lib/data/regions"
import { LayoutContext, LayoutComponentNode, } from "../../../vibentec/component-map"
import { loadDesignConfig } from "vibentec/configloader"
export const metadata: Metadata = {
metadataBase: new URL(getBaseURL()),
}
export default async function PageLayout(props: {
children: React.ReactNode
params: Promise<{ countryCode: string }>
}) {
const params = await props.params
const { countryCode } = params
const region = await getRegion(countryCode)
export default async function PageLayout(props: { children: React.ReactNode }) {
const customer = await retrieveCustomer()
const cart = await retrieveCart()
let shippingOptions: StoreCartShippingOption[] = []
@@ -31,14 +23,12 @@ export default async function PageLayout(props: {
shippingOptions = shipping_options
}
const nodes: LayoutComponentNode[] = await loadLayoutConfig()
const nodes: LayoutComponentNode[] = await loadDesignConfig()
const context: LayoutContext = {
customer,
cart,
shippingOptions,
contentChildren: props.children,
countryCode,
region,
}
+10 -21
View File
@@ -4,10 +4,6 @@ import FeaturedProducts from "@modules/home/components/featured-products"
import Hero from "@modules/home/components/hero"
import { listCollections } from "@lib/data/collections"
import { getRegion } from "@lib/data/regions"
import VtFeaturedProducts from "@modules/home/components/vt-featured-products"
import { DynamicLayoutRenderer } from "@vibentec/renderer"
import { LayoutContext, LayoutComponentNode } from "@vibentec/component-map"
import { loadPageConfig } from "@vibentec/configloader"
export const metadata: Metadata = {
title: "Medusa Next.js Starter Template",
@@ -28,25 +24,18 @@ export default async function Home(props: {
fields: "id, handle, title",
})
console.log('collections:',collections)
if (!collections || !region) {
return null
}
const nodes: LayoutComponentNode[] = await loadPageConfig("Home")
if (!region) {
return null
}
const context: LayoutContext = {
customer: null,
cart: null,
shippingOptions: [],
contentChildren: null,
countryCode,
region,
}
return <DynamicLayoutRenderer nodes={nodes} context={context} />
return (
<>
{/* <Hero /> */}
<div className="py-12">
<ul className="flex flex-col gap-x-6">
<FeaturedProducts collections={collections} region={region} />
</ul>
</div>
</>
)
}
@@ -3,9 +3,6 @@ import { notFound } from "next/navigation"
import { listProducts } from "@lib/data/products"
import { getRegion, listRegions } from "@lib/data/regions"
import ProductTemplate from "@modules/products/templates"
import { DynamicLayoutRenderer } from "@vibentec/renderer"
import { LayoutContext, LayoutComponentNode } from "@vibentec/component-map"
import { loadPageConfig } from "@vibentec/configloader"
type Props = {
params: Promise<{ countryCode: string; handle: string }>
@@ -99,25 +96,11 @@ export default async function ProductPage(props: Props) {
notFound()
}
const nodes: LayoutComponentNode[] = await loadPageConfig("Product")
const context: LayoutContext = {
customer: null,
cart: null,
shippingOptions: [],
contentChildren: null,
countryCode: params.countryCode,
region,
}
return (
<>
<ProductTemplate
product={pricedProduct}
region={region}
countryCode={params.countryCode}
/>
<DynamicLayoutRenderer nodes={nodes} context={context} />
</>
<ProductTemplate
product={pricedProduct}
region={region}
countryCode={params.countryCode}
/>
)
}
+7 -24
View File
@@ -2,10 +2,6 @@ import { Metadata } from "next"
import { SortOptions } from "@modules/store/components/refinement-list/sort-products"
import StoreTemplate from "@modules/store/templates"
import { LayoutComponentNode, LayoutContext } from "@vibentec/component-map"
import { getRegion } from "@lib/data/regions"
import { loadPageConfig } from "@vibentec/configloader"
import { DynamicLayoutRenderer } from "@vibentec/renderer"
export const metadata: Metadata = {
title: "Store",
@@ -23,28 +19,15 @@ type Params = {
}
export default async function StorePage(props: Params) {
const params = await props.params
const searchParams = await props.searchParams
const region = await getRegion(params.countryCode)
const params = await props.params;
const searchParams = await props.searchParams;
const { sortBy, page } = searchParams
const nodes: LayoutComponentNode[] = await loadPageConfig("Store")
const context: LayoutContext = {
customer: null,
cart: null,
shippingOptions: [],
contentChildren: null,
countryCode: params.countryCode,
region,
}
return (
<>
<StoreTemplate
sortBy={sortBy}
page={page}
countryCode={params.countryCode}
/>
<DynamicLayoutRenderer nodes={nodes} context={context} />
</>
<StoreTemplate
sortBy={sortBy}
page={page}
countryCode={params.countryCode}
/>
)
}
+1
View File
@@ -36,6 +36,7 @@ export const listCollections = async (
{
query: queryParams,
next,
cache: "force-cache",
}
)
.then(({ collections }) => ({ collections, count: collections.length }))
+2 -1
View File
@@ -63,11 +63,12 @@ export const listProducts = async ({
offset,
region_id: region?.id,
fields:
"*variants.calculated_price,+variants.inventory_quantity,*metadata,+tags",
"*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags",
...queryParams,
},
headers,
next,
cache: "force-cache",
}
)
.then(({ products, count }) => {
@@ -1,86 +0,0 @@
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 VtCategoryHighlight({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
const title: string = props.title ?? ""
const items = props.items ?? []
const classes = {
container: props.className ?? "content-container py-12",
title:
props.titleClassName ?? "text-[#003F31] text-[28px] font-semibold mb-6",
grid: props.gridClassName ?? "grid grid-cols-3 gap-6 w-full",
tile:
props.tileClassName ??
"relative rounded-2xl overflow-hidden bg-white aspect-square w-full",
label: props.labelClassName ?? "text-[#003F31] text-[18px] font-semibold",
image: props.imageClassName ?? "w-full h-full object-contain",
}
if (!items || items.length === 0) {
return null
}
const renderTile = (tile: any, idx: number) => {
const imageEl = tile.imageSrc ? (
<img
src={tile.imageSrc}
alt={tile.label ?? `category-${idx}`}
className={clx(classes.image, tile.imageClassName)}
/>
) : (
<div
className={clx(
"w-full h-full flex items-center justify-center",
tile.className
)}
>
<div className={clx(tile.headingClassName)}>{tile.headingLabel}</div>
<div className={tile.descriptionClassName}>{tile.descriptionLabel}</div>
<button className={tile.buttonClassName}>{tile.buttonLabel}</button>
</div>
)
const content = (
<div className={clx("relative w-full h-full")}>
{imageEl}
{tile.label && <span className={classes.label}>{tile.label}</span>}
</div>
)
return tile.href ? (
<LocalizedClientLink
key={`tile-${idx}`}
href={tile.href}
className={clx("w-full h-full", tile.className)}
>
{content}
</LocalizedClientLink>
) : (
<div className={clx(tile.className, "w-full h-full")} key={`tile-${idx}`}>
{content}
</div>
)
}
return (
<section className={classes.container}>
{title && <h2 className={classes.title}>{title}</h2>}
<div className={classes.grid}>
{items.map((tile: any, idx: number) => renderTile(tile, idx))}
</div>
</section>
)
}
@@ -1,52 +0,0 @@
import { HttpTypes } from "@medusajs/types"
import ProductRail from "./product-rail"
import { listCollections } from "@lib/data/collections"
import { LayoutComponentDefinition, LayoutContext } from "@vibentec/component-map"
export default async function VtFeaturedProducts(props: {
collections?: HttpTypes.StoreCollection[]
region?: HttpTypes.StoreRegion
countryCode?: string
nodes?: LayoutComponentDefinition
context?: LayoutContext
}) {
let { collections, region, countryCode } = props
const { nodes, context } = props
if (context) {
if (!region) region = context.region
if (!countryCode) countryCode = context.countryCode
}
if (!collections && region) {
const result = await listCollections({
fields: "id, handle, title",
})
collections = result.collections
}
if (!collections || !region || !countryCode) {
return null
}
const configTitle = nodes?.config?.title
const styles = nodes?.config?.styles
let displayCollections = collections
if (configTitle) {
displayCollections = collections.filter(
(c) => c.handle === configTitle || c.title === configTitle
)
}
return displayCollections.map((collection) => (
<li key={collection.id}>
<ProductRail
collection={collection}
region={region}
countryCode={countryCode}
styles={styles}
/>
</li>
))
}
@@ -1,68 +0,0 @@
import { listProducts } from "@lib/data/products"
import { HttpTypes } from "@medusajs/types"
import { Text, clx } from "@medusajs/ui"
import InteractiveLink from "@modules/common/components/interactive-link"
import ProductCard from "@modules/products/components/vt-product-card"
export default async function ProductRail({
collection,
region,
countryCode,
styles,
}: {
collection: HttpTypes.StoreCollection
region: HttpTypes.StoreRegion
countryCode: string
styles?: any
}) {
const {
response: { products: pricedProducts },
} = await listProducts({
regionId: region.id,
queryParams: {
collection_id: collection.id,
fields: "*variants.calculated_price",
},
})
if (!pricedProducts) {
return null
}
const classes = {
container: styles?.container ?? "content-container py-12 px-[100px] small:py-24",
header: {
container: styles?.header?.container ?? "flex justify-between mb-8",
title: styles?.header?.title ?? "txt-xlarge",
isShowViewAll: styles?.header.isShowViewAll ?? true,
},
list: styles?.list ?? "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
}
return (
<div className={classes.container}>
<div className={classes.header.container}>
<Text className={classes.header.title}>{collection.title}</Text>
{classes.header.isShowViewAll && (
<InteractiveLink href={`/collections/${collection.handle}`}>
View all
</InteractiveLink>
)}
</div>
<ul className={classes.list}>
{pricedProducts &&
pricedProducts.map((product) => (
<li key={product.id}>
<ProductCard
product={product}
countryCode={countryCode}
styles={styles?.productCard}
badgeText={styles?.productCard?.badgeText}
/>
</li>
))}
</ul>
</div>
)
}
@@ -1,45 +0,0 @@
import { Github } from "@medusajs/icons"
import { Button, Heading } from "@medusajs/ui"
import { VtCarousel } from "../vt-carousel"
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map"
export default function HeroDefault({ node, context }: { node: LayoutComponentDefinition; context: LayoutContext }) {
const props = node.config ?? {}
const imageDisplayer = props.ImageDisplayer
if (imageDisplayer) {
return (
<div className="absolute inset-0 z-auto w-full h-full">
<VtCarousel nodes={{ config: imageDisplayer.config }} context={context} />
</div>
)
}
return (
<div className="absolute inset-0 z-auto flex flex-col justify-center items-center text-center small:p-32 gap-6">
<span>
<Heading
level="h1"
className="text-3xl leading-10 text-ui-fg-base font-normal"
>
Ecommerce Starter Template
</Heading>
<Heading
level="h2"
className="text-3xl leading-10 text-ui-fg-subtle font-normal"
>
Powered by Medusa and Next.js
</Heading>
</span>
<a
href="https://github.com/medusajs/nextjs-starter-medusa"
target="_blank"
>
<Button variant="secondary">
View on GitHub
<Github />
</Button>
</a>
</div>
)
}
@@ -0,0 +1,31 @@
import { Github } from "@medusajs/icons"
import { Button, Heading } from "@medusajs/ui"
export default function HeroDefault() {
return (
<div className="absolute inset-0 z-10 flex flex-col justify-center items-center text-center small:p-32 gap-6">
<span>
<Heading
level="h1"
className="text-3xl leading-10 text-ui-fg-base font-normal"
>
Ecommerce Starter Template
</Heading>
<Heading
level="h2"
className="text-3xl leading-10 text-ui-fg-subtle font-normal"
>
Powered by Medusa and Next.js
</Heading>
</span>
<a
href="https://github.com/medusajs/nextjs-starter-medusa"
target="_blank"
>
<Button variant="secondary">
View on GitHub
<Github />
</Button>
</a>
</div>
)
}
@@ -0,0 +1,48 @@
import { Github } from "@medusajs/icons"
import { Button, clx, Heading } from "@medusajs/ui"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
interface Props {
node: LayoutComponentDefinition
context: LayoutContext
}
const HeroVibentec = ({ node, context }: Props) => {
const props = node.config ?? {}
return (
<div
className={clx(
"absolute inset-0 z-10 flex flex-col justify-center items-center text-center small:p-32 gap-6",
props.className
)}
>
<span>
<Heading
level="h1"
className="text-3xl leading-10 text-ui-fg-base font-normal"
>
{props.text ?? "Ecommerce Starter Template Vibentec"}
</Heading>
<Heading
level="h2"
className="text-3xl leading-10 text-ui-fg-subtle font-normal"
>
Powered by Medusa and Next.js
</Heading>
</span>
<a
href="https://github.com/medusajs/nextjs-starter-medusa"
target="_blank"
>
<Button variant="secondary">
View on GitHub
<Github />
</Button>
</a>
</div>
)
}
export default HeroVibentec
+18 -23
View File
@@ -3,9 +3,14 @@ import {
LayoutContext,
} from "vibentec/component-map"
import { clx } from "@medusajs/ui"
import BannerHero from "./banner-hero"
import { DynamicLayoutRenderer } from "vibentec/renderer"
import BannerHeroVibentec from "../hero/hero-vibentec"
import BannerHeroDefault from "../hero/hero-default"
interface BannerProps {
variant: "vibentec" | "default"
className?: string
speed?: number
}
export default async function Hero({
nodes,
context,
@@ -13,33 +18,23 @@ export default async function Hero({
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
const left = nodes.config?.left ?? []
const center = nodes.config?.center ?? []
const right = nodes.config?.right ?? []
const props = (nodes.config as BannerProps) ?? {}
const heroClassName = clx(
"min-h-[30rem] w-full border-b border-ui-border-base relative",
"h-[75vh] w-full border-b border-ui-border-base relative bg-ui-bg-subtle",
props.className
)
if (!props.variant) return null
const variants = {
vibentec: BannerHeroVibentec,
default: BannerHeroDefault
}
const Component = variants[props.variant]
return (
<div className={heroClassName}>
<BannerHero node={nodes} context={context} />
<div className="absolute z-20">
<nav className="content-container txt-xsmall-plus flex items-center justify-between text-small-regular">
<div className="flex items-center gap-x-4">
{left && <DynamicLayoutRenderer nodes={left} context={context} />}
</div>
<div className="flex items-center gap-x-4">
{center && (
<DynamicLayoutRenderer nodes={center} context={context} />
)}
</div>
<div className="flex items-center gap-x-4 justify-end">
{right && <DynamicLayoutRenderer nodes={right} context={context} />}
</div>
</nav>
</div>
<Component node={nodes} context={context} />
</div>
)
}
@@ -1,75 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react'
import style from './index.module.css'
export const usePrevNextButtons = (emblaApi: any) => {
const [prevBtnDisabled, setPrevBtnDisabled] = useState(true)
const [nextBtnDisabled, setNextBtnDisabled] = useState(true)
const onPrevButtonClick = useCallback(() => {
if (!emblaApi) return
emblaApi.scrollPrev()
}, [emblaApi])
const onNextButtonClick = useCallback(() => {
if (!emblaApi) return
emblaApi.scrollNext()
}, [emblaApi])
const onSelect = useCallback((emblaApi: any) => {
setPrevBtnDisabled(!emblaApi.canScrollPrev())
setNextBtnDisabled(!emblaApi.canScrollNext())
}, [])
useEffect(() => {
if (!emblaApi) return
onSelect(emblaApi)
emblaApi.on('reInit', onSelect).on('select', onSelect)
}, [emblaApi, onSelect])
return {
prevBtnDisabled,
nextBtnDisabled,
onPrevButtonClick,
onNextButtonClick
}
}
export const PrevButton = (props: any) => {
const { children, ...restProps } = props
return (
<button
className={style['embla__button']}
type="button"
{...restProps}
>
<svg className={style['embla__button__svg']} viewBox="0 0 532 532">
<path
fill="currentColor"
d="M355.66 11.354c13.793-13.805 36.208-13.805 50.001 0 13.785 13.804 13.785 36.238 0 50.034L201.22 266l204.442 204.61c13.785 13.805 13.785 36.239 0 50.044-13.793 13.796-36.208 13.796-50.002 0a5994246.277 5994246.277 0 0 0-229.332-229.454 35.065 35.065 0 0 1-10.326-25.126c0-9.2 3.393-18.26 10.326-25.2C172.192 194.973 332.731 34.31 355.66 11.354Z"
/>
</svg>
{children}
</button>
)
}
export const NextButton = (props: any) => {
const { children, ...restProps } = props
return (
<button
className={style['embla__button']}
type="button"
{...restProps}
>
<svg className={style['embla__button__svg']} viewBox="0 0 532 532">
<path
fill="currentColor"
d="M176.34 520.646c-13.793 13.805-36.208 13.805-50.001 0-13.785-13.804-13.785-36.238 0-50.034L330.78 266 126.34 61.391c-13.785-13.805-13.785-36.239 0-50.044 13.793-13.796 36.208-13.796 50.002 0 22.928 22.947 206.395 206.507 229.332 229.454a35.065 35.065 0 0 1 10.326 25.126c0 9.2-3.393 18.26-10.326 25.2-45.865 45.901-206.404 206.564-229.332 229.52Z"
/>
</svg>
{children}
</button>
)
}
@@ -1,46 +0,0 @@
import React, { useCallback, useEffect, useState } from 'react'
export const useDotButton = (emblaApi: any) => {
const [selectedIndex, setSelectedIndex] = useState(0)
const [scrollSnaps, setScrollSnaps] = useState([])
const onDotButtonClick = useCallback(
(index: number) => {
if (!emblaApi) return
emblaApi.scrollTo(index)
},
[emblaApi]
)
const onInit = useCallback((emblaApi: any) => {
setScrollSnaps(emblaApi.scrollSnapList())
}, [])
const onSelect = useCallback((emblaApi: any) => {
setSelectedIndex(emblaApi.selectedScrollSnap())
}, [])
useEffect(() => {
if (!emblaApi) return
onInit(emblaApi)
onSelect(emblaApi)
emblaApi.on('reInit', onInit).on('reInit', onSelect).on('select', onSelect)
}, [emblaApi, onInit, onSelect])
return {
selectedIndex,
scrollSnaps,
onDotButtonClick
}
}
export const DotButton = (props: any) => {
const { children, ...restProps } = props
return (
<button type="button" {...restProps}>
{children}
</button>
)
}
@@ -1,139 +0,0 @@
.embla {
width: 100%;
height: 100%;
position: relative;
margin: auto;
--slide-height: 19rem;
--slide-spacing: 1rem;
--slide-size: 100%;
}
.embla__viewport {
height: 100%;
overflow: hidden;
}
.embla__container {
display: flex;
height: 100%;
touch-action: pan-y pinch-zoom;
margin-left: calc(var(--slide-spacing) * -1);
--slide-spacing: 1rem;
}
.embla__slide {
--slide-size: 100%;
--slide-spacing: 1rem;
transform: translate3d(0, 0, 0);
flex: 0 0 var(--slide-size);
min-width: 0;
padding-left: var(--slide-spacing);
}
.embla__slide__number {
height: 100%;
font-size: 4rem;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
.embla__slide__image {
width: 100%;
height: 100%;
object-fit: cover;
}
.embla__controls {
display: grid;
position: absolute;
top: 0;
z-index: 1000;
width: 100%;
height: 100%;
grid-template-columns: auto 1fr;
justify-content: space-between;
gap: 1.2rem;
}
.embla__buttons {
position: absolute;
top: 45%;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
}
.embla__button {
--text-high-contrast-rgb-value: 49, 49, 49;
--detail-high-contrast: rgb(192, 192, 192);
--text-body: rgb(54, 49, 61);
-webkit-tap-highlight-color: rgba(var(--text-high-contrast-rgb-value), 0.5);
-webkit-appearance: none;
appearance: none;
background-color: transparent;
touch-action: manipulation;
text-decoration: none;
cursor: pointer;
border: 0;
padding: 0;
margin: 0;
width: 3.6rem;
height: 3.6rem;
z-index: 1001;
border-radius: 50%;
color: var(--text-body);
display: flex;
align-items: center;
justify-content: center;
}
.embla__button:disabled {
--detail-high-contrast: rgb(192, 192, 192);
color: var(--detail-high-contrast);
}
.embla__button__svg {
width: 35%;
height: 35%;
}
.embla__dots {
display: flex;
position: absolute;
bottom: 0;
left: 48%;
flex-wrap: wrap;
justify-content: flex-end;
align-items: center;
z-index: 1000;
}
.embla__dot {
--text-high-contrast-rgb-value: 49, 49, 49;
--text-body: rgb(54, 49, 61);
-webkit-tap-highlight-color: rgba(var(--text-high-contrast-rgb-value), 0.5);
-webkit-appearance: none;
appearance: none;
background-color: transparent;
touch-action: manipulation;
text-decoration: none;
cursor: pointer;
border: 0;
padding: 0;
margin: 0;
width: 1.6rem;
height: 1.6rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.embla__dot:after {
--detail-medium-contrast: rgb(234, 234, 234);
--detail-medium-contrast-rgb-value: 234, 234, 234;
box-shadow: inset 0 0 0 0.2rem var(--detail-medium-contrast);
width: 0.42rem;
height: 0.42rem;
border-radius: 50%;
display: flex;
align-items: center;
content: "";
}
.embla__dot--selected:after {
--text-body: black;
box-shadow: inset 0 0 0 0.2rem var(--text-body);
}
@@ -1,92 +0,0 @@
"use client"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import styles from "./index.module.css"
import useEmblaCarousel from "embla-carousel-react"
import Autoplay from "embla-carousel-autoplay"
import { useMemo } from "react"
import { DotButton, useDotButton } from "./carousel-dot-button"
import { NextButton, PrevButton, usePrevNextButtons } from "./carousel-arrow-button"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
export function VtCarousel({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
const { options } = props as any
const images: string[] = props.images ?? props.slides ?? []
const links: (string | undefined)[] = props.links ?? []
const durationSeconds: number = props.duration ?? 4
const showControls = images.length > 1
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 { selectedIndex, scrollSnaps, onDotButtonClick } =
useDotButton(emblaApi)
const {
prevBtnDisabled,
nextBtnDisabled,
onPrevButtonClick,
onNextButtonClick,
} = usePrevNextButtons(emblaApi)
return (
<section className={styles["embla"]}>
<div className={styles["embla__viewport"]} ref={emblaRef}>
<div className={styles["embla__container"]}>
{images && images.map((src: string, index: number) => (
<div className={styles["embla__slide"]} key={index + src}>
<div className={styles["embla__slide__number"]}>
{links[index] ? (
<LocalizedClientLink href={links[index]}>
<img src={src} alt={`slide-${index + 1}`} className={styles["embla__slide__image"]} />
</LocalizedClientLink>
) : (
<img src={src} alt={`slide-${index + 1}`} className={styles["embla__slide__image"]} />
)}
</div>
</div>
))}
</div>
</div>
{showControls && (
<div className={styles["embla__controls"]}>
<div className={styles["embla__buttons"]}>
<PrevButton onClick={onPrevButtonClick} disabled={prevBtnDisabled} />
<NextButton onClick={onNextButtonClick} disabled={nextBtnDisabled} />
</div>
<div className={styles["embla__dots"]}>
{scrollSnaps.map((_, index) => (
<DotButton
key={index}
onClick={() => onDotButtonClick(index)}
className={[
styles["embla__dot"],
index === selectedIndex ? styles["embla__dot--selected"] : "",
].filter(Boolean).join(" ")}
/>
))}
</div>
</div>
)}
</section>
)
}
@@ -23,6 +23,7 @@ export default function VtCountrySelectClient({
const triggerText = props?.trigger?.text
const [items, setItems] = useState<{ text: string; label?: string }[]>([])
const { countryCode } = useParams()
console.log(regions)
useEffect(() => {
if (!regions || regions.length === 0) {
setItems([])
@@ -38,6 +39,7 @@ export default function VtCountrySelectClient({
.flat()
.filter((o) => o.text)
.sort((a, b) => (a.label ?? "").localeCompare(b.label ?? ""))
console.log(opts)
setItems(opts)
}, [regions])
@@ -1,68 +0,0 @@
"use client"
import { Button, clx } from "@medusajs/ui"
import { ChevronRightMini } from "@medusajs/icons"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
export function DefaultCtaBanner({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
return (
<div
className={clx(
"relative w-[544px] bg-white rounded-[24px] border border-[#E6EFFC] shadow-[0_12px_40px_rgba(17,49,78,0.10)] p-6",
props.className
)}
>
{props.tagText && (
<div
className={clx(
"inline-flex items-center rounded-full bg-[#FCE9DA] text-[#E68445] px-3 py-1 text-sm font-medium",
props.tagTextClassName
)}
>
{props.tagText}
</div>
)}
{props.titleText && (
<h1
className={clx(
"mt-4 text-[#11314E] font-semibold leading-normal text-[56px]",
props.titleTextClassName
)}
>
{props.titleText}
</h1>
)}
{props.descriptionText && (
<p
className={clx(
"mt-5 text-[#285A86] text-[16px] sm:text-xl opacity-80",
props.descriptionTextClassName
)}
>
{props.descriptionText}
</p>
)}
<Button
className={clx(
"mt-8 inline-flex items-center gap-2 bg-[#0F2740] hover:bg-[#173551] text-white px-6 py-3 rounded-[12px] shadow-md",
props.buttonTextClassName
)}
>
{props.buttonText}
<ChevronRightMini />
</Button>
</div>
)
}
@@ -1,25 +0,0 @@
"use client"
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import { DefaultCtaBanner } from "./default-cta"
export function VtCtaBanner({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config ?? {}
const variant = props.variant ?? "default"
const variants: Record<string, any> = {
default: DefaultCtaBanner,
}
const Component = variants[variant] || DefaultCtaBanner
return <Component nodes={nodes} context={context} />
}
@@ -18,7 +18,7 @@ export default async function PreviewPrice({ price }: { price: VariantPrice }) {
)}
<Text
className={clx("text-ui-fg-muted", {
"text-green-700": price.price_type === "sale",
"text-ui-fg-interactive": price.price_type === "sale",
})}
data-testid="price"
>
@@ -1,225 +0,0 @@
import { HttpTypes } from "@medusajs/types"
import { Button, Heading, Text, clx } from "@medusajs/ui"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import Divider from "@modules/common/components/divider"
import PreviewPrice from "@modules/products/components/product-preview/price"
import { getProductPrice } from "@lib/util/get-product-price"
import { addToCart } from "@lib/data/cart"
import VtThumbnail from "../vt-thumbnail"
import { Plus, ChevronRight } from "@medusajs/icons"
type ProductCardProps = {
product: HttpTypes.StoreProduct
badgeText?: string
deliveryTime?: string
className?: string
countryCode: string
styles?: any
}
export default function ProductCard({
product,
badgeText = "Saved up to 20%",
deliveryTime = "2-4 Wochen",
className,
countryCode,
styles,
}: ProductCardProps) {
const firstVariant = product.variants?.[0]
const inStock = (() => {
if (!firstVariant) return false
if (!firstVariant.manage_inventory) return true
if (firstVariant.allow_backorder) return true
return (firstVariant.inventory_quantity || 0) > 0
})()
console.dir(product, { depth: null })
const { cheapestPrice } = getProductPrice({ product })
async function handleAddToCart() {
"use server"
if (!firstVariant?.id) return
await addToCart({
variantId: firstVariant.id,
quantity: 1,
countryCode,
})
}
const description = (() => {
const prodDescription = product.description || ""
const textSlice =
prodDescription.length > 120
? prodDescription.slice(0, 117) + "…"
: prodDescription
return textSlice
})()
const classes = {
card: styles?.card ?? className ?? "",
badge: {
container: styles?.badge?.container ?? " pb-4",
text:
styles?.badge?.text ??
"z-20 px-3 py-1 border-[0.5px] rounded bg-[#c9e0f5] txt-compact-small-plus shadow-borders-base text-[#285A86] ",
},
thumbnail: {
className: styles?.thumbnail?.className ?? "rounded-none h-[240px]",
size: styles?.thumbnail?.size ?? "full",
},
subtitle: styles?.subtitle ?? "",
content: styles?.content ?? "p-6 flex flex-col flex-1",
title: styles?.title ?? "",
price: styles?.price ?? "",
description: styles?.description ?? "",
reviews: {
container: styles?.reviews?.container ?? undefined,
stars: styles?.reviews?.stars ?? "flex gap-1",
star: styles?.reviews?.star ?? "text-[#C4622C] text-xl leading-none",
emptyStar:
styles?.reviews?.emptyStar ??
"text-[#C4622C] text-xl opacity-30 leading-none",
text: styles?.reviews?.text ?? "txt-small text-ui-fg-subtle",
rating: styles?.reviews?.rating,
count: styles?.reviews?.count,
},
button: {
addToCart: styles?.button?.addToCart ?? "",
moreInfo: styles?.button?.moreInfo ?? "",
isShowIcon: styles?.button?.isShowIcon ?? false,
},
}
return (
<div className={clx(classes.card)}>
<LocalizedClientLink
href={`/products/${product.handle}`}
className="block"
>
<div className="relative">
{badgeText && (
<div className={classes.badge.container}>
<span className={classes.badge.text}>{badgeText}</span>
</div>
)}
<VtThumbnail
thumbnail={product.thumbnail}
className={classes.thumbnail.className}
images={product.images}
size={classes.thumbnail.size}
isFeatured
/>
</div>
</LocalizedClientLink>
<div className={classes.content}>
{classes.subtitle && product.collection && (
<LocalizedClientLink
href={`/collections/${product.collection.handle}`}
className="txt-small text-ui-fg-muted hover:text-ui-fg-subtle"
>
{product.subtitle}
</LocalizedClientLink>
)}
<LocalizedClientLink
href={`/products/${product.handle}`}
className="block"
>
<Heading
level="h3"
className={classes.title}
data-testid="product-card-title"
>
{product.title}
</Heading>
</LocalizedClientLink>
{classes.price && (
<div className={classes.price}>
{cheapestPrice && <PreviewPrice price={cheapestPrice} />}
</div>
)}
{(classes.reviews.rating !== undefined ||
classes.reviews.count !== undefined) && (
<div
className={
classes.reviews.container || "mt-2 flex items-center gap-3"
}
>
<div className="relative inline-block">
<div className={classes.reviews.stars}>
{Array.from({ length: 5 }).map((_, i) => (
<span
key={`star-empty-${i}`}
className={classes.reviews.emptyStar}
>
</span>
))}
</div>
<div
className="absolute inset-0 overflow-hidden"
style={{
width: `${Math.max(
0,
Math.min(
100,
(((classes.reviews.rating as number) ?? 0) / 5) * 100
)
)}%`,
}}
>
<div className={classes.reviews.stars}>
{Array.from({ length: 5 }).map((_, i) => (
<span
key={`star-fills-${i}`}
className={classes.reviews.star}
>
</span>
))}
</div>
</div>
</div>
{typeof classes.reviews.count === "number" && (
<span className={classes.reviews.text}>
{classes.reviews.count} Reviews
</span>
)}
</div>
)}
{classes.description && (
<Text className={clx(classes.description, "txt-small my-4")}>
{description}
</Text>
)}
<div className="flex gap-3 mt-auto">
{classes.button?.addToCart && (
<Button
formAction={handleAddToCart}
disabled={!inStock}
variant="primary"
className={classes.button.addToCart}
>
Add to cart {classes.button.isShowIcon && <Plus />}
</Button>
)}
{classes.button?.moreInfo && (
<LocalizedClientLink
href={`/products/${product.handle}`}
className="flex-1"
>
<Button variant="secondary" className={classes.button.moreInfo}>
More Info {classes.button.isShowIcon && <ChevronRight />}
</Button>
</LocalizedClientLink>
)}
</div>
</div>
</div>
)
}
@@ -1,88 +0,0 @@
import { Container, clx } from "@medusajs/ui"
import Image from "next/image"
import React from "react"
import PlaceholderImage from "@modules/common/icons/placeholder-image"
type ThumbnailProps = {
thumbnail?: string | null
// TODO: Fix image typings
images?: any[] | null
size?: "small" | "medium" | "large" | "full" | "square"
isFeatured?: boolean
className?: string
"data-testid"?: string
}
const VtThumbnail: React.FC<ThumbnailProps> = ({
thumbnail,
images,
size = "small",
isFeatured,
className,
"data-testid": dataTestid,
}) => {
const imageUrls = images?.map((i: any) => i.url) || []
const initialImage = thumbnail || imageUrls?.[0]
let hoverImage: string | undefined = initialImage
if (imageUrls.length > 1) {
hoverImage = imageUrls.find((u) => u !== initialImage)
}
return (
<Container
className={clx(
"group relative w-full overflow-hidden p-4 bg-ui-bg-subtle shadow-elevation-card-rest group-hover:shadow-elevation-card-hover transition-shadow ease-in-out duration-150",
className,
{
"aspect-[11/14]": isFeatured,
"aspect-[9/16]": !isFeatured && size !== "square",
"aspect-[1/1]": size === "square",
"w-[180px]": size === "small",
"w-[290px]": size === "medium",
"w-[440px]": size === "large",
"w-full": size === "full",
}
)}
data-testid={dataTestid}
>
<ImageOrPlaceholder
image={initialImage}
size={size}
className="opacity-100 group-hover:opacity-0 transition-opacity duration-300"
/>
{hoverImage && (
<ImageOrPlaceholder
image={hoverImage}
size={size}
className="opacity-0 group-hover:opacity-100 transition-opacity duration-300"
/>
)}
</Container>
)
}
const ImageOrPlaceholder = ({
image,
size,
className,
}: Pick<ThumbnailProps, "size"> & { image?: string; className?: string }) => {
return image ? (
<Image
src={image}
alt="Thumbnail"
className={clx("absolute inset-0 object-cover object-center", className)}
draggable={false}
quality={50}
sizes="(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px"
fill
/>
) : (
<div className={clx("w-full h-full absolute inset-0 flex items-center justify-center", className)}>
<PlaceholderImage size={size === "small" ? 16 : 24} />
</div>
)
}
export default VtThumbnail
+1 -1
View File
@@ -113,4 +113,4 @@
}
[data-radix-popper-content-wrapper]{
z-index: 51 !important;
}
}
+19 -40
View File
@@ -23,57 +23,41 @@ import VtFooterBottom from "@modules/layout/templates/vt-footer/vt-footer-bottom
import VtLogo from "@modules/layout/templates/vt-logo"
import VtFooterSignUp from "@modules/layout/templates/vt-footer/vt-footer-signup"
import Hero from "@modules/layout/templates/hero"
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"
type ComponentConfig = Record<string, any>
type ComponentConfig = Record<string, any>;
export interface LayoutComponentDefinition {
config?: ComponentConfig
children?: LayoutComponentNode[]
}
export interface LayoutContext {
customer: any
cart: any
shippingOptions: any[]
contentChildren: React.ReactNode
countryCode?: string
region?: any
export interface LayoutContext {
customer: any;
cart: any;
shippingOptions: any[];
contentChildren: React.ReactNode;
}
export type ComponentRenderer = {
render: (
entry: LayoutComponentDefinition,
ctx: LayoutContext
) => React.ReactNode
render: (entry: LayoutComponentDefinition, ctx: LayoutContext) => React.ReactNode
}
// Utility methods
const configOnly = (
Component: React.ComponentType<any>
): ComponentRenderer => ({
render: (entry) => <Component {...entry.config} />,
const configOnly = (Component: React.ComponentType<any>): ComponentRenderer => ({
render: (entry) => <Component {...entry.config} />
})
const nodesContextRenderer = (
Component: React.ComponentType<any>
): ComponentRenderer => ({
render: (entry: any, ctx: LayoutContext) => (
<Component nodes={entry} context={ctx} />
),
})
const nodesContextRenderer = (Component: React.ComponentType<any>): ComponentRenderer => ({
render: (entry: any, ctx: LayoutContext) => <Component nodes={entry} context={ctx} />
});
const renderChildren = (entry: LayoutComponentDefinition, ctx: LayoutContext) =>
entry.children ? (
<DynamicLayoutRenderer nodes={entry.children} context={ctx} />
) : null
entry.children ? <DynamicLayoutRenderer nodes={entry.children} context={ctx} /> : null
// Component Map
export const componentMap: Record<string, ComponentRenderer> = {
Header: nodesContextRenderer(VtHeader),
Header: nodesContextRenderer(VtHeader),
Nav: nodesContextRenderer(VtNav),
Hero: nodesContextRenderer(Hero),
VtMegaMenu: nodesContextRenderer(VtMegaMenu),
@@ -93,22 +77,17 @@ export const componentMap: Record<string, ComponentRenderer> = {
CartMismatchBanner: configOnly(CartMismatchBanner),
FreeShippingPriceNudge: configOnly(FreeShippingPriceNudge),
PropsChildren: {
render: (_props, ctx) => ctx.contentChildren, // PageLayout's props.children
render: (_props, ctx) => ctx.contentChildren, // PageLayout's props.children
},
VtCtaBanner: nodesContextRenderer(VtCtaBanner),
VtFooterHero: nodesContextRenderer(VtFooterHero),
VtFooterBottom: nodesContextRenderer(VtFooterBottom),
VtFooterSignUp: nodesContextRenderer(VtFooterSignUp),
Footer: nodesContextRenderer(VtFooter),
ImageDisplayer: nodesContextRenderer(VtCarousel),
VtFeaturedProducts: nodesContextRenderer(VtFeaturedProducts),
VtCategoryHighlight: nodesContextRenderer(VtCategoryHighlight),
Footer: nodesContextRenderer(VtFooter)
}
export type ComponentName = keyof typeof componentMap
// //maps key = componentName to value = props + children
// export type LayoutComponentNode = Record<string, LayoutComponentDefinition>
export type LayoutComponentNode = {
[K in ComponentName]: LayoutComponentDefinition
}[ComponentName]
export type LayoutComponentNode = { [K in ComponentName]: LayoutComponentDefinition }[ComponentName]
+3 -20
View File
@@ -1,28 +1,11 @@
import fs from "fs"
import path from "path"
import { jsonFileNames } from "./devJsonFileNames"
import { jsonFileNames } from "./devJsonFileNames";
const fileName = jsonFileNames.nam3Bear
const fileName = jsonFileNames.namVibentec;
async function readDesignFile() {
export async function loadDesignConfig() {
const filePath = path.join(process.cwd(), "config", fileName)
const fileData = await fs.promises.readFile(filePath, "utf-8")
return JSON.parse(fileData)
}
export async function loadLayoutConfig() {
const config = await readDesignFile()
if (Array.isArray(config)) return config
return config.layout ?? []
}
export async function loadPageConfig(pageKey: string) {
const config = await readDesignFile()
if (Array.isArray(config)) return []
const pages = config.pages ?? {}
return pages[pageKey] ?? []
}
export async function loadDesignConfig() {
return loadLayoutConfig()
}
+3
View File
@@ -13,6 +13,9 @@ module.exports = {
],
theme: {
extend: {
backgroundImage: {
'custom-gradient': 'linear-gradient(90deg, #E6EFFC 0%, #285A86 100%)',
},
transitionProperty: {
width: "width margin",
height: "height",