@tapcart/mobile-components 0.7.52 → 0.7.53

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 (35) hide show
  1. package/dist/components/ThemeProvider.d.ts +3 -0
  2. package/dist/components/ThemeProvider.d.ts.map +1 -0
  3. package/dist/components/ThemeProvider.js +18 -0
  4. package/dist/components/ThemeToggle.d.ts +2 -0
  5. package/dist/components/ThemeToggle.d.ts.map +1 -0
  6. package/dist/components/ThemeToggle.js +8 -0
  7. package/dist/components/hooks/use-block-conditional-rendering.d.ts.map +1 -1
  8. package/dist/components/hooks/use-block-conditional-rendering.js +46 -38
  9. package/dist/components/hooks/use-block-conditionals.d.ts +2 -0
  10. package/dist/components/hooks/use-block-conditionals.d.ts.map +1 -0
  11. package/dist/components/hooks/use-block-conditionals.js +2 -0
  12. package/dist/components/hooks/use-nosto-recommendation.test.js +118 -25
  13. package/dist/components/hooks/use-tap.d.ts +8 -0
  14. package/dist/components/hooks/use-tap.d.ts.map +1 -0
  15. package/dist/components/hooks/use-tap.js +100 -0
  16. package/dist/components/ui/input.d.ts +17 -0
  17. package/dist/components/ui/input.d.ts.map +1 -0
  18. package/dist/components/ui/input.js +35 -0
  19. package/dist/components/ui/product-grid.d.ts +15 -0
  20. package/dist/components/ui/product-grid.d.ts.map +1 -0
  21. package/dist/components/ui/product-grid.js +22 -0
  22. package/dist/components/ui/text.d.ts +9 -1
  23. package/dist/components/ui/text.d.ts.map +1 -1
  24. package/dist/components/ui/text.js +39 -2
  25. package/dist/lib/utils.d.ts +2 -0
  26. package/dist/lib/utils.d.ts.map +1 -1
  27. package/dist/lib/utils.js +50 -0
  28. package/dist/styles.css +182 -159
  29. package/package.json +9 -7
  30. package/dist/components/hooks/use-debug-dependencies.d.ts +0 -10
  31. package/dist/components/hooks/use-debug-dependencies.d.ts.map +0 -1
  32. package/dist/components/hooks/use-debug-dependencies.js +0 -18
  33. package/dist/components/hooks/use-layout.d.ts +0 -13
  34. package/dist/components/hooks/use-layout.d.ts.map +0 -1
  35. package/dist/components/hooks/use-layout.js +0 -23
