Skip to content

Commit 88dccbb

Browse files
committed
fix: product detail data
1 parent 6868d2c commit 88dccbb

8 files changed

Lines changed: 229 additions & 43 deletions

File tree

apps/herbatika/src/components/product-detail/product-detail.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export type ProductOfferState = {
2929
standardAmount: number | null;
3030
actionAmount: number | null;
3131
hasActiveDiscount: boolean;
32+
applyLoyaltyDiscount: boolean;
33+
applyQuantityDiscount: boolean;
34+
applyVolumeDiscount: boolean;
3235
};
3336

3437
export type ProductMediaFact = {

apps/herbatika/src/components/product-detail/sections/product-detail-hero.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,15 @@ export function ProductDetailHero({
6363
return (
6464
<section className="grid gap-500 xl:grid-cols-2">
6565
<ProductDetailMediaColumn
66+
discountPercent={discountPercent}
6667
galleryItems={galleryItems}
6768
mediaFacts={mediaFacts}
68-
offerState={offerState}
6969
/>
7070

7171
<div className="space-y-300">
7272
<ProductDetailPurchasePanel
7373
canAddToCart={canAddToCart}
7474
currentAmountLabel={currentAmountLabel}
75-
discountPercent={discountPercent}
7675
displayOriginalLabel={displayOriginalLabel}
7776
isAdding={isAdding}
7877
offerState={offerState}

apps/herbatika/src/components/product-detail/sections/product-detail-media-column.tsx

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,33 @@
11
"use client";
22

3+
import { Badge } from "@techsio/ui-kit/atoms/badge";
34
import { Icon } from "@techsio/ui-kit/atoms/icon";
45
import { Link } from "@techsio/ui-kit/atoms/link";
56
import { LinkButton } from "@techsio/ui-kit/atoms/link-button";
67
import { Gallery, type GalleryItem } from "@techsio/ui-kit/organisms/gallery";
78
import NextLink from "next/link";
89
import NextImage from "next/image";
9-
import type {
10-
ProductMediaFact,
11-
ProductOfferState,
12-
} from "@/components/product-detail/product-detail.types";
10+
import type { ProductMediaFact } from "@/components/product-detail/product-detail.types";
1311
import { SupportingText } from "@/components/text/supporting-text";
1412

1513
type ProductDetailMediaColumnProps = {
14+
discountPercent: number | null;
1615
galleryItems: GalleryItem[];
1716
mediaFacts: ProductMediaFact[];
18-
offerState: ProductOfferState;
1917
};
2018

2119
export function ProductDetailMediaColumn({
20+
discountPercent,
2221
galleryItems,
2322
mediaFacts,
24-
offerState,
2523
}: ProductDetailMediaColumnProps) {
2624
return (
2725
<div className="space-y-300">
2826
<Gallery
2927
items={galleryItems}
3028
orientation="vertical"
31-
thumbnailSize={72}
29+
thumbnailSize={88}
30+
hideThumbnailsWhenSingle={false}
3231
className="md:grid-cols-[auto_minmax(0,1fr)]"
3332
carouselProps={{
3433
aspectRatio: "square",
@@ -43,16 +42,25 @@ export function ProductDetailMediaColumn({
4342
className="md:col-start-1 md:row-start-1"
4443
listClassName="gap-100"
4544
/>
46-
<Gallery.Main className="flex-col overflow-hidden rounded-lg border border-border-secondary bg-surface md:col-start-2 md:row-start-1">
45+
<Gallery.Main className="relative flex-col overflow-hidden rounded-lg border border-border-secondary bg-surface md:col-start-2 md:row-start-1">
46+
{typeof discountPercent === "number" && discountPercent > 0 ? (
47+
<Badge
48+
className="absolute top-300 right-300 z-1 flex w-850 aspect-square rounded-full text-sm"
49+
variant="discount"
50+
>
51+
{`-${discountPercent}%`}
52+
</Badge>
53+
) : null}
54+
4755
<Gallery.Carousel>
4856
<Gallery.Slides />
4957
</Gallery.Carousel>
5058

5159
{mediaFacts.length > 0 ? (
52-
<div className="flex items-center justify-center divide-x divide-border-secondary border-t border-border-secondary bg-surface">
60+
<div className="flex items-center justify-center divide-x divide-border-secondary border-t border-border-secondary bg-surface p-550">
5361
{mediaFacts.slice(0, 2).map((fact) => (
5462
<div className="flex items-center gap-200 px-350 py-250" key={fact.id}>
55-
<span className="flex h-600 w-600 items-center justify-center rounded-md bg-highlight">
63+
<span className="flex h-600 w-600 items-center justify-center rounded-xs bg-highlight">
5664
<Icon className="text-lg text-primary" icon={fact.icon} />
5765
</span>
5866
<SupportingText className="text-md leading-snug text-fg-secondary">

apps/herbatika/src/components/product-detail/sections/product-detail-purchase-panel.tsx

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,47 @@ import type {
1313
StorefrontProduct,
1414
} from "@/components/product-detail/product-detail.types"
1515
import { normalizeCategoryName } from "@/components/product-detail/utils/metadata-parsers"
16+
import { asRecord, asString } from "@/components/product-detail/utils/value-utils"
17+
import { resolveFlags } from "@/components/product-card/product-card.flags"
18+
19+
type ProductInfoLink = {
20+
href: string | null
21+
label: string
22+
}
23+
24+
const resolveProductInfoLink = (
25+
product: StorefrontProduct,
26+
primaryCategory?: HttpTypes.StoreProductCategory,
27+
): ProductInfoLink | null => {
28+
const producer = asRecord(
29+
(product as StorefrontProduct & { producer?: unknown }).producer,
30+
)
31+
const producerTitle = asString(producer?.title)
32+
33+
if (producerTitle) {
34+
const producerHandle = asString(producer?.handle)
35+
36+
return {
37+
href: producerHandle
38+
? `/search?brand=${encodeURIComponent(producerHandle)}`
39+
: null,
40+
label: producerTitle,
41+
}
42+
}
43+
44+
if (!primaryCategory?.handle) {
45+
return null
46+
}
47+
48+
return {
49+
href: `/c/${primaryCategory.handle}`,
50+
label: normalizeCategoryName(primaryCategory.name),
51+
}
52+
}
1653

1754
type ProductDetailPurchasePanelProps = {
1855
canAddToCart: boolean
1956
currentAmountLabel: string
20-
discountPercent: number | null
2157
displayOriginalLabel: string | null
2258
isAdding: boolean
2359
offerState: ProductOfferState
@@ -53,6 +89,8 @@ export function ProductDetailPurchasePanel({
5389
vipCreditLabel,
5490
}: ProductDetailPurchasePanelProps) {
5591
const primaryCategory = productCategories[0]
92+
const productInfoLink = resolveProductInfoLink(product, primaryCategory)
93+
const flags = resolveFlags(product, Boolean(displayOriginalLabel))
5694
const displayHighlights = productHighlights
5795
.map((highlight) => highlight.replace(/\s+/g, " ").trim())
5896
.filter(Boolean)
@@ -61,29 +99,39 @@ export function ProductDetailPurchasePanel({
6199
return (
62100
<div className="space-y-300 rounded-lg border border-border-secondary bg-surface p-350">
63101
<div className="flex min-h-600 flex-wrap items-start gap-200">
64-
{offerState.hasActiveDiscount ? (
65-
<Badge className="font-bold" variant="tertiary">
66-
Akcia
102+
{flags.map((flag) => (
103+
<Badge
104+
className="leading-tight font-bold"
105+
key={`${product.id}-${flag.label}`}
106+
variant={flag.variant}
107+
>
108+
{flag.label}
67109
</Badge>
68-
) : null}
110+
))}
69111

70112
<div className="ml-auto flex min-w-0 items-center gap-300 sm:gap-500">
71113
<div className="min-w-0 flex flex-wrap items-center gap-x-100 gap-y-50">
72114
<span className="text-fg-placeholder text-sm leading-tight">
73115
ID: {offerState.code ?? product.handle}
74116
</span>
75-
{primaryCategory?.handle ? (
117+
{productInfoLink ? (
76118
<>
77119
<span className="text-fg-placeholder text-sm leading-tight">
78120
79121
</span>
80-
<Link
81-
as={NextLink}
82-
className="min-w-0 break-words font-normal text-primary text-sm leading-tight underline hover:text-primary-strong"
83-
href={`/c/${primaryCategory.handle}`}
84-
>
85-
{normalizeCategoryName(primaryCategory.name)}
86-
</Link>
122+
{productInfoLink.href ? (
123+
<Link
124+
as={NextLink}
125+
className="min-w-0 break-words font-normal text-primary text-sm leading-tight underline hover:text-primary-strong"
126+
href={productInfoLink.href}
127+
>
128+
{productInfoLink.label}
129+
</Link>
130+
) : (
131+
<span className="min-w-0 break-words text-primary text-sm leading-tight">
132+
{productInfoLink.label}
133+
</span>
134+
)}
87135
</>
88136
) : null}
89137
</div>

apps/herbatika/src/components/product-detail/use-product-detail-data.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,13 @@ export function useProductDetailData({ handle }: UseProductDetailDataProps) {
164164
[currentAmount, displayOriginalAmount],
165165
);
166166
const vipCreditLabel = useMemo(
167-
() => resolveVipCreditLabel(currentAmount, currentCurrencyCode),
168-
[currentAmount, currentCurrencyCode],
167+
() =>
168+
resolveVipCreditLabel(
169+
currentAmount,
170+
currentCurrencyCode,
171+
offerState.applyLoyaltyDiscount,
172+
),
173+
[currentAmount, currentCurrencyCode, offerState.applyLoyaltyDiscount],
169174
);
170175

171176
const unitPriceLabel = useMemo(() => {
@@ -175,20 +180,32 @@ export function useProductDetailData({ handle }: UseProductDetailDataProps) {
175180
currentAmount,
176181
currentAmountWithoutTax,
177182
currencyCode: currentCurrencyCode,
183+
mediaFacts,
178184
unitLabel: offerState.unitLabel,
179185
vatRate,
180186
});
181187
}, [
182188
currentAmount,
183189
currentAmountWithoutTax,
184190
currentCurrencyCode,
191+
mediaFacts,
185192
offerState.offerSource,
186193
offerState.unitLabel,
187194
]);
188195

189196
const volumeDiscountOptions = useMemo(
190-
() => resolveVolumeDiscountOptions(currentAmount, currentCurrencyCode),
191-
[currentAmount, currentCurrencyCode],
197+
() =>
198+
resolveVolumeDiscountOptions(
199+
currentAmount,
200+
currentCurrencyCode,
201+
offerState.applyQuantityDiscount || offerState.applyVolumeDiscount,
202+
),
203+
[
204+
currentAmount,
205+
currentCurrencyCode,
206+
offerState.applyQuantityDiscount,
207+
offerState.applyVolumeDiscount,
208+
],
192209
);
193210

194211
const selectedVolumeDiscountOption = useMemo(() => {

apps/herbatika/src/components/product-detail/utils/metadata-parsers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ export const resolveOfferState = (
160160
standardAmount: asNumber(source?.standard_price),
161161
actionAmount,
162162
hasActiveDiscount,
163+
applyLoyaltyDiscount: asBoolean(source?.apply_loyalty_discount) === true,
164+
applyQuantityDiscount: asBoolean(source?.apply_quantity_discount) === true,
165+
applyVolumeDiscount: asBoolean(source?.apply_volume_discount) === true,
163166
};
164167
};
165168

0 commit comments

Comments
 (0)