@gfed-medusa/sf-lib-products 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/behavior-tracker/index.d.ts +12 -0
- package/dist/components/behavior-tracker/index.d.ts.map +1 -0
- package/dist/components/behavior-tracker/index.js +149 -0
- package/dist/components/behavior-tracker/index.js.map +1 -0
- package/dist/components/browse-product-preview/index.d.ts +2 -2
- package/dist/components/browse-product-preview/index.js +3 -3
- package/dist/components/browse-product-preview/index.js.map +1 -1
- package/dist/components/image-gallery/index.d.ts +2 -2
- package/dist/components/pagination/index.d.ts +16 -6
- package/dist/components/pagination/index.d.ts.map +1 -1
- package/dist/components/pagination/index.js +71 -54
- package/dist/components/pagination/index.js.map +1 -1
- package/dist/components/product-actions/index.d.ts +2 -2
- package/dist/components/product-actions/index.d.ts.map +1 -1
- package/dist/components/product-actions/index.js +8 -8
- package/dist/components/product-actions/index.js.map +1 -1
- package/dist/components/product-actions/mobile-actions.d.ts.map +1 -1
- package/dist/components/product-actions/mobile-actions.js +12 -27
- package/dist/components/product-actions/mobile-actions.js.map +1 -1
- package/dist/components/product-actions/option-select.js +1 -1
- package/dist/components/product-onboarding-cta/index.d.ts +2 -2
- package/dist/components/product-onboarding-cta/index.d.ts.map +1 -1
- package/dist/components/product-price/index.d.ts +2 -2
- package/dist/components/product-price/index.d.ts.map +1 -1
- package/dist/components/product-tabs/accordion.js +1 -1
- package/dist/components/product-tabs/index.d.ts +2 -2
- package/dist/components/refinement-list/index.d.ts +2 -2
- package/dist/components/refinement-list/index.d.ts.map +1 -1
- package/dist/components/refinement-list/index.js +1 -1
- package/dist/components/refinement-list/sort-products/index.d.ts +2 -2
- package/dist/components/refinement-list/sort-products/index.d.ts.map +1 -1
- package/dist/components/related-products/index.d.ts +2 -2
- package/dist/components/related-products/index.d.ts.map +1 -1
- package/dist/components/related-products/index.js +1 -1
- package/dist/components/related-products/index.js.map +1 -1
- package/dist/components/skeleton-product-grid/index.d.ts +2 -2
- package/dist/components/skeleton-product-grid/index.d.ts.map +1 -1
- package/dist/components/skeleton-product-grid/index.js +1 -1
- package/dist/components/skeleton-product-grid/index.js.map +1 -1
- package/dist/components/skeleton-product-preview/index.d.ts +2 -2
- package/dist/components/skeleton-product-preview/index.js +3 -3
- package/dist/components/skeleton-product-preview/index.js.map +1 -1
- package/dist/components/skeleton-related-products/index.d.ts +2 -2
- package/dist/components/skeleton-related-products/index.js +1 -1
- package/dist/components/skeleton-related-products/index.js.map +1 -1
- package/dist/lib/gql/fragments/product.d.ts +16 -16
- package/dist/lib/gql/mutations/cart.d.ts +10 -10
- package/dist/lib/gql/queries/cart.d.ts +2 -2
- package/dist/lib/gql/queries/cart.d.ts.map +1 -1
- package/dist/lib/gql/queries/product.d.ts +13 -13
- package/dist/templates/paginated-products/client.d.ts +30 -0
- package/dist/templates/paginated-products/client.d.ts.map +1 -0
- package/dist/templates/paginated-products/client.js +212 -0
- package/dist/templates/paginated-products/client.js.map +1 -0
- package/dist/templates/paginated-products/index.d.ts +3 -3
- package/dist/templates/paginated-products/index.d.ts.map +1 -1
- package/dist/templates/paginated-products/index.js +27 -23
- package/dist/templates/paginated-products/index.js.map +1 -1
- package/dist/templates/product-actions-wrapper/index.d.ts +2 -2
- package/dist/templates/product-actions-wrapper/index.d.ts.map +1 -1
- package/dist/templates/product-info/index.d.ts +2 -2
- package/dist/templates/product-info/index.d.ts.map +1 -1
- package/dist/templates/product-template/index.d.ts.map +1 -1
- package/dist/templates/product-template/index.js +5 -3
- package/dist/templates/product-template/index.js.map +1 -1
- package/dist/templates/store-template/index.d.ts +2 -2
- package/dist/templates/store-template/index.d.ts.map +1 -1
- package/dist/templates/store-template/index.js +2 -3
- package/dist/templates/store-template/index.js.map +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Product } from "../../types/graphql.js";
|
|
2
|
+
|
|
3
|
+
//#region src/components/behavior-tracker/index.d.ts
|
|
4
|
+
interface BehaviorTrackerProps {
|
|
5
|
+
product: Product;
|
|
6
|
+
}
|
|
7
|
+
declare function BehaviorTracker({
|
|
8
|
+
product
|
|
9
|
+
}: BehaviorTrackerProps): null;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { BehaviorTracker, BehaviorTracker as default, BehaviorTrackerProps };
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/behavior-tracker/index.tsx"],"sourcesContent":[],"mappings":";;;UAkJiB,oBAAA;WACN;AADX;AAIgB,iBAAA,eAAA,CAAkB;EAAA;AAA+B,CAApB,EAAA,oBAAoB,CAAA,EAAA,IAAA"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import Cookies from "js-cookie";
|
|
5
|
+
import { PERSONALIZATION_CONFIG, getSegmentIdFromCollection } from "@gfed-medusa/sf-lib-common/lib/personalization/config";
|
|
6
|
+
import { useStorefrontContext } from "@gfed-medusa/sf-lib-common/lib/data/context";
|
|
7
|
+
|
|
8
|
+
//#region src/components/behavior-tracker/index.tsx
|
|
9
|
+
const PDP_HESITATION_MS = PERSONALIZATION_CONFIG.pdpHesitationMs;
|
|
10
|
+
const HIGH_SCROLL_THRESHOLD = PERSONALIZATION_CONFIG.highScrollThreshold;
|
|
11
|
+
const PRICE_THRESHOLD_USD = PERSONALIZATION_CONFIG.priceThresholdUsd;
|
|
12
|
+
const SIGNAL_COOKIE = "_jg_segment";
|
|
13
|
+
const cookieOptions = {
|
|
14
|
+
path: "/",
|
|
15
|
+
sameSite: "none",
|
|
16
|
+
secure: true,
|
|
17
|
+
domain: ".justgood.win"
|
|
18
|
+
};
|
|
19
|
+
function getScrollPercentage() {
|
|
20
|
+
const scrollTop = window.scrollY;
|
|
21
|
+
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
22
|
+
return docHeight > 0 ? scrollTop / docHeight : 0;
|
|
23
|
+
}
|
|
24
|
+
function getCheapestVariantPrice(product) {
|
|
25
|
+
const variants = product.variants;
|
|
26
|
+
if (!variants || variants.length === 0) return null;
|
|
27
|
+
let cheapest = null;
|
|
28
|
+
for (const variant of variants) {
|
|
29
|
+
const priceObj = variant.price;
|
|
30
|
+
if (priceObj && typeof priceObj.amount === "number") {
|
|
31
|
+
if (cheapest === null || priceObj.amount < cheapest) cheapest = priceObj.amount;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return cheapest;
|
|
35
|
+
}
|
|
36
|
+
function throttle(fn, limit) {
|
|
37
|
+
let lastCall = 0;
|
|
38
|
+
return ((...args) => {
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
if (now - lastCall >= limit) {
|
|
41
|
+
lastCall = now;
|
|
42
|
+
fn(...args);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function getSegmentData() {
|
|
47
|
+
const value = Cookies.get(SIGNAL_COOKIE);
|
|
48
|
+
if (!value) return {};
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(decodeURIComponent(value));
|
|
51
|
+
} catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function emitSignal(type, payload) {
|
|
56
|
+
const data = getSegmentData();
|
|
57
|
+
if (!data.signals || typeof data.signals !== "object") data.signals = {};
|
|
58
|
+
data.signals[type] = payload ?? true;
|
|
59
|
+
Cookies.set(SIGNAL_COOKIE, encodeURIComponent(JSON.stringify(data)), {
|
|
60
|
+
...cookieOptions,
|
|
61
|
+
expires: 7
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const REPEAT_CATEGORY_THRESHOLD = 3;
|
|
65
|
+
function trackRepeatedCategoryView(segment) {
|
|
66
|
+
try {
|
|
67
|
+
let data = getSegmentData();
|
|
68
|
+
if (Object.keys(data).length === 0) data = { signals: {} };
|
|
69
|
+
if (!data.signals || typeof data.signals !== "object") data.signals = {};
|
|
70
|
+
const categoryCounts = data.signals["repeated-category-view"] ?? {};
|
|
71
|
+
const newCount = (categoryCounts[segment] ?? 0) + 1;
|
|
72
|
+
categoryCounts[segment] = newCount;
|
|
73
|
+
data.signals["repeated-category-view"] = categoryCounts;
|
|
74
|
+
if (newCount >= REPEAT_CATEGORY_THRESHOLD) emitSignal("repeated-category-view", {
|
|
75
|
+
segment,
|
|
76
|
+
count: newCount
|
|
77
|
+
});
|
|
78
|
+
Cookies.set(SIGNAL_COOKIE, encodeURIComponent(JSON.stringify(data)), {
|
|
79
|
+
...cookieOptions,
|
|
80
|
+
expires: 7
|
|
81
|
+
});
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
function addHistoryToCookie(segment) {
|
|
85
|
+
try {
|
|
86
|
+
let data = getSegmentData();
|
|
87
|
+
if (Object.keys(data).length === 0) data = { history: [] };
|
|
88
|
+
if (!data.history || !Array.isArray(data.history)) data.history = [];
|
|
89
|
+
const history = [...data.history, segment].slice(-PERSONALIZATION_CONFIG.historyMaxLength);
|
|
90
|
+
data.history = history;
|
|
91
|
+
Cookies.set(SIGNAL_COOKIE, encodeURIComponent(JSON.stringify(data)), {
|
|
92
|
+
...cookieOptions,
|
|
93
|
+
expires: 7
|
|
94
|
+
});
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
function BehaviorTracker({ product }) {
|
|
98
|
+
const { cartId } = useStorefrontContext();
|
|
99
|
+
const hesitationTimeoutRef = useRef(null);
|
|
100
|
+
const scrollHandlerRef = useRef(null);
|
|
101
|
+
const scrollTrackedRef = useRef(false);
|
|
102
|
+
useRef(false);
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (!product?.id) return;
|
|
105
|
+
const collectionHandle = product.collection?.handle;
|
|
106
|
+
const segment = getSegmentIdFromCollection(collectionHandle);
|
|
107
|
+
if (segment) {
|
|
108
|
+
addHistoryToCookie(segment);
|
|
109
|
+
trackRepeatedCategoryView(segment);
|
|
110
|
+
}
|
|
111
|
+
const productPrice = getCheapestVariantPrice(product);
|
|
112
|
+
const hasCart = Boolean(cartId);
|
|
113
|
+
if (productPrice && productPrice >= PRICE_THRESHOLD_USD && !hasCart) hesitationTimeoutRef.current = setTimeout(() => {
|
|
114
|
+
emitSignal("pdp-hesitation", {
|
|
115
|
+
productId: product.id,
|
|
116
|
+
price: productPrice
|
|
117
|
+
});
|
|
118
|
+
}, PDP_HESITATION_MS);
|
|
119
|
+
const handleScroll = throttle(() => {
|
|
120
|
+
if (scrollTrackedRef.current) return;
|
|
121
|
+
if (getScrollPercentage() >= HIGH_SCROLL_THRESHOLD && !hasCart) {
|
|
122
|
+
scrollTrackedRef.current = true;
|
|
123
|
+
emitSignal("high-scroll-no-action", {
|
|
124
|
+
productId: product.id,
|
|
125
|
+
scrollDepth: HIGH_SCROLL_THRESHOLD
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}, 500);
|
|
129
|
+
scrollHandlerRef.current = handleScroll;
|
|
130
|
+
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
131
|
+
return () => {
|
|
132
|
+
if (hesitationTimeoutRef.current !== null) {
|
|
133
|
+
clearTimeout(hesitationTimeoutRef.current);
|
|
134
|
+
hesitationTimeoutRef.current = null;
|
|
135
|
+
}
|
|
136
|
+
if (scrollHandlerRef.current !== null) {
|
|
137
|
+
window.removeEventListener("scroll", scrollHandlerRef.current);
|
|
138
|
+
scrollHandlerRef.current = null;
|
|
139
|
+
}
|
|
140
|
+
scrollTrackedRef.current = false;
|
|
141
|
+
};
|
|
142
|
+
}, [product, cartId]);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
var behavior_tracker_default = BehaviorTracker;
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
export { BehaviorTracker, behavior_tracker_default as default };
|
|
149
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["cookieOptions: Cookies.CookieAttributes","cheapest: number | null"],"sources":["../../../src/components/behavior-tracker/index.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\nimport Cookies from 'js-cookie';\n\nimport {\n PERSONALIZATION_CONFIG,\n getSegmentIdFromCollection,\n} from '@gfed-medusa/sf-lib-common/lib/personalization/config';\nimport { useStorefrontContext } from '@gfed-medusa/sf-lib-common/lib/data/context';\nimport { Product } from '@/types/graphql';\n\nconst PDP_HESITATION_MS = PERSONALIZATION_CONFIG.pdpHesitationMs;\nconst HIGH_SCROLL_THRESHOLD = PERSONALIZATION_CONFIG.highScrollThreshold;\nconst PRICE_THRESHOLD_USD = PERSONALIZATION_CONFIG.priceThresholdUsd;\n\nconst SIGNAL_COOKIE = '_jg_segment';\nconst isProd = process.env.NODE_ENV === 'production';\n\nconst cookieOptions: Cookies.CookieAttributes = isProd\n ? { path: '/', sameSite: 'none', secure: true, domain: '.justgood.win' }\n : { path: '/', sameSite: 'lax' };\n\nfunction getScrollPercentage(): number {\n const scrollTop = window.scrollY;\n const docHeight =\n document.documentElement.scrollHeight - window.innerHeight;\n return docHeight > 0 ? scrollTop / docHeight : 0;\n}\n\nfunction getCheapestVariantPrice(product: Product): number | null {\n const variants = product.variants;\n if (!variants || variants.length === 0) return null;\n\n let cheapest: number | null = null;\n\n for (const variant of variants) {\n const priceObj = variant.price;\n if (priceObj && typeof priceObj.amount === 'number') {\n if (cheapest === null || priceObj.amount < cheapest) {\n cheapest = priceObj.amount;\n }\n }\n }\n\n return cheapest;\n}\n\nfunction throttle<T extends (...args: unknown[]) => void>(\n fn: T,\n limit: number\n): T {\n let lastCall = 0;\n return ((...args: unknown[]) => {\n const now = Date.now();\n if (now - lastCall >= limit) {\n lastCall = now;\n fn(...args);\n }\n }) as T;\n}\n\nfunction getSegmentData(): Record<string, unknown> {\n const value = Cookies.get(SIGNAL_COOKIE);\n if (!value) return {};\n try {\n return JSON.parse(decodeURIComponent(value));\n } catch {\n return {};\n }\n}\n\nfunction emitSignal(type: string, payload?: unknown) {\n const data = getSegmentData();\n if (!data.signals || typeof data.signals !== 'object') {\n data.signals = {};\n }\n\n (data.signals as Record<string, unknown>)[type] = payload ?? true;\n\n Cookies.set(\n SIGNAL_COOKIE,\n encodeURIComponent(JSON.stringify(data)),\n { ...cookieOptions, expires: 7 }\n );\n}\n\nconst REPEAT_CATEGORY_THRESHOLD = 3;\n\nfunction trackRepeatedCategoryView(segment: string) {\n try {\n let data = getSegmentData();\n if (Object.keys(data).length === 0) {\n data = { signals: {} };\n }\n\n if (!data.signals || typeof data.signals !== 'object') {\n data.signals = {};\n }\n\n const categoryCounts = (data.signals as Record<string, unknown>)['repeated-category-view'] as Record<string, number> ?? {};\n const currentCount = categoryCounts[segment] ?? 0;\n const newCount = currentCount + 1;\n\n categoryCounts[segment] = newCount;\n (data.signals as Record<string, unknown>)['repeated-category-view'] = categoryCounts;\n\n if (newCount >= REPEAT_CATEGORY_THRESHOLD) {\n emitSignal('repeated-category-view', { segment, count: newCount });\n }\n\n Cookies.set(\n SIGNAL_COOKIE,\n encodeURIComponent(JSON.stringify(data)),\n { ...cookieOptions, expires: 7 }\n );\n } catch {\n // Ignore\n }\n}\n\nfunction addHistoryToCookie(segment: string): void {\n try {\n let data = getSegmentData();\n if (Object.keys(data).length === 0) {\n data = { history: [] };\n }\n\n if (!data.history || !Array.isArray(data.history)) {\n data.history = [];\n }\n\n const history = [...(data.history as string[]), segment].slice(\n -PERSONALIZATION_CONFIG.historyMaxLength\n );\n data.history = history;\n\n Cookies.set(\n SIGNAL_COOKIE,\n encodeURIComponent(JSON.stringify(data)),\n { ...cookieOptions, expires: 7 }\n );\n } catch {\n }\n}\n\nexport interface BehaviorTrackerProps {\n product: Product;\n}\n\nexport function BehaviorTracker({ product }: BehaviorTrackerProps) {\n const { cartId } = useStorefrontContext();\n const hesitationTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(\n null\n );\n const scrollHandlerRef = useRef<(() => void) | null>(null);\n const scrollTrackedRef = useRef(false);\n const historyTrackedRef = useRef(false);\n\n useEffect(() => {\n if (!product?.id) return;\n\n const collectionHandle = product.collection?.handle;\n const segment = getSegmentIdFromCollection(collectionHandle);\n\n if (segment) {\n addHistoryToCookie(segment);\n trackRepeatedCategoryView(segment);\n }\n\n const productPrice = getCheapestVariantPrice(product);\n const hasCart = Boolean(cartId);\n\n if (productPrice && productPrice >= PRICE_THRESHOLD_USD && !hasCart) {\n hesitationTimeoutRef.current = setTimeout(() => {\n emitSignal('pdp-hesitation', {\n productId: product.id,\n price: productPrice,\n });\n }, PDP_HESITATION_MS);\n }\n\n const handleScroll = throttle(() => {\n if (scrollTrackedRef.current) return;\n if (getScrollPercentage() >= HIGH_SCROLL_THRESHOLD && !hasCart) {\n scrollTrackedRef.current = true;\n emitSignal('high-scroll-no-action', {\n productId: product.id,\n scrollDepth: HIGH_SCROLL_THRESHOLD,\n });\n }\n }, 500);\n\n scrollHandlerRef.current = handleScroll;\n window.addEventListener('scroll', handleScroll, { passive: true });\n\n return () => {\n if (hesitationTimeoutRef.current !== null) {\n clearTimeout(hesitationTimeoutRef.current);\n hesitationTimeoutRef.current = null;\n }\n if (scrollHandlerRef.current !== null) {\n window.removeEventListener('scroll', scrollHandlerRef.current);\n scrollHandlerRef.current = null;\n }\n scrollTrackedRef.current = false;\n };\n }, [product, cartId]);\n\n return null;\n}\n\nexport default BehaviorTracker;"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BrowseProductHitFragment } from "../../types/graphql.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/components/browse-product-preview/index.d.ts
|
|
5
5
|
type BrowseProductPreviewProps = {
|
|
@@ -13,7 +13,7 @@ declare const BrowseProductPreview: ({
|
|
|
13
13
|
isFeatured,
|
|
14
14
|
imagePriority,
|
|
15
15
|
imageFetchPriority
|
|
16
|
-
}: BrowseProductPreviewProps) =>
|
|
16
|
+
}: BrowseProductPreviewProps) => react_jsx_runtime0.JSX.Element;
|
|
17
17
|
//#endregion
|
|
18
18
|
export { BrowseProductPreview };
|
|
19
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -42,13 +42,13 @@ const BrowseProductPreview = ({ product, isFeatured, imagePriority = false, imag
|
|
|
42
42
|
imagePriority,
|
|
43
43
|
imageFetchPriority
|
|
44
44
|
}), /* @__PURE__ */ jsxs("div", {
|
|
45
|
-
className: "txt-compact-medium mt-4 flex items-start
|
|
45
|
+
className: "txt-compact-medium mt-4 flex min-w-0 flex-col items-start gap-y-1 text-left",
|
|
46
46
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
47
|
-
className: "text-ui-fg-subtle
|
|
47
|
+
className: "text-ui-fg-subtle w-full min-w-0 overflow-hidden break-words whitespace-normal [display:-webkit-box] [-webkit-box-orient:vertical] [-webkit-line-clamp:2]",
|
|
48
48
|
"data-testid": "product-title",
|
|
49
49
|
children: product.title
|
|
50
50
|
}), /* @__PURE__ */ jsx("div", {
|
|
51
|
-
className: "flex
|
|
51
|
+
className: "flex w-full min-w-0 flex-wrap items-baseline gap-x-2 gap-y-1 text-left",
|
|
52
52
|
children: price && /* @__PURE__ */ jsx(PreviewPrice, { price })
|
|
53
53
|
})]
|
|
54
54
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/browse-product-preview/index.tsx"],"sourcesContent":["import { LocalizedClientLink } from '@gfed-medusa/sf-lib-common/components/localized-client-link';\nimport { PreviewPrice } from '@gfed-medusa/sf-lib-common/components/preview-price';\nimport { Thumbnail } from '@gfed-medusa/sf-lib-common/components/thumbnail';\nimport { getPercentageDiff } from '@gfed-medusa/sf-lib-common/lib/utils/get-percentage-diff';\nimport { convertToLocale } from '@gfed-medusa/sf-lib-common/lib/utils/money';\nimport type { VariantPrice } from '@gfed-medusa/sf-lib-common/types/prices';\nimport { Text } from '@medusajs/ui';\n\nimport type { BrowseProductHitFragment } from '@/types/graphql';\n\ntype BrowseProductPreviewProps = {\n product: BrowseProductHitFragment;\n isFeatured?: boolean;\n imagePriority?: boolean;\n imageFetchPriority?: 'auto' | 'high' | 'low';\n};\n\nconst getBrowseProductPrice = (\n product: BrowseProductHitFragment\n): VariantPrice | null => {\n if (\n typeof product.priceAmount !== 'number' ||\n !product.currencyCode ||\n !product.currencyCode.trim()\n ) {\n return null;\n }\n\n const originalPrice = product.originalPriceAmount ?? product.priceAmount;\n const isSale =\n typeof originalPrice === 'number' && originalPrice > product.priceAmount;\n\n return {\n calculated_price_number: product.priceAmount,\n calculated_price: convertToLocale({\n amount: product.priceAmount,\n currency_code: product.currencyCode,\n }),\n original_price_number: originalPrice,\n original_price: convertToLocale({\n amount: originalPrice,\n currency_code: product.currencyCode,\n }),\n currency_code: product.currencyCode,\n price_type: isSale ? 'sale' : 'default',\n percentage_diff:\n isSale && originalPrice > 0\n ? getPercentageDiff(originalPrice, product.priceAmount)\n : '0',\n };\n};\n\nconst BrowseProductPreview = ({\n product,\n isFeatured,\n imagePriority = false,\n imageFetchPriority,\n}: BrowseProductPreviewProps) => {\n const price = getBrowseProductPrice(product);\n\n return (\n <LocalizedClientLink href={`/products/${product.handle}`} className=\"group\">\n <div data-testid=\"product-wrapper\">\n <Thumbnail\n thumbnail={product.thumbnail}\n images={[]}\n size=\"full\"\n isFeatured={isFeatured}\n imagePriority={imagePriority}\n imageFetchPriority={imageFetchPriority}\n />\n <div className=\"txt-compact-medium mt-4 flex items-start
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/browse-product-preview/index.tsx"],"sourcesContent":["import { LocalizedClientLink } from '@gfed-medusa/sf-lib-common/components/localized-client-link';\nimport { PreviewPrice } from '@gfed-medusa/sf-lib-common/components/preview-price';\nimport { Thumbnail } from '@gfed-medusa/sf-lib-common/components/thumbnail';\nimport { getPercentageDiff } from '@gfed-medusa/sf-lib-common/lib/utils/get-percentage-diff';\nimport { convertToLocale } from '@gfed-medusa/sf-lib-common/lib/utils/money';\nimport type { VariantPrice } from '@gfed-medusa/sf-lib-common/types/prices';\nimport { Text } from '@medusajs/ui';\n\nimport type { BrowseProductHitFragment } from '@/types/graphql';\n\ntype BrowseProductPreviewProps = {\n product: BrowseProductHitFragment;\n isFeatured?: boolean;\n imagePriority?: boolean;\n imageFetchPriority?: 'auto' | 'high' | 'low';\n};\n\nconst getBrowseProductPrice = (\n product: BrowseProductHitFragment\n): VariantPrice | null => {\n if (\n typeof product.priceAmount !== 'number' ||\n !product.currencyCode ||\n !product.currencyCode.trim()\n ) {\n return null;\n }\n\n const originalPrice = product.originalPriceAmount ?? product.priceAmount;\n const isSale =\n typeof originalPrice === 'number' && originalPrice > product.priceAmount;\n\n return {\n calculated_price_number: product.priceAmount,\n calculated_price: convertToLocale({\n amount: product.priceAmount,\n currency_code: product.currencyCode,\n }),\n original_price_number: originalPrice,\n original_price: convertToLocale({\n amount: originalPrice,\n currency_code: product.currencyCode,\n }),\n currency_code: product.currencyCode,\n price_type: isSale ? 'sale' : 'default',\n percentage_diff:\n isSale && originalPrice > 0\n ? getPercentageDiff(originalPrice, product.priceAmount)\n : '0',\n };\n};\n\nconst BrowseProductPreview = ({\n product,\n isFeatured,\n imagePriority = false,\n imageFetchPriority,\n}: BrowseProductPreviewProps) => {\n const price = getBrowseProductPrice(product);\n\n return (\n <LocalizedClientLink href={`/products/${product.handle}`} className=\"group\">\n <div data-testid=\"product-wrapper\">\n <Thumbnail\n thumbnail={product.thumbnail}\n images={[]}\n size=\"full\"\n isFeatured={isFeatured}\n imagePriority={imagePriority}\n imageFetchPriority={imageFetchPriority}\n />\n <div className=\"txt-compact-medium mt-4 flex min-w-0 flex-col items-start gap-y-1 text-left\">\n <Text\n className=\"text-ui-fg-subtle w-full min-w-0 overflow-hidden break-words whitespace-normal [display:-webkit-box] [-webkit-box-orient:vertical] [-webkit-line-clamp:2]\"\n data-testid=\"product-title\"\n >\n {product.title}\n </Text>\n <div className=\"flex w-full min-w-0 flex-wrap items-baseline gap-x-2 gap-y-1 text-left\">\n {price && <PreviewPrice price={price} />}\n </div>\n </div>\n </div>\n </LocalizedClientLink>\n );\n};\n\nexport { BrowseProductPreview };\n"],"mappings":";;;;;;;;;AAiBA,MAAM,yBACJ,YACwB;AACxB,KACE,OAAO,QAAQ,gBAAgB,YAC/B,CAAC,QAAQ,gBACT,CAAC,QAAQ,aAAa,MAAM,CAE5B,QAAO;CAGT,MAAM,gBAAgB,QAAQ,uBAAuB,QAAQ;CAC7D,MAAM,SACJ,OAAO,kBAAkB,YAAY,gBAAgB,QAAQ;AAE/D,QAAO;EACL,yBAAyB,QAAQ;EACjC,kBAAkB,gBAAgB;GAChC,QAAQ,QAAQ;GAChB,eAAe,QAAQ;GACxB,CAAC;EACF,uBAAuB;EACvB,gBAAgB,gBAAgB;GAC9B,QAAQ;GACR,eAAe,QAAQ;GACxB,CAAC;EACF,eAAe,QAAQ;EACvB,YAAY,SAAS,SAAS;EAC9B,iBACE,UAAU,gBAAgB,IACtB,kBAAkB,eAAe,QAAQ,YAAY,GACrD;EACP;;AAGH,MAAM,wBAAwB,EAC5B,SACA,YACA,gBAAgB,OAChB,yBAC+B;CAC/B,MAAM,QAAQ,sBAAsB,QAAQ;AAE5C,QACE,oBAAC;EAAoB,MAAM,aAAa,QAAQ;EAAU,WAAU;YAClE,qBAAC;GAAI,eAAY;cACf,oBAAC;IACC,WAAW,QAAQ;IACnB,QAAQ,EAAE;IACV,MAAK;IACO;IACG;IACK;KACpB,EACF,qBAAC;IAAI,WAAU;eACb,oBAAC;KACC,WAAU;KACV,eAAY;eAEX,QAAQ;MACJ,EACP,oBAAC;KAAI,WAAU;eACZ,SAAS,oBAAC,gBAAoB,QAAS;MACpC;KACF;IACF;GACc"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProductImage } from "../../types/graphql.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime0 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/components/image-gallery/index.d.ts
|
|
5
5
|
type ImageGalleryProps = {
|
|
@@ -7,7 +7,7 @@ type ImageGalleryProps = {
|
|
|
7
7
|
};
|
|
8
8
|
declare const ImageGallery: ({
|
|
9
9
|
images
|
|
10
|
-
}: ImageGalleryProps) =>
|
|
10
|
+
}: ImageGalleryProps) => react_jsx_runtime0.JSX.Element;
|
|
11
11
|
//#endregion
|
|
12
12
|
export { ImageGallery as default };
|
|
13
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region src/components/pagination/index.d.ts
|
|
4
4
|
declare function Pagination({
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
loadedCount,
|
|
6
|
+
totalItems,
|
|
7
|
+
itemsPerPage,
|
|
8
|
+
hasNextPage,
|
|
9
|
+
disabled,
|
|
10
|
+
isLoading,
|
|
11
|
+
onLoadMore,
|
|
7
12
|
'data-testid': dataTestid
|
|
8
13
|
}: {
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
loadedCount: number;
|
|
15
|
+
totalItems: number;
|
|
16
|
+
itemsPerPage: number;
|
|
17
|
+
hasNextPage: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
isLoading?: boolean;
|
|
20
|
+
onLoadMore: () => void;
|
|
11
21
|
'data-testid'?: string;
|
|
12
|
-
}):
|
|
22
|
+
}): react_jsx_runtime1.JSX.Element;
|
|
13
23
|
//#endregion
|
|
14
24
|
export { Pagination };
|
|
15
25
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/pagination/index.tsx"],"sourcesContent":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/pagination/index.tsx"],"sourcesContent":[],"mappings":";;;iBAKgB,UAAA;;;;;;;;iBAQC;;;;EARD,YAAA,EAAU,MAAA;EACxB,WAAA,EAAA,OAAA;EACA,QAAA,CAAA,EAAA,OAAA;EACA,SAAA,CAAA,EAAA,OAAA;EACA,UAAA,EAAA,GAAA,GAAA,IAAA;EACA,aAAA,CAAA,EAAA,MAAA;CACA,CAAA,EAYD,kBAAA,CAAA,GAAA,CAAA,OAZC"}
|
|
@@ -1,63 +1,80 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { clx } from "@medusajs/ui";
|
|
4
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
|
-
import {
|
|
3
|
+
import { Button, clx } from "@medusajs/ui";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { ArrowUpMini } from "@medusajs/icons";
|
|
6
6
|
|
|
7
7
|
//#region src/components/pagination/index.tsx
|
|
8
|
-
function Pagination({
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
type: "button",
|
|
20
|
-
"aria-current": isCurrent ? "page" : void 0,
|
|
21
|
-
"aria-label": isCurrent ? `Page ${p}, current page` : `Go to page ${p}`,
|
|
22
|
-
className: clx("txt-xlarge-plus inline-flex h-12 min-w-12 items-center justify-center rounded-full px-2 text-ui-fg-muted transition-colors", {
|
|
23
|
-
"border-ui-border-base text-ui-fg-base cursor-default border": isCurrent,
|
|
24
|
-
"cursor-pointer hover:bg-ui-bg-subtle hover:text-ui-fg-base": !isCurrent
|
|
25
|
-
}),
|
|
26
|
-
disabled: isCurrent,
|
|
27
|
-
onClick: () => handlePageChange(p),
|
|
28
|
-
children: label
|
|
29
|
-
}, p);
|
|
30
|
-
const renderEllipsis = (key) => /* @__PURE__ */ jsx("span", {
|
|
31
|
-
className: "txt-xlarge-plus text-ui-fg-muted inline-flex h-12 items-center justify-center px-1",
|
|
32
|
-
children: "..."
|
|
33
|
-
}, key);
|
|
34
|
-
const renderPageButtons = () => {
|
|
35
|
-
const buttons = [];
|
|
36
|
-
if (totalPages <= 7) buttons.push(...arrayRange(1, totalPages).map((p) => renderPageButton(p, p, p === page)));
|
|
37
|
-
else if (page <= 4) {
|
|
38
|
-
buttons.push(...arrayRange(1, 5).map((p) => renderPageButton(p, p, p === page)));
|
|
39
|
-
buttons.push(renderEllipsis("ellipsis1"));
|
|
40
|
-
buttons.push(renderPageButton(totalPages, totalPages, totalPages === page));
|
|
41
|
-
} else if (page >= totalPages - 3) {
|
|
42
|
-
buttons.push(renderPageButton(1, 1, 1 === page));
|
|
43
|
-
buttons.push(renderEllipsis("ellipsis2"));
|
|
44
|
-
buttons.push(...arrayRange(totalPages - 4, totalPages).map((p) => renderPageButton(p, p, p === page)));
|
|
45
|
-
} else {
|
|
46
|
-
buttons.push(renderPageButton(1, 1, 1 === page));
|
|
47
|
-
buttons.push(renderEllipsis("ellipsis3"));
|
|
48
|
-
buttons.push(...arrayRange(page - 1, page + 1).map((p) => renderPageButton(p, p, p === page)));
|
|
49
|
-
buttons.push(renderEllipsis("ellipsis4"));
|
|
50
|
-
buttons.push(renderPageButton(totalPages, totalPages, totalPages === page));
|
|
51
|
-
}
|
|
52
|
-
return buttons;
|
|
8
|
+
function Pagination({ loadedCount, totalItems, itemsPerPage, hasNextPage, disabled = false, isLoading = false, onLoadMore, "data-testid": dataTestid }) {
|
|
9
|
+
const visibleItemsCount = Math.min(loadedCount, totalItems);
|
|
10
|
+
const productsLabel = totalItems === 1 ? "product" : "products";
|
|
11
|
+
const formattedTotalItems = new Intl.NumberFormat().format(totalItems);
|
|
12
|
+
const formattedVisibleItems = new Intl.NumberFormat().format(visibleItemsCount);
|
|
13
|
+
const progressValue = totalItems > 0 ? Math.round(visibleItemsCount / totalItems * 100) : 0;
|
|
14
|
+
const scrollToTop = () => {
|
|
15
|
+
window.scrollTo({
|
|
16
|
+
top: 0,
|
|
17
|
+
behavior: "smooth"
|
|
18
|
+
});
|
|
53
19
|
};
|
|
54
20
|
return /* @__PURE__ */ jsx("nav", {
|
|
55
|
-
"aria-label": "
|
|
56
|
-
className: "mt-12
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
children:
|
|
21
|
+
"aria-label": "Load more products",
|
|
22
|
+
className: "mt-12 w-full",
|
|
23
|
+
"data-testid": dataTestid,
|
|
24
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
25
|
+
className: "mx-auto flex w-full flex-col items-center gap-3 text-center",
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ jsxs("div", {
|
|
28
|
+
className: "xsmall:w-[300px] w-full space-y-2",
|
|
29
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
30
|
+
className: "text-small-regular text-ui-fg-subtle",
|
|
31
|
+
"aria-live": "polite",
|
|
32
|
+
children: [
|
|
33
|
+
"Showing ",
|
|
34
|
+
formattedVisibleItems,
|
|
35
|
+
" of ",
|
|
36
|
+
formattedTotalItems,
|
|
37
|
+
" ",
|
|
38
|
+
productsLabel
|
|
39
|
+
]
|
|
40
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
41
|
+
"aria-label": "Loaded products progress",
|
|
42
|
+
"aria-valuemax": totalItems,
|
|
43
|
+
"aria-valuemin": 0,
|
|
44
|
+
"aria-valuenow": visibleItemsCount,
|
|
45
|
+
className: "border-ui-border-base bg-ui-bg-subtle h-2 w-full overflow-hidden rounded-full border",
|
|
46
|
+
role: "progressbar",
|
|
47
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
48
|
+
className: "bg-ui-fg-base h-full rounded-full transition-[width] duration-300 ease-out",
|
|
49
|
+
style: { width: `${progressValue}%` }
|
|
50
|
+
})
|
|
51
|
+
})]
|
|
52
|
+
}),
|
|
53
|
+
hasNextPage && /* @__PURE__ */ jsx(Button, {
|
|
54
|
+
type: "button",
|
|
55
|
+
variant: "secondary",
|
|
56
|
+
className: "xsmall:w-[300px] h-[45px] w-full justify-center rounded-md px-6",
|
|
57
|
+
disabled: disabled || isLoading,
|
|
58
|
+
isLoading,
|
|
59
|
+
"aria-label": `Load ${Math.min(itemsPerPage, totalItems - visibleItemsCount)} more ${productsLabel}`,
|
|
60
|
+
onClick: onLoadMore,
|
|
61
|
+
children: "Load more"
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ jsxs("button", {
|
|
64
|
+
type: "button",
|
|
65
|
+
"aria-label": "Scroll back to the top of the page",
|
|
66
|
+
className: clx("text-small-regular text-ui-fg-subtle hover:text-ui-fg-base mt-8 inline-flex items-center gap-2 transition-colors"),
|
|
67
|
+
onClick: scrollToTop,
|
|
68
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
className: "flex h-4 w-4 items-center justify-center",
|
|
71
|
+
children: /* @__PURE__ */ jsx(ArrowUpMini, {
|
|
72
|
+
className: "h-4 w-4",
|
|
73
|
+
color: "currentColor"
|
|
74
|
+
})
|
|
75
|
+
}), /* @__PURE__ */ jsx("span", { children: "Scroll to top" })]
|
|
76
|
+
})
|
|
77
|
+
]
|
|
61
78
|
})
|
|
62
79
|
});
|
|
63
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/pagination/index.tsx"],"sourcesContent":["'use client';\n\nimport {
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/components/pagination/index.tsx"],"sourcesContent":["'use client';\n\nimport { ArrowUpMini } from '@medusajs/icons';\nimport { Button, clx } from '@medusajs/ui';\n\nexport function Pagination({\n loadedCount,\n totalItems,\n itemsPerPage,\n hasNextPage,\n disabled = false,\n isLoading = false,\n onLoadMore,\n 'data-testid': dataTestid,\n}: {\n loadedCount: number;\n totalItems: number;\n itemsPerPage: number;\n hasNextPage: boolean;\n disabled?: boolean;\n isLoading?: boolean;\n onLoadMore: () => void;\n 'data-testid'?: string;\n}) {\n const visibleItemsCount = Math.min(loadedCount, totalItems);\n const productsLabel = totalItems === 1 ? 'product' : 'products';\n const formattedTotalItems = new Intl.NumberFormat().format(totalItems);\n const formattedVisibleItems = new Intl.NumberFormat().format(\n visibleItemsCount\n );\n const progressValue =\n totalItems > 0 ? Math.round((visibleItemsCount / totalItems) * 100) : 0;\n\n const scrollToTop = () => {\n window.scrollTo({ top: 0, behavior: 'smooth' });\n };\n\n return (\n <nav\n aria-label=\"Load more products\"\n className=\"mt-12 w-full\"\n data-testid={dataTestid}\n >\n <div className=\"mx-auto flex w-full flex-col items-center gap-3 text-center\">\n <div className=\"xsmall:w-[300px] w-full space-y-2\">\n <p\n className=\"text-small-regular text-ui-fg-subtle\"\n aria-live=\"polite\"\n >\n Showing {formattedVisibleItems} of {formattedTotalItems}{' '}\n {productsLabel}\n </p>\n <div\n aria-label=\"Loaded products progress\"\n aria-valuemax={totalItems}\n aria-valuemin={0}\n aria-valuenow={visibleItemsCount}\n className=\"border-ui-border-base bg-ui-bg-subtle h-2 w-full overflow-hidden rounded-full border\"\n role=\"progressbar\"\n >\n <div\n className=\"bg-ui-fg-base h-full rounded-full transition-[width] duration-300 ease-out\"\n style={{ width: `${progressValue}%` }}\n />\n </div>\n </div>\n {hasNextPage && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n className=\"xsmall:w-[300px] h-[45px] w-full justify-center rounded-md px-6\"\n disabled={disabled || isLoading}\n isLoading={isLoading}\n aria-label={`Load ${Math.min(itemsPerPage, totalItems - visibleItemsCount)} more ${productsLabel}`}\n onClick={onLoadMore}\n >\n Load more\n </Button>\n )}\n <button\n type=\"button\"\n aria-label=\"Scroll back to the top of the page\"\n className={clx(\n 'text-small-regular text-ui-fg-subtle hover:text-ui-fg-base mt-8 inline-flex items-center gap-2 transition-colors'\n )}\n onClick={scrollToTop}\n >\n <span\n aria-hidden=\"true\"\n className=\"flex h-4 w-4 items-center justify-center\"\n >\n <ArrowUpMini className=\"h-4 w-4\" color=\"currentColor\" />\n </span>\n <span>Scroll to top</span>\n </button>\n </div>\n </nav>\n );\n}\n"],"mappings":";;;;;;;AAKA,SAAgB,WAAW,EACzB,aACA,YACA,cACA,aACA,WAAW,OACX,YAAY,OACZ,YACA,eAAe,cAUd;CACD,MAAM,oBAAoB,KAAK,IAAI,aAAa,WAAW;CAC3D,MAAM,gBAAgB,eAAe,IAAI,YAAY;CACrD,MAAM,sBAAsB,IAAI,KAAK,cAAc,CAAC,OAAO,WAAW;CACtE,MAAM,wBAAwB,IAAI,KAAK,cAAc,CAAC,OACpD,kBACD;CACD,MAAM,gBACJ,aAAa,IAAI,KAAK,MAAO,oBAAoB,aAAc,IAAI,GAAG;CAExE,MAAM,oBAAoB;AACxB,SAAO,SAAS;GAAE,KAAK;GAAG,UAAU;GAAU,CAAC;;AAGjD,QACE,oBAAC;EACC,cAAW;EACX,WAAU;EACV,eAAa;YAEb,qBAAC;GAAI,WAAU;;IACb,qBAAC;KAAI,WAAU;gBACb,qBAAC;MACC,WAAU;MACV,aAAU;;OACX;OACU;OAAsB;OAAK;OAAqB;OACxD;;OACC,EACJ,oBAAC;MACC,cAAW;MACX,iBAAe;MACf,iBAAe;MACf,iBAAe;MACf,WAAU;MACV,MAAK;gBAEL,oBAAC;OACC,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,cAAc,IAAI;QACrC;OACE;MACF;IACL,eACC,oBAAC;KACC,MAAK;KACL,SAAQ;KACR,WAAU;KACV,UAAU,YAAY;KACX;KACX,cAAY,QAAQ,KAAK,IAAI,cAAc,aAAa,kBAAkB,CAAC,QAAQ;KACnF,SAAS;eACV;MAEQ;IAEX,qBAAC;KACC,MAAK;KACL,cAAW;KACX,WAAW,IACT,mHACD;KACD,SAAS;gBAET,oBAAC;MACC,eAAY;MACZ,WAAU;gBAEV,oBAAC;OAAY,WAAU;OAAU,OAAM;QAAiB;OACnD,EACP,oBAAC,oBAAK,kBAAoB;MACnB;;IACL;GACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProductActionsProduct } from "../../types/index.js";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/components/product-actions/index.d.ts
|
|
5
5
|
type ProductActionsProps = {
|
|
@@ -13,7 +13,7 @@ declare function ProductActions({
|
|
|
13
13
|
regionId,
|
|
14
14
|
disabled,
|
|
15
15
|
enableMobileActions
|
|
16
|
-
}: ProductActionsProps):
|
|
16
|
+
}: ProductActionsProps): react_jsx_runtime2.JSX.Element;
|
|
17
17
|
//#endregion
|
|
18
18
|
export { ProductActionsProps, ProductActions as default };
|
|
19
19
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/product-actions/index.tsx"],"sourcesContent":[],"mappings":";;;;KAyBY,mBAAA;WACD;;EADC,QAAA,CAAA,EAAA,OAAA;EA2BY,mBAAc,CAAA,EAAA,OAAA;CACpC;AACA,iBAFsB,cAAA,CAEtB;EAAA,OAAA;EAAA,QAAA;EAAA,QAAA;EAAA;AAAA,CAAA,EAGC,mBAHD,CAAA,EAGoB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/product-actions/index.tsx"],"sourcesContent":[],"mappings":";;;;KAyBY,mBAAA;WACD;;EADC,QAAA,CAAA,EAAA,OAAA;EA2BY,mBAAc,CAAA,EAAA,OAAA;CACpC;AACA,iBAFsB,cAAA,CAEtB;EAAA,OAAA;EAAA,QAAA;EAAA,QAAA;EAAA;AAAA,CAAA,EAGC,mBAHD,CAAA,EAGoB,kBAAA,CAAA,GAAA,CAAA,OAHpB"}
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import option_select_default from "./option-select.js";
|
|
4
4
|
import { addToCart } from "../../lib/data/cart.js";
|
|
5
|
-
import { useProductPrice } from "../../lib/hooks/use-product-price.js";
|
|
6
5
|
import { useIntersection } from "../../lib/hooks/use-intersection.js";
|
|
6
|
+
import { useProductPrice } from "../../lib/hooks/use-product-price.js";
|
|
7
7
|
import { mergeProductPricing } from "../../lib/utils/merge-product-pricing.js";
|
|
8
|
-
import ProductPrice from "../product-price/index.js";
|
|
9
8
|
import mobile_actions_default from "./mobile-actions.js";
|
|
9
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
10
|
import { Button } from "@medusajs/ui";
|
|
11
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
import { useParams } from "next/navigation";
|
|
13
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
14
13
|
import { isEqual } from "lodash";
|
|
15
14
|
import { ErrorMessage } from "@gfed-medusa/sf-lib-common/components/error-message";
|
|
15
|
+
import { WebComponent } from "@gfed-medusa/sf-lib-common/components/web-component";
|
|
16
16
|
import { mutateCart } from "@gfed-medusa/sf-lib-common/lib/hooks/use-cart";
|
|
17
17
|
import { Divider } from "@gfed-medusa/sf-lib-ui/components/divider";
|
|
18
18
|
|
|
@@ -92,7 +92,7 @@ function ProductActions({ product, regionId, disabled, enableMobileActions = tru
|
|
|
92
92
|
setStatus(AddToCartStatus.ERROR);
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
|
-
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
|
|
95
|
+
return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsxs("div", {
|
|
96
96
|
className: "flex flex-col gap-y-2",
|
|
97
97
|
ref: actionsRef,
|
|
98
98
|
children: [
|
|
@@ -109,9 +109,9 @@ function ProductActions({ product, regionId, disabled, enableMobileActions = tru
|
|
|
109
109
|
}) }, option.id);
|
|
110
110
|
}), /* @__PURE__ */ jsx(Divider, {})]
|
|
111
111
|
}) }),
|
|
112
|
-
/* @__PURE__ */ jsx(
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
/* @__PURE__ */ jsx(WebComponent, {
|
|
113
|
+
tag: "mfe-product-price",
|
|
114
|
+
"data-props": JSON.stringify({ selectedVariantId: selectedVariant?.id ?? "" })
|
|
115
115
|
}),
|
|
116
116
|
status === AddToCartStatus.ERROR && /* @__PURE__ */ jsx(ErrorMessage, { error: "Failed adding product to cart. Please try again." }),
|
|
117
117
|
status === AddToCartStatus.SUCCESS && /* @__PURE__ */ jsx("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["OptionSelect","MobileActions"],"sources":["../../../src/components/product-actions/index.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useParams } from 'next/navigation';\n\nimport { isEqual } from 'lodash';\n\nimport { ErrorMessage } from '@gfed-medusa/sf-lib-common/components/error-message';\nimport { mutateCart } from '@gfed-medusa/sf-lib-common/lib/hooks/use-cart';\nimport { Divider } from '@gfed-medusa/sf-lib-ui/components/divider';\nimport { HttpTypes } from '@medusajs/types';\nimport { Button } from '@medusajs/ui';\n\nimport OptionSelect from '@/components/product-actions/option-select';\nimport { addToCart } from '@/lib/data/cart';\nimport { useProductPrice } from '@/lib/hooks/use-product-price';\nimport { useIntersection } from '@/lib/hooks/use-intersection';\nimport { mergeProductPricing } from '@/lib/utils/merge-product-pricing';\nimport { ProductActionsProduct } from '@/types';\nimport { ProductVariant } from '@/types/graphql';\n\nimport ProductPrice from '../product-price';\nimport MobileActions from './mobile-actions';\n\nexport type ProductActionsProps = {\n product: ProductActionsProduct;\n regionId?: string;\n disabled?: boolean;\n enableMobileActions?: boolean;\n};\n\nenum AddToCartStatus {\n IDLE = 'idle',\n ADDING = 'adding',\n SUCCESS = 'success',\n ERROR = 'error',\n}\n\nconst optionsAsKeymap = (\n variantOptions:\n | HttpTypes.StoreProductVariant['options']\n | ProductVariant['options']\n | null\n | undefined\n) => {\n return variantOptions?.reduce((acc: Record<string, string>, varopt: any) => {\n acc[varopt.optionId] = varopt.value;\n return acc;\n }, {});\n};\n\nexport default function ProductActions({\n product,\n regionId,\n disabled,\n enableMobileActions = true,\n}: ProductActionsProps) {\n const [options, setOptions] = useState<Record<string, string | undefined>>(\n {}\n );\n const [status, setStatus] = useState<AddToCartStatus>(AddToCartStatus.IDLE);\n const countryCode = useParams().countryCode as string;\n const { product: pricingProduct } = useProductPrice(product.id, regionId);\n const pricedProduct = useMemo(\n () => mergeProductPricing(product, pricingProduct),\n [product, pricingProduct]\n );\n\n // If there is only 1 variant, preselect the options\n useEffect(() => {\n if (pricedProduct?.variants && pricedProduct.variants.length === 1) {\n const variantOptions = optionsAsKeymap(\n pricedProduct.variants[0]?.options || null\n );\n setOptions(variantOptions ?? {});\n }\n }, [pricedProduct.variants]);\n\n const selectedVariant = useMemo(() => {\n if (!pricedProduct.variants || pricedProduct.variants.length === 0) {\n return;\n }\n\n return pricedProduct.variants.find((v) => {\n const variantOptions = optionsAsKeymap(v.options);\n return isEqual(variantOptions, options);\n });\n }, [pricedProduct.variants, options]);\n\n // update the options when a variant is selected\n const setOptionValue = (optionId: string, value: string) => {\n setOptions((prev) => ({\n ...prev,\n [optionId]: value,\n }));\n };\n\n //check if the selected options produce a valid variant\n const isValidVariant = useMemo(() => {\n return pricedProduct.variants?.some((v) => {\n const variantOptions = optionsAsKeymap(v.options);\n return isEqual(variantOptions, options);\n });\n }, [pricedProduct.variants, options]);\n\n const stockStatus = useMemo(() => {\n if (!selectedVariant) {\n return 'unknown' as const;\n }\n\n if (!selectedVariant.manageInventory) {\n return 'in_stock' as const;\n }\n\n if (selectedVariant.allowBackorder) {\n return 'in_stock' as const;\n }\n\n const qty = selectedVariant.inventoryQuantity;\n\n if (typeof qty !== 'number') {\n return 'in_stock' as const;\n }\n\n return qty > 0 ? ('in_stock' as const) : ('out_of_stock' as const);\n }, [selectedVariant]);\n\n const inStock = useMemo(() => {\n // If we don't manage inventory, we can always add to cart\n if (selectedVariant && !selectedVariant.manageInventory) {\n return true;\n }\n\n // If we allow back orders on the variant, we can add to cart\n if (selectedVariant?.allowBackorder) {\n return true;\n }\n\n if (selectedVariant?.manageInventory) {\n const qty = selectedVariant.inventoryQuantity;\n if (typeof qty === 'number') {\n return qty > 0;\n }\n // If we don't have a numeric quantity, don't block add-to-cart\n return true;\n }\n\n // Otherwise, we can't add to cart\n return false;\n }, [selectedVariant]);\n\n const actionsRef = useRef<HTMLDivElement>(null);\n\n const inView = useIntersection(actionsRef, '0px');\n\n // add the selected variant to the cart\n const handleAddToCart = async () => {\n if (!selectedVariant?.id) return null;\n\n setStatus(AddToCartStatus.ADDING);\n\n try {\n await addToCart({\n variantId: selectedVariant.id,\n quantity: 1,\n countryCode,\n });\n\n mutateCart();\n\n setStatus(AddToCartStatus.SUCCESS);\n } catch (error) {\n console.log(error);\n setStatus(AddToCartStatus.ERROR);\n }\n };\n\n return (\n <>\n <div className=\"flex flex-col gap-y-2\" ref={actionsRef}>\n <div>\n {(product.variants?.length ?? 0) > 1 && (\n <div className=\"flex flex-col gap-y-4\">\n {(product.options || []).map((option) => {\n return (\n <div key={option.id}>\n <OptionSelect\n option={option}\n current={options[option.id]}\n updateOption={setOptionValue}\n title={option.title ?? ''}\n data-testid=\"product-options\"\n disabled={!!disabled || status === AddToCartStatus.ADDING}\n />\n </div>\n );\n })}\n <Divider />\n </div>\n )}\n </div>\n\n <ProductPrice product={pricedProduct} variant={selectedVariant} />\n\n {status === AddToCartStatus.ERROR && (\n <ErrorMessage error=\"Failed adding product to cart. Please try again.\" />\n )}\n {status === AddToCartStatus.SUCCESS && (\n <div className=\"text-small-regular pt-2 text-green-700\">\n Successfully added product to cart\n </div>\n )}\n <Button\n onClick={handleAddToCart}\n disabled={\n !inStock ||\n !selectedVariant ||\n !!disabled ||\n status === AddToCartStatus.ADDING ||\n !isValidVariant\n }\n variant=\"primary\"\n className=\"h-10 w-full\"\n isLoading={status === AddToCartStatus.ADDING}\n data-testid=\"add-product-button\"\n >\n {!selectedVariant\n ? 'Add to cart'\n : !inStock || !isValidVariant\n ? 'Out of stock'\n : 'Add to cart'}\n </Button>\n {enableMobileActions && (\n <MobileActions\n product={pricedProduct}\n variant={selectedVariant}\n options={options}\n updateOptions={setOptionValue}\n stockStatus={stockStatus}\n handleAddToCart={handleAddToCart}\n isAdding={status === AddToCartStatus.ADDING}\n show={!inView}\n optionsDisabled={!!disabled || status === AddToCartStatus.ADDING}\n />\n )}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["OptionSelect","MobileActions"],"sources":["../../../src/components/product-actions/index.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useMemo, useRef, useState } from 'react';\n\nimport { useParams } from 'next/navigation';\n\nimport { isEqual } from 'lodash';\n\nimport { ErrorMessage } from '@gfed-medusa/sf-lib-common/components/error-message';\nimport { WebComponent } from '@gfed-medusa/sf-lib-common/components/web-component';\nimport { mutateCart } from '@gfed-medusa/sf-lib-common/lib/hooks/use-cart';\nimport { Divider } from '@gfed-medusa/sf-lib-ui/components/divider';\nimport { HttpTypes } from '@medusajs/types';\nimport { Button } from '@medusajs/ui';\n\nimport OptionSelect from '@/components/product-actions/option-select';\nimport { addToCart } from '@/lib/data/cart';\nimport { useIntersection } from '@/lib/hooks/use-intersection';\nimport { useProductPrice } from '@/lib/hooks/use-product-price';\nimport { mergeProductPricing } from '@/lib/utils/merge-product-pricing';\nimport { ProductActionsProduct } from '@/types';\nimport { ProductVariant } from '@/types/graphql';\n\nimport MobileActions from './mobile-actions';\n\nexport type ProductActionsProps = {\n product: ProductActionsProduct;\n regionId?: string;\n disabled?: boolean;\n enableMobileActions?: boolean;\n};\n\nenum AddToCartStatus {\n IDLE = 'idle',\n ADDING = 'adding',\n SUCCESS = 'success',\n ERROR = 'error',\n}\n\nconst optionsAsKeymap = (\n variantOptions:\n | HttpTypes.StoreProductVariant['options']\n | ProductVariant['options']\n | null\n | undefined\n) => {\n return variantOptions?.reduce((acc: Record<string, string>, varopt: any) => {\n acc[varopt.optionId] = varopt.value;\n return acc;\n }, {});\n};\n\nexport default function ProductActions({\n product,\n regionId,\n disabled,\n enableMobileActions = true,\n}: ProductActionsProps) {\n const [options, setOptions] = useState<Record<string, string | undefined>>(\n {}\n );\n const [status, setStatus] = useState<AddToCartStatus>(AddToCartStatus.IDLE);\n const countryCode = useParams().countryCode as string;\n const { product: pricingProduct } = useProductPrice(product.id, regionId);\n const pricedProduct = useMemo(\n () => mergeProductPricing(product, pricingProduct),\n [product, pricingProduct]\n );\n\n // If there is only 1 variant, preselect the options\n useEffect(() => {\n if (pricedProduct?.variants && pricedProduct.variants.length === 1) {\n const variantOptions = optionsAsKeymap(\n pricedProduct.variants[0]?.options || null\n );\n setOptions(variantOptions ?? {});\n }\n }, [pricedProduct.variants]);\n\n const selectedVariant = useMemo(() => {\n if (!pricedProduct.variants || pricedProduct.variants.length === 0) {\n return;\n }\n\n return pricedProduct.variants.find((v) => {\n const variantOptions = optionsAsKeymap(v.options);\n return isEqual(variantOptions, options);\n });\n }, [pricedProduct.variants, options]);\n\n // update the options when a variant is selected\n const setOptionValue = (optionId: string, value: string) => {\n setOptions((prev) => ({\n ...prev,\n [optionId]: value,\n }));\n };\n\n //check if the selected options produce a valid variant\n const isValidVariant = useMemo(() => {\n return pricedProduct.variants?.some((v) => {\n const variantOptions = optionsAsKeymap(v.options);\n return isEqual(variantOptions, options);\n });\n }, [pricedProduct.variants, options]);\n\n const stockStatus = useMemo(() => {\n if (!selectedVariant) {\n return 'unknown' as const;\n }\n\n if (!selectedVariant.manageInventory) {\n return 'in_stock' as const;\n }\n\n if (selectedVariant.allowBackorder) {\n return 'in_stock' as const;\n }\n\n const qty = selectedVariant.inventoryQuantity;\n\n if (typeof qty !== 'number') {\n return 'in_stock' as const;\n }\n\n return qty > 0 ? ('in_stock' as const) : ('out_of_stock' as const);\n }, [selectedVariant]);\n\n const inStock = useMemo(() => {\n // If we don't manage inventory, we can always add to cart\n if (selectedVariant && !selectedVariant.manageInventory) {\n return true;\n }\n\n // If we allow back orders on the variant, we can add to cart\n if (selectedVariant?.allowBackorder) {\n return true;\n }\n\n if (selectedVariant?.manageInventory) {\n const qty = selectedVariant.inventoryQuantity;\n if (typeof qty === 'number') {\n return qty > 0;\n }\n // If we don't have a numeric quantity, don't block add-to-cart\n return true;\n }\n\n // Otherwise, we can't add to cart\n return false;\n }, [selectedVariant]);\n\n const actionsRef = useRef<HTMLDivElement>(null);\n\n const inView = useIntersection(actionsRef, '0px');\n\n // add the selected variant to the cart\n const handleAddToCart = async () => {\n if (!selectedVariant?.id) return null;\n\n setStatus(AddToCartStatus.ADDING);\n\n try {\n await addToCart({\n variantId: selectedVariant.id,\n quantity: 1,\n countryCode,\n });\n\n mutateCart();\n\n setStatus(AddToCartStatus.SUCCESS);\n } catch (error) {\n console.log(error);\n setStatus(AddToCartStatus.ERROR);\n }\n };\n\n return (\n <>\n <div className=\"flex flex-col gap-y-2\" ref={actionsRef}>\n <div>\n {(product.variants?.length ?? 0) > 1 && (\n <div className=\"flex flex-col gap-y-4\">\n {(product.options || []).map((option) => {\n return (\n <div key={option.id}>\n <OptionSelect\n option={option}\n current={options[option.id]}\n updateOption={setOptionValue}\n title={option.title ?? ''}\n data-testid=\"product-options\"\n disabled={!!disabled || status === AddToCartStatus.ADDING}\n />\n </div>\n );\n })}\n <Divider />\n </div>\n )}\n </div>\n\n <WebComponent\n tag=\"mfe-product-price\"\n data-props={JSON.stringify({\n selectedVariantId: selectedVariant?.id ?? '',\n })}\n />\n\n {status === AddToCartStatus.ERROR && (\n <ErrorMessage error=\"Failed adding product to cart. Please try again.\" />\n )}\n {status === AddToCartStatus.SUCCESS && (\n <div className=\"text-small-regular pt-2 text-green-700\">\n Successfully added product to cart\n </div>\n )}\n <Button\n onClick={handleAddToCart}\n disabled={\n !inStock ||\n !selectedVariant ||\n !!disabled ||\n status === AddToCartStatus.ADDING ||\n !isValidVariant\n }\n variant=\"primary\"\n className=\"h-10 w-full\"\n isLoading={status === AddToCartStatus.ADDING}\n data-testid=\"add-product-button\"\n >\n {!selectedVariant\n ? 'Add to cart'\n : !inStock || !isValidVariant\n ? 'Out of stock'\n : 'Add to cart'}\n </Button>\n {enableMobileActions && (\n <MobileActions\n product={pricedProduct}\n variant={selectedVariant}\n options={options}\n updateOptions={setOptionValue}\n stockStatus={stockStatus}\n handleAddToCart={handleAddToCart}\n isAdding={status === AddToCartStatus.ADDING}\n show={!inView}\n optionsDisabled={!!disabled || status === AddToCartStatus.ADDING}\n />\n )}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mobile-actions.d.ts","names":[],"sources":["../../../src/components/product-actions/mobile-actions.tsx"],"sourcesContent":[],"mappings":";;;;;KAiBK,kBAAA;WACM;EADN,OAAA,CAAA,EAEO,cAFW;EACZ,OAAA,EAEA,MAFA,CAAA,MAAA,EAAA,MAAA,GAAA,SAAA,CAAA;EACC,aAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EACD,WAAA,CAAA,EAAA,SAAA,GAAA,UAAA,GAAA,cAAA;EAAM,eAAA,EAAA,GAAA,GAAA,IAAA;EAuBX,QAAA,CAAA,EAAA,
|
|
1
|
+
{"version":3,"file":"mobile-actions.d.ts","names":[],"sources":["../../../src/components/product-actions/mobile-actions.tsx"],"sourcesContent":[],"mappings":";;;;;KAiBK,kBAAA;WACM;EADN,OAAA,CAAA,EAEO,cAFW;EACZ,OAAA,EAEA,MAFA,CAAA,MAAA,EAAA,MAAA,GAAA,SAAA,CAAA;EACC,aAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,KAAA,EAAA,MAAA,EAAA,GAAA,IAAA;EACD,WAAA,CAAA,EAAA,SAAA,GAAA,UAAA,GAAA,cAAA;EAAM,eAAA,EAAA,GAAA,GAAA,IAAA;EAuBX,QAAA,CAAA,EAAA,OAqKL;;;;cArKK,eAAe,KAAA,CAAM,GAAG"}
|