feat: implement footer and update json file design

This commit is contained in:
Nam Doan 2025-12-01 16:34:47 +07:00
parent c0b492b394
commit 154d52732d
4 changed files with 262 additions and 146 deletions

View File

@ -156,7 +156,7 @@
{
"Button": {
"config": {
"icon": "user",
"icon": "User",
"className": " flex items-center gap-1 shadow-none w-[50px]"
}
}
@ -164,7 +164,7 @@
{
"Button": {
"config": {
"icon": "heart",
"icon": "Heart",
"className": " flex items-center gap-1 shadow-none w-[50px]"
}
}
@ -204,7 +204,228 @@
{
"Footer": {
"config": {
"copyrightText": "© 2025 MyShop"
"copyrightText": "© 2025 MyShop",
"children": [
{
"Image": {
"config": {
"src": "/VibentecIT-logo.svg",
"alt": "MyShop",
"className": "h-[128px] w-[324px] ml-[3rem]",
"objectFit": "contain"
}
}
},
{
"Section": {
"config": {
"className": "flex items-start px-[3rem] justify-between"
},
"children": [
{
"Section": {
"config": {
"className": "flex flex-col"
},
"children": [
{
"Link": {
"config": {
"label": "Der Wegbereiter für innovative IT-Lösungen",
"href": "/",
"className": "text-[24px] leading-[125%] text-[#11314E] flex mr-8 gap-1 hover:underline font-bold w-[336px] pl-[2rem] mb-[22px]"
}
}
},
{
"Link": {
"config": {
"label": "Tauchen Sie ein in eine Welt modernster Technologien, zuverlässiger Support und proaktiver Innovation gemeinsam gestalten wir die digitale Zukunft Ihres Unternehmens.",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[336px] pl-[2rem] hover:no-underline"
}
}
},
{
"Button": {
"config": {
"label": "Kontaktieren Sie Uns",
"labelClassName": "order-[1]",
"iconClassName": "order-[2]",
"icon": "ChevronRight",
"className": "mt-[24px] flex items-center w-fit hover:bg-black gap-1 shadow-none p-[1rem] bg-[#18181B] text-white ml-[2rem]"
}
}
}
]
}
},
{
"Section": {
"config": {
"className": "flex mt-[2rem]"
},
"children": [
{
"Section": {
"config": {
"className": "flex flex-col"
},
"children": [
{
"Link": {
"config": {
"label": "Unternehmen",
"href": "/",
"className": "text-[24px] leading-[125%] text-[#11314E] flex mr-8 gap-1 hover:underline font-bold w-[200px] pl-[2rem] mb-[16px]"
}
}
},
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[200px] pl-[2rem] hover:no-underline"
}
}
},
{
"Link": {
"config": {
"label": "Placeholder",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[200px] pl-[2rem] hover:no-underline"
}
}
},
{
"Link": {
"config": {
"label": "Placeholder",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[200px] pl-[2rem] hover:no-underline"
}
}
}
]
}
},
{
"Section": {
"config": {
"className": "flex flex-col gap-[16px]"
},
"children": [
{
"Link": {
"config": {
"label": "Social Media",
"href": "/",
"className": "text-[24px] leading-[125%] text-[#11314E] flex mr-8 gap-1 hover:underline font-bold w-[200px] pl-[2rem] mt-[-15px] mb-[-16px]"
}
}
},
{
"Button": {
"config": {
"label": "Über Uns",
"icon": "Twitter",
"className": "text-[13px] leading-[160%] text-[#11314E] flex justify-start font-semibold mr-8 gap-1 shadow-none w-[200px] pl-[2rem] hover:bg-transparent"
}
}
},
{
"Button": {
"config": {
"label": "Über Uns",
"icon": "Twitter",
"className": "text-[13px] leading-[160%] text-[#11314E] flex justify-start font-semibold mr-8 gap-1 shadow-none w-[200px] pl-[2rem] hover:bg-transparent"
}
}
},
{
"Button": {
"config": {
"label": "Über Uns",
"icon": "Twitter",
"className": "text-[13px] leading-[160%] text-[#11314E] flex justify-start font-semibold mr-8 gap-1 shadow-none w-[200px] pl-[2rem] hover:bg-transparent"
}
}
}
]
}
},
{
"Section": {
"config": {
"className": "flex flex-col"
},
"children": [
{
"Link": {
"config": {
"label": "Addresse",
"href": "/",
"className": "text-[24px] leading-[125%] text-[#11314E] flex mr-8 gap-1 hover:underline font-bold w-[200px] pl-[2rem] mb-[16px]"
}
}
},
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[200px] pl-[2rem] hover:no-underline"
}
}
},
{
"Link": {
"config": {
"label": "Social Media",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[200px] pl-[2rem] hover:no-underline"
}
}
},
{
"Link": {
"config": {
"label": "Über Uns",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[200px] pl-[2rem] hover:no-underline"
}
}
}
]
}
}
]
}
},
{
"Section": {
"config": {
"className": "flex flex-col mt-[5rem]"
},
"children": [
{
"Link": {
"config": {
"label": "Tauchen Sie ein in eine Welt modernster Technologien, zuverlässiger Support und proaktiver Innovation gemeinsam gestalten wir die digitale Zukunft Ihres Unternehmens.",
"href": "/",
"className": "text-[13px] leading-[160%] text-[#11314E] flex items-center mr-8 gap-1 w-[336px] pl-[2rem] hover:no-underline"
}
}
}
]
}
}
]
}
}
]
}
}
}