@@ -0,0 +1,3 @@
1
+ import { ThemeProviderProps } from "next-themes/dist/types";
2
+ export declare function ThemeProvider({ children, ...props }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
3
+ //# sourceMappingURL=ThemeProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../components/ThemeProvider.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAE3D,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,kBAAkB,2CAEvE"}
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ import { jsx as _jsx } from "react/jsx-runtime";
14
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
15
+ export function ThemeProvider(_a) {
16
+ var { children } = _a, props = __rest(_a, ["children"]);
17
+ return _jsx(NextThemesProvider, Object.assign({}, props, { children: children }));
18
+ }
@@ -0,0 +1,2 @@
1
+ export declare function ThemeToggle(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=ThemeToggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeToggle.d.ts","sourceRoot":"","sources":["../../components/ThemeToggle.tsx"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,4CAc1B"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTheme } from "next-themes";
4
+ import { Button } from "../components/ui/button";
5
+ export function ThemeToggle() {
6
+ const { setTheme, theme } = useTheme();
7
+ return (_jsxs(Button, Object.assign({ variant: "ghost", size: "sm", onClick: () => setTheme(theme === "light" ? "dark" : "light") }, { children: [_jsx("div", { className: "rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" }), _jsx("div", { className: "absolute rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" }), _jsx("span", Object.assign({ className: "sr-only" }, { children: "Toggle theme" }))] })));
8
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"use-block-conditional-rendering.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-block-conditional-rendering.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,YAAY,EAGb,MAAM,kBAAkB,CAAA;AAezB,eAAO,MAAM,4BAA4B,WAC/B;IACN,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,UACO,YAAY;;;CAyJrB,CAAA"}
1
+ {"version":3,"file":"use-block-conditional-rendering.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-block-conditional-rendering.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,YAAY,EAGb,MAAM,kBAAkB,CAAA;AAgBzB,eAAO,MAAM,4BAA4B,WAC/B;IACN,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf,UACO,YAAY;;;CA6KrB,CAAA"}
@@ -16,11 +16,11 @@ import { useCollection } from "./use-collection";
16
16
  import { useSearchParams } from "next/navigation";
17
17
  //@ts-expect-error
18
18
  import { useVariables } from "@tapcart/webbridge-react";
19
- import { gidFromId, countNumberOfTagsInState, getEnvState, shouldShowBlock, } from "../../lib/utils";
19
+ import { gidFromId, countNumberOfTagsInState, getEnvState, shouldShowBlock, evaluateConditions, } from "../../lib/utils";
20
20
  export const useBlockConditionalRendering = (_props, _block) => {
21
- var _a, _b, _c, _d, _e, _f;
22
- const _g = ((_a = _block === null || _block === void 0 ? void 0 : _block.visibilityConditions) === null || _a === void 0 ? void 0 : _a.conditions) || {}, { enabled: isEnabled = false, exclude: isInverse = false } = _g, restOfProps = __rest(_g, ["enabled", "exclude"]);
23
- let shouldShow = true;
21
+ var _a, _b, _c, _d, _e;
22
+ const _f = ((_a = _block === null || _block === void 0 ? void 0 : _block.visibilityConditions) === null || _a === void 0 ? void 0 : _a.conditions) || {}, { enabled: conditionalsV1IsEnabled = false, exclude: isInverse = false } = _f, restOfProps = __rest(_f, ["enabled", "exclude"]);
23
+ const { _version = 1, conditions, enabled: conditionalsV2IsEnabled = false, } = (_block === null || _block === void 0 ? void 0 : _block.visibilityConditions) || {};
24
24
  const blockState = restOfProps;
25
25
  const { appId = "", apiUrl = "" } = _props;
26
26
  const searchParams = useSearchParams();
@@ -29,7 +29,7 @@ export const useBlockConditionalRendering = (_props, _block) => {
29
29
  const customerVariables = variables === null || variables === void 0 ? void 0 : variables.customer;
30
30
  // locale is provided in this form en_US
31
31
  const [language, country] = ((_b = deviceVariables === null || deviceVariables === void 0 ? void 0 : deviceVariables.locale) === null || _b === void 0 ? void 0 : _b.split("_")) || [];
32
- const productId = (_c = searchParams.get("productId")) !== null && _c !== void 0 ? _c : undefined;
32
+ const productId = (_c = searchParams === null || searchParams === void 0 ? void 0 : searchParams.get("productId")) !== null && _c !== void 0 ? _c : undefined;
33
33
  const location = {
34
34
  country: country || "US",
35
35
  language: language || "en",
@@ -45,7 +45,7 @@ export const useBlockConditionalRendering = (_props, _block) => {
45
45
  };
46
46
  }), [blockProductMetafields]);
47
47
  // Fetch product if productId is present
48
- const { products, error: useProductsError, isLoading: useProductsLoading, } = useProducts(productId !== undefined && isEnabled
48
+ const { products, error: useProductsError, isLoading: isProductsLoading, } = useProducts(productId !== undefined && conditionalsV1IsEnabled
49
49
  ? {
50
50
  productIds: [gidFromId(productId)],
51
51
  baseURL: apiUrl,
@@ -68,9 +68,9 @@ export const useBlockConditionalRendering = (_props, _block) => {
68
68
  .map((m) => `${m.namespace}.${m.key}`)
69
69
  .join(",")
70
70
  : undefined, [collectionMetafieldsQuery]);
71
- const collectionId = (_d = searchParams.get("collectionId")) !== null && _d !== void 0 ? _d : undefined;
72
- const collectionHandle = (_e = searchParams.get("collectionHandle")) !== null && _e !== void 0 ? _e : undefined;
73
- const { specificCollection = {}, error: useCollectionError, loading: useCollectionLoading, } = useCollection({
71
+ const collectionId = (_d = searchParams === null || searchParams === void 0 ? void 0 : searchParams.get("collectionId")) !== null && _d !== void 0 ? _d : undefined;
72
+ const collectionHandle = (_e = searchParams === null || searchParams === void 0 ? void 0 : searchParams.get("collectionHandle")) !== null && _e !== void 0 ? _e : undefined;
73
+ const { specificCollection = {}, error: useCollectionError, loading: isCollectionLoading, } = useCollection({
74
74
  appId,
75
75
  apiUrl,
76
76
  collectionId,
@@ -78,42 +78,50 @@ export const useBlockConditionalRendering = (_props, _block) => {
78
78
  language: location === null || location === void 0 ? void 0 : location.language,
79
79
  metafields: memoizedMetafields,
80
80
  });
81
+ const isLoading = isProductsLoading || isCollectionLoading;
81
82
  if (useProductsError || useCollectionError) {
82
83
  console.error("Unable to load products in conditional block rendering hook: ", useProductsError !== null && useProductsError !== void 0 ? useProductsError : useCollectionError);
83
84
  }
85
+ let shouldShow = true;
86
+ const blockConditionalsIsEnabled = conditionalsV1IsEnabled || conditionalsV2IsEnabled;
87
+ const blockHasTags = countNumberOfTagsInState(blockState) > 0;
88
+ const product = products.length === 1 ? products[0] : undefined;
89
+ const envState = getEnvState({
90
+ location,
91
+ product,
92
+ collection: specificCollection,
93
+ customer: customerVariables,
94
+ });
95
+ const evaluateV1Conditions = () => {
96
+ if (!blockHasTags) {
97
+ // No tags configured - default to showing
98
+ return true;
99
+ }
100
+ //@ts-expect-error: envState typing mismatch
101
+ const shouldShowPreInverse = shouldShowBlock(envState, blockState);
102
+ // TODO: Remove when inverse is available for all blocks
103
+ return isInverse ? !shouldShowPreInverse : shouldShowPreInverse;
104
+ };
105
+ const evaluateV2Conditions = () => {
106
+ return evaluateConditions(conditions, envState);
107
+ };
84
108
  try {
85
- // if block has tags
86
- if (((_f = _block.visibilityConditions) === null || _f === void 0 ? void 0 : _f.conditions) !== undefined && isEnabled) {
87
- const blockHasTags = countNumberOfTagsInState(blockState) > 0;
88
- if (blockHasTags) {
89
- const product = products.length === 1 ? products[0] : undefined;
90
- const envState = getEnvState({
91
- location,
92
- product,
93
- collection: specificCollection,
94
- customer: customerVariables,
95
- });
96
- //@ts-expect-errors
97
- const shouldShowPreInverse = shouldShowBlock(envState, blockState);
98
- // TODO: Currently negation of properties is not available but is required for migration of certain merchants
99
- // This code is to be removed and updated when inverse feature is available for all blocks
100
- // If exclude tag is applied then block should show inversely
101
- if (isInverse) {
102
- shouldShow = !shouldShowPreInverse;
103
- }
104
- else {
105
- shouldShow = shouldShowPreInverse;
106
- }
109
+ if (blockConditionalsIsEnabled) {
110
+ switch (_version) {
111
+ case 2:
112
+ shouldShow = evaluateV2Conditions();
113
+ break;
114
+ case 1:
115
+ default:
116
+ // Handles v1 and any unexpected versions
117
+ shouldShow = evaluateV1Conditions();
118
+ break;
107
119
  }
108
120
  }
109
- // if conditionals is disabled, show the block.
110
- if (!isEnabled) {
111
- return { shouldShow: true, isLoading: false };
112
- }
113
- return { shouldShow, isLoading: useProductsLoading || useCollectionLoading };
114
121
  }
115
122
  catch (e) {
116
- console.error("conditionals error: ", e);
117
- return { shouldShow: true, isLoading: false };
123
+ console.error("Error evaluating block visibility conditions:", e);
124
+ shouldShow = true; // Fail-safe to show block
118
125
  }
126
+ return { shouldShow, isLoading };
119
127
  };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=use-block-conditionals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-block-conditionals.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-block-conditionals.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ export {};
@@ -7,7 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { renderHook } from "@testing-library/react-hooks";
10
+ import { renderHook } from "@testing-library/react";
11
+ import { act } from "react-dom/test-utils";
11
12
  import { NostoRecommendationsType, useNostoRecommendations, } from "./use-nosto-recommendation";
12
13
  import { useProducts } from "./use-products";
13
14
  jest.mock("./use-products", () => ({
@@ -111,8 +112,14 @@ describe("useNostoRecommendations", () => {
111
112
  });
112
113
  it("should return front page recommendations if productIds is empty and slotId is provided", () => __awaiter(void 0, void 0, void 0, function* () {
113
114
  const slotId = "mockSlotId";
114
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, undefined, undefined, undefined));
115
- yield waitFor(() => result.current.isLoading === false);
115
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, undefined, undefined, undefined));
116
+ // Initial state check
117
+ expect(result.current.isLoading).toBe(true);
118
+ // Wait for all promises with act
119
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
120
+ yield new Promise((resolve) => setTimeout(resolve, 0));
121
+ }));
122
+ // Check final state
116
123
  expect(result.current.isLoading).toBe(false);
117
124
  expect(result.current.error).toBe(null);
118
125
  expect(useProducts).toHaveBeenCalledWith({
@@ -127,8 +134,15 @@ describe("useNostoRecommendations", () => {
127
134
  }));
128
135
  it("should return best sellers recommendations if productIds is empty and slotId is not provided", () => __awaiter(void 0, void 0, void 0, function* () {
129
136
  const slotId = "";
130
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, undefined, undefined));
131
- yield waitFor(() => result.current.isLoading === false);
137
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, undefined, undefined));
138
+ // Initial state check
139
+ expect(result.current.isLoading).toBe(true);
140
+ // Wait for all promises with act
141
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
142
+ yield new Promise((resolve) => setTimeout(resolve, 0));
143
+ }));
144
+ // Check final state
145
+ expect(result.current.isLoading).toBe(false);
132
146
  expect(result.current.error).toBe(null);
133
147
  expect(useProducts).toHaveBeenCalledWith({
134
148
  productIds: ["best-seller-1"],
@@ -143,8 +157,15 @@ describe("useNostoRecommendations", () => {
143
157
  it("should return best sellers recommendations if productIds is not empty, Best Sellers is the recommendation type and slotId is not provided", () => __awaiter(void 0, void 0, void 0, function* () {
144
158
  const slotId = "";
145
159
  const productIds = ["mockProductId"];
146
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, NostoRecommendationsType.BEST_SELLERS, productIds));
147
- yield waitFor(() => result.current.isLoading === false);
160
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, NostoRecommendationsType.BEST_SELLERS, productIds));
161
+ // Initial state check
162
+ expect(result.current.isLoading).toBe(true);
163
+ // Wait for all promises with act
164
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
165
+ yield new Promise((resolve) => setTimeout(resolve, 0));
166
+ }));
167
+ // Check final state
168
+ expect(result.current.isLoading).toBe(false);
148
169
  expect(result.current.error).toBe(null);
149
170
  expect(useProducts).toHaveBeenCalledWith({
150
171
  productIds: ["best-seller-1"],
@@ -158,8 +179,15 @@ describe("useNostoRecommendations", () => {
158
179
  }));
159
180
  it("should return default front page recommendations if slotId is provided but layoutType is not", () => __awaiter(void 0, void 0, void 0, function* () {
160
181
  const slotId = "mockSlotId";
161
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId));
162
- yield waitFor(() => result.current.isLoading === false);
182
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId));
183
+ // Initial state check
184
+ expect(result.current.isLoading).toBe(true);
185
+ // Wait for all promises with act
186
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
187
+ yield new Promise((resolve) => setTimeout(resolve, 0));
188
+ }));
189
+ // Check final state
190
+ expect(result.current.isLoading).toBe(false);
163
191
  expect(result.current.error).toBe(null);
164
192
  expect(useProducts).toHaveBeenCalledWith({
165
193
  productIds: ["front-page-id-1"],
@@ -172,8 +200,15 @@ describe("useNostoRecommendations", () => {
172
200
  });
173
201
  }));
