Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f35fd8b09a | |||
| f21a4251e3 | |||
| a3c683e62f | |||
| b330582ad3 | |||
| 1bace8a023 |
+387
-293
@@ -1,177 +1,403 @@
|
||||
[
|
||||
{
|
||||
"Header": {
|
||||
"config": {
|
||||
"sticky": true,
|
||||
"variant": "ticker"
|
||||
},
|
||||
"AnnouncementBanner": {
|
||||
"props": {
|
||||
"label": [
|
||||
{
|
||||
"text": "Free shipping on orders over $100",
|
||||
"className": "font-medium"
|
||||
},
|
||||
{
|
||||
"text": ".",
|
||||
"className": ""
|
||||
},
|
||||
{
|
||||
"text": "Free gift with every purchase",
|
||||
"className": ""
|
||||
},
|
||||
{
|
||||
"text": ".",
|
||||
"className": ""
|
||||
},
|
||||
{
|
||||
"text": "Free returns",
|
||||
"className": ""
|
||||
}
|
||||
],
|
||||
"className": "sticky top-0 z-20 bg-[#009b93] text-white"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Nav": {
|
||||
"props": {},
|
||||
"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": "/"
|
||||
}
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full ml-16"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"Image": {
|
||||
"props": {
|
||||
"src": "https://3bears.de/cdn/shop/files/3Bears_Logo-Schutzzone_negativ_RGB.png?v=1676382997&width=335",
|
||||
"alt": "Medusa Store",
|
||||
"width": 160,
|
||||
"height": 40
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full gap-10"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"NavMenu": {
|
||||
"props": {
|
||||
"label": "Shop",
|
||||
"className": "",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"menuItems": [
|
||||
{
|
||||
"title": {
|
||||
"text": "Categories",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Overnight Oats",
|
||||
"href": "/categories/overnight-oats",
|
||||
"className": "text-red-500"
|
||||
},
|
||||
{
|
||||
"label": "Porridge",
|
||||
"href": "/categories/porridge"
|
||||
},
|
||||
{
|
||||
"label": "Cereals",
|
||||
"href": "/categories/cereals"
|
||||
},
|
||||
{
|
||||
"label": "Granola",
|
||||
"href": "/categories/granola"
|
||||
},
|
||||
{
|
||||
"label": "Glasses & Bowls",
|
||||
"href": "/categories/glasses-bowls"
|
||||
},
|
||||
{
|
||||
"label": "Oat Bars",
|
||||
"href": "/categories/oat-bars"
|
||||
},
|
||||
{
|
||||
"label": "Nut butters",
|
||||
"href": "/categories/nut-butters"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "Specials",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Advent calendar ✨",
|
||||
"href": "/collections/advent-calendar"
|
||||
},
|
||||
{
|
||||
"label": "Saver subscription",
|
||||
"href": "/collections/saver-subscription"
|
||||
},
|
||||
{
|
||||
"label": "bestseller",
|
||||
"href": "/collections/bestseller"
|
||||
},
|
||||
{
|
||||
"label": "New 🔥",
|
||||
"href": "/collections/new"
|
||||
},
|
||||
{
|
||||
"label": "Bluey Kidsrange",
|
||||
"href": "/collections/bluey-kidsrange"
|
||||
},
|
||||
{
|
||||
"label": "Value sets",
|
||||
"href": "/collections/value-sets"
|
||||
},
|
||||
{
|
||||
"label": "Sale",
|
||||
"href": "/collections/sale"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "All products",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Shop all",
|
||||
"href": "/store"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"NavMenu": {
|
||||
"props": {
|
||||
"label": "About us",
|
||||
"className": "",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"menuItems": [
|
||||
{
|
||||
"title": {
|
||||
"text": "About us",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Check",
|
||||
"href": "/categories/overnight-oats",
|
||||
"className": "text-red-500"
|
||||
},
|
||||
{
|
||||
"label": "Porridge",
|
||||
"href": "/categories/porridge"
|
||||
},
|
||||
{
|
||||
"label": "Cereals",
|
||||
"href": "/categories/cereals"
|
||||
},
|
||||
{
|
||||
"label": "Granola",
|
||||
"href": "/categories/granola"
|
||||
},
|
||||
{
|
||||
"label": "Glasses & Bowls",
|
||||
"href": "/categories/glasses-bowls"
|
||||
},
|
||||
{
|
||||
"label": "Oat Bars",
|
||||
"href": "/categories/oat-bars"
|
||||
},
|
||||
{
|
||||
"label": "Nut butters",
|
||||
"href": "/categories/nut-butters"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "Specials",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Advent calendar ✨",
|
||||
"href": "/collections/advent-calendar"
|
||||
},
|
||||
{
|
||||
"label": "Saver subscription",
|
||||
"href": "/collections/saver-subscription"
|
||||
},
|
||||
{
|
||||
"label": "bestseller",
|
||||
"href": "/collections/bestseller"
|
||||
},
|
||||
{
|
||||
"label": "New 🔥",
|
||||
"href": "/collections/new"
|
||||
},
|
||||
{
|
||||
"label": "Bluey Kidsrange",
|
||||
"href": "/collections/bluey-kidsrange"
|
||||
},
|
||||
{
|
||||
"label": "Value sets",
|
||||
"href": "/collections/value-sets"
|
||||
},
|
||||
{
|
||||
"label": "Sale",
|
||||
"href": "/collections/sale"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": {
|
||||
"text": "All products",
|
||||
"className": "text-[#003F31] text-xl font-bold"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"label": "Shop all",
|
||||
"href": "/store"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "About our product",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "All Categories",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/recipe",
|
||||
"label": "Recipes",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-register-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/meet-harry-kane",
|
||||
"label": "Meet Harry Kane",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-register-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center gap-x-6 h-full justify-end"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "Germany (EUR €)",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "All Categories",
|
||||
"className": "hover:text-ui-fg-base",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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]"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"SearchButton": {
|
||||
"props": {
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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
|
||||
},
|
||||
{
|
||||
"UserButton": {
|
||||
"props": {
|
||||
"className": "hover:text-ui-fg-base flex items-center gap-2",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Suspense": {
|
||||
"props": {
|
||||
"fallback": [
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/cart",
|
||||
"label": "Cart (0)",
|
||||
"className": "hover:text-ui-fg-base flex gap-2",
|
||||
"data-testid": "nav-cart-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"CartButton": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"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]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -179,16 +405,12 @@
|
||||
},
|
||||
{
|
||||
"CartMismatchBanner": {
|
||||
"config": {
|
||||
"show": true
|
||||
}
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"FreeShippingPriceNudge": {
|
||||
"config": {
|
||||
"variant": "popup"
|
||||
}
|
||||
"variant": "popup"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -196,135 +418,7 @@
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"copyrightText": "© 2025 MyShop"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+251
-280
@@ -1,130 +1,265 @@
|
||||
[
|
||||
{
|
||||
"Header": {
|
||||
"config": {
|
||||
"sticky": true
|
||||
"AnnouncementBanner": {
|
||||
"props": {
|
||||
"label": [
|
||||
{
|
||||
"text": "BLACK FRIDAY PRE-SALE",
|
||||
"className": " #b31b1f font-bold"
|
||||
},
|
||||
{
|
||||
"text": "FREE MYSTERY GIFT on $70",
|
||||
"className": ""
|
||||
},
|
||||
{
|
||||
"text": "SHOP NOW",
|
||||
"className": "font-bold text-sm underline"
|
||||
}
|
||||
],
|
||||
"className": "bg-[#b31b1f] text-white"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Nav": {
|
||||
"props": {
|
||||
"className": "bg-[#1f3621] text-white"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"Image": {
|
||||
"props": {
|
||||
"src": "https://www.drsquatch.com/cdn/shop/files/2drsq_horiz_logo_white_3_1.svg?v=1743539246",
|
||||
"alt": "Medusa Store",
|
||||
"width": 120,
|
||||
"height": 40,
|
||||
"className": "cursor-pointer ml-8"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full gap-16 font-bold"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "SUBCRIBE",
|
||||
"className": "hover:underline flex items-center gap-2 ml-16",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": false
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "All Categories",
|
||||
"className": "",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "REWARD",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-categories-link"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "All Categories",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:underline",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "SHOP",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": false
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "All Categories",
|
||||
"className": "hover:underline",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:underline",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:underline",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "OUR STORY",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": false
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "OUR STORY",
|
||||
"className": "hover:underline",
|
||||
"data-testid": "nav-all-categories-link",
|
||||
"isShowArrow": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "New Arrivals",
|
||||
"className": "hover:underline",
|
||||
"data-testid": "nav-new-arrivals-link",
|
||||
"isShowArrow": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "Best Sellers",
|
||||
"className": "hover:underline",
|
||||
"data-testid": "nav-best-sellers-link",
|
||||
"isShowArrow": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center gap-x-6 h-full justify-end"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"SearchButton": {
|
||||
"props": {
|
||||
"className": "hover:underline hover:text-black flex items-center gap-2 text-white",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"UserButton": {
|
||||
"props": {
|
||||
"className": "hover:underline hover:text-black flex items-center gap-2 text-white",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Suspense": {
|
||||
"props": {
|
||||
"fallback": [
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/cart",
|
||||
"label": "Cart (0)",
|
||||
"className": "hover:underline flex gap-2",
|
||||
"data-testid": "nav-cart-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"CartButton": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -132,16 +267,12 @@
|
||||
},
|
||||
{
|
||||
"CartMismatchBanner": {
|
||||
"config": {
|
||||
"show": true
|
||||
}
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"FreeShippingPriceNudge": {
|
||||
"config": {
|
||||
"variant": "popup"
|
||||
}
|
||||
"variant": "popup"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -149,167 +280,7 @@
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
"copyrightText": "© 2025 MyShop"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"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",
|
||||
"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": "/"
|
||||
},
|
||||
{
|
||||
"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]"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
+250
-309
@@ -1,202 +1,282 @@
|
||||
[
|
||||
{
|
||||
"Header": {
|
||||
"config": {
|
||||
"sticky": true
|
||||
"AnnouncementBanner": {
|
||||
"props": {
|
||||
"className": "bg-[#E6EFFC] text-[#285A86] flex items-center text-xs"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full gap-8 "
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"Text": {
|
||||
"props": {
|
||||
"className": "text-sm font-medium",
|
||||
"label": "Über Uns"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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] "
|
||||
}
|
||||
},
|
||||
{
|
||||
"Text": {
|
||||
"props": {
|
||||
"className": "text-sm font-medium",
|
||||
"label": "Kontaktieren Uns"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full gap-6"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"Text": {
|
||||
"props": {
|
||||
"className": "text-sm font-medium",
|
||||
"label": "Einsparung durch Digitalisierung in der Arztpraxis"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Button": {
|
||||
"props": {
|
||||
"className": "text-xs font-medium",
|
||||
"label": "Mehr Info"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full gap-4"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "EURO",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "USD",
|
||||
"className": "",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "VND",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "EURO",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenus": {
|
||||
"props": {
|
||||
"label": "DE",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-categories-link",
|
||||
"isShowArrow": true
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "EN",
|
||||
"className": "",
|
||||
"data-testid": "nav-all-categories-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "KR",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-new-arrivals-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"DropdownMenuItems": {
|
||||
"props": {
|
||||
"label": "DE",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-best-sellers-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Hero": {
|
||||
"config": {
|
||||
"variant": "vibentec",
|
||||
"text": "alo",
|
||||
"className": "bg-custom-gradient"
|
||||
}
|
||||
"Nav": {
|
||||
"props": {
|
||||
"className": "bg-white text-black text-sm pr-16"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"Image": {
|
||||
"props": {
|
||||
"src": "/VibentecIT-logo.svg",
|
||||
"alt": "Medusa Store",
|
||||
"width": 150,
|
||||
"height": 80,
|
||||
"className": "cursor-pointer ml-14"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center h-full gap-8 ml-16"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/",
|
||||
"label": "Home",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-home-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/collections/all",
|
||||
"label": "Shop",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-collections-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/pages/about-us",
|
||||
"label": "Sale",
|
||||
"className": "hover:underline flex items-center gap-2",
|
||||
"data-testid": "nav-about-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Div": {
|
||||
"props": {
|
||||
"className": "flex items-center gap-x-6 h-full justify-end"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"InputSearchButton": {
|
||||
"props": {
|
||||
"className": "flex items-center",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"UserButton": {
|
||||
"props": {
|
||||
"className": "hover:underline hover:text-black flex items-center",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"FavoriteButton": {
|
||||
"props": {
|
||||
"className": "hover:underline hover:text-black flex items-center",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Suspense": {
|
||||
"props": {
|
||||
"fallback": [
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/cart",
|
||||
"label": "Cart (0)",
|
||||
"className": "hover:underline flex gap-2",
|
||||
"data-testid": "nav-cart-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"CartButton": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"CartMismatchBanner": {
|
||||
"config": {
|
||||
"show": true
|
||||
}
|
||||
"show": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"FreeShippingPriceNudge": {
|
||||
"config": {
|
||||
"variant": "popup"
|
||||
}
|
||||
"variant": "popup"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -204,146 +284,7 @@
|
||||
},
|
||||
{
|
||||
"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": "/"
|
||||
},
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"copyrightText": "© 2025 MyShop"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,35 +1,68 @@
|
||||
[
|
||||
{
|
||||
"Header" : {
|
||||
"config" : {"sticky": true},
|
||||
"children" : [
|
||||
{
|
||||
"Nav": {
|
||||
"props": {},
|
||||
"children": [
|
||||
{
|
||||
"Nav": {
|
||||
"config": {
|
||||
"left": [
|
||||
{ "VtSideMenu": {} }
|
||||
],
|
||||
"center": [
|
||||
{ "HomeButton": { "config" : {"label":"Medusa Store"}} }
|
||||
],
|
||||
"right": [
|
||||
{ "AccountButton": {
|
||||
"config": {
|
||||
"label": "Account",
|
||||
"className": "hover:text-ui-fg-base"
|
||||
}
|
||||
"Div": {
|
||||
"props": { "className": "flex items-center h-full" },
|
||||
"children": [
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/",
|
||||
"label": "Medusa Store",
|
||||
"className": "bg-black txt-compact-xlarge-plus hover:text-ui-fg-base uppercase",
|
||||
"data-testid": "nav-store-link"
|
||||
}
|
||||
},
|
||||
{ "VtCartButton" : {} }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Div": {
|
||||
"props": { "className": "flex items-center gap-x-6 h-full flex-1 basis-0 justify-end" },
|
||||
"children": [
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/account",
|
||||
"label": "Account",
|
||||
"className": "hover:text-ui-fg-base bg-black",
|
||||
"data-testid": "nav-account-link"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Suspense": {
|
||||
"props": {
|
||||
"fallback": [
|
||||
{
|
||||
"LocalizedClientLink": {
|
||||
"props": {
|
||||
"href": "/cart",
|
||||
"label": "Cart (0)",
|
||||
"className": "bg-black hover:text-ui-fg-base flex gap-2",
|
||||
"data-testid": "nav-cart-link"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{ "CartButton": {} }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "CartMismatchBanner": { "config": {"show": true} } },
|
||||
{ "FreeShippingPriceNudge": { "config": {"variant": "popup" }} },
|
||||
{ "CartMismatchBanner": { "show": true } },
|
||||
{ "FreeShippingPriceNudge": { "variant": "popup" } },
|
||||
{ "PropsChildren" : {}},
|
||||
{ "Footer": { "config" : {"copyrightText": "© 2025 MyShop"} } }
|
||||
{ "Footer": { "copyrightText": "© 2025 MyShop" } }
|
||||
]
|
||||
@@ -1,70 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Header" : {
|
||||
"config" : {"sticky": true},
|
||||
"children" : [
|
||||
{
|
||||
"Banner": {
|
||||
"config": {
|
||||
"variant": "nav",
|
||||
"className": "h-12 bg-[#E6EFFC] text-[#285A86]",
|
||||
"left":[ { "Link": { "config" : {"label":"About us", "href":"/vt-about"}} }],
|
||||
"center":[ { "Link": { "config" : {"label":"Contact", "href":"/vt-contact"}} }],
|
||||
"right":[ { "HomeButton": { "config" : {"label":"Vibentec IT"}} }]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Banner": {
|
||||
"config": {
|
||||
"variant": "ticker",
|
||||
"speed":24,
|
||||
"className": "h-12 bg-[#E6EFFC] text-[#285A86]",
|
||||
"items":[ {
|
||||
"Link": { "config" : {"label":"Vibentec IT"}}
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Banner": {
|
||||
"config": {
|
||||
"variant": "cta",
|
||||
"text": "**Black Friday Vorverkauf**\nKOSTENLOSES MISTERY GESCHENK ab 60€ <u>jetzt shoppen</u>",
|
||||
"href" : "/",
|
||||
"className": "h-12 bg-[#E6EFFC] text-[#285A86]"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Nav": {
|
||||
"config": {
|
||||
"left": [
|
||||
{ "VtMegaMenu": {} } ,
|
||||
{ "HomeButton": { "config" : {"label":"Vibentec IT"}} }
|
||||
],
|
||||
"center": [
|
||||
{ "AccountButton": {
|
||||
"config": {
|
||||
"label": "Accounto",
|
||||
"bgColor": "#123456",
|
||||
"textColor": "#abcdef",
|
||||
"className": "hover:text-ui-fg-base"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"right": [
|
||||
{ "VtCartButton" : {} }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{ "CartMismatchBanner": { "config": {"show": true} } },
|
||||
{ "FreeShippingPriceNudge": { "config": {"variant": "popup" }} },
|
||||
{ "PropsChildren" : {}},
|
||||
{ "Footer": { "config" : {"copyrightText": "© 2025 MyShop"} } }
|
||||
]
|
||||
Generated
+5
-1411
File diff suppressed because it is too large
Load Diff
@@ -28,9 +28,6 @@
|
||||
"react": "19.0.0-rc-66855b96-20241106",
|
||||
"react-country-flag": "^3.1.0",
|
||||
"react-dom": "19.0.0-rc-66855b96-20241106",
|
||||
"react-markdown": "^10.1.0",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"server-only": "^0.0.1",
|
||||
"tailwindcss-radix": "^2.8.0",
|
||||
"webpack": "^5"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 393 KiB |
@@ -0,0 +1,18 @@
|
||||
export const DESIGN_JSON_FILE = [
|
||||
{
|
||||
id: "drsquatch",
|
||||
file: "nam.drsquatch.design.json",
|
||||
},
|
||||
{
|
||||
id: "3bear",
|
||||
file: "nam.3bear.design.json",
|
||||
},
|
||||
{
|
||||
id: "vibentec",
|
||||
file: "nam.vibentec.design.json",
|
||||
},
|
||||
{
|
||||
id: "medusa-starter",
|
||||
file: "ste.medusa-starter.design.json",
|
||||
}
|
||||
]
|
||||
@@ -4,15 +4,17 @@ import { listCartOptions, retrieveCart } from "@lib/data/cart"
|
||||
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 { LayoutContext, LayoutComponentNode } from "vibentec/component-map"
|
||||
import { DynamicLayoutRenderer } from "vibentec/renderer"
|
||||
import { loadDesignConfig } from "vibentec/configloader"
|
||||
import { DESIGN_JSON_FILE } from "./config-json-file"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(getBaseURL()),
|
||||
}
|
||||
|
||||
export default async function PageLayout(props: { children: React.ReactNode }) {
|
||||
// Choose which design JSON to load. Swap this constant as needed.
|
||||
const customer = await retrieveCustomer()
|
||||
const cart = await retrieveCart()
|
||||
let shippingOptions: StoreCartShippingOption[] = []
|
||||
@@ -23,14 +25,16 @@ export default async function PageLayout(props: { children: React.ReactNode }) {
|
||||
shippingOptions = shipping_options
|
||||
}
|
||||
|
||||
const nodes: LayoutComponentNode[] = await loadDesignConfig()
|
||||
const nodes: LayoutComponentNode[] = await loadDesignConfig(
|
||||
DESIGN_JSON_FILE[1].file
|
||||
)
|
||||
const context: LayoutContext = {
|
||||
customer,
|
||||
cart,
|
||||
shippingOptions,
|
||||
contentChildren: props.children,
|
||||
designId: DESIGN_JSON_FILE[1].id,
|
||||
}
|
||||
|
||||
|
||||
return <DynamicLayoutRenderer nodes={nodes} context={context} />
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export default async function Home(props: {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <Hero /> */}
|
||||
<Hero />
|
||||
<div className="py-12">
|
||||
<ul className="flex flex-col gap-x-6">
|
||||
<FeaturedProducts collections={collections} region={region} />
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<div className="content-container py-16">
|
||||
<h1 className="text-3xl font-bold mb-4">Über uns</h1>
|
||||
<p>
|
||||
Willkommen bei Vibentec IT! Wir bieten maßgeschneiderte Softwarelösungen.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export default function ContactPage() {
|
||||
return (
|
||||
<div className="content-container py-16">
|
||||
<h1 className="text-3xl font-bold mb-4">Kontakt</h1>
|
||||
<p>
|
||||
Schreibe uns: <a href="mailto:info@vibentec-it.de">info@vibentec-it.de</a>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export { default as Back } from "./back"
|
||||
export { default as Bancontact } from "./bancontact"
|
||||
export { default as ChevronDown } from "./chevron-down"
|
||||
export { default as Eye } from "./eye"
|
||||
export { default as EyeOff } from "./eye-off"
|
||||
export { default as FastDelivery } from "./fast-delivery"
|
||||
export { default as Ideal } from "./ideal"
|
||||
export { default as MapPin } from "./map-pin"
|
||||
export { default as Medusa } from "./medusa"
|
||||
export { default as Nextjs } from "./nextjs"
|
||||
export { default as Package } from "./package"
|
||||
export { default as PayPal } from "./paypal"
|
||||
export { default as PlaceholderImage } from "./placeholder-image"
|
||||
export { default as Refresh } from "./refresh"
|
||||
export { default as Spinner } from "./spinner"
|
||||
export { default as Trash } from "./trash"
|
||||
export { default as User } from "./user"
|
||||
export { default as X } from "./x"
|
||||
export { default as Twitter } from "./twitter"
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react"
|
||||
|
||||
import { IconProps } from "types/icon"
|
||||
|
||||
const Twitter: React.FC<IconProps> = ({
|
||||
size = "20",
|
||||
color = "currentColor",
|
||||
...attributes
|
||||
}) => {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 15 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...attributes}
|
||||
>
|
||||
<path
|
||||
d="M12.8069 2.87388C12.8069 3.01339 12.8069 3.125 12.8069 3.26451C12.8069 7.14286 9.8772 11.5792 4.49215 11.5792C2.81805 11.5792 1.28345 11.1049 -3.52859e-05 10.2679C0.223179 10.2958 0.446393 10.3237 0.697509 10.3237C2.0647 10.3237 3.32028 9.84933 4.32474 9.06808C3.04126 9.04018 1.95309 8.20312 1.59037 7.03125C1.78568 7.05915 1.95309 7.08705 2.1484 7.08705C2.39952 7.08705 2.67854 7.03125 2.90175 6.97545C1.56246 6.69643 0.558 5.52455 0.558 4.10156V4.07366C0.948625 4.29687 1.42296 4.40848 1.89729 4.43638C1.08813 3.90625 0.585902 3.01339 0.585902 2.00893C0.585902 1.45089 0.725411 0.94866 0.976527 0.530133C2.42742 2.28795 4.60376 3.45982 7.03122 3.59933C6.97541 3.37612 6.94751 3.1529 6.94751 2.92969C6.94751 1.31138 8.25889 -6.37025e-07 9.8772 -6.37025e-07C10.7143 -6.37025e-07 11.4676 0.334821 12.0256 0.920758C12.6674 0.781249 13.3091 0.530133 13.8672 0.195312C13.6439 0.892857 13.1975 1.45089 12.5837 1.81362C13.1696 1.75781 13.7555 1.5904 14.2578 1.36719C13.8672 1.95312 13.3649 2.45536 12.8069 2.87388Z"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default Twitter
|
||||
@@ -1,8 +1,8 @@
|
||||
import { retrieveCart } from "@lib/data/cart"
|
||||
import CartDropdown from "../cart-dropdown"
|
||||
|
||||
export default async function CartButton({ iconName }: { iconName?: string }) {
|
||||
export default async function CartButton() {
|
||||
const cart = await retrieveCart().catch(() => null)
|
||||
|
||||
return <CartDropdown cart={cart} iconName={iconName} />
|
||||
return <CartDropdown cart={cart} />
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import {
|
||||
PopoverPanel,
|
||||
Transition,
|
||||
} from "@headlessui/react"
|
||||
import { ShoppingBag } from "@medusajs/icons"
|
||||
import { convertToLocale } from "@lib/util/money"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import * as MedusaIcons from "@medusajs/icons"
|
||||
import DeleteButton from "@modules/common/components/delete-button"
|
||||
import LineItemOptions from "@modules/common/components/line-item-options"
|
||||
import LineItemPrice from "@modules/common/components/line-item-price"
|
||||
@@ -20,10 +20,8 @@ import { Fragment, useEffect, useRef, useState } from "react"
|
||||
|
||||
const CartDropdown = ({
|
||||
cart: cartState,
|
||||
iconName,
|
||||
}: {
|
||||
cart?: HttpTypes.StoreCart | null
|
||||
iconName?: string
|
||||
}) => {
|
||||
const [activeTimer, setActiveTimer] = useState<NodeJS.Timer | undefined>(
|
||||
undefined
|
||||
@@ -76,10 +74,6 @@ const CartDropdown = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [totalItems, itemRef.current])
|
||||
|
||||
const Icon = iconName
|
||||
? (MedusaIcons as Record<string, React.ComponentType<any>>)[iconName]
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-full z-50"
|
||||
@@ -88,15 +82,13 @@ const CartDropdown = ({
|
||||
>
|
||||
<Popover className="relative h-full">
|
||||
<PopoverButton className="h-full">
|
||||
{Icon ? (
|
||||
<Icon />
|
||||
) : (
|
||||
<LocalizedClientLink
|
||||
className="hover:text-ui-fg-base"
|
||||
href="/cart"
|
||||
data-testid="nav-cart-link"
|
||||
>{`Cart (${totalItems})`}</LocalizedClientLink>
|
||||
)}
|
||||
<LocalizedClientLink
|
||||
className="hover:text-ui-fg-base mr-10 flex items-center"
|
||||
href="/cart"
|
||||
data-testid="nav-cart-link"
|
||||
>
|
||||
<ShoppingBag />
|
||||
</LocalizedClientLink>
|
||||
</PopoverButton>
|
||||
<Transition
|
||||
show={cartDropdownOpen}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
"use client"
|
||||
import * as React from "react"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { ChevronDownMini } from "@medusajs/icons"
|
||||
|
||||
interface NavigationMenuProps {
|
||||
props: {
|
||||
label: string,
|
||||
className: string,
|
||||
"data-testid": string,
|
||||
isShowArrow: boolean
|
||||
},
|
||||
menuItems: MenuSection[]
|
||||
}
|
||||
type MenuSection = {
|
||||
title: { text: string; className?: string }
|
||||
links: MenuLink[]
|
||||
}
|
||||
type MenuLink = { label: string; href: string; className?: string }
|
||||
|
||||
// Structured menu data used to map UI
|
||||
const menuSections: MenuSection[] = [
|
||||
{
|
||||
title: { text: "Categories", className: "text-red" },
|
||||
links: [
|
||||
{
|
||||
label: "Overnight Oats",
|
||||
href: "/categories/overnight-oats",
|
||||
className: "text-red",
|
||||
},
|
||||
{ label: "Porridge", href: "/categories/porridge" },
|
||||
{ label: "Cereals", href: "/categories/cereals" },
|
||||
{ label: "Granola", href: "/categories/granola" },
|
||||
{ label: "Glasses & Bowls", href: "/categories/glasses-bowls" },
|
||||
{ label: "Oat Bars", href: "/categories/oat-bars" },
|
||||
{ label: "Nut butters", href: "/categories/nut-butters" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: { text: "Specials" },
|
||||
links: [
|
||||
{ label: "Advent calendar ✨", href: "/collections/advent-calendar" },
|
||||
{ label: "Saver subscription", href: "/collections/saver-subscription" },
|
||||
{ label: "bestseller", href: "/collections/bestseller" },
|
||||
{ label: "New 🔥", href: "/collections/new" },
|
||||
{ label: "Bluey Kidsrange", href: "/collections/bluey-kidsrange" },
|
||||
{ label: "Value sets", href: "/collections/value-sets" },
|
||||
{ label: "Sale", href: "/collections/sale" },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: { text: "All products" },
|
||||
links: [{ label: "Shop all", href: "/store" }],
|
||||
},
|
||||
]
|
||||
|
||||
const MenuLinkItem = ({ href, label, className }: MenuLink) => (
|
||||
<li key={label}>
|
||||
<LocalizedClientLink
|
||||
href={href}
|
||||
className={clx(
|
||||
"block rounded-md py-2 hover:bg-ui-bg-subtle ",
|
||||
className ?? ""
|
||||
)}
|
||||
role="menuitem"
|
||||
>
|
||||
<div className="txt-small text-ui-fg-muted">{label}</div>
|
||||
</LocalizedClientLink>
|
||||
</li>
|
||||
)
|
||||
|
||||
const MenuSectionItem = ({ title, links }: MenuSection) => (
|
||||
<div key={title.text} className="space-y-1">
|
||||
<div className={clx("txt-small text-ui-fg-muted", title.className ?? "")}>
|
||||
{title.text}
|
||||
</div>
|
||||
<ul className="space-y-1">
|
||||
{links.map((link) => (
|
||||
<MenuLinkItem
|
||||
key={link.label}
|
||||
href={link.href}
|
||||
label={link.label}
|
||||
className={link.className ?? ""}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
export default function NavigationMenu({
|
||||
props,
|
||||
menuItems = menuSections,
|
||||
}: NavigationMenuProps) {
|
||||
return (
|
||||
<div className="group relative h-full flex items-center">
|
||||
<LocalizedClientLink
|
||||
href="/"
|
||||
className="txt-compact-xlarge-plus text-ui-fg-subtle hover:text-ui-fg-base group-hover:text-ui-fg-base flex items-center gap-2 transition-colors duration-200"
|
||||
>
|
||||
{props.label} {props?.isShowArrow ? (
|
||||
<span className="transition-transform duration-400 ease-out group-hover:rotate-180">
|
||||
<ChevronDownMini />
|
||||
</span>
|
||||
) : null}
|
||||
</LocalizedClientLink>
|
||||
|
||||
<div
|
||||
role="menu"
|
||||
className="pointer-events-none left-0 fixed top-[180px] invisible opacity-0 transform-gpu translate-y-2 transition-all duration-600 ease-out group-hover:visible group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto group-focus-within:visible group-focus-within:opacity-100 group-focus-within:translate-y-0 group-focus-within:pointer-events-auto"
|
||||
>
|
||||
<div className="w-[100vw] bg-white shadow-borders-base py-4 px-20">
|
||||
<div className="grid grid-cols-1 small:grid-cols-3 gap-6">
|
||||
{menuItems.map((section) => (
|
||||
<MenuSectionItem
|
||||
key={section.title.text}
|
||||
title={section.title}
|
||||
links={section.links}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import { MagnifyingGlassMini } from "@medusajs/icons"
|
||||
|
||||
type SearchButtonProps = {
|
||||
label?: string
|
||||
shortcut?: string
|
||||
onClick?: () => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function SearchButton({
|
||||
label = "Search",
|
||||
shortcut = "⌘K",
|
||||
onClick,
|
||||
className,
|
||||
}: SearchButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={
|
||||
[
|
||||
"w-[250px] h-11 justify-between gap-3 border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-field-hover rounded-lg",
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
}
|
||||
onClick={onClick}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<MagnifyingGlassMini className="text-ui-fg-base" />
|
||||
<span className="text-ui-fg-base">{label}</span>
|
||||
</span>
|
||||
<span className="flex items-center rounded-md border border-ui-border-base px-2 py-0.5 bg-white text-xs text-ui-fg-muted">
|
||||
{shortcut}
|
||||
</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "vibentec/component-map"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import * as MedusaIcons from "@medusajs/icons"
|
||||
export const AccountButton = ({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) => {
|
||||
const props = nodes.config ?? {}
|
||||
const className = clx("hover:text-ui-fg-base", props.className)
|
||||
const style: React.CSSProperties = {}
|
||||
if (props.bgColor) style.backgroundColor = props.bgColor
|
||||
if (props.textColor) style.color = props.textColor
|
||||
const href = props.href ?? "/account"
|
||||
const label = props.label ?? "Account"
|
||||
const iconName = props.icon
|
||||
const Icon = iconName
|
||||
? (MedusaIcons as Record<string, React.ComponentType<any>>)[iconName]
|
||||
: undefined
|
||||
return (
|
||||
<div className="flex items-center h-full">
|
||||
<LocalizedClientLink
|
||||
href={href}
|
||||
className={className}
|
||||
data-testid="nav-account-link"
|
||||
>
|
||||
{Icon ? <Icon /> : label}
|
||||
</LocalizedClientLink>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountButton
|
||||
@@ -1,36 +0,0 @@
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "vibentec/component-map"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { Suspense } from "react"
|
||||
import CartButton from "@modules/layout/components/cart-button"
|
||||
|
||||
export const VtCartButton = ({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) => {
|
||||
const props = nodes.config ?? {}
|
||||
const className = clx("hover:text-ui-fg-base flex gap-2", props.className)
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<LocalizedClientLink
|
||||
className={className}
|
||||
href="/cart"
|
||||
data-testid="nav-cart-link"
|
||||
>
|
||||
Cart (0)
|
||||
</LocalizedClientLink>
|
||||
}
|
||||
>
|
||||
<CartButton iconName={props.icon} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
export default VtCartButton
|
||||
@@ -1,24 +0,0 @@
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
|
||||
import { clx } from "@medusajs/ui"
|
||||
|
||||
export const HomeButton = ({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) => {
|
||||
const props = nodes.config ?? {}
|
||||
const className = clx("txt-compact-xlarge-plus hover:text-ui-fg-base uppercase", props.className)
|
||||
const href = props.href ?? "/"
|
||||
const label = props.label ?? "Medusa Store"
|
||||
|
||||
return (
|
||||
<div className="flex items-center h-full">
|
||||
<LocalizedClientLink
|
||||
href={href}
|
||||
className={className}
|
||||
data-testid="nav-store-link"
|
||||
>
|
||||
{label}
|
||||
</LocalizedClientLink>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HomeButton
|
||||
@@ -1,28 +0,0 @@
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
|
||||
import { clx } from "@medusajs/ui"
|
||||
|
||||
interface VtLinkProps {
|
||||
className?: string
|
||||
href?: string
|
||||
label?: string
|
||||
}
|
||||
export const VtLink = ({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) => {
|
||||
const props = nodes.config as VtLinkProps ?? {}
|
||||
const className = clx("txt-compact-xlarge-plus hover:text-ui-fg-base", props.className)
|
||||
const href = props.href ?? "/"
|
||||
const label = props.label ?? "Medusa Store"
|
||||
|
||||
return (
|
||||
<div className="flex items-center h-full">
|
||||
<LocalizedClientLink
|
||||
href={href}
|
||||
className={className}
|
||||
>
|
||||
{label}
|
||||
</LocalizedClientLink>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VtLink
|
||||
@@ -1,34 +0,0 @@
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "vibentec/component-map"
|
||||
import React, { Suspense } from "react"
|
||||
import SkeletonMegaMenu from "@modules/skeletons/components/vt-skeleton-mega-menu"
|
||||
import MegaMenuWrapper from "@modules/layout/components/vt-mega-menu/mega-menu-wrapper"
|
||||
|
||||
interface MegaMenuProps {
|
||||
navLabel: {
|
||||
text: string
|
||||
className?: string
|
||||
}
|
||||
}
|
||||
export default function VtMegaMenu({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const { navLabel } = nodes.config as MegaMenuProps ?? {}
|
||||
return (
|
||||
<nav>
|
||||
<ul className="space-x-4 hidden small:flex">
|
||||
<li>
|
||||
<Suspense fallback={<SkeletonMegaMenu />}>
|
||||
<MegaMenuWrapper navLabel={navLabel} />
|
||||
</Suspense>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { listCategories } from "@lib/data/categories"
|
||||
import MegaMenu from "./mega-menu"
|
||||
|
||||
|
||||
|
||||
export async function MegaMenuWrapper({ navLabel }: { navLabel: { text: string; className?: string } }) {
|
||||
const categories = await listCategories().catch(() => [])
|
||||
|
||||
return <MegaMenu navLabel={navLabel} categories={categories} />
|
||||
}
|
||||
|
||||
export default MegaMenuWrapper
|
||||
@@ -1,149 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import ChevronDown from "@modules/common/icons/chevron-down"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
|
||||
|
||||
const MegaMenu = ({
|
||||
navLabel,
|
||||
categories,
|
||||
}: {
|
||||
navLabel: { text: string; className?: string, isShowArrow?: boolean },
|
||||
categories: HttpTypes.StoreProductCategory[]
|
||||
}) => {
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
const [selectedCategory, setSelectedCategory] = useState<
|
||||
HttpTypes.StoreProductCategory["id"] | null
|
||||
>(null)
|
||||
|
||||
const pathname = usePathname()
|
||||
|
||||
const mainCategories = categories.filter(
|
||||
(category) => !category.parent_category_id
|
||||
)
|
||||
|
||||
const getSubCategories = (categoryId: string) => {
|
||||
return categories.filter(
|
||||
(category) => category.parent_category_id === categoryId
|
||||
)
|
||||
}
|
||||
|
||||
let menuTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
const handleMenuHover = () => {
|
||||
if (menuTimeout) {
|
||||
clearTimeout(menuTimeout)
|
||||
}
|
||||
setIsHovered(true)
|
||||
}
|
||||
|
||||
const handleMenuLeave = () => {
|
||||
menuTimeout = setTimeout(() => {
|
||||
setIsHovered(false)
|
||||
}, 300)
|
||||
|
||||
return () => {
|
||||
if (menuTimeout) {
|
||||
clearTimeout(menuTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let categoryTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
const handleCategoryHover = (categoryId: string) => {
|
||||
categoryTimeout = setTimeout(() => {
|
||||
setSelectedCategory(categoryId)
|
||||
}, 200)
|
||||
|
||||
return () => {
|
||||
if (categoryTimeout) {
|
||||
clearTimeout(categoryTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCategoryLeave = () => {
|
||||
if (categoryTimeout) {
|
||||
clearTimeout(categoryTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsHovered(false)
|
||||
}, [pathname])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
onMouseEnter={handleMenuHover}
|
||||
onMouseLeave={handleMenuLeave}
|
||||
className="z-50"
|
||||
>
|
||||
<LocalizedClientLink
|
||||
className={clx(
|
||||
"hover:text-ui-fg-base hover:bg-neutral-100 rounded-full px-3 py-2",
|
||||
navLabel?.className
|
||||
)}
|
||||
href="/store"
|
||||
>
|
||||
{navLabel?.text ?? "Product"} {navLabel?.isShowArrow && <ChevronDown />}
|
||||
</LocalizedClientLink>
|
||||
{isHovered && (
|
||||
<div className="absolute top-full left-0 right-0 flex gap-32 py-10 px-20 bg-white border-b border-neutral-200 ">
|
||||
<div className="flex flex-col gap-2">
|
||||
{mainCategories.map((category) => (
|
||||
<LocalizedClientLink
|
||||
key={category.id}
|
||||
href={`/categories/${category.handle}`}
|
||||
className={clx(
|
||||
"hover:bg-neutral-100 text-black hover:cursor-pointer rounded-full px-3 py-2 w-fit font-medium",
|
||||
selectedCategory === category.id && "bg-neutral-100"
|
||||
)}
|
||||
onMouseEnter={() => handleCategoryHover(category.id)}
|
||||
onMouseLeave={handleCategoryLeave}
|
||||
>
|
||||
{category.name}
|
||||
</LocalizedClientLink>
|
||||
))}
|
||||
</div>
|
||||
{selectedCategory && (
|
||||
<div className="grid grid-cols-4 gap-16">
|
||||
{getSubCategories(selectedCategory).map((category) => (
|
||||
<div key={category.id} className="flex flex-col gap-2">
|
||||
<LocalizedClientLink
|
||||
className="font-medium text-zinc-500 hover:underline"
|
||||
href={`/categories/${category.handle}`}
|
||||
>
|
||||
{category.name}
|
||||
</LocalizedClientLink>
|
||||
<div className="flex flex-col gap-2">
|
||||
{getSubCategories(category.id).map((subCategory) => (
|
||||
<LocalizedClientLink
|
||||
key={subCategory.id}
|
||||
className="hover:underline"
|
||||
href={`/categories/${subCategory.handle}`}
|
||||
>
|
||||
{subCategory.name}
|
||||
</LocalizedClientLink>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isHovered && (
|
||||
<div className="fixed inset-0 mt-[60px] blur-sm backdrop-blur-sm z-[-1]" />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MegaMenu
|
||||
@@ -1,20 +0,0 @@
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
|
||||
import React from "react";
|
||||
import SideMenu from "../side-menu";
|
||||
import { listRegions } from "@lib/data/regions"
|
||||
import { StoreRegion } from "@medusajs/types"
|
||||
|
||||
export default async function VtSideMenu({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) {
|
||||
const regions = await listRegions().then((regions: StoreRegion[]) => regions)
|
||||
return (
|
||||
<div className="flex-1 basis-0 h-full flex items-center">
|
||||
<div className="h-full">
|
||||
<SideMenu regions={regions} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
type TextItem = { text: string; className?: string }
|
||||
|
||||
export default async function AnnouncementBanner({
|
||||
className,
|
||||
label,
|
||||
...props
|
||||
}: { className: string; label: TextItem[] }) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="container mx-auto flex justify-center items-center py-2">
|
||||
<div className="flex items-center gap-20">
|
||||
|
||||
{label.map((item, index) => {
|
||||
return (
|
||||
<div key={`${index}-${item.text}`} className={`last:mr-0 ${item.className || ""}`}>
|
||||
{item.text}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
'use client'
|
||||
import { DropdownMenu } from "@medusajs/ui"
|
||||
import { ChevronDownMini } from "@medusajs/icons"
|
||||
|
||||
interface DropdownMenuProps {
|
||||
className?: string
|
||||
"data-testid"?: string
|
||||
label?: string
|
||||
isShowArrow?: boolean
|
||||
}
|
||||
export default function DropdownMenuComponent({
|
||||
children,
|
||||
props,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
props?: DropdownMenuProps
|
||||
}) {
|
||||
const itemNodes = Array.isArray(children) ? children : []
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button
|
||||
className={props?.className}
|
||||
data-testid={props?.["data-testid"]}
|
||||
>
|
||||
{props?.label ?? "Menu"} {props?.isShowArrow ? <ChevronDownMini /> : null}
|
||||
</button>
|
||||
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content className="abc">
|
||||
{itemNodes.map((item, index) => {
|
||||
const props = item?.DropdownMenuItems?.props
|
||||
return props ? <DropdownMenu.Item key={props.label ?? index} className={props.className} data-testid={props["data-testid"]}>{props.label}</DropdownMenu.Item> : null
|
||||
})}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
type TextItem = { text: string; className?: string }
|
||||
|
||||
export default async function AnnouncementBanner({
|
||||
className,
|
||||
label,
|
||||
...props
|
||||
}: { className: string; label: TextItem[] }) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="container mx-auto flex justify-center items-center py-2">
|
||||
<div className="flex items-center gap-4">
|
||||
|
||||
{label.map((item, index) => {
|
||||
return (
|
||||
<div key={`${index}-${item.text}`} className={`last:mr-0 ${item.className || ""}`}>
|
||||
{item.text}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
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
|
||||
@@ -1,40 +0,0 @@
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "vibentec/component-map"
|
||||
import { clx } from "@medusajs/ui"
|
||||
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,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = (nodes.config as BannerProps) ?? {}
|
||||
const heroClassName = clx(
|
||||
"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}>
|
||||
<Component node={nodes} context={context} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { DynamicLayoutRenderer, DynamicLayoutRendererProps } from "vibentec/renderer"
|
||||
|
||||
type TextItem = { text: string; className?: string }
|
||||
|
||||
export default async function AnnouncementBanner({
|
||||
className,
|
||||
nodes,
|
||||
context,
|
||||
}: DynamicLayoutRendererProps & { className: string; label: TextItem[] }) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="w-full mx-auto flex justify-between items-center py-2 px-20">
|
||||
{nodes && <DynamicLayoutRenderer nodes={nodes} context={context} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import remarkBreaks from "remark-breaks"
|
||||
|
||||
interface BannerCTAProps {
|
||||
node: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}
|
||||
|
||||
interface BannerConfigProps {
|
||||
text?: string
|
||||
href?: string
|
||||
iconLeft?: string
|
||||
iconRight?: string
|
||||
className?: string
|
||||
}
|
||||
export default function BannerCTA({ node, context }: BannerCTAProps) {
|
||||
const props = node.config as BannerConfigProps ?? {}
|
||||
const text = props.text ?? ""
|
||||
const href = props.href ?? undefined
|
||||
const iconLeft = props.iconLeft
|
||||
const iconRight = props.iconRight
|
||||
|
||||
const className = clx(
|
||||
"content-container flex justify-center items-center text-center w-full h-full text-xs font-medium",
|
||||
props.className
|
||||
)
|
||||
|
||||
const content = (
|
||||
<div className="flex items-center gap-2">
|
||||
{iconLeft && <DynamicIcon name={iconLeft} />}
|
||||
<div className="leading-none">
|
||||
<ReactMarkdown
|
||||
children={text}
|
||||
remarkPlugins={[remarkBreaks]}
|
||||
rehypePlugins={[rehypeRaw]}
|
||||
/>
|
||||
</div>
|
||||
{iconRight && <DynamicIcon name={iconRight} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
return href ? (
|
||||
<LocalizedClientLink href={href} className={className}>
|
||||
{content}
|
||||
</LocalizedClientLink>
|
||||
) : (
|
||||
<div className={className}>{content}</div>
|
||||
)
|
||||
}
|
||||
|
||||
function DynamicIcon({ name }: { name: string }) {
|
||||
return <span className="material-symbols-outlined text-sm">{name}</span>
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { DynamicLayoutRenderer } from "vibentec/renderer"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
|
||||
|
||||
interface BannerNavProps {
|
||||
left?: LayoutComponentDefinition[]
|
||||
center?: LayoutComponentDefinition[]
|
||||
right?: LayoutComponentDefinition[]
|
||||
}
|
||||
export default function BannerNav({ node, context }: { node: LayoutComponentDefinition; context: LayoutContext }) {
|
||||
const props = node.config as BannerNavProps ?? {};
|
||||
|
||||
return (
|
||||
<nav className="content-container txt-xsmall-plus flex items-center justify-between w-full h-full text-small-regular">
|
||||
<div className="flex items-center gap-x-4 w-full h-full">
|
||||
{props.left && <DynamicLayoutRenderer nodes={props.left} context={context} />}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 w-full h-full">
|
||||
{props.center && <DynamicLayoutRenderer nodes={props.center} context={context} />}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 w-full h-full justify-end">
|
||||
{props.right && <DynamicLayoutRenderer nodes={props.right} context={context} />}
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
@keyframes bannerTicker {
|
||||
0% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.ticker {
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
animation: bannerTicker linear infinite;
|
||||
height: 100%;
|
||||
gap: 3rem
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import styles from "./banner-ticker.module.css"
|
||||
import { DynamicLayoutRenderer } from "vibentec/renderer"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map"
|
||||
|
||||
interface BannerTickerProps {
|
||||
node: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}
|
||||
interface BannerConfigProps {
|
||||
items?: LayoutComponentDefinition[]
|
||||
speed?: number
|
||||
}
|
||||
export default function BannerTicker({ node, context }: BannerTickerProps) {
|
||||
const props = node.config as BannerConfigProps ?? {}
|
||||
const speed = props.speed ?? 10;
|
||||
return (
|
||||
<div className="relative overflow-hidden w-full h-full">
|
||||
<div className={styles.ticker} style={{ animationDuration: `${speed}s` }} >
|
||||
<DynamicLayoutRenderer nodes={props.items ?? []} context={context} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "vibentec/component-map"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import BannerNav from "./banner-nav"
|
||||
import BannerCTA from "./banner-cta"
|
||||
import BannerTicker from "./banner-ticker"
|
||||
|
||||
interface BannerProps {
|
||||
variant: "nav" | "cta" | "ticker"
|
||||
className?: string
|
||||
speed?: number
|
||||
}
|
||||
export default async function Banner({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = (nodes.config as BannerProps) ?? {}
|
||||
const bannerClassName = clx(
|
||||
"relative h-8 mx-auto border-b duration-200 bg-white border-ui-border-base",
|
||||
props.className
|
||||
)
|
||||
if (!props.variant) return null
|
||||
|
||||
const variants = {
|
||||
nav: BannerNav,
|
||||
cta: BannerCTA,
|
||||
ticker: BannerTicker,
|
||||
}
|
||||
|
||||
const Component = variants[props.variant]
|
||||
|
||||
return (
|
||||
<div className={bannerClassName}>
|
||||
<Component node={nodes} context={context} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Button, IconButton } from "@medusajs/ui"
|
||||
import * as MedusaIcons from "@medusajs/icons"
|
||||
import * as CustomIcons from "@modules/common/icons"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
|
||||
export default function VtButton({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config || {}
|
||||
const iconName = props.icon as string | undefined
|
||||
const IconComponent = iconName
|
||||
? (MedusaIcons as Record<string, any>)[iconName] ??
|
||||
(CustomIcons as Record<string, any>)[iconName]
|
||||
: undefined
|
||||
return (
|
||||
<IconButton className={props?.className ?? ""}>
|
||||
{IconComponent && (
|
||||
<IconComponent className={props?.iconClassName ?? ""} />
|
||||
)}
|
||||
{props?.label && (
|
||||
<span className={props?.labelClassName ?? ""}>{props.label}</span>
|
||||
)}
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
"use client"
|
||||
import { Select } from "@medusajs/ui"
|
||||
import ChevronDown from "@modules/common/icons/chevron-down"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
import { useMemo, useState, useEffect } from "react"
|
||||
import { useParams, usePathname, useRouter } from "next/navigation"
|
||||
import ReactCountryFlag from "react-country-flag"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export default function VtCountrySelectClient({
|
||||
nodes,
|
||||
context,
|
||||
regions,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
regions?: HttpTypes.StoreRegion[]
|
||||
}) {
|
||||
const props = nodes.config ?? {}
|
||||
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([])
|
||||
return
|
||||
}
|
||||
const opts = regions
|
||||
.map((r) =>
|
||||
(r.countries || []).map((c) => ({
|
||||
text: c.iso_2 ?? "",
|
||||
label: c.display_name,
|
||||
}))
|
||||
)
|
||||
.flat()
|
||||
.filter((o) => o.text)
|
||||
.sort((a, b) => (a.label ?? "").localeCompare(b.label ?? ""))
|
||||
console.log(opts)
|
||||
setItems(opts)
|
||||
}, [regions])
|
||||
|
||||
const initialValue = (countryCode as string) || triggerText || ""
|
||||
const [value, setValue] = useState(initialValue)
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
|
||||
const handleChange = (next: string) => {
|
||||
setValue(next)
|
||||
if (!pathname || !next) return
|
||||
const parts = pathname.split("/")
|
||||
if (parts.length > 1) {
|
||||
parts[1] = next.toLowerCase()
|
||||
const newPath = parts.join("/")
|
||||
router.replace(newPath)
|
||||
}
|
||||
}
|
||||
const selectedItem = useMemo(() => {
|
||||
return items.find((i) => i.text === value)
|
||||
}, [items, value])
|
||||
if (!triggerText && items.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<Select value={value} onValueChange={handleChange}>
|
||||
<Select.Trigger
|
||||
className={
|
||||
(props.trigger?.className ?? "") +
|
||||
"flex items-center gap-1 [&_svg:not(:first-of-type)]:hidden"
|
||||
}
|
||||
>
|
||||
<span className="txt-compact-small flex items-center">
|
||||
{/* @ts-ignore */}
|
||||
{props.trigger?.isFlag && (
|
||||
<ReactCountryFlag
|
||||
svg
|
||||
style={{
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
}}
|
||||
countryCode={value ?? ""}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
{props.trigger?.isDisplayFullname ? (selectedItem?.label || value) : (selectedItem?.text.toUpperCase() || value)} <ChevronDown />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{items.length > 0 &&
|
||||
items.map((item: { text: string; label?: string }, index: number) => (
|
||||
<Select.Item value={item.text || ""} key={item.text + index}>
|
||||
<div className="flex items-center w-full gap-3">
|
||||
{props.trigger?.isFlag && item.text && (
|
||||
<ReactCountryFlag
|
||||
svg
|
||||
style={{
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
}}
|
||||
countryCode={item.text ?? ""}
|
||||
/>
|
||||
)}
|
||||
{item.text.toUpperCase()}
|
||||
</div>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { listRegions, getRegion } from "@lib/data/regions"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
import VtCountryCodeSelectClient from "./index"
|
||||
|
||||
export default async function VtCountryCodeSelect({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const regions = (await listRegions()) as HttpTypes.StoreRegion[]
|
||||
|
||||
return (
|
||||
<VtCountryCodeSelectClient nodes={nodes} context={context} regions={regions} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
"use client"
|
||||
import { Select } from "@medusajs/ui"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import ChevronDown from "@modules/common/icons/chevron-down"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
|
||||
export default function VtCurrencySelect({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config ?? {}
|
||||
if (!props.trigger.text || props.items.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<Select>
|
||||
<Select.Trigger
|
||||
className={props.trigger.className + " flex items-center gap-1"}
|
||||
>
|
||||
{props.trigger.icon && (
|
||||
<img
|
||||
src={props.trigger.icon}
|
||||
alt={props.trigger.text}
|
||||
className="w-5 h-5 rounded-[50%] mr-3"
|
||||
/>
|
||||
)}
|
||||
{props.trigger.text} {props.trigger.isShowArrow && <ChevronDown />}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{props.items.length > 0 &&
|
||||
props.items.map(
|
||||
(
|
||||
item: {
|
||||
text: string
|
||||
className?: string
|
||||
href?: string
|
||||
icon?: string
|
||||
},
|
||||
index: number
|
||||
) => (
|
||||
<Select.Item
|
||||
value={item.text || ""}
|
||||
key={item.text + index}
|
||||
className={item.className || ""}
|
||||
>
|
||||
{item.icon && (
|
||||
<img
|
||||
src={item.icon}
|
||||
alt={item.text}
|
||||
className="w-5 h-5 rounded-[50%] mr-3"
|
||||
/>
|
||||
)}
|
||||
{item.href ? (
|
||||
<LocalizedClientLink href={item.href}>
|
||||
{item.text}
|
||||
</LocalizedClientLink>
|
||||
) : (
|
||||
item.text
|
||||
)}
|
||||
</Select.Item>
|
||||
)
|
||||
)}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
"use client"
|
||||
import { DropdownMenu } from "@medusajs/ui"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import ChevronDown from "@modules/common/icons/chevron-down"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
|
||||
export default function VtDropdown({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config ?? {}
|
||||
if (!props.trigger.text || props.items.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger
|
||||
className={props.trigger.className + " flex items-center gap-1"}
|
||||
>
|
||||
{props.trigger.icon && (
|
||||
<img
|
||||
src={props.trigger.icon}
|
||||
alt={props.trigger.text}
|
||||
className="w-5 h-5 rounded-[50%] mr-3"
|
||||
/>
|
||||
)}
|
||||
{props.trigger.text} {props.trigger.isShowArrow && <ChevronDown />}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content>
|
||||
{props.items.length > 0 &&
|
||||
props.items.map(
|
||||
(
|
||||
item: {
|
||||
text: string
|
||||
className?: string
|
||||
href?: string
|
||||
icon?: string
|
||||
},
|
||||
index: number
|
||||
) => (
|
||||
<DropdownMenu.Item
|
||||
key={item.text + index}
|
||||
className={item.className || ""}
|
||||
>
|
||||
{item.icon && (
|
||||
<img
|
||||
src={item.icon}
|
||||
alt={item.text}
|
||||
className="w-5 h-5 rounded-[50%] mr-3"
|
||||
/>
|
||||
)}
|
||||
{item.href ? (
|
||||
<LocalizedClientLink href={item.href}>
|
||||
{item.text}
|
||||
</LocalizedClientLink>
|
||||
) : (
|
||||
item.text
|
||||
)}
|
||||
</DropdownMenu.Item>
|
||||
)
|
||||
)}
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,44 +1,158 @@
|
||||
import { listCategories } from "@lib/data/categories"
|
||||
import { listCollections } from "@lib/data/collections"
|
||||
import { Text, clx } from "@medusajs/ui"
|
||||
import { FooterProps } from "vibentec/component-props"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import MedusaCTA from "@modules/layout/components/medusa-cta"
|
||||
import { DynamicLayoutRenderer } from "@vibentec/renderer"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "vibentec/component-map"
|
||||
|
||||
export default async function VtFooter({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes?: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
|
||||
export default async function VtFooter({copyrightText}:FooterProps) {
|
||||
const { collections } = await listCollections({
|
||||
fields: "*products",
|
||||
})
|
||||
const productCategories = await listCategories()
|
||||
const props = nodes?.config ?? {}
|
||||
|
||||
return (
|
||||
<footer className={props?.className ?? ""}>
|
||||
{props?.left && (
|
||||
<div className={clx("flex h-full", props?.leftClassName)}>
|
||||
<DynamicLayoutRenderer nodes={props?.left} context={context} />
|
||||
<footer className="border-t border-ui-border-base w-full">
|
||||
<div className="content-container flex flex-col w-full">
|
||||
<div className="flex flex-col gap-y-6 xsmall:flex-row items-start justify-between py-40">
|
||||
<div>
|
||||
<LocalizedClientLink
|
||||
href="/"
|
||||
className="txt-compact-xlarge-plus text-ui-fg-subtle hover:text-ui-fg-base uppercase"
|
||||
>
|
||||
Medusa Store
|
||||
</LocalizedClientLink>
|
||||
</div>
|
||||
<div className="text-small-regular gap-10 md:gap-x-16 grid grid-cols-2 sm:grid-cols-3">
|
||||
{productCategories && productCategories?.length > 0 && (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<span className="txt-small-plus txt-ui-fg-base">
|
||||
Categories
|
||||
</span>
|
||||
<ul
|
||||
className="grid grid-cols-1 gap-2"
|
||||
data-testid="footer-categories"
|
||||
>
|
||||
{productCategories?.slice(0, 6).map((c) => {
|
||||
if (c.parent_category) {
|
||||
return
|
||||
}
|
||||
|
||||
const children =
|
||||
c.category_children?.map((child) => ({
|
||||
name: child.name,
|
||||
handle: child.handle,
|
||||
id: child.id,
|
||||
})) || null
|
||||
|
||||
return (
|
||||
<li
|
||||
className="flex flex-col gap-2 text-ui-fg-subtle txt-small"
|
||||
key={c.id}
|
||||
>
|
||||
<LocalizedClientLink
|
||||
className={clx(
|
||||
"hover:text-ui-fg-base",
|
||||
children && "txt-small-plus"
|
||||
)}
|
||||
href={`/categories/${c.handle}`}
|
||||
data-testid="category-link"
|
||||
>
|
||||
{c.name}
|
||||
</LocalizedClientLink>
|
||||
{children && (
|
||||
<ul className="grid grid-cols-1 ml-3 gap-2">
|
||||
{children &&
|
||||
children.map((child) => (
|
||||
<li key={child.id}>
|
||||
<LocalizedClientLink
|
||||
className="hover:text-ui-fg-base"
|
||||
href={`/categories/${child.handle}`}
|
||||
data-testid="category-link"
|
||||
>
|
||||
{child.name}
|
||||
</LocalizedClientLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{collections && collections.length > 0 && (
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<span className="txt-small-plus txt-ui-fg-base">
|
||||
Collections
|
||||
</span>
|
||||
<ul
|
||||
className={clx(
|
||||
"grid grid-cols-1 gap-2 text-ui-fg-subtle txt-small",
|
||||
{
|
||||
"grid-cols-2": (collections?.length || 0) > 3,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{collections?.slice(0, 6).map((c) => (
|
||||
<li key={c.id}>
|
||||
<LocalizedClientLink
|
||||
className="hover:text-ui-fg-base"
|
||||
href={`/collections/${c.handle}`}
|
||||
>
|
||||
{c.title}
|
||||
</LocalizedClientLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-y-2">
|
||||
<span className="txt-small-plus txt-ui-fg-base">Medusa</span>
|
||||
<ul className="grid grid-cols-1 gap-y-2 text-ui-fg-subtle txt-small">
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/medusajs"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:text-ui-fg-base"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://docs.medusajs.com"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:text-ui-fg-base"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/medusajs/nextjs-starter-medusa"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="hover:text-ui-fg-base"
|
||||
>
|
||||
Source code
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{props?.center && (
|
||||
<div className={clx("flex h-full", props?.centerClassName)}>
|
||||
<DynamicLayoutRenderer nodes={props?.center} context={context} />
|
||||
<div className="flex w-full mb-16 justify-between text-ui-fg-muted">
|
||||
<Text className="txt-compact-small">
|
||||
{copyrightText || `© ${new Date().getFullYear()} Medusa Store. All rights reserved.`}
|
||||
</Text>
|
||||
<MedusaCTA />
|
||||
</div>
|
||||
)}
|
||||
{props?.right && (
|
||||
<div className={clx("flex h-full", props?.rightClassName)}>
|
||||
<DynamicLayoutRenderer nodes={props?.right} context={context} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import * as MedusaIcons from "@medusajs/icons"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
|
||||
export default function VtFooterBottom({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config ?? {}
|
||||
|
||||
const renderIcon = (name: string, idx: number) => {
|
||||
const IconComp = (MedusaIcons as Record<string, any>)[name]
|
||||
if (!IconComp) return null
|
||||
return <IconComp key={`${name}-${idx}`} className="shadow-none" />
|
||||
}
|
||||
|
||||
const renderLink = (item: any, idx: number, total: number) => {
|
||||
const LinkEl = (
|
||||
<LocalizedClientLink
|
||||
href={item?.href ?? "#"}
|
||||
className={clx(
|
||||
"hover:underline",
|
||||
props.linkClassName
|
||||
)}
|
||||
>
|
||||
{item?.label ?? ""}
|
||||
</LocalizedClientLink>
|
||||
)
|
||||
|
||||
return (
|
||||
<span
|
||||
key={`link-${idx}`}
|
||||
className={clx("flex items-center", props.linkItemClassName)}
|
||||
>
|
||||
{LinkEl}
|
||||
{idx < total - 1 && (
|
||||
<span className={clx("mx-2 text-white", props.separatorClassName)}>|</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clx(
|
||||
"flex w-full items-center justify-between",
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
<span className={clx("text-[14px] font-[400] pt-2", props.textClassName)}>
|
||||
{props.text}
|
||||
</span>
|
||||
<div className={clx("flex items-center gap-4")}>
|
||||
{props.links && props.links.length > 0 && (
|
||||
<div className={clx("flex items-center", props.linksClassName)}>
|
||||
{props.links.map((l: any, idx: number) =>
|
||||
renderLink(l, idx, props.links.length)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={clx("flex items-center gap-2", props.iconsClassName)}>
|
||||
{(props.icons ?? []).map(renderIcon)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
import { clx } from "@medusajs/ui"
|
||||
import { ChevronRightMini } from "@medusajs/icons"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
import * as MedusaIcons from "@medusajs/icons"
|
||||
import * as CustomIcons from "@modules/common/icons"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
|
||||
interface SocialIcon {
|
||||
href?: string
|
||||
icon?: string
|
||||
imgSrc?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function VtFooterHero({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config ?? {}
|
||||
const IconComp = (name?: string) => {
|
||||
if (!name) return null
|
||||
return (
|
||||
(MedusaIcons as Record<string, any>)[name] ||
|
||||
(CustomIcons as Record<string, any>)[name] ||
|
||||
null
|
||||
)
|
||||
}
|
||||
const renderSocialContent = (item: SocialIcon) => {
|
||||
const Icon = IconComp(item.icon)
|
||||
if (Icon) return <Icon className={item.className} />
|
||||
if (item.imgSrc)
|
||||
return (
|
||||
<img
|
||||
src={item.imgSrc}
|
||||
alt={item.icon ?? "social"}
|
||||
className={item.className}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<span className="w-4 h-4 rounded-md bg-white/10 text-white flex items-center justify-center text-sm">
|
||||
{item.icon?.[0] ?? "?"}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const renderSocialIcon = (icon: SocialIcon, idx: number) => {
|
||||
const content = renderSocialContent(icon)
|
||||
return icon.href ? (
|
||||
<a
|
||||
key={idx}
|
||||
href={icon.href}
|
||||
className={clx("hover:opacity-80", icon.className)}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
) : (
|
||||
<span key={idx} className={clx("", icon.className)}>
|
||||
{content}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={clx("flex flex-col items-start", props.className)}>
|
||||
{props.logoSrc && (
|
||||
<img
|
||||
src={props.logoSrc}
|
||||
alt={props.logoAlt ?? "logo"}
|
||||
className={clx("object-contain", props.logoClassName)}
|
||||
/>
|
||||
)}
|
||||
{props.title && (
|
||||
<h2
|
||||
className={clx(
|
||||
"mt-4 w-[320px] text-[24px] font-semibold text-[#11314E]",
|
||||
props.titleClassName
|
||||
)}
|
||||
>
|
||||
{props.title}
|
||||
</h2>
|
||||
)}
|
||||
{props.description && (
|
||||
<p
|
||||
className={clx(
|
||||
"mt-2 text-ui-fg-subtle txt-small",
|
||||
props.descriptionClassName
|
||||
)}
|
||||
>
|
||||
{props.description}
|
||||
</p>
|
||||
)}
|
||||
{props.cta && (
|
||||
<LocalizedClientLink
|
||||
href={props.cta.href}
|
||||
className={clx(
|
||||
"bg-black text-white items-center flex gap-2 px-4 mt-[24px] py-2 rounded-md w-fit text-[14px]",
|
||||
props.ctaClassName
|
||||
)}
|
||||
>
|
||||
<span>{props.cta.label}</span>
|
||||
<ChevronRightMini className="order-1" />
|
||||
</LocalizedClientLink>
|
||||
)}
|
||||
|
||||
{props.email && (
|
||||
<form
|
||||
className={clx(
|
||||
"mt-4 w-full max-w-[500px]",
|
||||
props.email?.emailInputClassName
|
||||
)}
|
||||
>
|
||||
<div className="relative flex items-center justify-between border border-white/40 rounded-md px-4 py-3">
|
||||
<input
|
||||
id="vt-footer-hero-email"
|
||||
type="email"
|
||||
name="contact[email]"
|
||||
placeholder=" "
|
||||
autoComplete="email"
|
||||
required
|
||||
className="peer bg-transparent outline-none text-white/90 placeholder-transparent flex-1"
|
||||
/>
|
||||
<label
|
||||
htmlFor="vt-footer-hero-email"
|
||||
className="absolute left-4 text-white/60 pointer-events-none transition-all duration-300
|
||||
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:text-white/50
|
||||
peer-focus:top-3 peer-focus:-translate-y-2 peer-focus:left-3 peer-focus:text-white
|
||||
peer-[:not(:placeholder-shown)]:translate-y-[-0.85rem]"
|
||||
>
|
||||
E-Mail
|
||||
</label>
|
||||
<button
|
||||
type="submit"
|
||||
className="ml-4 w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center"
|
||||
>
|
||||
<span className="sr-only">Abonnieren</span>
|
||||
<ChevronRightMini />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
{props.socials && props.socials.length > 0 ? (
|
||||
<div
|
||||
className={clx(
|
||||
"flex items-center gap-4 mt-2",
|
||||
props.socialsClassName
|
||||
)}
|
||||
>
|
||||
{props.socials.map(renderSocialIcon)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
"use client"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { clx } from "@medusajs/ui"
|
||||
import * as MedusaIcons from "@medusajs/icons"
|
||||
import * as CustomIcons from "@modules/common/icons"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "@vibentec/component-map"
|
||||
import React, { useState } from "react"
|
||||
|
||||
interface SocialIcon {
|
||||
href?: string
|
||||
icon?: string
|
||||
imgSrc?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface VtFooterSignupConfig {
|
||||
title?: string
|
||||
placeholder?: string
|
||||
buttonLabel?: string
|
||||
className?: string
|
||||
titleClassName?: string
|
||||
formClassName?: string
|
||||
inputClassName?: string
|
||||
buttonClassName?: string
|
||||
socialsClassName?: string
|
||||
socials?: SocialIcon[]
|
||||
}
|
||||
|
||||
export default function VtFooterSignUp({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = (nodes.config as VtFooterSignupConfig) ?? {}
|
||||
const [email, setEmail] = useState("")
|
||||
|
||||
const IconComp = (name?: string) => {
|
||||
if (!name) return null
|
||||
return (
|
||||
(MedusaIcons as Record<string, any>)[name] ||
|
||||
(CustomIcons as Record<string, any>)[name] ||
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
const onSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
console.log("newsletter_signup", { email })
|
||||
}
|
||||
|
||||
const renderSocialContent = (item: SocialIcon) => {
|
||||
const Icon = IconComp(item.icon)
|
||||
if (Icon) return <Icon className={item.className} />
|
||||
if (item.imgSrc) return <img src={item.imgSrc} alt={item.icon ?? "social"} className={item.className} />
|
||||
return (
|
||||
<span className="w-4 h-4 rounded-md bg-white/10 text-white flex items-center justify-center text-sm">
|
||||
{item.icon?.[0] ?? "?"}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const renderSocialIcon = (icon: SocialIcon, idx: number) => {
|
||||
const content = renderSocialContent(icon)
|
||||
return icon.href ? (
|
||||
<a key={idx} href={icon.href} className={clx("hover:opacity-80", icon.className)}>
|
||||
{content}
|
||||
</a>
|
||||
) : (
|
||||
<span key={idx} className={clx("", icon.className)}>
|
||||
{content}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clx("flex flex-col gap-4", props.className)}>
|
||||
{props.title && (
|
||||
<p className={clx("text-white text-[18px]", props.titleClassName)}>
|
||||
{props.title}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<form onSubmit={onSubmit} className={clx("flex items-center gap-4", props.formClassName)}>
|
||||
<input
|
||||
type="email"
|
||||
placeholder={props.placeholder ?? "Email"}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className={clx(
|
||||
"h-[48px] rounded-lg bg-white text-black px-4",
|
||||
props.inputClassName
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
className={clx(
|
||||
"h-[48px] rounded-lg bg-[#C4622C] text-white",
|
||||
props.buttonClassName
|
||||
)}
|
||||
>
|
||||
{props.buttonLabel ?? "Sign Up"}
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
{props.socials && props.socials.length > 0 ? (
|
||||
<div className={clx("flex items-center gap-4 mt-2", props.socialsClassName)}>
|
||||
{props.socials.map(renderSocialIcon)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { DynamicLayoutRenderer } from "vibentec/renderer"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
|
||||
import { clx } from "@medusajs/ui";
|
||||
|
||||
export default function VtHeader({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) {
|
||||
const { sticky = true } = nodes.config ?? {};
|
||||
const cName = clx(sticky && "sticky top-0","inset-x-0 z-50 group");
|
||||
|
||||
return (
|
||||
<header className={cName}>
|
||||
{ nodes.children && <DynamicLayoutRenderer nodes={nodes.children} context={context} /> }
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { clx } from "@medusajs/ui"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
export interface VtImageConfig {
|
||||
src: string
|
||||
alt: string
|
||||
className?: string,
|
||||
objectFit?: string,
|
||||
}
|
||||
export default function VtLogo({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = (nodes.config as VtImageConfig) ?? {}
|
||||
return (
|
||||
<div className={clx("relative", props.className)}>
|
||||
<img src={props.src} alt={props.alt} className={clx("w-full h-full", props.objectFit)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
"use client"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import * as MedusaIcons from "@medusajs/icons"
|
||||
import * as CustomIcons from "@modules/common/icons"
|
||||
export default function VtMenuItem({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config ?? {}
|
||||
const title = props.title ?? ""
|
||||
const items = props.items ?? []
|
||||
const icon = props.icon ?? ""
|
||||
|
||||
const getIconComponent = (icon: string | undefined) => {
|
||||
return icon
|
||||
? (MedusaIcons as Record<string, any>)[icon] ??
|
||||
(CustomIcons as Record<string, any>)[icon]
|
||||
: undefined
|
||||
}
|
||||
return (
|
||||
<div className={props.className ?? "flex flex-col gap-y-2"}>
|
||||
<span>{title}</span>
|
||||
<ul className="grid grid-cols-1 gap-2" data-testid="footer-categories">
|
||||
{items.map((item: { text: string; href?: string; icon?: string }, index: number) => {
|
||||
const Icon = getIconComponent(item.icon)
|
||||
return (
|
||||
<li
|
||||
key={`${item.text}-${index}`}
|
||||
className={props.itemClassName ?? "text-ui-fg-subtle txt-small"}
|
||||
>
|
||||
{Icon && <Icon className={props.iconClassName ?? "inline-block mr-2"} />}
|
||||
{item.href ? (
|
||||
<LocalizedClientLink
|
||||
className="hover:text-ui-fg-base"
|
||||
href={item.href}
|
||||
data-testid="category-link"
|
||||
>
|
||||
{item.text}
|
||||
</LocalizedClientLink>
|
||||
) : (
|
||||
<span className="hover:text-ui-fg-base">{item.text}</span>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,29 +1,28 @@
|
||||
import { DynamicLayoutRenderer } from "vibentec/renderer"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
|
||||
import { clx } from "@medusajs/ui";
|
||||
import { listRegions } from "@lib/data/regions"
|
||||
import { StoreRegion } from "@medusajs/types"
|
||||
import SideMenu from "@modules/layout/components/side-menu"
|
||||
import { DynamicLayoutRenderer, DynamicLayoutRendererProps } from "vibentec/renderer"
|
||||
import { clx } from "@medusajs/ui"
|
||||
|
||||
interface BannerNavProps {
|
||||
className?: string;
|
||||
left?: LayoutComponentDefinition[];
|
||||
center?: LayoutComponentDefinition[];
|
||||
right?: LayoutComponentDefinition[];
|
||||
}
|
||||
export default function VtNav({ nodes, context }: { nodes: LayoutComponentDefinition; context: LayoutContext }) {
|
||||
const props = nodes.config as BannerNavProps ?? {}
|
||||
export default async function VtNav({ nodes, context, className }: DynamicLayoutRendererProps & { className?: string }) {
|
||||
console.log({nodes, context})
|
||||
const regions = await listRegions().then((regions: StoreRegion[]) => regions)
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto border-b duration-200 bg-white border-ui-border-base">
|
||||
<nav className={clx("content-container txt-xsmall-plus flex items-center justify-between w-full h-full text-small-regular", props.className)}>
|
||||
<div className="flex items-center gap-x-4 h-full">
|
||||
{props.left && <DynamicLayoutRenderer nodes={props.left} context={context} />}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 h-full">
|
||||
{props.center && <DynamicLayoutRenderer nodes={props.center} context={context} />}
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4 h-full justify-end">
|
||||
{props.right && <DynamicLayoutRenderer nodes={props.right} context={context} />}
|
||||
</div>
|
||||
</nav>
|
||||
<div className="sticky top-[40px] inset-x-0 z-50">
|
||||
<header className={clx("relative mx-auto border-b duration-200 border-ui-border-base", className ?? "bg-white") }>
|
||||
<nav className="flex justify-between w-full items-center h-full">
|
||||
{/* <div className="flex-1 basis-0 h-full flex items-center">
|
||||
<div className="h-full">
|
||||
<SideMenu regions={regions} />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{nodes && (
|
||||
<DynamicLayoutRenderer nodes={nodes} context={context} />
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from "react"
|
||||
import { MagnifyingGlass } from "@medusajs/icons"
|
||||
import {
|
||||
LayoutComponentDefinition,
|
||||
LayoutContext,
|
||||
} from "@vibentec/component-map"
|
||||
|
||||
export default function VtSearchInput({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config || {}
|
||||
const placeholder = props.placeholder ?? "Search"
|
||||
const shortcut = props.shortcut ?? "⌘K"
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-3 w-full max-w-xl bg-white border border-grey-20 rounded-rounded shadow-sm px-4 py-2 ${
|
||||
props?.className ?? ""
|
||||
}`
|
||||
}
|
||||
>
|
||||
<MagnifyingGlass className="text-[#11314E]" />
|
||||
<input
|
||||
type="search"
|
||||
placeholder={placeholder}
|
||||
className="flex-1 bg-transparent outline-none text-[#11314E] placeholder:text-[#11314E]"
|
||||
/>
|
||||
{shortcut && (
|
||||
<span className="text-[#11314E] border border-grey-30 rounded-rounded px-2 py-0.5 text-sm">
|
||||
{shortcut}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { LayoutComponentDefinition, LayoutContext } from "@vibentec/component-map"
|
||||
import { DynamicLayoutRenderer } from "@vibentec/renderer"
|
||||
|
||||
export default function VtSocialLinks({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config ?? {}
|
||||
return (
|
||||
<div className={props.className ?? ""}>
|
||||
{nodes.children && <DynamicLayoutRenderer nodes={nodes.children} context={context} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { LayoutComponentDefinition } from "@vibentec/component-map"
|
||||
|
||||
import { LayoutContext } from "@vibentec/component-map"
|
||||
|
||||
export default function VtText({
|
||||
nodes,
|
||||
context,
|
||||
}: {
|
||||
nodes: LayoutComponentDefinition
|
||||
context: LayoutContext
|
||||
}) {
|
||||
const props = nodes.config || {}
|
||||
return (
|
||||
<span className={props?.className ?? ""}>
|
||||
{props?.label ?? ""}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
|
||||
export default function SkeletonMegaMenu() {
|
||||
return (
|
||||
<LocalizedClientLink
|
||||
className="hover:text-ui-fg-base hover:bg-neutral-100 rounded-full px-3 py-2"
|
||||
href="/store"
|
||||
>
|
||||
Products
|
||||
</LocalizedClientLink>
|
||||
)
|
||||
}
|
||||
@@ -51,8 +51,7 @@
|
||||
|
||||
@layer components {
|
||||
.content-container {
|
||||
/* @apply max-w-[1440px] w-full mx-auto px-6; */
|
||||
@apply w-full mx-auto px-6;
|
||||
@apply max-w-[1440px] w-full mx-auto px-6;
|
||||
}
|
||||
|
||||
.contrast-btn {
|
||||
@@ -111,6 +110,7 @@
|
||||
@apply text-[32px] leading-[44px] font-semibold;
|
||||
}
|
||||
}
|
||||
|
||||
[data-radix-popper-content-wrapper]{
|
||||
z-index: 51 !important;
|
||||
}
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
+142
-68
@@ -1,93 +1,167 @@
|
||||
import CartMismatchBanner from "@modules/layout/components/cart-mismatch-banner"
|
||||
import FreeShippingPriceNudge from "@modules/shipping/components/free-shipping-price-nudge"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import CartButton from "@modules/layout/components/cart-button"
|
||||
|
||||
import { DynamicLayoutRenderer } from "./renderer"
|
||||
import React from "react"
|
||||
import VtNav from "@modules/layout/templates/vt-nav"
|
||||
import VtFooter from "@modules/layout/templates/vt-footer"
|
||||
import HomeButton from "@modules/layout/components/vt-homebutton"
|
||||
import AccountButton from "@modules/layout/components/vt-accountbutton"
|
||||
import VtCartButton from "@modules/layout/components/vt-cartbutton"
|
||||
import VtHeader from "@modules/layout/templates/vt-header"
|
||||
import Banner from "@modules/layout/templates/vt-banner"
|
||||
import VtMegaMenu from "@modules/layout/components/vt-mega-menu"
|
||||
import VtLink from "@modules/layout/components/vt-linkbutton"
|
||||
import VtSideMenu from "@modules/layout/components/vt-sidemenu"
|
||||
import VtDropdown from "@modules/layout/templates/vt-dropdown"
|
||||
import VtSearchInput from "@modules/layout/templates/vt-search-input"
|
||||
import VtCurrencySelect from "@modules/layout/templates/vt-currency-select"
|
||||
import VtMenuItem from "@modules/layout/templates/vt-menu-item"
|
||||
import VtCountryCodeSelect from "@modules/layout/templates/vt-country-select/server"
|
||||
import VtSocialLinks from "@modules/layout/templates/vt-social-link"
|
||||
import VtFooterHero from "@modules/layout/templates/vt-footer/vt-footer-hero"
|
||||
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"
|
||||
|
||||
type ComponentConfig = Record<string, any>;
|
||||
import AnnouncementBannerDefault from "@modules/layout/templates/3bear-template/announcement-bar"
|
||||
import AnnouncementBannerDrsquatch from "@modules/layout/templates/drsquatch-template/announcement-bar"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import { User, MagnifyingGlassMini, Heart } from "@medusajs/icons"
|
||||
import DropdownMenuComponent from "@modules/layout/templates/dropdown-menu/dropdown-menu"
|
||||
import AnnouncementBannerVibenTec from "@modules/layout/templates/vibentec-template/announcement-bar"
|
||||
import SearchButton from "@modules/layout/components/search-button"
|
||||
import NavigationMenu from "@modules/layout/components/navigation-menu"
|
||||
|
||||
export interface LayoutComponentDefinition {
|
||||
config?: ComponentConfig
|
||||
props?: Record<string, any>
|
||||
children?: LayoutComponentNode[]
|
||||
}
|
||||
|
||||
export interface LayoutContext {
|
||||
customer: any;
|
||||
cart: any;
|
||||
shippingOptions: any[];
|
||||
contentChildren: React.ReactNode;
|
||||
//maps key = componentName to value = props + children
|
||||
export type LayoutComponentNode = Record<string, LayoutComponentDefinition>
|
||||
|
||||
export interface LayoutContext {
|
||||
customer: any
|
||||
cart: any
|
||||
shippingOptions: any[]
|
||||
contentChildren: React.ReactNode
|
||||
designId?: string
|
||||
}
|
||||
|
||||
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} />
|
||||
// Utility, wenn eine Komponente nur props hat und keine children
|
||||
const simple = (Component: React.ComponentType<any>): ComponentRenderer => ({
|
||||
render: (entry) => <Component {...entry.props} />,
|
||||
})
|
||||
|
||||
const nodesContextRenderer = (Component: React.ComponentType<any>): ComponentRenderer => ({
|
||||
render: (entry: any, ctx: LayoutContext) => <Component nodes={entry} context={ctx} />
|
||||
});
|
||||
|
||||
// Helper für Kinder-Rendering
|
||||
const renderChildren = (entry: LayoutComponentDefinition, ctx: LayoutContext) =>
|
||||
entry.children ? <DynamicLayoutRenderer nodes={entry.children} context={ctx} /> : null
|
||||
entry.children ? (
|
||||
<DynamicLayoutRenderer nodes={entry.children} context={ctx} />
|
||||
) : null
|
||||
|
||||
// Map design identifiers to banner component variants (no conditionals)
|
||||
const AnnouncementBannerVariants: Record<string, React.ComponentType<any>> = {
|
||||
drsquatch: AnnouncementBannerDrsquatch,
|
||||
vibentec: AnnouncementBannerVibenTec,
|
||||
"3bear": AnnouncementBannerDefault,
|
||||
"medusa-starter": AnnouncementBannerDefault,
|
||||
default: AnnouncementBannerDefault,
|
||||
}
|
||||
|
||||
// Component Map
|
||||
export const componentMap: Record<string, ComponentRenderer> = {
|
||||
Header: nodesContextRenderer(VtHeader),
|
||||
Nav: nodesContextRenderer(VtNav),
|
||||
Hero: nodesContextRenderer(Hero),
|
||||
VtMegaMenu: nodesContextRenderer(VtMegaMenu),
|
||||
VtSideMenu: nodesContextRenderer(VtSideMenu),
|
||||
Banner: nodesContextRenderer(Banner),
|
||||
HomeButton: nodesContextRenderer(HomeButton),
|
||||
Logo: nodesContextRenderer(VtLogo),
|
||||
AccountButton: nodesContextRenderer(AccountButton),
|
||||
SearchInput: nodesContextRenderer(VtSearchInput),
|
||||
VtCartButton: nodesContextRenderer(VtCartButton),
|
||||
VtCurrencySelect: nodesContextRenderer(VtCurrencySelect),
|
||||
VtCountryCodeSelect: nodesContextRenderer(VtCountryCodeSelect),
|
||||
VtSocialLinks: nodesContextRenderer(VtSocialLinks),
|
||||
Link: nodesContextRenderer(VtLink),
|
||||
Dropdown: nodesContextRenderer(VtDropdown),
|
||||
VtMenuItem: nodesContextRenderer(VtMenuItem),
|
||||
CartMismatchBanner: configOnly(CartMismatchBanner),
|
||||
FreeShippingPriceNudge: configOnly(FreeShippingPriceNudge),
|
||||
PropsChildren: {
|
||||
render: (_props, ctx) => ctx.contentChildren, // PageLayout's props.children
|
||||
AnnouncementBanner: {
|
||||
render: (entry: any, ctx: LayoutContext) => {
|
||||
const key = ctx.designId ?? "default"
|
||||
const Comp =
|
||||
AnnouncementBannerVariants[key] ?? AnnouncementBannerVariants.default
|
||||
return <Comp {...entry.props} nodes={entry.children} context={ctx} />
|
||||
},
|
||||
},
|
||||
VtFooterHero: nodesContextRenderer(VtFooterHero),
|
||||
VtFooterBottom: nodesContextRenderer(VtFooterBottom),
|
||||
VtFooterSignUp: nodesContextRenderer(VtFooterSignUp),
|
||||
Footer: nodesContextRenderer(VtFooter)
|
||||
Nav: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<VtNav {...entry.props} nodes={entry.children} context={ctx} />
|
||||
),
|
||||
},
|
||||
Div: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<div {...entry.props}>
|
||||
{entry.children ? (
|
||||
<DynamicLayoutRenderer nodes={entry.children} context={ctx} />
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
Text: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<div {...entry.props}>{entry.props.label}</div>
|
||||
),
|
||||
},
|
||||
DropdownMenus: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<DropdownMenuComponent {...entry} />
|
||||
),
|
||||
},
|
||||
NavMenu: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<NavigationMenu props={entry.props} menuItems={entry.menuItems} />
|
||||
),
|
||||
},
|
||||
LocalizedClientLink: {
|
||||
render: (entry: any) => (
|
||||
<LocalizedClientLink {...entry.props}>
|
||||
{entry.props.label}
|
||||
</LocalizedClientLink>
|
||||
),
|
||||
},
|
||||
Image: {
|
||||
render: (entry: any) => {
|
||||
console.log(entry.props)
|
||||
return <img {...entry.props} alt={entry.props?.alt ?? ""} />
|
||||
},
|
||||
},
|
||||
Button: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<Button {...entry.props}>{entry.props.label}</Button>
|
||||
),
|
||||
},
|
||||
SearchButton: {
|
||||
render: (entry: any) => (
|
||||
<Button variant="transparent">
|
||||
<MagnifyingGlassMini {...entry.props}>
|
||||
{entry.props.label}
|
||||
</MagnifyingGlassMini>
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
InputSearchButton: {
|
||||
render: (entry: any) => <SearchButton {...entry.props} />,
|
||||
},
|
||||
UserButton: {
|
||||
render: (entry: any) => (
|
||||
<Button variant="transparent">
|
||||
<User {...entry.props}>{entry.props.label}</User>
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
FavoriteButton: {
|
||||
render: (entry: any) => (
|
||||
<Button variant="transparent">
|
||||
<Heart {...entry.props}>{entry.props.label}</Heart>
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
CartButton: simple(CartButton),
|
||||
Suspense: {
|
||||
render: (entry: any, ctx: LayoutContext) => (
|
||||
<React.Suspense
|
||||
fallback={
|
||||
entry.props?.fallback ? (
|
||||
<DynamicLayoutRenderer nodes={entry.props.fallback} context={ctx} />
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{entry.children ? (
|
||||
<DynamicLayoutRenderer nodes={entry.children} context={ctx} />
|
||||
) : null}
|
||||
</React.Suspense>
|
||||
),
|
||||
},
|
||||
CartMismatchBanner: simple(CartMismatchBanner),
|
||||
FreeShippingPriceNudge: simple(FreeShippingPriceNudge),
|
||||
PropsChildren: {
|
||||
render: (_props, ctx) => ctx.contentChildren, // PageLayout's props.children
|
||||
},
|
||||
Footer: simple(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]
|
||||
@@ -0,0 +1 @@
|
||||
export interface FooterProps { copyrightText?: string }
|
||||
@@ -1,11 +1,8 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import { jsonFileNames } from "./devJsonFileNames";
|
||||
|
||||
const fileName = jsonFileNames.namVibentec;
|
||||
|
||||
export async function loadDesignConfig() {
|
||||
const filePath = path.join(process.cwd(), "config", fileName)
|
||||
export async function loadDesignConfig(designFile: string) {
|
||||
const filePath = path.join(process.cwd(), "config", designFile)
|
||||
const fileData = await fs.promises.readFile(filePath, "utf-8")
|
||||
return JSON.parse(fileData)
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export const jsonFileNames = {
|
||||
steMedusaStarter: "ste.medusa-starter.design.json",
|
||||
stePlayGround: "ste.playground.design.json",
|
||||
nam3Bear: "nam.3bear.design.json",
|
||||
namDrsquatch: "nam.drsquatch.design.json",
|
||||
namVibentec: "nam.vibentec.design.json",
|
||||
namStarter: "nam.mds-starter-design.json",
|
||||
};
|
||||
+20
-21
@@ -1,28 +1,27 @@
|
||||
import React from "react"
|
||||
import { ComponentName, LayoutComponentDefinition, LayoutComponentNode, LayoutContext, componentMap } from "./component-map"
|
||||
import {
|
||||
LayoutComponentNode,
|
||||
LayoutContext,
|
||||
componentMap,
|
||||
} from "./component-map"
|
||||
|
||||
export interface DynamicLayoutRendererProps {
|
||||
nodes: LayoutComponentNode[]
|
||||
context: LayoutContext
|
||||
}
|
||||
|
||||
export function DynamicLayoutRenderer({ nodes, context } : DynamicLayoutRendererProps) {
|
||||
|
||||
const nodeArray = Array.isArray(nodes) ? nodes : [nodes];
|
||||
|
||||
return nodeArray.map((entry, index) => {
|
||||
const [key, value] = Object.entries(entry)[0] as [ComponentName, LayoutComponentDefinition]
|
||||
|
||||
if (!value) {
|
||||
console.warn(`[UI-Builder] Component definition is undefined: ${key}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const component = componentMap[key];
|
||||
if (!component) {
|
||||
console.warn(`[UI-Builder] Unknown component: ${key}`);
|
||||
return null;
|
||||
}
|
||||
return <React.Fragment key={`${key}-${index}`}>{component.render(value, context)}</React.Fragment>
|
||||
})
|
||||
}
|
||||
export function DynamicLayoutRenderer({
|
||||
nodes,
|
||||
context,
|
||||
}: DynamicLayoutRendererProps) {
|
||||
return nodes.map((entry, index) => {
|
||||
const [key, value] = Object.entries(entry)[0] as [string, any]
|
||||
const component = componentMap[key]
|
||||
if (!component) return null
|
||||
return (
|
||||
<React.Fragment key={index}>
|
||||
{component.render(value, context)}
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
|
||||
export interface LayoutContext {
|
||||
customer: HttpTypes.StoreCustomer | null
|
||||
cart: HttpTypes.StoreCart | null
|
||||
shippingOptions: HttpTypes.StoreShippingOption[]
|
||||
contentChildren: React.ReactNode
|
||||
}
|
||||
|
||||
export interface LayoutComponentDefinition<P = Record<string, unknown>> {
|
||||
props?: P
|
||||
children?: LayoutComponentNode[]
|
||||
}
|
||||
|
||||
// Maps key = componentName to value = props + children
|
||||
export type LayoutComponentNode = Record<string, LayoutComponentDefinition>
|
||||
|
||||
export type ComponentRenderer<P = unknown> = {
|
||||
render: (entry: LayoutComponentDefinition<P>, ctx: LayoutContext) => React.ReactNode
|
||||
}
|
||||
|
||||
export type ComponentMap = Record<string, ComponentRenderer>
|
||||
+41
-4
@@ -9,13 +9,10 @@ module.exports = {
|
||||
"./src/components/**/*.{js,ts,jsx,tsx}",
|
||||
"./src/modules/**/*.{js,ts,jsx,tsx}",
|
||||
"./node_modules/@medusajs/ui/dist/**/*.{js,jsx,ts,tsx}",
|
||||
"./config/*json",
|
||||
"./config/**/*.json",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
'custom-gradient': 'linear-gradient(90deg, #E6EFFC 0%, #285A86 100%)',
|
||||
},
|
||||
transitionProperty: {
|
||||
width: "width margin",
|
||||
height: "height",
|
||||
@@ -144,6 +141,38 @@ module.exports = {
|
||||
"0%": { transform: "translateY(-100%)" },
|
||||
"100%": { transform: "translateY(0)" },
|
||||
},
|
||||
enterFromRight: {
|
||||
from: { opacity: "0", transform: "translateX(200px)" },
|
||||
to: { opacity: "1", transform: "translateX(0)" },
|
||||
},
|
||||
enterFromLeft: {
|
||||
from: { opacity: "0", transform: "translateX(-200px)" },
|
||||
to: { opacity: "1", transform: "translateX(0)" },
|
||||
},
|
||||
exitToRight: {
|
||||
from: { opacity: "1", transform: "translateX(0)" },
|
||||
to: { opacity: "0", transform: "translateX(200px)" },
|
||||
},
|
||||
exitToLeft: {
|
||||
from: { opacity: "1", transform: "translateX(0)" },
|
||||
to: { opacity: "0", transform: "translateX(-200px)" },
|
||||
},
|
||||
scaleIn: {
|
||||
from: { opacity: "0", transform: "rotateX(-10deg) scale(0.9)" },
|
||||
to: { opacity: "1", transform: "rotateX(0deg) scale(1)" },
|
||||
},
|
||||
scaleOut: {
|
||||
from: { opacity: "1", transform: "rotateX(0deg) scale(1)" },
|
||||
to: { opacity: "0", transform: "rotateX(-10deg) scale(0.95)" },
|
||||
},
|
||||
fadeIn: {
|
||||
from: { opacity: "0" },
|
||||
to: { opacity: "1" },
|
||||
},
|
||||
fadeOut: {
|
||||
from: { opacity: "1" },
|
||||
to: { opacity: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
ring: "ring 2.2s cubic-bezier(0.5, 0, 0.5, 1) infinite",
|
||||
@@ -159,6 +188,14 @@ module.exports = {
|
||||
enter: "enter 200ms ease-out",
|
||||
"slide-in": "slide-in 1.2s cubic-bezier(.41,.73,.51,1.02)",
|
||||
leave: "leave 150ms ease-in forwards",
|
||||
scaleIn: "scaleIn 200ms ease",
|
||||
scaleOut: "scaleOut 200ms ease",
|
||||
fadeIn: "fadeIn 200ms ease",
|
||||
fadeOut: "fadeOut 200ms ease",
|
||||
enterFromLeft: "enterFromLeft 250ms ease",
|
||||
enterFromRight: "enterFromRight 250ms ease",
|
||||
exitToLeft: "exitToLeft 250ms ease",
|
||||
exitToRight: "exitToRight 250ms ease",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+1
-2
@@ -18,8 +18,7 @@
|
||||
"paths": {
|
||||
"@lib/*": ["lib/*"],
|
||||
"@modules/*": ["modules/*"],
|
||||
"@pages/*": ["pages/*"],
|
||||
"@vibentec/*": ["vibentec/*"],
|
||||
"@pages/*": ["pages/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user