View File

@ -3,157 +3,28 @@ import { listCollections } from "@lib/data/collections"
import { Text, clx } from "@medusajs/ui"
import LocalizedClientLink from "@modules/common/components/localized-client-link"
import MedusaCTA from "@modules/layout/components/medusa-cta"
import { LayoutComponentDefinition, LayoutContext } from "vibentec/component-map";
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({
nodes,
context,
}: {
nodes?: LayoutComponentDefinition
context: LayoutContext
}) {
const { collections } = await listCollections({
fields: "*products",
})
const productCategories = await listCategories()
const props = nodes?.config ?? {}
const copyrightText = props.copyrightText ?? "";
return (
<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>
<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>
</div>
<footer className="relative mx-auto border-b duration-200 bg-white border-ui-border-base border-t-2">
{props.children && <DynamicLayoutRenderer nodes={props.children} context={context} />}
</footer>
)
}

View File

@ -0,0 +1,22 @@
import {
LayoutComponentDefinition,
LayoutContext,
} from "@vibentec/component-map"
import { DynamicLayoutRenderer } from "@vibentec/renderer"
export default function VtSection({
nodes,
context,
}: {
nodes: LayoutComponentDefinition
context: LayoutContext
}) {
const props = nodes.config || {}
return (
<section className={props.className}>
{nodes.children && (
<DynamicLayoutRenderer nodes={nodes.children} context={context} />
)}
</section>
)
}

View File

@ -16,6 +16,7 @@ import VtImage from "@modules/layout/templates/vt-image"
import VtDropdown from "@modules/layout/templates/vt-dropdown"
import VtButton from "@modules/layout/templates/vt-button"
import VtSearchInput from "@modules/layout/templates/vt-search-input"
import VtSection from "@modules/layout/templates/vt-section"
type ComponentConfig = Record<string, any>;
@ -58,6 +59,7 @@ export const componentMap: Record<string, ComponentRenderer> = {
HomeButton: nodesContextRenderer(HomeButton),
AccountButton: nodesContextRenderer(AccountButton),
Button: nodesContextRenderer(VtButton),
Section: nodesContextRenderer(VtSection),
SearchInput: nodesContextRenderer(VtSearchInput),
VtCartButton: nodesContextRenderer(VtCartButton),
Link: nodesContextRenderer(VtLink),