174
202
  it("should return the correct recommendations for best sellers", () => __awaiter(void 0, void 0, void 0, function* () {
175
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.BEST_SELLERS, undefined, undefined));
176
- yield waitFor(() => result.current.isLoading === false);
203
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.BEST_SELLERS, undefined, undefined));
204
+ // Initial state check
205
+ expect(result.current.isLoading).toBe(true);
206
+ // Wait for all promises with act
207
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
208
+ yield new Promise((resolve) => setTimeout(resolve, 0));
209
+ }));
210
+ // Check final state
211
+ expect(result.current.isLoading).toBe(false);
177
212
  expect(result.current.error).toBe(null);
178
213
  expect(useProducts).toHaveBeenCalledWith({
179
214
  productIds: ["best-seller-1"],
@@ -187,8 +222,15 @@ describe("useNostoRecommendations", () => {
187
222
  }));
188
223
  it("should return the related recommendations when only productIds and a recommendation type are provided on a non-home layout", () => __awaiter(void 0, void 0, void 0, function* () {
189
224
  const productIds = ["mockProductId"];
190
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
191
- yield waitFor(() => result.current.isLoading === false);
225
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
226
+ // Initial state check
227
+ expect(result.current.isLoading).toBe(true);
228
+ // Wait for all promises with act
229
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
230
+ yield new Promise((resolve) => setTimeout(resolve, 0));
231
+ }));
232
+ // Check final state
233
+ expect(result.current.isLoading).toBe(false);
192
234
  expect(result.current.error).toBe(null);
193
235
  expect(useProducts).toHaveBeenCalledWith({
194
236
  productIds: ["related-1"],
@@ -203,8 +245,15 @@ describe("useNostoRecommendations", () => {
203
245
  it("should return the correct recommendations when productIds and slotIds are provided", () => __awaiter(void 0, void 0, void 0, function* () {
204
246
  const slotId = "mockSlotId";
205
247
  const productIds = ["mockProductId"];
206
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
207
- yield waitFor(() => result.current.isLoading === false);
248
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, slotId, NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
249
+ // Initial state check
250
+ expect(result.current.isLoading).toBe(true);
251
+ // Wait for all promises with act
252
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
253
+ yield new Promise((resolve) => setTimeout(resolve, 0));
254
+ }));
255
+ // Check final state
256
+ expect(result.current.isLoading).toBe(false);
208
257
  expect(result.current.error).toBe(null);
209
258
  expect(useProducts).toHaveBeenCalledWith({
210
259
  productIds: ["product-page-id-1"],
@@ -218,8 +267,15 @@ describe("useNostoRecommendations", () => {
218
267
  }));
219
268
  it("should return the related products fallback recommendations when productIds are provided and no slotId and a type other than viewed_category and best_sellers is provided", () => __awaiter(void 0, void 0, void 0, function* () {
220
269
  const productIds = ["mockProductId"];
221
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
222
- yield waitFor(() => result.current.isLoading === false);
270
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
271
+ // Initial state check
272
+ expect(result.current.isLoading).toBe(true);
273
+ // Wait for all promises with act
274
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
275
+ yield new Promise((resolve) => setTimeout(resolve, 0));
276
+ }));
277
+ // Check final state
278
+ expect(result.current.isLoading).toBe(false);
223
279
  expect(result.current.error).toBe(null);
224
280
  expect(useProducts).toHaveBeenCalledWith({
225
281
  productIds: ["related-1"],
@@ -233,8 +289,15 @@ describe("useNostoRecommendations", () => {
233
289
  }));
234
290
  it("should return the category fallback recommendations when no slotId, productIds are provided and a recommendation type of viewed_category is provided", () => __awaiter(void 0, void 0, void 0, function* () {
235
291
  const productIds = ["mockProductId"];
236
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_CATEGORY, productIds, undefined));
237
- yield waitFor(() => result.current.isLoading === false);
292
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_CATEGORY, productIds, undefined));
293
+ // Initial state check
294
+ expect(result.current.isLoading).toBe(true);
295
+ // Wait for all promises with act
296
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
297
+ yield new Promise((resolve) => setTimeout(resolve, 0));
298
+ }));
299
+ // Check final state
300
+ expect(result.current.isLoading).toBe(false);
238
301
  expect(result.current.error).toBe(null);
239
302
  expect(useProducts).toHaveBeenCalledWith({
240
303
  productIds: ["category-page-id-1"],
@@ -248,7 +311,15 @@ describe("useNostoRecommendations", () => {
248
311
  }));
249
312
  it("should handle loading state", () => __awaiter(void 0, void 0, void 0, function* () {
250
313
  const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, ""));
314
+ // Check initial loading state
251
315
  expect(result.current.isLoading).toBe(true);
316
+ // Wait for all state updates to complete
317
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
318
+ yield new Promise((resolve) => setTimeout(resolve, 0));
319
+ }));
320
+ // Verify loading has finished
321
+ expect(result.current.isLoading).toBe(false);
322
+ expect(result.current.error).toBe(null);
252
323
  }));
