@gfed-medusa/sf-lib-products 1.9.4 → 1.10.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.
Files changed (61) hide show
  1. package/dist/components/behavior-tracker/index.d.ts.map +1 -1
  2. package/dist/components/behavior-tracker/index.js +46 -33
  3. package/dist/components/behavior-tracker/index.js.map +1 -1
  4. package/dist/components/browse-product-preview/index.d.ts +2 -4
  5. package/dist/components/browse-product-preview/index.d.ts.map +1 -1
  6. package/dist/components/browse-product-preview/index.js +26 -3
  7. package/dist/components/browse-product-preview/index.js.map +1 -1
  8. package/dist/components/image-gallery/index.d.ts +3 -1
  9. package/dist/components/image-gallery/index.d.ts.map +1 -1
  10. package/dist/components/image-gallery/index.js +12 -2
  11. package/dist/components/image-gallery/index.js.map +1 -1
  12. package/dist/components/pagination/index.d.ts +2 -2
  13. package/dist/components/product-actions/index.d.ts +2 -2
  14. package/dist/components/product-actions/index.js +1 -1
  15. package/dist/components/product-onboarding-cta/index.d.ts +2 -2
  16. package/dist/components/product-onboarding-cta/index.d.ts.map +1 -1
  17. package/dist/components/product-price/index.d.ts +2 -2
  18. package/dist/components/product-tabs/index.d.ts +2 -2
  19. package/dist/components/product-tabs/index.d.ts.map +1 -1
  20. package/dist/components/product-tabs/index.js +29 -7
  21. package/dist/components/product-tabs/index.js.map +1 -1
  22. package/dist/components/refinement-list/index.d.ts +2 -2
  23. package/dist/components/refinement-list/index.d.ts.map +1 -1
  24. package/dist/components/refinement-list/index.js +7 -1
  25. package/dist/components/refinement-list/index.js.map +1 -1
  26. package/dist/components/refinement-list/sort-products/index.d.ts.map +1 -1
  27. package/dist/components/refinement-list/sort-products/index.js +3 -0
  28. package/dist/components/refinement-list/sort-products/index.js.map +1 -1
  29. package/dist/components/related-products/index.d.ts +2 -2
  30. package/dist/components/skeleton-product-grid/index.d.ts +2 -2
  31. package/dist/components/skeleton-product-preview/index.d.ts +2 -2
  32. package/dist/components/skeleton-product-preview/index.d.ts.map +1 -1
  33. package/dist/lib/data/cart.d.ts +1 -1
  34. package/dist/lib/data/cart.d.ts.map +1 -1
  35. package/dist/lib/data/cart.js +15 -5
  36. package/dist/lib/data/cart.js.map +1 -1
  37. package/dist/lib/gql/fragments/cart.d.ts +9 -9
  38. package/dist/lib/gql/fragments/cart.d.ts.map +1 -1
  39. package/dist/lib/gql/fragments/product.d.ts +16 -16
  40. package/dist/lib/gql/fragments/product.d.ts.map +1 -1
  41. package/dist/lib/gql/fragments/product.js +1 -0
  42. package/dist/lib/gql/fragments/product.js.map +1 -1
  43. package/dist/lib/gql/mutations/cart.d.ts +10 -10
  44. package/dist/lib/gql/queries/cart.d.ts +2 -2
  45. package/dist/lib/gql/queries/cart.d.ts.map +1 -1
  46. package/dist/lib/gql/queries/cart.js +1 -1
  47. package/dist/lib/gql/queries/cart.js.map +1 -1
  48. package/dist/lib/gql/queries/product.d.ts +13 -13
  49. package/dist/templates/paginated-products/client.js +0 -1
  50. package/dist/templates/paginated-products/client.js.map +1 -1
  51. package/dist/templates/paginated-products/index.d.ts +2 -2
  52. package/dist/templates/product-actions-wrapper/index.d.ts +2 -2
  53. package/dist/templates/product-info/index.d.ts +2 -2
  54. package/dist/templates/product-template/index.js +5 -2
  55. package/dist/templates/product-template/index.js.map +1 -1
  56. package/dist/templates/store-template/index.d.ts +2 -2
  57. package/dist/types/graphql.d.ts +160 -3
  58. package/dist/types/graphql.d.ts.map +1 -1
  59. package/dist/types/graphql.js +56 -2
  60. package/dist/types/graphql.js.map +1 -1
  61. package/package.json +4 -4
@@ -1 +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;EAAW;AAAoB,CAApB,EAAA,oBAAoB,CAAA,EAAA,IAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/behavior-tracker/index.tsx"],"sourcesContent":[],"mappings":";;;UAuHiB,oBAAA;WACN;AADX;AAIgB,iBAAA,eAAA,CAAkB;EAAA;AAA+B,CAApB,EAAA,oBAAoB,CAAA,EAAA,IAAA"}
@@ -1,16 +1,17 @@
1
1
  'use client';
2
2
 
3
3
  import { useEffect, useRef } from "react";
4
+ import { sendClientSignal } from "@gfed-medusa/sf-lib-common/lib/personalization/client-signal";
5
+ import { SignalType } from "@gfed-medusa/sf-lib-common/types/graphql";
4
6
  import { PERSONALIZATION_CONFIG, getSegmentIdFromCollection } from "@gfed-medusa/sf-lib-common/lib/personalization/config";
7
+ import { getSegmentCookie, setSegmentCookie } from "@gfed-medusa/sf-lib-common/lib/personalization/behavior-tracker";
5
8
  import { useStorefrontContext } from "@gfed-medusa/sf-lib-common/lib/data/context";
9
+ import { useTimeOnPage } from "@gfed-medusa/sf-lib-common/lib/hooks/use-time-on-page";
6
10
 
7
11
  //#region src/components/behavior-tracker/index.tsx
8
- function buildCookieAttrs() {
9
- return `path=/;max-age=${3600 * 24 * 7};SameSite=none;secure;domain=.justgood.win`;
10
- }
11
12
  const PDP_HESITATION_MS = PERSONALIZATION_CONFIG.pdpHesitationMs;
12
13
  const HIGH_SCROLL_THRESHOLD = PERSONALIZATION_CONFIG.highScrollThreshold;
13
- const PRICE_THRESHOLD_USD = PERSONALIZATION_CONFIG.priceThresholdUsd;
14
+ const PRICE_THRESHOLD = PERSONALIZATION_CONFIG.priceThreshold;
14
15
  function getScrollPercentage() {
15
16
  const scrollTop = window.scrollY;
16
17
  const docHeight = document.documentElement.scrollHeight - window.innerHeight;
@@ -39,27 +40,15 @@ function throttle(fn, limit) {
39
40
  });
40
41
  }
41
42
  function emitSignal(type, payload) {
42
- const match = document.cookie.match(/_jg_segment=([^;]+)/);
43
- let data = {};
44
- if (match) try {
45
- data = JSON.parse(decodeURIComponent(match[1]));
46
- } catch {
47
- data = {};
48
- }
43
+ const data = getSegmentCookie();
49
44
  if (!data.signals || typeof data.signals !== "object") data.signals = {};
50
45
  data.signals[type] = payload ?? true;
51
- document.cookie = `_jg_segment=${encodeURIComponent(JSON.stringify(data))};${buildCookieAttrs()}`;
46
+ setSegmentCookie(data);
52
47
  }
