update: style of show button icon product card

This commit is contained in:
Nam Doan 2025-12-22 15:01:18 +07:00
parent 6912f2605c
commit 005b10484b
8 changed files with 194 additions and 79 deletions

View File

@ -168,6 +168,52 @@
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "drsquatch-best-seller",
"styles": {
"container": "content-container py-12 px-[100px] small:py-24",
"header": {
"container": "flex justify-center mb-8",
"title": "text-[28px] font-bold text-[#1f3521]",
"isShowViewAll": false
},
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
"productCard": {
"card": "shadow-none border-none",
"className": "relative overflow-hidden rounded-2xl bg-white shadow-elevation-card-rest h-full flex flex-col",
"badgeText": "LIMITED EDITION",
"badge": {
"container": "absolute z-[1] top-0 left-0 pt-4",
"text": "uppercase px-4 py-2 bg-[#3B6F47] text-white"
},
"thumbnail": {
"className": "rounded-none h-[300px] shadow-none",
"size": "full"
},
"content": " flex flex-col flex-1",
"title": "mt-2 text-[#1f3521] text-[22px] font-bold",
"price": "mt-2 text-[#3B6F47] font-bold text-[20px] flex gap-3 flex-row-reverse justify-end",
"reviews": {
"container": "mt-3 flex items-center gap-2",
"stars": "flex gap-1",
"star": "text-[#C4622C] text-xl leading-none",
"emptyStar": "text-[#C4622C] text-xl opacity-30 leading-none",
"text": "text-[#1f3521]",
"rating": 3.6,
"count": 59
},
"button": {
"addToCart": "mt-6 w-full bg-[#C4622C] hover:bg-[#C4622C]/90 shadow-none text-white rounded-none h-fit font-bold",
"moreInfo": "hidden"
}
}
}
}
}
},
{
"CartMismatchBanner": {
"config": {

View File

@ -142,6 +142,13 @@
}
}
},
{
"VtFeaturedProducts": {
"config": {
"title": "best-seller"
}
}
},
{
"CartMismatchBanner": {
"config": {

View File

@ -188,7 +188,11 @@
"./banner-hero-1.webp",
"./banner-hero-2.webp"
],
"links": ["/", "/account", "/product"]
"links": [
"/",
"/account",
"/product"
]
}
},
"left": [
@ -219,12 +223,12 @@
"container": "content-container py-12 px-[100px] small:py-24",
"header": {
"container": "flex justify-between mb-8",
"title": "txt-xlarge",
"link": ""
"title": "text-[56px] text-[#11314E]",
"isShowViewAll": false
},
"list": "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
"productCard": {
"className": "relative overflow-hidden rounded-2xl border border-[#285A86] bg-ui-bg-base shadow-elevation-card-rest h-full flex flex-col",
"card": "relative overflow-hidden rounded-2xl border border-[#285A86] bg-ui-bg-base shadow-elevation-card-rest h-full flex flex-col",
"badge": {
"container": "p-4",
"text": "z-20 px-3 py-1 border-[0.5px] rounded bg-[#c9e0f5] txt-compact-small-plus shadow-borders-base text-[#285A86]"
@ -233,19 +237,19 @@
"className": "rounded-none h-[240px]",
"size": "full"
},
"subtitle": "text-ui-fg-subtle text-[14px]",
"content": "flex flex-col flex-1 justify-between p-4",
"title": "text-ui-fg-subtle text-[18px]",
"price": "flex items-center gap-x-1 text-[#285A86] font-bold",
"price": "flex items-center gap-x-1 text-[#285A86] font-bold border-b pb-4",
"button": {
"addToCart": "w-full h-[40px] bg-black text-white rounded-md",
"moreInfo": "w-full h-[40px] border border-[#285A86] text-[#285A86] rounded-md"
"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": {

View File

@ -63,7 +63,7 @@ export const listProducts = async ({
offset,
region_id: region?.id,
fields:
"*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags",
"*variants.calculated_price,+variants.inventory_quantity,*metadata,+tags",
...queryParams,
},
headers,

View File

@ -31,21 +31,24 @@ export default async function ProductRail({
}
const classes = {
container: styles?.container || "content-container py-12 px-[100px] small:py-24",
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",
container: styles?.header?.container ?? "flex justify-between mb-8",
title: styles?.header?.title ?? "txt-xlarge",
isShowViewAll: styles?.header.isShowViewAll ?? true,
},
list: styles?.list || "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
list: styles?.list ?? "grid grid-cols-2 small:grid-cols-3 gap-x-6 gap-y-24 small:gap-y-36",
}
return (
<div className={classes.container}>
<div className={classes.header.container}>
<Text className={classes.header.title}>{collection.title}</Text>
{classes.header.isShowViewAll && (
<InteractiveLink href={`/collections/${collection.handle}`}>
View all
</InteractiveLink>
)}
</div>
<ul className={classes.list}>
{pricedProducts &&
@ -55,6 +58,7 @@ export default async function ProductRail({
product={product}
countryCode={countryCode}
styles={styles?.productCard}
badgeText={styles?.productCard?.badgeText}
/>
</li>
))}

View File

@ -18,7 +18,7 @@ export default async function PreviewPrice({ price }: { price: VariantPrice }) {
)}
<Text
className={clx("text-ui-fg-muted", {
"text-ui-fg-interactive": price.price_type === "sale",
"text-green-700": price.price_type === "sale",
})}
data-testid="price"
>

View File

@ -6,7 +6,7 @@ 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
@ -32,7 +32,7 @@ export default function ProductCard({
if (firstVariant.allow_backorder) return true
return (firstVariant.inventory_quantity || 0) > 0
})()
console.dir(product, { depth: null })
const { cheapestPrice } = getProductPrice({ product })
async function handleAddToCart() {
@ -46,42 +46,55 @@ export default function ProductCard({
}
const description = (() => {
const description = product.description || ""
const textSlice = description.length > 120 ? description.slice(0, 117) + "…" : description
const prodDescription = product.description || ""
const textSlice =
prodDescription.length > 120
? prodDescription.slice(0, 117) + "…"
: prodDescription
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",
card: styles?.card ?? className ?? "",
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] ",
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",
className: styles?.thumbnail?.className ?? "rounded-none h-[240px]",
size: styles?.thumbnail?.size ?? "full",
},
subtitle: styles?.subtitle ?? "",
content: styles?.content ?? "p-6 flex flex-col flex-1",
title: styles?.title ?? "mt-2 text-ui-fg-base",
price: styles?.price ?? "mt-2 flex items-baseline gap-2",
reviews: {
container: styles?.reviews?.container ?? undefined,
stars: styles?.reviews?.stars ?? "flex gap-1",
star: styles?.reviews?.star ?? "text-[#C4622C] text-xl leading-none",
emptyStar:
styles?.reviews?.emptyStar ??
"text-[#C4622C] text-xl opacity-30 leading-none",
text: styles?.reviews?.text ?? "txt-small text-ui-fg-subtle",
rating: styles?.reviews?.rating,
count: styles?.reviews?.count,
},
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",
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={clx(classes.card)}>
<LocalizedClientLink href={`/products/${product.handle}`} className="block">
<div className="relative">
{badgeText && (
<div className={classes.badge.container}>
<span className={classes.badge.text}>
{badgeText}
</span>
<span className={classes.badge.text}>{badgeText}</span>
</div>
)}
<VtThumbnail
@ -92,17 +105,19 @@ export default function ProductCard({
isFeatured
/>
</div>
</LocalizedClientLink>
<div className={classes.content}>
{product.collection && (
{classes.subtitle && product.collection && (
<LocalizedClientLink
href={`/collections/${product.collection.handle}`}
className="txt-small text-ui-fg-muted hover:text-ui-fg-subtle"
>
{product.collection.title}
{product.subtitle}
</LocalizedClientLink>
)}
<LocalizedClientLink href={`/products/${product.handle}`} className="block">
<Heading
level="h3"
className={classes.title}
@ -110,22 +125,66 @@ export default function ProductCard({
>
{product.title}
</Heading>
</LocalizedClientLink>
<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 />
{(classes.reviews.rating !== undefined ||
classes.reviews.count !== undefined) && (
<div
className={
classes.reviews.container || "mt-2 flex items-center gap-3"
}
>
<div className="relative inline-block">
<div className={classes.reviews.stars}>
{Array.from({ length: 5 }).map((_, i) => (
<span
key={`star-empty-${i}`}
className={classes.reviews.emptyStar}
>
</span>
))}
</div>
<div
className="absolute inset-0 overflow-hidden"
style={{
width: `${Math.max(
0,
Math.min(
100,
(((classes.reviews.rating as number) ?? 0) / 5) * 100
)
)}%`,
}}
>
<div className={classes.reviews.stars}>
{Array.from({ length: 5 }).map((_, i) => (
<span
key={`star-fills-${i}`}
className={classes.reviews.star}
>
</span>
))}
</div>
</div>
</div>
{typeof classes.reviews.count === "number" && (
<span className={classes.reviews.text}>
{classes.reviews.count} Reviews
</span>
)}
</div>
)}
<Text className="txt-small text-ui-fg-subtle">{description}</Text>
<Text className="mt-1 txt-small text-ui-fg-muted">
Lieferzeit: {deliveryTime}
</Text>
{/* <div className="my-4">
<Divider />
</div> */}
<Text className="txt-small text-[#285A86] my-4">{description}</Text>
<div className="flex gap-3 mt-auto">
<Button
@ -133,20 +192,15 @@ export default function ProductCard({
disabled={!inStock}
variant="primary"
className={classes.button.addToCart}
data-testid="product-card-add-to-cart"
>
Add to cart
Add to cart {classes.button.isShowIcon && <Plus />}
</Button>
<LocalizedClientLink
href={`/products/${product.handle}`}
className="flex-1"
>
<Button
variant="secondary"
className={classes.button.moreInfo}
data-testid="product-card-more-info"
>
More Info
<Button variant="secondary" className={classes.button.moreInfo}>
More Info {classes.button.isShowIcon && <ChevronRight />}
</Button>
</LocalizedClientLink>
</div>

View File

@ -2,7 +2,7 @@ import fs from "fs"
import path from "path"
import { jsonFileNames } from "./devJsonFileNames";
const fileName = jsonFileNames.namVibentec;
const fileName = jsonFileNames.namDrsquatch;
export async function loadDesignConfig() {
const filePath = path.join(process.cwd(), "config", fileName)