253
324
  it("should handle error state", () => __awaiter(void 0, void 0, void 0, function* () {
254
325
  const mockError = () => "Failed to fetch products";
@@ -257,14 +328,29 @@ describe("useNostoRecommendations", () => {
257
328
  isLoading: false,
258
329
  error: mockError,
259
330
  });
260
- const { result, waitForNextUpdate } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL));
261
- yield waitForNextUpdate();
331
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL));
332
+ // Initial state check
333
+ expect(result.current.isLoading).toBe(true);
334
+ // Wait for all promises and state updates to resolve
335
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
336
+ yield new Promise((resolve) => setTimeout(resolve, 0));
337
+ }));
338
+ // Verify final error state
339
+ expect(result.current.isLoading).toBe(false);
262
340
  expect(result.current.error).toBe(mockError);
341
+ expect(result.current.recommendations).toEqual([]);
263
342
  }));
264
343
  it("should default to best sellers if productIds are empty but a recommendation type other than Best Sellers is used", () => __awaiter(void 0, void 0, void 0, function* () {
265
344
  const productIds = [];
266
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
267
- yield waitFor(() => result.current.isLoading === false);
345
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.VIEWED_TOGETHER, productIds, undefined));
346
+ // Initial state check
347
+ expect(result.current.isLoading).toBe(true);
348
+ // Wait for all promises with act
349
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
350
+ yield new Promise((resolve) => setTimeout(resolve, 0));
351
+ }));
352
+ // Check final state
353
+ expect(result.current.isLoading).toBe(false);
268
354
  expect(result.current.error).toBe(null);