53
48
  const REPEAT_CATEGORY_THRESHOLD = 3;
54
49
  function trackRepeatedCategoryView(segment) {
55
50
  try {
56
- const match = document.cookie.match(/_jg_segment=([^;]+)/);
57
- let data = { signals: {} };
58
- if (match) try {
59
- data = JSON.parse(decodeURIComponent(match[1]));
60
- } catch {
61
- data = {};
62
- }
51
+ const data = getSegmentCookie();
63
52
  if (!data.signals || typeof data.signals !== "object") data.signals = {};
64
53
  const categoryCounts = data.signals["repeated-category-view"] ?? {};
65
54
  const newCount = (categoryCounts[segment] ?? 0) + 1;
@@ -69,22 +58,15 @@ function trackRepeatedCategoryView(segment) {
69
58
  segment,
70
59
  count: newCount
71
60
  });
72
- document.cookie = `_jg_segment=${encodeURIComponent(JSON.stringify(data))};${buildCookieAttrs()}`;
61
+ setSegmentCookie(data);
73
62
  } catch {}
74
63
  }
75
64
  function addHistoryToCookie(segment) {
76
65
  try {
77
- const match = document.cookie.match(/_jg_segment=([^;]+)/);
78
- let data = { history: [] };
79
- if (match) try {
80
- data = JSON.parse(decodeURIComponent(match[1]));
81
- } catch {
82
- data = {};
83
- }
66
+ const data = getSegmentCookie();
84
67
  if (!data.history || !Array.isArray(data.history)) data.history = [];
85
- const history = [...data.history, segment].slice(-PERSONALIZATION_CONFIG.historyMaxLength);
86
- data.history = history;
87
- document.cookie = `_jg_segment=${encodeURIComponent(JSON.stringify(data))};${buildCookieAttrs()}`;
68
+ data.history = [...data.history, segment].slice(-PERSONALIZATION_CONFIG.historyMaxLength);
69
+ setSegmentCookie(data);
88
70
  } catch {}
89
71
  }
