Compare commits
No commits in common. "2067a237566be6097f717321047abd0483435e6c" and "a2e5b56eb8b856802006e10ec54755c106eb998c" have entirely different histories.
2067a23756
...
a2e5b56eb8
|
|
@ -142,13 +142,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"VtFeaturedProducts": {
|
||||
"config": {
|
||||
"title": "best-seller"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"CartMismatchBanner": {
|
||||
"config": {
|
||||
|
|
|
|||
|
|
@ -211,40 +211,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"VtFeaturedProducts": {
|
||||
"config": {
|
||||
"title": "best-seller",
|
||||
"styles": {
|
||||
"container": "content-container py-12 px-[100px] small:py-24",
|
||||
"header": {
|
||||
"container": "flex justify-between mb-8",
|
||||
"title": "txt-xlarge",
|
||||
"link": ""
|
||||
},
|
||||
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
|
||||
"productCard": {
|
||||
"className": "relative overflow-hidden rounded-2xl border border-[#285A86] bg-ui-bg-base shadow-elevation-card-rest h-full flex flex-col",
|
||||
"badge": {
|
||||
"container": "p-4",
|
||||
"text": "z-20 px-3 py-1 border-[0.5px] rounded bg-[#c9e0f5] txt-compact-small-plus shadow-borders-base text-[#285A86]"
|
||||
},
|
||||
"thumbnail": {
|
||||
"className": "rounded-none h-[240px]",
|
||||
"size": "full"
|
||||
},
|
||||
"content": "flex flex-col flex-1 justify-between p-4",
|
||||
"title": "text-ui-fg-subtle text-[18px]",
|
||||
"price": "flex items-center gap-x-1 text-[#285A86] font-bold",
|
||||
"button": {
|
||||
"addToCart": "w-fit h-[40px] bg-black text-white rounded-md",
|
||||
"moreInfo": "w-fit h-[40px] border border-[#285A86] text-[#285A86] rounded-md"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"CartMismatchBanner": {
|
||||
"config": {
|
||||
|
|
|
|||
|
|
@ -8,19 +8,11 @@ import { DynamicLayoutRenderer } from "../../../vibentec/renderer"
|
|||
import { LayoutContext, LayoutComponentNode, } from "../../../vibentec/component-map"
|
||||
import { loadDesignConfig } from "vibentec/configloader"
|
||||
|
||||
import { getRegion } from "@lib/data/regions"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(getBaseURL()),
|
||||
}
|
||||
|
||||
export default async function PageLayout(props: {
|
||||
children: React.ReactNode
|
||||
params: Promise<{ countryCode: string }>
|
||||
}) {
|
||||
const params = await props.params
|
||||
const { countryCode } = params
|
||||
const region = await getRegion(countryCode)
|
||||
export default async function PageLayout(props: { children: React.ReactNode }) {
|
||||
const customer = await retrieveCustomer()
|
||||
const cart = await retrieveCart()
|
||||
let shippingOptions: StoreCartShippingOption[] = []
|
||||
|
|
@ -37,8 +29,6 @@ export default async function PageLayout(props: {
|
|||
cart,
|
||||
shippingOptions,
|
||||
contentChildren: props.children,
|
||||
countryCode,
|
||||
region,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import FeaturedProducts from "@modules/home/components/featured-products"
|
|||
import Hero from "@modules/home/components/hero"
|
||||
import { listCollections } from "@lib/data/collections"
|
||||
import { getRegion } from "@lib/data/regions"
|
||||
import VtFeaturedProducts from "@modules/home/components/vt-featured-products"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Medusa Next.js Starter Template",
|
||||
|
|
@ -24,20 +23,22 @@ export default async function Home(props: {
|
|||
const { collections } = await listCollections({
|
||||
fields: "id, handle, title",
|
||||
})
|
||||
|
||||
console.log('collections:',collections)
|
||||
const res = await listCollections({
|
||||
fields: "id, handle, title",
|
||||
})
|
||||
|
||||
if (!collections || !region) {
|
||||
return null
|
||||
}
|
||||
console.log(res, '--------------')
|
||||
return (
|
||||
<>
|
||||
{/* <Hero /> */}
|
||||
{/* <div className="py-12">
|
||||
<div className="py-12">
|
||||
<ul className="flex flex-col gap-x-6">
|
||||
<VtFeaturedProducts collections={collections} region={region} countryCode={countryCode} />
|
||||
<FeaturedProducts collections={collections} region={region} />
|
||||
</ul>
|
||||
</div> */}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export const listCollections = async (
|
|||
{
|
||||
query: queryParams,
|
||||
next,
|
||||
cache: "force-cache",
|
||||
}
|
||||
)
|
||||
.then(({ collections }) => ({ collections, count: collections.length }))
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export const listProducts = async ({
|
|||
},
|
||||
headers,
|
||||
next,
|
||||
cache: "force-cache",
|
||||
}
|
||||
)
|
||||
.then(({ products, count }) => {
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
import { HttpTypes } from "@medusajs/types"
|
||||
import ProductRail from "./product-rail"
|
||||
import { listCollections } from "@lib/data/collections"
|
||||
import { LayoutComponentDefinition, LayoutContext } from "@vibentec/component-map"
|
||||
|
||||
export default async function VtFeaturedProducts(props: {
|
||||
collections?: HttpTypes.StoreCollection[]
|
||||
region?: HttpTypes.StoreRegion
|
||||
countryCode?: string
|
||||
nodes?: LayoutComponentDefinition
|
||||
context?: LayoutContext
|
||||
}) {
|
||||
let { collections, region, countryCode } = props
|
||||
const { nodes, context } = props
|
||||
|
||||
if (context) {
|
||||
if (!region) region = context.region
|
||||
if (!countryCode) countryCode = context.countryCode
|
||||
}
|
||||
|
||||
if (!collections && region) {
|
||||
const result = await listCollections({
|
||||
fields: "id, handle, title",
|
||||
})
|
||||
collections = result.collections
|
||||
}
|
||||
|
||||
if (!collections || !region || !countryCode) {
|
||||
return null
|
||||
}
|
||||
|
||||
const configTitle = nodes?.config?.title
|
||||
const styles = nodes?.config?.styles
|
||||
|
||||
let displayCollections = collections
|
||||
if (configTitle) {
|
||||
displayCollections = collections.filter(
|
||||
(c) => c.handle === configTitle || c.title === configTitle
|
||||
)
|
||||
}
|
||||
|
||||
return displayCollections.map((collection) => (
|
||||
<li key={collection.id}>
|
||||
<ProductRail
|
||||
collection={collection}
|
||||
region={region}
|
||||
countryCode={countryCode}
|
||||
styles={styles}
|
||||
/>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import { listProducts } from "@lib/data/products"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Text, clx } from "@medusajs/ui"
|
||||
|
||||
import InteractiveLink from "@modules/common/components/interactive-link"
|
||||
import ProductCard from "@modules/products/components/vt-product-card"
|
||||
|
||||
export default async function ProductRail({
|
||||
collection,
|
||||
region,
|
||||
countryCode,
|
||||
styles,
|
||||
}: {
|
||||
collection: HttpTypes.StoreCollection
|
||||
region: HttpTypes.StoreRegion
|
||||
countryCode: string
|
||||
styles?: any
|
||||
}) {
|
||||
const {
|
||||
response: { products: pricedProducts },
|
||||
} = await listProducts({
|
||||
regionId: region.id,
|
||||
queryParams: {
|
||||
collection_id: collection.id,
|
||||
fields: "*variants.calculated_price",
|
||||
},
|
||||
})
|
||||
|
||||
if (!pricedProducts) {
|
||||
return null
|
||||
}
|
||||
|
||||
const classes = {
|
||||
container: styles?.container || "content-container py-12 px-[100px] small:py-24",
|
||||
header: {
|
||||
container: styles?.header?.container || "flex justify-between mb-8",
|
||||
title: styles?.header?.title || "txt-xlarge",
|
||||
},
|
||||
list: styles?.list || "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<div className={classes.header.container}>
|
||||
<Text className={classes.header.title}>{collection.title}</Text>
|
||||
<InteractiveLink href={`/collections/${collection.handle}`}>
|
||||
View all
|
||||
</InteractiveLink>
|
||||
</div>
|
||||
<ul className={classes.list}>
|
||||
{pricedProducts &&
|
||||
pricedProducts.map((product) => (
|
||||
<li key={product.id}>
|
||||
<ProductCard
|
||||
product={product}
|
||||
countryCode={countryCode}
|
||||
styles={styles?.productCard}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
import { HttpTypes } from "@medusajs/types"
|
||||
import { Button, Heading, Text, clx } from "@medusajs/ui"
|
||||
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
||||
import Divider from "@modules/common/components/divider"
|
||||
import PreviewPrice from "@modules/products/components/product-preview/price"
|
||||
import { getProductPrice } from "@lib/util/get-product-price"
|
||||
import { addToCart } from "@lib/data/cart"
|
||||
import VtThumbnail from "../vt-thumbnail"
|
||||
import { Plus, ChevronRight } from "@medusajs/icons"
|
||||
type ProductCardProps = {
|
||||
product: HttpTypes.StoreProduct
|
||||
badgeText?: string
|
||||
deliveryTime?: string
|
||||
className?: string
|
||||
countryCode: string
|
||||
styles?: any
|
||||
}
|
||||
|
||||
export default function ProductCard({
|
||||
product,
|
||||
badgeText = "Saved up to 20%",
|
||||
deliveryTime = "2-4 Wochen",
|
||||
className,
|
||||
countryCode,
|
||||
styles,
|
||||
}: ProductCardProps) {
|
||||
const firstVariant = product.variants?.[0]
|
||||
|
||||
const inStock = (() => {
|
||||
if (!firstVariant) return false
|
||||
if (!firstVariant.manage_inventory) return true
|
||||
if (firstVariant.allow_backorder) return true
|
||||
return (firstVariant.inventory_quantity || 0) > 0
|
||||
})()
|
||||
|
||||
const { cheapestPrice } = getProductPrice({ product })
|
||||
|
||||
async function handleAddToCart() {
|
||||
"use server"
|
||||
if (!firstVariant?.id) return
|
||||
await addToCart({
|
||||
variantId: firstVariant.id,
|
||||
quantity: 1,
|
||||
countryCode,
|
||||
})
|
||||
}
|
||||
|
||||
const description = (() => {
|
||||
const description = product.description || ""
|
||||
const textSlice = description.length > 120 ? description.slice(0, 117) + "…" : description
|
||||
return textSlice
|
||||
})()
|
||||
|
||||
const classes = {
|
||||
card: styles?.className || className || "relative overflow-hidden rounded-2xl border border-[#285A86] bg-ui-bg-base shadow-elevation-card-rest h-full flex flex-col",
|
||||
badge: {
|
||||
container: styles?.badge?.container || "p-4",
|
||||
text: styles?.badge?.text || "z-20 px-3 py-1 border-[0.5px] rounded bg-[#c9e0f5] txt-compact-small-plus shadow-borders-base text-[#285A86] ",
|
||||
},
|
||||
thumbnail: {
|
||||
className: styles?.thumbnail?.className || "rounded-none h-[240px]",
|
||||
size: styles?.thumbnail?.size || "full",
|
||||
},
|
||||
content: styles?.content || "p-6 flex flex-col flex-1",
|
||||
title: styles?.title || "mt-2 text-ui-fg-base",
|
||||
price: styles?.price || "mt-2 flex items-baseline gap-2",
|
||||
button: {
|
||||
addToCart: styles?.button?.addToCart || "flex-1",
|
||||
moreInfo: styles?.button?.moreInfo || "w-full",
|
||||
isShowIcon: styles?.button?.isShowIcon || true,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clx(
|
||||
classes.card
|
||||
)}
|
||||
>
|
||||
<div className="relative">
|
||||
{badgeText && (
|
||||
<div className={classes.badge.container}>
|
||||
<span className={classes.badge.text}>
|
||||
{badgeText}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<VtThumbnail
|
||||
thumbnail={product.thumbnail}
|
||||
className={classes.thumbnail.className}
|
||||
images={product.images}
|
||||
size={classes.thumbnail.size}
|
||||
isFeatured
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={classes.content}>
|
||||
{product.collection && (
|
||||
<LocalizedClientLink
|
||||
href={`/collections/${product.collection.handle}`}
|
||||
className="txt-small text-ui-fg-muted hover:text-ui-fg-subtle"
|
||||
>
|
||||
{product.collection.title}
|
||||
</LocalizedClientLink>
|
||||
)}
|
||||
|
||||
<Heading
|
||||
level="h3"
|
||||
className={classes.title}
|
||||
data-testid="product-card-title"
|
||||
>
|
||||
{product.title}
|
||||
</Heading>
|
||||
|
||||
<div className={classes.price}>
|
||||
{cheapestPrice && <PreviewPrice price={cheapestPrice} />}
|
||||
</div>
|
||||
<Text className="mt-1 txt-compact-small text-ui-fg-muted">
|
||||
inkl. MwSt. zzgl. Versandkosten
|
||||
</Text>
|
||||
|
||||
<div className="my-4">
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
<Text className="txt-small text-ui-fg-subtle">{description}</Text>
|
||||
<Text className="my-3 txt-small text-ui-fg-muted">
|
||||
Lieferzeit: {deliveryTime}
|
||||
</Text>
|
||||
|
||||
<div className="flex gap-3 mt-auto">
|
||||
<Button
|
||||
formAction={handleAddToCart}
|
||||
disabled={!inStock}
|
||||
variant="primary"
|
||||
className={classes.button.addToCart}
|
||||
>
|
||||
Add to cart {classes.button.isShowIcon && <Plus />}
|
||||
</Button>
|
||||
<LocalizedClientLink
|
||||
href={`/products/${product.handle}`}
|
||||
className="flex-1"
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className={classes.button.moreInfo}
|
||||
>
|
||||
More Info {classes.button.isShowIcon && <ChevronRight />}
|
||||
</Button>
|
||||
</LocalizedClientLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import { Container, clx } from "@medusajs/ui"
|
||||
import Image from "next/image"
|
||||
import React from "react"
|
||||
|
||||
import PlaceholderImage from "@modules/common/icons/placeholder-image"
|
||||
|
||||
type ThumbnailProps = {
|
||||
thumbnail?: string | null
|
||||
// TODO: Fix image typings
|
||||
images?: any[] | null
|
||||
size?: "small" | "medium" | "large" | "full" | "square"
|
||||
isFeatured?: boolean
|
||||
className?: string
|
||||
"data-testid"?: string
|
||||
}
|
||||
|
||||
const VtThumbnail: React.FC<ThumbnailProps> = ({
|
||||
thumbnail,
|
||||
images,
|
||||
size = "small",
|
||||
isFeatured,
|
||||
className,
|
||||
"data-testid": dataTestid,
|
||||
}) => {
|
||||
const initialImage = thumbnail || images?.[0]?.url
|
||||
|
||||
return (
|
||||
<Container
|
||||
className={clx(
|
||||
"relative w-full overflow-hidden p-4 bg-ui-bg-subtle shadow-elevation-card-rest group-hover:shadow-elevation-card-hover transition-shadow ease-in-out duration-150",
|
||||
className,
|
||||
{
|
||||
"aspect-[11/14]": isFeatured,
|
||||
"aspect-[9/16]": !isFeatured && size !== "square",
|
||||
"aspect-[1/1]": size === "square",
|
||||
"w-[180px]": size === "small",
|
||||
"w-[290px]": size === "medium",
|
||||
"w-[440px]": size === "large",
|
||||
"w-full": size === "full",
|
||||
}
|
||||
)}
|
||||
data-testid={dataTestid}
|
||||
>
|
||||
<ImageOrPlaceholder image={initialImage} size={size} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const ImageOrPlaceholder = ({
|
||||
image,
|
||||
size,
|
||||
}: Pick<ThumbnailProps, "size"> & { image?: string }) => {
|
||||
return image ? (
|
||||
<Image
|
||||
src={image}
|
||||
alt="Thumbnail"
|
||||
className="absolute inset-0 object-cover object-center"
|
||||
draggable={false}
|
||||
quality={50}
|
||||
sizes="(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px"
|
||||
fill
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full absolute inset-0 flex items-center justify-center">
|
||||
<PlaceholderImage size={size === "small" ? 16 : 24} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default VtThumbnail
|
||||
|
|
@ -25,7 +25,6 @@ import VtFooterSignUp from "@modules/layout/templates/vt-footer/vt-footer-signup
|
|||
import Hero from "@modules/layout/templates/hero"
|
||||
import { VtCarousel } from "@modules/layout/templates/vt-carousel"
|
||||
import { VtCtaBanner } from "@modules/layout/templates/vt-cta-banner"
|
||||
import VtFeaturedProducts from "@modules/home/components/vt-featured-products"
|
||||
|
||||
type ComponentConfig = Record<string, any>
|
||||
|
||||
|
|
@ -39,8 +38,6 @@ export interface LayoutContext {
|
|||
cart: any
|
||||
shippingOptions: any[]
|
||||
contentChildren: React.ReactNode
|
||||
countryCode?: string
|
||||
region?: any
|
||||
}
|
||||
|
||||
export type ComponentRenderer = {
|
||||
|
|
@ -100,7 +97,6 @@ export const componentMap: Record<string, ComponentRenderer> = {
|
|||
VtFooterSignUp: nodesContextRenderer(VtFooterSignUp),
|
||||
Footer: nodesContextRenderer(VtFooter),
|
||||
ImageDisplayer: nodesContextRenderer(VtCarousel),
|
||||
VtFeaturedProducts: nodesContextRenderer(VtFeaturedProducts),
|
||||
}
|
||||
|
||||
export type ComponentName = keyof typeof componentMap
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import fs from "fs"
|
|||
import path from "path"
|
||||
import { jsonFileNames } from "./devJsonFileNames";
|
||||
|
||||
const fileName = jsonFileNames.namVibentec;
|
||||
const fileName = jsonFileNames.namStarter;
|
||||
|
||||
export async function loadDesignConfig() {
|
||||
const filePath = path.join(process.cwd(), "config", fileName)
|
||||
|
|
|
|||
Loading…
Reference in New Issue