269
355
  expect(useProducts).toHaveBeenCalledWith({
270
356
  productIds: ["best-seller-1"],
@@ -277,8 +363,15 @@ describe("useNostoRecommendations", () => {
277
363
  });
278
364
  }));
279
365
  it("should return an empty array if skip is true", () => __awaiter(void 0, void 0, void 0, function* () {
280
- const { result, waitFor } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.BEST_SELLERS, undefined, true));
281
- yield waitFor(() => result.current.isLoading === false);
366
+ const { result } = renderHook(() => useNostoRecommendations(mockIntegrations, mockBaseURL, "", NostoRecommendationsType.BEST_SELLERS, undefined, true));
367
+ // Initial state check
368
+ expect(result.current.isLoading).toBe(true);
369
+ // Wait for all promises with act
370
+ yield act(() => __awaiter(void 0, void 0, void 0, function* () {
371
+ yield new Promise((resolve) => setTimeout(resolve, 0));
372
+ }));
373
+ // Check final state
374
+ expect(result.current.isLoading).toBe(false);
282
375
  expect(useProducts).toHaveBeenCalledWith({
283
376
  productIds: [],
284
377
  baseURL: mockBaseURL,
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ declare const useTap: (tapThreshold?: number) => {
3
+ onTap: (handler: (event: any) => void) => (event: any) => void;
4
+ isPressed: boolean;
5
+ ref: React.MutableRefObject<null>;
6
+ };
7
+ export { useTap };
8
+ //# sourceMappingURL=use-tap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-tap.d.ts","sourceRoot":"","sources":["../../../components/hooks/use-tap.ts"],"names":[],"mappings":"AACA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAuFvE,QAAA,MAAM,MAAM;6BAuBkC,GAAG,KAAK,IAAI,aACvC,GAAG;;;CAerB,CAAA;AAED,OAAO,EAAE,MAAM,EAAE,CAAA"}
@@ -0,0 +1,100 @@
1
+ "use client";
2
+ import { useState, useEffect, useCallback, useRef } from "react";
3
+ // Shared manager for all instances of the hook
4
+ const tapManager = (() => {
5
+ const elements = new Map();
6
+ let isListening = false;
7
+ const startListening = () => {
8
+ if (isListening)
9
+ return;
10
+ const handleTouchStart = (e) => {
11
+ const touch = e.touches[0];
12
+ elements.forEach((data, el) => {
13
+ if (el.contains(touch.target)) {
14
+ data.touchStarted = true;
15
+ data.touchMoved = false;
16
+ data.startPosition = { x: touch.clientX, y: touch.clientY };
17
+ // Don't set isPressed here, wait to determine if it's a tap or drag
18
+ }
19
+ });
20
+ };
21
+ const handleTouchMove = (e) => {
22
+ const touch = e.touches[0];
23
+ elements.forEach((data, el) => {
24
+ if (data.touchStarted) {
25
+ const deltaX = Math.abs(touch.clientX - data.startPosition.x);
26
+ const deltaY = Math.abs(touch.clientY - data.startPosition.y);
27
+ if (deltaX > data.tapThreshold || deltaY > data.tapThreshold) {
28
+ data.touchMoved = true;
29
+ data.setIsPressed(false);
30
+ }
31
+ }
32
+ });
33
+ };
34
+ const handleTouchEnd = () => {
35
+ elements.forEach((data) => {
36
+ if (data.touchStarted) {
37
+ data.touchStarted = false;
38
+ if (!data.touchMoved) {
39
+ // It's a tap, set isPressed briefly
40
+ data.setIsPressed(true);
41
+ setTimeout(() => data.setIsPressed(false), 100);
42
+ }
43
+ }
44
+ });
45
+ };
46
+ document.addEventListener("touchstart", (e) => handleTouchStart(e), { passive: true });
47
+ document.addEventListener("touchmove", (e) => handleTouchMove(e), { passive: true });
48
+ document.addEventListener("touchend", () => handleTouchEnd(), {
49
+ passive: true,
50
+ });
51
+ isListening = true;
52
+ };
53
+ return {
54
+ register: (el, data) => {
55
+ elements.set(el, data);
56
+ startListening();
57
+ },
58
+ unregister: (el) => {
59
+ elements.delete(el);
60
+ },
61
+ elements,
62
+ };
63
+ })();
64
+ const useTap = (tapThreshold = 10) => {
65
+ const [isPressed, setIsPressed] = useState(false);
66
+ const elementRef = useRef(null);
67
+ useEffect(() => {
68
+ const element = elementRef.current;
69
+ if (!element)
70
+ return;
71
+ const data = {
72
+ touchStarted: false,
73
+ touchMoved: false,
74
+ startPosition: { x: 0, y: 0 },
75
+ setIsPressed,
76
+ tapThreshold,
77
+ };
78
+ tapManager.register(element, data);
79
+ return () => {
80
+ tapManager.unregister(element);
81
+ };
82
+ }, [tapThreshold]);
83
+ const onTap = useCallback((handler) => {
84
+ return (event) => {
85
+ const data = tapManager.elements.get(elementRef.current);
86
+ if (!data)
87
+ return;
88
+ if (event.type === "touchend" && !data.touchMoved) {
89
+ handler(event);
90
+ }
91
+ else if (event.type === "click" && !data.touchStarted) {
92
+ handler(event);
93
+ setIsPressed(true);
94
+ setTimeout(() => setIsPressed(false), 100);
95
+ }
96
+ };
97
+ }, []);
98
+ return { onTap, isPressed, ref: elementRef };
99
+ };
100
+ export { useTap };
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ declare const inputVariants: (props?: ({
4
+ error?: boolean | null | undefined;
5
+ } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
6
+ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange">, VariantProps<typeof inputVariants> {
7
+ id: string;
8
+ label?: string;
9
+ icon?: string;
10
+ asChild?: boolean;
11
+ value: string;
12
+ placeholder: string;
13
+ onChange: (_: string) => void;
14
+ }
15
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
16
+ export { Input };
17
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../components/ui/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,aAAa;;mFAalB,CAAA;AAED,MAAM,WAAW,UACf,SAAQ,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,UAAU,CAAC,EACnE,YAAY,CAAC,OAAO,aAAa,CAAC;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B;AAED,QAAA,MAAM,KAAK,qFAkDV,CAAA;AAGD,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -0,0 +1,35 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
+ import * as React from "react";
14
+ import { Slot } from "@radix-ui/react-slot";
15
+ import { cva } from "class-variance-authority";
16
+ import { cn } from "../../lib/utils";
17
+ import { Icon } from "./icon";
18
+ const inputVariants = cva("flex h-14 w-full rounded border border-coreColors-dividingLines bg-coreColors-inputBackground px-4 pt-5 pb-2 placeholder-shown:p-4 text-textColors-primaryColor text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-textColors-secondaryColor focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed disabled:opacity-50 focus:border-coreColors-brandColorPrimary peer data-[icon=true]:pr-10", {
19
+ variants: {
20
+ error: {
21
+ true: "border-stateColors-error text-stateColors-error placeholder:text-stateColors-error focus:border-stateColors-error [&+label]:text-stateColors-error",
22
+ false: "",
23
+ },
24
+ },
25
+ defaultVariants: {
26
+ error: false,
27
+ },
28
+ });
29
+ const Input = React.forwardRef((_a, ref) => {
30
+ var { className, error = false, id, type, label, icon, asChild, value, placeholder, onChange } = _a, props = __rest(_a, ["className", "error", "id", "type", "label", "icon", "asChild", "value", "placeholder", "onChange"]);
31
+ const Comp = asChild ? Slot : "div";
32
+ return (_jsxs(Comp, Object.assign({ className: "relative group" }, { children: [_jsx("input", Object.assign({ placeholder: placeholder, value: value, onChange: (e) => onChange(e.target.value), id: id, type: type, className: cn(inputVariants({ error }), className), "data-icon": !!icon, ref: ref }, props)), label ? (_jsx("label", Object.assign({ htmlFor: id, className: "absolute text-[10px] text-textColors-secondaryColor group-active:text-coreColors-brandColorPrimary top-2 z-10 h-4 origin-[0] start-4 opacity-100 peer-placeholder-shown:opacity-0" }, { children: label }))) : null, icon ? (_jsx(Icon, { name: icon, "data-error": error, size: "sm", className: "absolute w-5 aspect-square fill-current text-textColors-secondaryColor top-[18px] z-10 origin-[0] end-4 peer-pr-8 icon data-[error=true]:text-stateColors-error" })) : null] })));
33
+ });
34
+ Input.displayName = "Input";
35
+ export { Input };
@@ -0,0 +1,15 @@
1
+ type Product = any;
2
+ interface PageData {
3
+ products: Product[];
4
+ cursorBlob?: string;
5
+ filtersData: any;
6
+ }
7
+ interface ProductGridItemsProps {
8
+ initialData: PageData;
9
+ loadMoreProducts: (params: any) => Promise<PageData>;
10
+ queryVariables: Record<string, any>;
11
+ config: Record<string, any>;
12
+ }
13
+ declare function ProductGrid({ loadMoreProducts, initialData, queryVariables, config, }: ProductGridItemsProps): import("react/jsx-runtime").JSX.Element;
14
+ export { ProductGrid };
15
+ //# sourceMappingURL=product-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"product-grid.d.ts","sourceRoot":"","sources":["../../../components/ui/product-grid.tsx"],"names":[],"mappings":"AAkBA,KAAK,OAAO,GAAG,GAAG,CAAA;AAClB,UAAU,QAAQ;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,GAAG,CAAA;CACjB;AAED,UAAU,qBAAqB;IAC7B,WAAW,EAAE,QAAQ,CAAA;IACrB,gBAAgB,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpD,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CAC5B;AAED,iBAAS,WAAW,CAAC,EACnB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,MAAM,GACP,EAAE,qBAAqB,2CAmCvB;AAED,OAAO,EAAE,WAAW,EAAE,CAAA"}