90
72
  function BehaviorTracker({ product }) {
@@ -93,6 +75,9 @@ function BehaviorTracker({ product }) {
93
75
  const scrollHandlerRef = useRef(null);
94
76
  const scrollTrackedRef = useRef(false);
95
77
  useRef(false);
78
+ const firedDepthRef = useRef(/* @__PURE__ */ new Set());
79
+ const exitIntentFiredRef = useRef(false);
80
+ useTimeOnPage("pdp", Boolean(product?.id));
96
81
  useEffect(() => {
97
82
  if (!product?.id) return;
98
83
  const collectionHandle = product.collection?.handle;
@@ -102,16 +87,34 @@ function BehaviorTracker({ product }) {
102
87
  trackRepeatedCategoryView(segment);
103
88
  }
104
89
  const productPrice = getCheapestVariantPrice(product);
90
+ sendClientSignal(SignalType.ProductView, {
91
+ productId: product.id,
92
+ productHandle: product.handle,
93
+ category: collectionHandle,
94
+ price: productPrice
95
+ });
105
96
  const hasCart = Boolean(cartId);
106
- if (productPrice && productPrice >= PRICE_THRESHOLD_USD && !hasCart) hesitationTimeoutRef.current = setTimeout(() => {
97
+ if (productPrice && productPrice >= PRICE_THRESHOLD && !hasCart) hesitationTimeoutRef.current = setTimeout(() => {
107
98
  emitSignal("pdp-hesitation", {
108
99
  productId: product.id,
109
100
  price: productPrice
110
101
  });
111
102
  }, PDP_HESITATION_MS);
112
103
  const handleScroll = throttle(() => {
113
- if (scrollTrackedRef.current) return;
114
- if (getScrollPercentage() >= HIGH_SCROLL_THRESHOLD && !hasCart) {
104
+ const scrollPct = Math.round(getScrollPercentage() * 100);
105
+ for (const threshold of [
106
+ 25,
107
+ 50,
108
+ 75,
109
+ 90
110
+ ]) if (scrollPct >= threshold && !firedDepthRef.current.has(threshold)) {
111
+ firedDepthRef.current.add(threshold);
112
+ sendClientSignal(SignalType.ScrollDepth, {
113
+ depth: threshold,
114
+ page: window.location.pathname
115
+ });
116
+ }
117
+ if (getScrollPercentage() >= HIGH_SCROLL_THRESHOLD && !hasCart && !scrollTrackedRef.current) {
115
118
  scrollTrackedRef.current = true;
116
119
  emitSignal("high-scroll-no-action", {
117
120
  productId: product.id,
@@ -133,6 +136,16 @@ function BehaviorTracker({ product }) {
133
136
  scrollTrackedRef.current = false;
134
137
  };
135
138
  }, [product, cartId]);
139
+ useEffect(() => {
140
+ const handleMouseLeave = (e) => {
141
+ if (e.clientY <= 0 && !exitIntentFiredRef.current) {
142
+ exitIntentFiredRef.current = true;
143
+ sendClientSignal(SignalType.ExitIntent, { page: window.location.pathname });
144
+ }
145
+ };
146
+ document.documentElement.addEventListener("mouseleave", handleMouseLeave);
147
+ return () => document.documentElement.removeEventListener("mouseleave", handleMouseLeave);
148
+ }, []);
136
149
  return null;
137
150
  }
138
151
  var behavior_tracker_default = BehaviorTracker;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["cheapest: number | null","data: Record<string, unknown>"],"sources":["../../../src/components/behavior-tracker/index.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\n\nimport {\n PERSONALIZATION_CONFIG,\n getSegmentIdFromCollection,\n} from '@gfed-medusa/sf-lib-common/lib/personalization/config';\n\nfunction buildCookieAttrs(): string {\n const maxAge = 60 * 60 * 24 * 7;\n if (process.env.NODE_ENV === 'production') {\n return `path=/;max-age=${maxAge};SameSite=none;secure;domain=.justgood.win`;\n }\n return `path=/;max-age=${maxAge};SameSite=Lax`;\n}\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\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 emitSignal(type: string, payload?: unknown) {\n const match = document.cookie.match(/_jg_segment=([^;]+)/);\n let data: Record<string, unknown> = {};\n\n if (match) {\n try {\n data = JSON.parse(decodeURIComponent(match[1] as string));\n } catch {\n data = {};\n }\n }\n\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 document.cookie = `_jg_segment=${encodeURIComponent(JSON.stringify(data))};${buildCookieAttrs()}`;\n}\n\nconst REPEAT_CATEGORY_THRESHOLD = 3;\n\nfunction trackRepeatedCategoryView(segment: string) {\n try {\n const match = document.cookie.match(/_jg_segment=([^;]+)/);\n let data: Record<string, unknown> = { signals: {} };\n\n if (match) {\n try {\n data = JSON.parse(decodeURIComponent(match[1] as string));\n } catch {\n data = {};\n }\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 document.cookie = `_jg_segment=${encodeURIComponent(JSON.stringify(data))};${buildCookieAttrs()}`;\n } catch {\n // Ignore\n }\n}\n\nfunction addHistoryToCookie(segment: string): void {\n try {\n const match = document.cookie.match(/_jg_segment=([^;]+)/);\n let data: Record<string, unknown> = { history: [] };\n\n if (match) {\n try {\n data = JSON.parse(decodeURIComponent(match[1] as string));\n } catch {\n data = {};\n }\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 document.cookie = `_jg_segment=${encodeURIComponent(JSON.stringify(data))};${buildCookieAttrs()}`;\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;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["cheapest: number | null"],"sources":["../../../src/components/behavior-tracker/index.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef } from 'react';\n\nimport { sendClientSignal } from '@gfed-medusa/sf-lib-common/lib/personalization/client-signal';\nimport { SignalType } from '@gfed-medusa/sf-lib-common/types/graphql';\n\nimport {\n PERSONALIZATION_CONFIG,\n getSegmentIdFromCollection,\n} from '@gfed-medusa/sf-lib-common/lib/personalization/config';\nimport {\n getSegmentCookie,\n setSegmentCookie,\n} from '@gfed-medusa/sf-lib-common/lib/personalization/behavior-tracker';\nimport { useStorefrontContext } from '@gfed-medusa/sf-lib-common/lib/data/context';\nimport { useTimeOnPage } from '@gfed-medusa/sf-lib-common/lib/hooks/use-time-on-page';\nimport { Product } from '@/types/graphql';\n\nconst PDP_HESITATION_MS = PERSONALIZATION_CONFIG.pdpHesitationMs;\nconst HIGH_SCROLL_THRESHOLD = PERSONALIZATION_CONFIG.highScrollThreshold;\nconst PRICE_THRESHOLD = PERSONALIZATION_CONFIG.priceThreshold;\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 emitSignal(type: string, payload?: unknown) {\n const data = getSegmentCookie();\n\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 setSegmentCookie(data);\n}\n\nconst REPEAT_CATEGORY_THRESHOLD = 3;\n\nfunction trackRepeatedCategoryView(segment: string) {\n try {\n const data = getSegmentCookie();\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 setSegmentCookie(data);\n } catch {\n // Ignore\n }\n}\n\nfunction addHistoryToCookie(segment: string): void {\n try {\n const data = getSegmentCookie();\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 setSegmentCookie(data);\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 const firedDepthRef = useRef<Set<number>>(new Set());\n const exitIntentFiredRef = useRef(false);\n\n useTimeOnPage('pdp', Boolean(product?.id));\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 void sendClientSignal(SignalType.ProductView, {\n productId: product.id,\n productHandle: product.handle,\n category: collectionHandle,\n price: productPrice,\n });\n\n const hasCart = Boolean(cartId);\n\n if (productPrice && productPrice >= PRICE_THRESHOLD && !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 // SCROLL_DEPTH analytics signal — always runs independently\n const scrollPct = Math.round(getScrollPercentage() * 100);\n const depthThresholds = [25, 50, 75, 90];\n for (const threshold of depthThresholds) {\n if (scrollPct >= threshold && !firedDepthRef.current.has(threshold)) {\n firedDepthRef.current.add(threshold);\n void sendClientSignal(SignalType.ScrollDepth, {\n depth: threshold,\n page: window.location.pathname,\n });\n }\n }\n\n if (getScrollPercentage() >= HIGH_SCROLL_THRESHOLD && !hasCart && !scrollTrackedRef.current) {\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 useEffect(() => {\n const handleMouseLeave = (e: MouseEvent) => {\n if (e.clientY <= 0 && !exitIntentFiredRef.current) {\n exitIntentFiredRef.current = true;\n void sendClientSignal(SignalType.ExitIntent, {\n page: window.location.pathname,\n });\n }\n };\n document.documentElement.addEventListener('mouseleave', handleMouseLeave);\n return () => document.documentElement.removeEventListener('mouseleave', handleMouseLeave);\n }, []);\n\n return null;\n}\n\nexport default BehaviorTracker;\n"],"mappings":"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"}
@@ -1,19 +1,17 @@
1
1
  import { BrowseProductHitFragment } from "../../types/graphql.js";
2
- import * as react_jsx_runtime1 from "react/jsx-runtime";
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 = {
6
6
  product: BrowseProductHitFragment;
7
7
  isFeatured?: boolean;
8
- imagePriority?: boolean;
9
8
  imageFetchPriority?: 'auto' | 'high' | 'low';
10
9
  };
11
10
  declare const BrowseProductPreview: ({
12
11
  product,
13
12
  isFeatured,
14
- imagePriority,
15
13
  imageFetchPriority
16
- }: BrowseProductPreviewProps) => react_jsx_runtime1.JSX.Element;
14
+ }: BrowseProductPreviewProps) => react_jsx_runtime0.JSX.Element;
17
15
  //#endregion
18
16
  export { BrowseProductPreview };
19
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/browse-product-preview/index.tsx"],"sourcesContent":[],"mappings":";;;;KAUK,yBAAA;WACM;;EADN,aAAA,CAAA,EAAA,OAAA;EA0CC,kBAAA,CAAA,EAAA,MAiCL,GAAA,MAAA,GAAA,KAAA;CAjC6B;cAAxB,oBAAwB,EAAA,CAAA;EAAA,OAAA;EAAA,UAAA;EAAA,aAAA;EAAA;AAAA,CAAA,EAK3B,yBAL2B,EAAA,GAKF,kBAAA,CAAA,GAAA,CAAA,OALE"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/browse-product-preview/index.tsx"],"sourcesContent":[],"mappings":";;;;KAkBK,yBAAA;WACM;;EADN,kBAAA,CAAA,EAAA,MAAA,GAAyB,MAAA,GAAA,KACnB;AAAwB,CAAA;cAwC7B,oBAAwB,EAAA,CAAA;EAAA,OAAA;EAAA,UAAA;EAAA;AAAA,CAAA,EAI3B,yBAJ2B,EAAA,GAIF,kBAAA,CAAA,GAAA,CAAA,OAJE"}
@@ -1,3 +1,9 @@
1
+ 'use client';
2
+
3
+ import { useRef } from "react";
4
+ import { sendClientSignal } from "@gfed-medusa/sf-lib-common/lib/personalization/client-signal";
5
+ import { SignalType } from "@gfed-medusa/sf-lib-common/types/graphql";
6
+ import { useSearchParams } from "next/navigation";
1
7
  import { LocalizedClientLink } from "@gfed-medusa/sf-lib-common/components/localized-client-link";
2
8
  import { PreviewPrice } from "@gfed-medusa/sf-lib-common/components/preview-price";
3
9
  import { Thumbnail } from "@gfed-medusa/sf-lib-common/components/thumbnail";
@@ -27,19 +33,36 @@ const getBrowseProductPrice = (product) => {
27
33
  percentage_diff: isSale && originalPrice > 0 ? getPercentageDiff(originalPrice, product.priceAmount) : "0"
28
34
  };
29
35
  };
30
- const BrowseProductPreview = ({ product, isFeatured, imagePriority = false, imageFetchPriority }) => {
36
+ const BrowseProductPreview = ({ product, isFeatured, imageFetchPriority }) => {
31
37
  const price = getBrowseProductPrice(product);
38
+ const hoveredRef = useRef(false);
39
+ const query = useSearchParams()?.get("q") ?? "";
32
40
  return /* @__PURE__ */ jsx(LocalizedClientLink, {
33
- href: `/products/${product.handle}`,
41
+ href: `/products/${product.handle ?? ""}`,
34
42
  className: "group",
43
+ onClick: () => {
44
+ sendClientSignal(SignalType.SearchResultClick, {
45
+ productId: product.id ?? "",
46
+ productHandle: product.handle ?? "",
47
+ query
48
+ });
49
+ },
35
50
  children: /* @__PURE__ */ jsxs("div", {
36
51
  "data-testid": "product-wrapper",
52
+ onMouseEnter: () => {
53
+ if (!hoveredRef.current) {
54
+ hoveredRef.current = true;
55
+ sendClientSignal(SignalType.ProductHover, {
56
+ productId: product.id ?? "",
57
+ productHandle: product.handle ?? ""
58
+ });
59
+ }
60
+ },
37
61
  children: [/* @__PURE__ */ jsx(Thumbnail, {
38
62
  thumbnail: product.thumbnail,
39
63
  images: [],
40
64
  size: "full",
41
65
  isFeatured,
42
- imagePriority,
43
66
  imageFetchPriority
44
67
  }), /* @__PURE__ */ jsxs("div", {
45
68
  className: "txt-compact-medium mt-4 flex min-w-0 flex-col items-start gap-y-1 text-left",
@@ -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 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
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/browse-product-preview/index.tsx"],"sourcesContent":["'use client';\n\nimport { useRef } from 'react';\n\nimport { useSearchParams } from 'next/navigation';\n\nimport { 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 { sendClientSignal } from '@gfed-medusa/sf-lib-common/lib/personalization/client-signal';\nimport { SignalType } from '@gfed-medusa/sf-lib-common/types/graphql';\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 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 imageFetchPriority,\n}: BrowseProductPreviewProps) => {\n const price = getBrowseProductPrice(product);\n const hoveredRef = useRef(false);\n const searchParams = useSearchParams();\n const query = searchParams?.get('q') ?? '';\n\n return (\n <LocalizedClientLink\n href={`/products/${product.handle ?? ''}`}\n className=\"group\"\n onClick={() => {\n void sendClientSignal(SignalType.SearchResultClick, {\n productId: product.id ?? '',\n productHandle: product.handle ?? '',\n query,\n });\n }}\n >\n <div\n data-testid=\"product-wrapper\"\n onMouseEnter={() => {\n if (!hoveredRef.current) {\n hoveredRef.current = true;\n void sendClientSignal(SignalType.ProductHover, {\n productId: product.id ?? '',\n productHandle: product.handle ?? '',\n });\n }\n }}\n >\n <Thumbnail\n thumbnail={product.thumbnail}\n images={[]}\n size=\"full\"\n isFeatured={isFeatured}\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":";;;;;;;;;;;;;;;AAwBA,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,yBAC+B;CAC/B,MAAM,QAAQ,sBAAsB,QAAQ;CAC5C,MAAM,aAAa,OAAO,MAAM;CAEhC,MAAM,QADe,iBAAiB,EACV,IAAI,IAAI,IAAI;AAExC,QACE,oBAAC;EACC,MAAM,aAAa,QAAQ,UAAU;EACrC,WAAU;EACV,eAAe;AACb,GAAK,iBAAiB,WAAW,mBAAmB;IAClD,WAAW,QAAQ,MAAM;IACzB,eAAe,QAAQ,UAAU;IACjC;IACD,CAAC;;YAGJ,qBAAC;GACC,eAAY;GACZ,oBAAoB;AAClB,QAAI,CAAC,WAAW,SAAS;AACvB,gBAAW,UAAU;AACrB,KAAK,iBAAiB,WAAW,cAAc;MAC7C,WAAW,QAAQ,MAAM;MACzB,eAAe,QAAQ,UAAU;MAClC,CAAC;;;cAIN,oBAAC;IACC,WAAW,QAAQ;IACnB,QAAQ,EAAE;IACV,MAAK;IACO;IACQ;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"}
@@ -4,9 +4,11 @@ import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
  //#region src/components/image-gallery/index.d.ts
5
5
  type ImageGalleryProps = {
6
6
  images: ProductImage[];
7
+ productId: string;
7
8
  };
8
9
  declare const ImageGallery: ({
9
- images
10
+ images,
11
+ productId
10
12
  }: ImageGalleryProps) => react_jsx_runtime0.JSX.Element;
11
13
  //#endregion
12
14
  export { ImageGallery as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/image-gallery/index.tsx"],"sourcesContent":[],"mappings":";;;;KAMK,iBAAA;UACK;;AAHqC,cAMzC,YAJgB,EAAA,CAAA;EAAA;AACA,CAAZ,EAGwB,iBAHZ,EAAA,GAG6B,kBAAA,CAAA,GAAA,CAAA,OAH7B"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/image-gallery/index.tsx"],"sourcesContent":[],"mappings":";;;;KAUK,iBAAA;UACK;;AAHqC,CAAA;AAGzB,cAIhB,YAsCL,EAAA,CAAA;EAAA,MAAA;EAAA;AAAA,CAAA,EAtC4C,iBAsC5C,EAAA,GAtC6D,kBAAA,CAAA,GAAA,CAAA,OAsC7D"}
@@ -1,9 +1,13 @@
1
+ 'use client';
2
+
3
+ import { sendClientSignal } from "@gfed-medusa/sf-lib-common/lib/personalization/client-signal";
4
+ import { SignalType } from "@gfed-medusa/sf-lib-common/types/graphql";
1
5
  import { Container } from "@medusajs/ui";
2
6
  import { jsx } from "react/jsx-runtime";
3
7
  import Image from "next/image";
4
8
 
5
9
  //#region src/components/image-gallery/index.tsx
6
- const ImageGallery = ({ images }) => {
10
+ const ImageGallery = ({ images, productId }) => {
7
11
  return /* @__PURE__ */ jsx("div", {
8
12
  className: "relative flex items-start",
9
13
  children: /* @__PURE__ */ jsx("div", {
@@ -21,7 +25,13 @@ const ImageGallery = ({ images }) => {
21
25
  quality: 60,
22
26
  sizes: "(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px",
23
27
  style: { objectFit: "cover" },
24
- ...index === 0 ? { fetchPriority: "high" } : {}
28
+ ...index === 0 ? { fetchPriority: "high" } : {},
29
+ onClick: () => {
30
+ sendClientSignal(SignalType.ImageZoom, {
31
+ productId,
32
+ imageUrl: image.url
33
+ });
34
+ }
25
35
  })
26
36
  }, image.id);
27
37
  })
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/image-gallery/index.tsx"],"sourcesContent":["import Image from 'next/image';\n\nimport { Container } from '@medusajs/ui';\n\nimport { ProductImage } from '@/types/graphql';\n\ntype ImageGalleryProps = {\n images: ProductImage[];\n};\n\nconst ImageGallery = ({ images }: ImageGalleryProps) => {\n return (\n <div className=\"relative flex items-start\">\n <div className=\"small:mx-16 flex flex-1 flex-col gap-y-4\">\n {images.map((image, index) => {\n return (\n <Container\n key={image.id}\n className=\"bg-ui-bg-subtle relative aspect-[29/34] w-full overflow-hidden\"\n id={image.id}\n >\n {!!image.url && (\n <Image\n src={image.url}\n priority={index <= 2 ? true : false}\n className=\"rounded-rounded absolute inset-0\"\n alt={`Product image ${index + 1}`}\n fill\n quality={60}\n sizes=\"(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px\"\n style={{\n objectFit: 'cover',\n }}\n {...(index === 0 ? { fetchPriority: 'high' } : {})}\n />\n )}\n </Container>\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default ImageGallery;\n"],"mappings":";;;;;AAUA,MAAM,gBAAgB,EAAE,aAAgC;AACtD,QACE,oBAAC;EAAI,WAAU;YACb,oBAAC;GAAI,WAAU;aACZ,OAAO,KAAK,OAAO,UAAU;AAC5B,WACE,oBAAC;KAEC,WAAU;KACV,IAAI,MAAM;eAET,CAAC,CAAC,MAAM,OACP,oBAAC;MACC,KAAK,MAAM;MACX,UAAU,SAAS,IAAI,OAAO;MAC9B,WAAU;MACV,KAAK,iBAAiB,QAAQ;MAC9B;MACA,SAAS;MACT,OAAM;MACN,OAAO,EACL,WAAW,SACZ;MACD,GAAK,UAAU,IAAI,EAAE,eAAe,QAAQ,GAAG,EAAE;OACjD;OAjBC,MAAM,GAmBD;KAEd;IACE;GACF;;AAIV,4BAAe"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/image-gallery/index.tsx"],"sourcesContent":["'use client';\n\nimport Image from 'next/image';\n\nimport { Container } from '@medusajs/ui';\n\nimport { sendClientSignal } from '@gfed-medusa/sf-lib-common/lib/personalization/client-signal';\nimport { SignalType } from '@gfed-medusa/sf-lib-common/types/graphql';\nimport { ProductImage } from '@/types/graphql';\n\ntype ImageGalleryProps = {\n images: ProductImage[];\n productId: string;\n};\n\nconst ImageGallery = ({ images, productId }: ImageGalleryProps) => {\n return (\n <div className=\"relative flex items-start\">\n <div className=\"small:mx-16 flex flex-1 flex-col gap-y-4\">\n {images.map((image, index) => {\n return (\n <Container\n key={image.id}\n className=\"bg-ui-bg-subtle relative aspect-[29/34] w-full overflow-hidden\"\n id={image.id}\n >\n {!!image.url && (\n <Image\n src={image.url}\n priority={index <= 2 ? true : false}\n className=\"rounded-rounded absolute inset-0\"\n alt={`Product image ${index + 1}`}\n fill\n quality={60}\n sizes=\"(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px\"\n style={{\n objectFit: 'cover',\n }}\n {...(index === 0 ? { fetchPriority: 'high' } : {})}\n onClick={() => {\n void sendClientSignal(SignalType.ImageZoom, {\n productId,\n imageUrl: image.url,\n });\n }}\n />\n )}\n </Container>\n );\n })}\n </div>\n </div>\n );\n};\n\nexport default ImageGallery;\n"],"mappings":";;;;;;;;;AAeA,MAAM,gBAAgB,EAAE,QAAQ,gBAAmC;AACjE,QACE,oBAAC;EAAI,WAAU;YACb,oBAAC;GAAI,WAAU;aACZ,OAAO,KAAK,OAAO,UAAU;AAC5B,WACE,oBAAC;KAEC,WAAU;KACV,IAAI,MAAM;eAET,CAAC,CAAC,MAAM,OACP,oBAAC;MACC,KAAK,MAAM;MACX,UAAU,SAAS,IAAI,OAAO;MAC9B,WAAU;MACV,KAAK,iBAAiB,QAAQ;MAC9B;MACA,SAAS;MACT,OAAM;MACN,OAAO,EACL,WAAW,SACZ;MACD,GAAK,UAAU,IAAI,EAAE,eAAe,QAAQ,GAAG,EAAE;MACjD,eAAe;AACb,OAAK,iBAAiB,WAAW,WAAW;QAC1C;QACA,UAAU,MAAM;QACjB,CAAC;;OAEJ;OAvBC,MAAM,GAyBD;KAEd;IACE;GACF;;AAIV,4BAAe"}
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime3 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime2 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/components/pagination/index.d.ts
4
4
  declare function Pagination({
@@ -19,7 +19,7 @@ declare function Pagination({
19
19
  isLoading?: boolean;
20
20
  onLoadMore: () => void;
21
21
  'data-testid'?: string;
22
- }): react_jsx_runtime3.JSX.Element;
22
+ }): react_jsx_runtime2.JSX.Element;
23
23
  //#endregion
24
24
  export { Pagination };
25
25
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { ProductActionsProduct } from "../../types/index.js";
2
- import * as react_jsx_runtime5 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime4 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): react_jsx_runtime5.JSX.Element;
16
+ }: ProductActionsProps): react_jsx_runtime4.JSX.Element;
17
17
  //#endregion
18
18
  export { ProductActionsProps, ProductActions as default };
19
19
  //# sourceMappingURL=index.d.ts.map
@@ -7,9 +7,9 @@ import { useProductPrice } from "../../lib/hooks/use-product-price.js";
7
7
  import { mergeProductPricing } from "../../lib/utils/merge-product-pricing.js";
8
8
  import mobile_actions_default from "./mobile-actions.js";
9
9
  import { useEffect, useMemo, useRef, useState } from "react";
10
+ import { useParams } from "next/navigation";
10
11
  import { Button } from "@medusajs/ui";
11
12
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
- import { useParams } from "next/navigation";
13
13
  import { isEqual } from "lodash";
14
14
  import { ErrorMessage } from "@gfed-medusa/sf-lib-common/components/error-message";
15
15
  import { WebComponent } from "@gfed-medusa/sf-lib-common/components/web-component";
@@ -1,7 +1,7 @@
1
- import * as react_jsx_runtime10 from "react/jsx-runtime";
1
+ import * as react_jsx_runtime1 from "react/jsx-runtime";
2
2
 
3
3
  //#region src/components/product-onboarding-cta/index.d.ts
4
- declare function ProductOnboardingCta(): Promise<react_jsx_runtime10.JSX.Element | null>;
4
+ declare function ProductOnboardingCta(): Promise<react_jsx_runtime1.JSX.Element | null>;
5
5
  //#endregion
6
6
  export { ProductOnboardingCta as default };
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/product-onboarding-cta/index.tsx"],"sourcesContent":[],"mappings":";;;iBAIe,oBAAA,CAAA,GAAoB,QAAA,mBAAA,CAAA,GAAA,CAAA,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/product-onboarding-cta/index.tsx"],"sourcesContent":[],"mappings":";;;iBAIe,oBAAA,CAAA,GAAoB,QAAA,kBAAA,CAAA,GAAA,CAAA,OAAA"}
@@ -1,6 +1,6 @@
1
1
  import { ProductVariant } from "../../types/graphql.js";
2
2
  import { ProductActionsProduct } from "../../types/index.js";
3
- import * as react_jsx_runtime9 from "react/jsx-runtime";
3
+ import * as react_jsx_runtime3 from "react/jsx-runtime";
4
4
 
5
5
  //#region src/components/product-price/index.d.ts
6
6
  declare function ProductPrice({
@@ -9,7 +9,7 @@ declare function ProductPrice({
9
9
  }: {
10
10
  product: ProductActionsProduct;
11
11
  variant?: ProductVariant;
12
- }): react_jsx_runtime9.JSX.Element;
12
+ }): react_jsx_runtime3.JSX.Element;
13
13
  //#endregion
14
14
  export { ProductPrice as default };
15
15
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { Product } from "../../types/graphql.js";
2
- import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime5 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/components/product-tabs/index.d.ts
5
5
  type ProductTabsProps = {
@@ -7,7 +7,7 @@ type ProductTabsProps = {
7
7
  };
8
8
  declare const ProductTabs: ({
9
9
  product
10
- }: ProductTabsProps) => react_jsx_runtime0.JSX.Element;
10
+ }: ProductTabsProps) => react_jsx_runtime5.JSX.Element;
11
11
  //#endregion
12
12
  export { ProductTabs as default };
13
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/product-tabs/index.tsx"],"sourcesContent":[],"mappings":";;;;KAUK,gBAAA;WACM;;AAL+B,cAQpC,WAJe,EAAA,CAAA;EAAA;AACH,CAAP,EAGuB,gBAHhB,EAAA,GAGgC,kBAAA,CAAA,GAAA,CAAA,OAHhC"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/product-tabs/index.tsx"],"sourcesContent":[],"mappings":";;;;KAcK,gBAAA;WACM;;AAL+B,cAQpC,WAJe,EAAA,CAAA;EAAA;AACH,CAAP,EAGuB,gBAHhB,EAAA,GAGgC,kBAAA,CAAA,GAAA,CAAA,OAHhC"}
@@ -1,6 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import accordion_default from "./accordion.js";
4
+ import { useEffect, useRef, useState } from "react";
5
+ import { sendClientSignal } from "@gfed-medusa/sf-lib-common/lib/personalization/client-signal";
6
+ import { SignalType } from "@gfed-medusa/sf-lib-common/types/graphql";
4
7
  import { jsx, jsxs } from "react/jsx-runtime";
5
8
  import { Back } from "@gfed-medusa/sf-lib-ui/icons/back";
6
9
  import { FastDelivery } from "@gfed-medusa/sf-lib-ui/icons/fast-delivery";
@@ -8,17 +11,36 @@ import { Refresh } from "@gfed-medusa/sf-lib-ui/icons/refresh";
8
11
 
9
12
  //#region src/components/product-tabs/index.tsx
10
13
  const ProductTabs = ({ product }) => {
14
+ const hasSentReviewsViewRef = useRef(false);
15
+ const productId = product.id;
16
+ const [openTabValues, setOpenTabValues] = useState([]);
17
+ const tabs = [{
18
+ label: "Product Information",
19
+ component: /* @__PURE__ */ jsx(ProductInfoTab, { product })
20
+ }, {
21
+ label: "Shipping & Returns",
22
+ component: /* @__PURE__ */ jsx(ShippingInfoTab, {})
23
+ }];
24
+ useEffect(() => {
25
+ if (openTabValues.includes("Shipping & Returns") && !hasSentReviewsViewRef.current) {
26
+ hasSentReviewsViewRef.current = true;
27
+ sendClientSignal(SignalType.ReviewsView, { productId });
28
+ sendClientSignal(SignalType.ReturnPolicyView, { productId });
29
+ }
30
+ }, [openTabValues, productId]);
11
31
  return /* @__PURE__ */ jsx("div", {
12
32
  className: "w-full",
13
33
  children: /* @__PURE__ */ jsx(accordion_default, {
14
34
  type: "multiple",
15
- children: [{
16
- label: "Product Information",
17
- component: /* @__PURE__ */ jsx(ProductInfoTab, { product })
18
- }, {
19
- label: "Shipping & Returns",
20
- component: /* @__PURE__ */ jsx(ShippingInfoTab, {})
21
- }].map((tab, i) => /* @__PURE__ */ jsx(accordion_default.Item, {
35
+ value: openTabValues,
36
+ onValueChange: (values) => {
37
+ setOpenTabValues(values);
38
+ sendClientSignal(SignalType.TabSwitch, {
39
+ activeTabs: values,
40
+ productId
41
+ });
42
+ },
43
+ children: tabs.map((tab, i) => /* @__PURE__ */ jsx(accordion_default.Item, {
22
44
  title: tab.label,
23
45
  headingSize: "medium",
24
46
  value: tab.label,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Accordion"],"sources":["../../../src/components/product-tabs/index.tsx"],"sourcesContent":["'use client';\n\nimport { Back } from '@gfed-medusa/sf-lib-ui/icons/back';\nimport { FastDelivery } from '@gfed-medusa/sf-lib-ui/icons/fast-delivery';\nimport { Refresh } from '@gfed-medusa/sf-lib-ui/icons/refresh';\n\nimport { Product } from '@/types/graphql';\n\nimport Accordion from './accordion';\n\ntype ProductTabsProps = {\n product: Product;\n};\n\nconst ProductTabs = ({ product }: ProductTabsProps) => {\n const tabs = [\n {\n label: 'Product Information',\n component: <ProductInfoTab product={product} />,\n },\n {\n label: 'Shipping & Returns',\n component: <ShippingInfoTab />,\n },\n ];\n\n return (\n <div className=\"w-full\">\n <Accordion type=\"multiple\">\n {tabs.map((tab, i) => (\n <Accordion.Item\n key={i}\n title={tab.label}\n headingSize=\"medium\"\n value={tab.label}\n >\n {tab.component}\n </Accordion.Item>\n ))}\n </Accordion>\n </div>\n );\n};\n\nconst ProductInfoTab = ({ product }: ProductTabsProps) => {\n return (\n <div className=\"text-small-regular py-8\">\n <div className=\"grid grid-cols-2 gap-x-8\">\n <div className=\"flex flex-col gap-y-4\">\n <div>\n <span className=\"font-semibold\">Material</span>\n <p>{product.material ? product.material : '-'}</p>\n </div>\n <div>\n <span className=\"font-semibold\">Country of origin</span>\n <p>{product.originCountry ? product.originCountry : '-'}</p>\n </div>\n <div>\n <span className=\"font-semibold\">Type</span>\n <p>{product.type || '-'}</p>\n </div>\n </div>\n <div className=\"flex flex-col gap-y-4\">\n <div>\n <span className=\"font-semibold\">Weight</span>\n <p>{product.weight ? `${product.weight} g` : '-'}</p>\n </div>\n <div>\n <span className=\"font-semibold\">Dimensions</span>\n <p>\n {product.length && product.width && product.height\n ? `${product.length}L x ${product.width}W x ${product.height}H`\n : '-'}\n </p>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nconst ShippingInfoTab = () => {\n return (\n <div className=\"text-small-regular py-8\">\n <div className=\"grid grid-cols-1 gap-y-8\">\n <div className=\"flex items-start gap-x-2\">\n <FastDelivery />\n <div>\n <span className=\"font-semibold\">Fast delivery</span>\n <p className=\"max-w-sm\">\n Your package will arrive in 3-5 business days at your pick up\n location or in the comfort of your home.\n </p>\n </div>\n </div>\n <div className=\"flex items-start gap-x-2\">\n <Refresh />\n <div>\n <span className=\"font-semibold\">Simple exchanges</span>\n <p className=\"max-w-sm\">\n Is the fit not quite right? No worries - we&apos;ll exchange your\n product for a new one.\n </p>\n </div>\n </div>\n <div className=\"flex items-start gap-x-2\">\n <Back />\n <div>\n <span className=\"font-semibold\">Easy returns</span>\n <p className=\"max-w-sm\">\n Just return your product and we&apos;ll refund your money. No\n questions asked – we&apos;ll do our best to make sure your return\n is hassle-free.\n </p>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default ProductTabs;\n"],"mappings":";;;;;;;;;AAcA,MAAM,eAAe,EAAE,cAAgC;AAYrD,QACE,oBAAC;EAAI,WAAU;YACb,oBAACA;GAAU,MAAK;aAbP,CACX;IACE,OAAO;IACP,WAAW,oBAAC,kBAAwB,UAAW;IAChD,EACD;IACE,OAAO;IACP,WAAW,oBAAC,oBAAkB;IAC/B,CACF,CAKW,KAAK,KAAK,MACd,oBAACA,kBAAU;IAET,OAAO,IAAI;IACX,aAAY;IACZ,OAAO,IAAI;cAEV,IAAI;MALA,EAMU,CACjB;IACQ;GACR;;AAIV,MAAM,kBAAkB,EAAE,cAAgC;AACxD,QACE,oBAAC;EAAI,WAAU;YACb,qBAAC;GAAI,WAAU;cACb,qBAAC;IAAI,WAAU;;KACb,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAe,EAC/C,oBAAC,iBAAG,QAAQ,WAAW,QAAQ,WAAW,MAAQ,IAC9C;KACN,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAwB,EACxD,oBAAC,iBAAG,QAAQ,gBAAgB,QAAQ,gBAAgB,MAAQ,IACxD;KACN,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAW,EAC3C,oBAAC,iBAAG,QAAQ,QAAQ,MAAQ,IACxB;;KACF,EACN,qBAAC;IAAI,WAAU;eACb,qBAAC,oBACC,oBAAC;KAAK,WAAU;eAAgB;MAAa,EAC7C,oBAAC,iBAAG,QAAQ,SAAS,GAAG,QAAQ,OAAO,MAAM,MAAQ,IACjD,EACN,qBAAC,oBACC,oBAAC;KAAK,WAAU;eAAgB;MAAiB,EACjD,oBAAC,iBACE,QAAQ,UAAU,QAAQ,SAAS,QAAQ,SACxC,GAAG,QAAQ,OAAO,MAAM,QAAQ,MAAM,MAAM,QAAQ,OAAO,KAC3D,MACF,IACA;KACF;IACF;GACF;;AAIV,MAAM,wBAAwB;AAC5B,QACE,oBAAC;EAAI,WAAU;YACb,qBAAC;GAAI,WAAU;;IACb,qBAAC;KAAI,WAAU;gBACb,oBAAC,iBAAe,EAChB,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAoB,EACpD,oBAAC;MAAE,WAAU;gBAAW;OAGpB,IACA;MACF;IACN,qBAAC;KAAI,WAAU;gBACb,oBAAC,YAAU,EACX,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAuB,EACvD,oBAAC;MAAE,WAAU;gBAAW;OAGpB,IACA;MACF;IACN,qBAAC;KAAI,WAAU;gBACb,oBAAC,SAAO,EACR,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAmB,EACnD,oBAAC;MAAE,WAAU;gBAAW;OAIpB,IACA;MACF;;IACF;GACF;;AAIV,2BAAe"}
1
+ {"version":3,"file":"index.js","names":["Accordion"],"sources":["../../../src/components/product-tabs/index.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nimport { Back } from '@gfed-medusa/sf-lib-ui/icons/back';\nimport { FastDelivery } from '@gfed-medusa/sf-lib-ui/icons/fast-delivery';\nimport { Refresh } from '@gfed-medusa/sf-lib-ui/icons/refresh';\n\nimport { sendClientSignal } from '@gfed-medusa/sf-lib-common/lib/personalization/client-signal';\nimport { SignalType } from '@gfed-medusa/sf-lib-common/types/graphql';\nimport { Product } from '@/types/graphql';\n\nimport Accordion from './accordion';\n\ntype ProductTabsProps = {\n product: Product;\n};\n\nconst ProductTabs = ({ product }: ProductTabsProps) => {\n const hasSentReviewsViewRef = useRef(false);\n const productId = product.id;\n const [openTabValues, setOpenTabValues] = useState<string[]>([]);\n\n const tabs = [\n {\n label: 'Product Information',\n component: <ProductInfoTab product={product} />,\n },\n {\n label: 'Shipping & Returns',\n component: <ShippingInfoTab />,\n },\n ];\n\n // Fire REVIEWS_VIEW and RETURN_POLICY_VIEW when the\n // \"Shipping & Returns\" tab is open — whether initially\n // (via defaultValue) or via user interaction.\n useEffect(() => {\n if (\n openTabValues.includes('Shipping & Returns') &&\n !hasSentReviewsViewRef.current\n ) {\n hasSentReviewsViewRef.current = true;\n void sendClientSignal(SignalType.ReviewsView, { productId });\n void sendClientSignal(SignalType.ReturnPolicyView, { productId });\n }\n }, [openTabValues, productId]);\n\n return (\n <div className=\"w-full\">\n <Accordion\n type=\"multiple\"\n value={openTabValues}\n onValueChange={(values: string[]) => {\n setOpenTabValues(values);\n void sendClientSignal(SignalType.TabSwitch, {\n activeTabs: values,\n productId,\n });\n }}\n >\n {tabs.map((tab, i) => (\n <Accordion.Item\n key={i}\n title={tab.label}\n headingSize=\"medium\"\n value={tab.label}\n >\n {tab.component}\n </Accordion.Item>\n ))}\n </Accordion>\n </div>\n );\n};\n\nconst ProductInfoTab = ({ product }: ProductTabsProps) => {\n return (\n <div className=\"text-small-regular py-8\">\n <div className=\"grid grid-cols-2 gap-x-8\">\n <div className=\"flex flex-col gap-y-4\">\n <div>\n <span className=\"font-semibold\">Material</span>\n <p>{product.material ? product.material : '-'}</p>\n </div>\n <div>\n <span className=\"font-semibold\">Country of origin</span>\n <p>{product.originCountry ? product.originCountry : '-'}</p>\n </div>\n <div>\n <span className=\"font-semibold\">Type</span>\n <p>{product.type || '-'}</p>\n </div>\n </div>\n <div className=\"flex flex-col gap-y-4\">\n <div>\n <span className=\"font-semibold\">Weight</span>\n <p>{product.weight ? `${product.weight} g` : '-'}</p>\n </div>\n <div>\n <span className=\"font-semibold\">Dimensions</span>\n <p>\n {product.length && product.width && product.height\n ? `${product.length}L x ${product.width}W x ${product.height}H`\n : '-'}\n </p>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nconst ShippingInfoTab = () => {\n return (\n <div className=\"text-small-regular py-8\">\n <div className=\"grid grid-cols-1 gap-y-8\">\n <div className=\"flex items-start gap-x-2\">\n <FastDelivery />\n <div>\n <span className=\"font-semibold\">Fast delivery</span>\n <p className=\"max-w-sm\">\n Your package will arrive in 3-5 business days at your pick up\n location or in the comfort of your home.\n </p>\n </div>\n </div>\n <div className=\"flex items-start gap-x-2\">\n <Refresh />\n <div>\n <span className=\"font-semibold\">Simple exchanges</span>\n <p className=\"max-w-sm\">\n Is the fit not quite right? No worries - we&apos;ll exchange your\n product for a new one.\n </p>\n </div>\n </div>\n <div className=\"flex items-start gap-x-2\">\n <Back />\n <div>\n <span className=\"font-semibold\">Easy returns</span>\n <p className=\"max-w-sm\">\n Just return your product and we&apos;ll refund your money. No\n questions asked – we&apos;ll do our best to make sure your return\n is hassle-free.\n </p>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default ProductTabs;\n"],"mappings":";;;;;;;;;;;;AAkBA,MAAM,eAAe,EAAE,cAAgC;CACrD,MAAM,wBAAwB,OAAO,MAAM;CAC3C,MAAM,YAAY,QAAQ;CAC1B,MAAM,CAAC,eAAe,oBAAoB,SAAmB,EAAE,CAAC;CAEhE,MAAM,OAAO,CACX;EACE,OAAO;EACP,WAAW,oBAAC,kBAAwB,UAAW;EAChD,EACD;EACE,OAAO;EACP,WAAW,oBAAC,oBAAkB;EAC/B,CACF;AAKD,iBAAgB;AACd,MACE,cAAc,SAAS,qBAAqB,IAC5C,CAAC,sBAAsB,SACvB;AACA,yBAAsB,UAAU;AAChC,GAAK,iBAAiB,WAAW,aAAa,EAAE,WAAW,CAAC;AAC5D,GAAK,iBAAiB,WAAW,kBAAkB,EAAE,WAAW,CAAC;;IAElE,CAAC,eAAe,UAAU,CAAC;AAE9B,QACE,oBAAC;EAAI,WAAU;YACb,oBAACA;GACC,MAAK;GACL,OAAO;GACP,gBAAgB,WAAqB;AACnC,qBAAiB,OAAO;AACxB,IAAK,iBAAiB,WAAW,WAAW;KAC1C,YAAY;KACZ;KACD,CAAC;;aAGH,KAAK,KAAK,KAAK,MACd,oBAACA,kBAAU;IAET,OAAO,IAAI;IACX,aAAY;IACZ,OAAO,IAAI;cAEV,IAAI;MALA,EAMU,CACjB;IACQ;GACR;;AAIV,MAAM,kBAAkB,EAAE,cAAgC;AACxD,QACE,oBAAC;EAAI,WAAU;YACb,qBAAC;GAAI,WAAU;cACb,qBAAC;IAAI,WAAU;;KACb,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAe,EAC/C,oBAAC,iBAAG,QAAQ,WAAW,QAAQ,WAAW,MAAQ,IAC9C;KACN,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAwB,EACxD,oBAAC,iBAAG,QAAQ,gBAAgB,QAAQ,gBAAgB,MAAQ,IACxD;KACN,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAW,EAC3C,oBAAC,iBAAG,QAAQ,QAAQ,MAAQ,IACxB;;KACF,EACN,qBAAC;IAAI,WAAU;eACb,qBAAC,oBACC,oBAAC;KAAK,WAAU;eAAgB;MAAa,EAC7C,oBAAC,iBAAG,QAAQ,SAAS,GAAG,QAAQ,OAAO,MAAM,MAAQ,IACjD,EACN,qBAAC,oBACC,oBAAC;KAAK,WAAU;eAAgB;MAAiB,EACjD,oBAAC,iBACE,QAAQ,UAAU,QAAQ,SAAS,QAAQ,SACxC,GAAG,QAAQ,OAAO,MAAM,QAAQ,MAAM,MAAM,QAAQ,OAAO,KAC3D,MACF,IACA;KACF;IACF;GACF;;AAIV,MAAM,wBAAwB;AAC5B,QACE,oBAAC;EAAI,WAAU;YACb,qBAAC;GAAI,WAAU;;IACb,qBAAC;KAAI,WAAU;gBACb,oBAAC,iBAAe,EAChB,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAoB,EACpD,oBAAC;MAAE,WAAU;gBAAW;OAGpB,IACA;MACF;IACN,qBAAC;KAAI,WAAU;gBACb,oBAAC,YAAU,EACX,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAuB,EACvD,oBAAC;MAAE,WAAU;gBAAW;OAGpB,IACA;MACF;IACN,qBAAC;KAAI,WAAU;gBACb,oBAAC,SAAO,EACR,qBAAC,oBACC,oBAAC;MAAK,WAAU;gBAAgB;OAAmB,EACnD,oBAAC;MAAE,WAAU;gBAAW;OAIpB,IACA;MACF;;IACF;GACF;;AAIV,2BAAe"}
@@ -1,5 +1,5 @@
1
1
  import { SortOptions } from "./sort-products/index.js";
2
- import * as react_jsx_runtime2 from "react/jsx-runtime";
2
+ import * as react_jsx_runtime6 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/components/refinement-list/index.d.ts
5
5
  type RefinementListProps = {
@@ -12,7 +12,7 @@ declare const RefinementList: ({
12
12
  sortBy,
13
13
  "data-testid": dataTestId,
14
14
  className
15
- }: RefinementListProps) => react_jsx_runtime2.JSX.Element;
15
+ }: RefinementListProps) => react_jsx_runtime6.JSX.Element;
16
16
  //#endregion
17
17
  export { RefinementList as default };
18
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/refinement-list/index.tsx"],"sourcesContent":[],"mappings":";;;;KAUK,mBAAA;UACK;;EADL,aAAA,CAAA,EAAA,MAAmB;EAOlB,SAAA,CAAA,EAAA,MA8CL;CA9CuB;cAAlB,cAAkB,EAAA,CAAA;EAAA,MAAA;EAAA,aAAA,EAAA,UAAA;EAAA;AAAA,CAAA,EAIrB,mBAJqB,EAAA,GAIF,kBAAA,CAAA,GAAA,CAAA,OAJE"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/components/refinement-list/index.tsx"],"sourcesContent":[],"mappings":";;;;KAaK,mBAAA;UACK;;EADL,aAAA,CAAA,EAAA,MAAmB;EAOlB,SAAA,CAAA,EAAA,MAmDL;CAnDuB;cAAlB,cAAkB,EAAA,CAAA;EAAA,MAAA;EAAA,aAAA,EAAA,UAAA;EAAA;AAAA,CAAA,EAIrB,mBAJqB,EAAA,GAIF,kBAAA,CAAA,GAAA,CAAA,OAJE"}
@@ -2,9 +2,11 @@
2
2
 
3
3
  import sort_products_default from "./sort-products/index.js";
4
4
  import { useCallback } from "react";
5
+ import { sendClientSignal } from "@gfed-medusa/sf-lib-common/lib/personalization/client-signal";
6
+ import { SignalType } from "@gfed-medusa/sf-lib-common/types/graphql";
7
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
5
8
  import { clx } from "@medusajs/ui";
6
9
  import { jsx, jsxs } from "react/jsx-runtime";
7
- import { usePathname, useRouter, useSearchParams } from "next/navigation";
8
10
 
9
11
  //#region src/components/refinement-list/index.tsx
10
12
  const RefinementList = ({ sortBy, "data-testid": dataTestId, className }) => {
@@ -19,6 +21,10 @@ const RefinementList = ({ sortBy, "data-testid": dataTestId, className }) => {
19
21
  }, [searchParams]);
20
22
  const setQueryParams = (name, value) => {
21
23
  const query = createQueryString(name, value);
24
+ if (name !== "sortBy") sendClientSignal(SignalType.FilterApplied, {
25
+ filterName: name,
26
+ filterValue: value
27
+ });
22
28
  router.push(`${pathname}?${query}`);
23
29
  };
24
30
  return /* @__PURE__ */ jsxs("div", {