@akinon/projectzero 2.0.78-rc.0 → 2.0.78-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @akinon/projectzero
2
2
 
3
+ ## 2.0.78-rc.2
4
+
5
+ ## 2.0.78-rc.1
6
+
3
7
  ## 2.0.78-rc.0
4
8
 
5
9
  ### Patch Changes
@@ -1,5 +1,71 @@
1
1
  # projectzeronext
2
2
 
3
+ ## 2.0.78-rc.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [86187b1c]
8
+ - @akinon/next@2.0.78-rc.2
9
+ - @akinon/pz-theme@2.0.78-rc.2
10
+ - @akinon/pz-virtual-try-on@2.0.78-rc.2
11
+ - @akinon/pz-akifast@2.0.78-rc.2
12
+ - @akinon/pz-apple-pay@2.0.78-rc.2
13
+ - @akinon/pz-b2b@2.0.78-rc.2
14
+ - @akinon/pz-basket-gift-pack@2.0.78-rc.2
15
+ - @akinon/pz-bkm@2.0.78-rc.2
16
+ - @akinon/pz-checkout-gift-pack@2.0.78-rc.2
17
+ - @akinon/pz-click-collect@2.0.78-rc.2
18
+ - @akinon/pz-credit-payment@2.0.78-rc.2
19
+ - @akinon/pz-cybersource-uc@2.0.78-rc.2
20
+ - @akinon/pz-flow-payment@2.0.78-rc.2
21
+ - @akinon/pz-google-pay@2.0.78-rc.2
22
+ - @akinon/pz-gpay@2.0.78-rc.2
23
+ - @akinon/pz-haso@2.0.78-rc.2
24
+ - @akinon/pz-hepsipay@2.0.78-rc.2
25
+ - @akinon/pz-masterpass@2.0.78-rc.2
26
+ - @akinon/pz-masterpass-rest@2.0.78-rc.2
27
+ - @akinon/pz-multi-basket@2.0.78-rc.2
28
+ - @akinon/pz-one-click-checkout@2.0.78-rc.2
29
+ - @akinon/pz-otp@2.0.78-rc.2
30
+ - @akinon/pz-pay-on-delivery@2.0.78-rc.2
31
+ - @akinon/pz-saved-card@2.0.78-rc.2
32
+ - @akinon/pz-similar-products@2.0.78-rc.2
33
+ - @akinon/pz-tabby-extension@2.0.78-rc.2
34
+ - @akinon/pz-tamara-extension@2.0.78-rc.2
35
+
36
+ ## 2.0.78-rc.1
37
+
38
+ ### Patch Changes
39
+
40
+ - 8a0379e6: ZERO-4845: Implement FavouriteProductIds context and provider; enhance fav button logic and add tests for fallback functionality
41
+ - @akinon/next@2.0.78-rc.1
42
+ - @akinon/pz-akifast@2.0.78-rc.1
43
+ - @akinon/pz-apple-pay@2.0.78-rc.1
44
+ - @akinon/pz-b2b@2.0.78-rc.1
45
+ - @akinon/pz-basket-gift-pack@2.0.78-rc.1
46
+ - @akinon/pz-bkm@2.0.78-rc.1
47
+ - @akinon/pz-checkout-gift-pack@2.0.78-rc.1
48
+ - @akinon/pz-click-collect@2.0.78-rc.1
49
+ - @akinon/pz-credit-payment@2.0.78-rc.1
50
+ - @akinon/pz-cybersource-uc@2.0.78-rc.1
51
+ - @akinon/pz-flow-payment@2.0.78-rc.1
52
+ - @akinon/pz-google-pay@2.0.78-rc.1
53
+ - @akinon/pz-gpay@2.0.78-rc.1
54
+ - @akinon/pz-haso@2.0.78-rc.1
55
+ - @akinon/pz-hepsipay@2.0.78-rc.1
56
+ - @akinon/pz-masterpass@2.0.78-rc.1
57
+ - @akinon/pz-masterpass-rest@2.0.78-rc.1
58
+ - @akinon/pz-multi-basket@2.0.78-rc.1
59
+ - @akinon/pz-one-click-checkout@2.0.78-rc.1
60
+ - @akinon/pz-otp@2.0.78-rc.1
61
+ - @akinon/pz-pay-on-delivery@2.0.78-rc.1
62
+ - @akinon/pz-saved-card@2.0.78-rc.1
63
+ - @akinon/pz-similar-products@2.0.78-rc.1
64
+ - @akinon/pz-tabby-extension@2.0.78-rc.1
65
+ - @akinon/pz-tamara-extension@2.0.78-rc.1
66
+ - @akinon/pz-theme@2.0.78-rc.1
67
+ - @akinon/pz-virtual-try-on@2.0.78-rc.1
68
+
3
69
  ## 2.0.78-rc.0
4
70
 
5
71
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projectzeronext",
3
- "version": "2.0.78-rc.0",
3
+ "version": "2.0.78-rc.2",
4
4
  "private": true,
5
5
  "license": "MIT",
6
6
  "scripts": {
@@ -24,33 +24,33 @@
24
24
  "test:middleware": "jest middleware-matcher.test.ts --bail"
25
25
  },
26
26
  "dependencies": {
27
- "@akinon/next": "2.0.78-rc.0",
28
- "@akinon/pz-akifast": "2.0.78-rc.0",
29
- "@akinon/pz-apple-pay": "2.0.78-rc.0",
30
- "@akinon/pz-b2b": "2.0.78-rc.0",
31
- "@akinon/pz-basket-gift-pack": "2.0.78-rc.0",
32
- "@akinon/pz-bkm": "2.0.78-rc.0",
33
- "@akinon/pz-checkout-gift-pack": "2.0.78-rc.0",
34
- "@akinon/pz-click-collect": "2.0.78-rc.0",
35
- "@akinon/pz-credit-payment": "2.0.78-rc.0",
36
- "@akinon/pz-cybersource-uc": "2.0.78-rc.0",
37
- "@akinon/pz-flow-payment": "2.0.78-rc.0",
38
- "@akinon/pz-google-pay": "2.0.78-rc.0",
39
- "@akinon/pz-gpay": "2.0.78-rc.0",
40
- "@akinon/pz-haso": "2.0.78-rc.0",
41
- "@akinon/pz-hepsipay": "2.0.78-rc.0",
42
- "@akinon/pz-masterpass": "2.0.78-rc.0",
43
- "@akinon/pz-masterpass-rest": "2.0.78-rc.0",
44
- "@akinon/pz-multi-basket": "2.0.78-rc.0",
45
- "@akinon/pz-one-click-checkout": "2.0.78-rc.0",
46
- "@akinon/pz-otp": "2.0.78-rc.0",
47
- "@akinon/pz-pay-on-delivery": "2.0.78-rc.0",
48
- "@akinon/pz-saved-card": "2.0.78-rc.0",
49
- "@akinon/pz-similar-products": "2.0.78-rc.0",
50
- "@akinon/pz-tabby-extension": "2.0.78-rc.0",
51
- "@akinon/pz-tamara-extension": "2.0.78-rc.0",
52
- "@akinon/pz-theme": "2.0.78-rc.0",
53
- "@akinon/pz-virtual-try-on": "2.0.78-rc.0",
27
+ "@akinon/next": "2.0.78-rc.2",
28
+ "@akinon/pz-akifast": "2.0.78-rc.2",
29
+ "@akinon/pz-apple-pay": "2.0.78-rc.2",
30
+ "@akinon/pz-b2b": "2.0.78-rc.2",
31
+ "@akinon/pz-basket-gift-pack": "2.0.78-rc.2",
32
+ "@akinon/pz-bkm": "2.0.78-rc.2",
33
+ "@akinon/pz-checkout-gift-pack": "2.0.78-rc.2",
34
+ "@akinon/pz-click-collect": "2.0.78-rc.2",
35
+ "@akinon/pz-credit-payment": "2.0.78-rc.2",
36
+ "@akinon/pz-cybersource-uc": "2.0.78-rc.2",
37
+ "@akinon/pz-flow-payment": "2.0.78-rc.2",
38
+ "@akinon/pz-google-pay": "2.0.78-rc.2",
39
+ "@akinon/pz-gpay": "2.0.78-rc.2",
40
+ "@akinon/pz-haso": "2.0.78-rc.2",
41
+ "@akinon/pz-hepsipay": "2.0.78-rc.2",
42
+ "@akinon/pz-masterpass": "2.0.78-rc.2",
43
+ "@akinon/pz-masterpass-rest": "2.0.78-rc.2",
44
+ "@akinon/pz-multi-basket": "2.0.78-rc.2",
45
+ "@akinon/pz-one-click-checkout": "2.0.78-rc.2",
46
+ "@akinon/pz-otp": "2.0.78-rc.2",
47
+ "@akinon/pz-pay-on-delivery": "2.0.78-rc.2",
48
+ "@akinon/pz-saved-card": "2.0.78-rc.2",
49
+ "@akinon/pz-similar-products": "2.0.78-rc.2",
50
+ "@akinon/pz-tabby-extension": "2.0.78-rc.2",
51
+ "@akinon/pz-tamara-extension": "2.0.78-rc.2",
52
+ "@akinon/pz-theme": "2.0.78-rc.2",
53
+ "@akinon/pz-virtual-try-on": "2.0.78-rc.2",
54
54
  "@hookform/resolvers": "2.9.0",
55
55
  "@next/third-parties": "16.2.6",
56
56
  "@react-google-maps/api": "2.17.1",
@@ -74,7 +74,7 @@
74
74
  "yup": "0.32.11"
75
75
  },
76
76
  "devDependencies": {
77
- "@akinon/eslint-plugin-projectzero": "2.0.78-rc.0",
77
+ "@akinon/eslint-plugin-projectzero": "2.0.78-rc.2",
78
78
  "@semantic-release/changelog": "6.0.2",
79
79
  "@semantic-release/exec": "6.0.3",
80
80
  "@semantic-release/git": "10.0.1",
@@ -0,0 +1,104 @@
1
+ 'use client';
2
+
3
+ import {
4
+ createContext,
5
+ ReactNode,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useState
10
+ } from 'react';
11
+ import { useSession } from 'next-auth/react';
12
+ import { useGetFavouriteProductIdsQuery } from '@akinon/next/data/client/wishlist';
13
+ import {
14
+ isFavouriteProductIdsSuspended,
15
+ suspendFavouriteProductIds
16
+ } from '@theme/utils/favourite-product-ids-fallback';
17
+
18
+ interface FavouriteProductIdsContextValue {
19
+ // False while the batch flow is unavailable (endpoint 404'd or errored), so
20
+ // consumers can fall back to the legacy favourites-list request.
21
+ isActive: boolean;
22
+ getFavouriteId: (productPk: number) => number | undefined;
23
+ }
24
+
25
+ const FavouriteProductIdsContext =
26
+ createContext<FavouriteProductIdsContextValue | null>(null);
27
+
28
+ export const useFavouriteProductIds = () =>
29
+ useContext(FavouriteProductIdsContext);
30
+
31
+ type Phase = 'init' | 'batch' | 'fallback';
32
+
33
+ interface FavouriteProductIdsProviderProps {
34
+ productIds: Array<number | string>;
35
+ children: ReactNode;
36
+ }
37
+
38
+ export function FavouriteProductIdsProvider({
39
+ productIds,
40
+ children
41
+ }: FavouriteProductIdsProviderProps) {
42
+ const { status } = useSession();
43
+
44
+ // Storage can only be read on the client, so the first render stays neutral
45
+ // to keep SSR and hydration output identical.
46
+ const [phase, setPhase] = useState<Phase>('init');
47
+
48
+ useEffect(() => {
49
+ setPhase(isFavouriteProductIdsSuspended() ? 'fallback' : 'batch');
50
+ }, []);
51
+
52
+ const sortedProductIds = useMemo(() => {
53
+ const unique = Array.from(
54
+ new Set(productIds.map(Number).filter((id) => Number.isFinite(id)))
55
+ );
56
+ // Stable order keeps the RTK Query cache key deterministic for the page.
57
+ return unique.sort((a, b) => a - b);
58
+ }, [productIds]);
59
+
60
+ const { data, error } = useGetFavouriteProductIdsQuery(
61
+ { productIds: sortedProductIds },
62
+ {
63
+ skip:
64
+ status !== 'authenticated' ||
65
+ phase !== 'batch' ||
66
+ sortedProductIds.length === 0
67
+ }
68
+ );
69
+
70
+ useEffect(() => {
71
+ if (!error) return;
72
+
73
+ if ('status' in error && error.status === 404) {
74
+ suspendFavouriteProductIds();
75
+ }
76
+
77
+ setPhase('fallback');
78
+ }, [error]);
79
+
80
+ const favouriteIdByProductPk = useMemo(() => {
81
+ const map = new Map<number, number>();
82
+
83
+ data?.favourite_products?.forEach((item) => {
84
+ map.set(Number(item.product_id), item.favourite_id);
85
+ });
86
+
87
+ return map;
88
+ }, [data]);
89
+
90
+ const value = useMemo<FavouriteProductIdsContextValue>(
91
+ () => ({
92
+ isActive: phase !== 'fallback',
93
+ getFavouriteId: (productPk: number) =>
94
+ favouriteIdByProductPk.get(Number(productPk))
95
+ }),
96
+ [phase, favouriteIdByProductPk]
97
+ );
98
+
99
+ return (
100
+ <FavouriteProductIdsContext.Provider value={value}>
101
+ {children}
102
+ </FavouriteProductIdsContext.Provider>
103
+ );
104
+ }
@@ -8,9 +8,11 @@ import {
8
8
  useGetFavoritesQuery,
9
9
  useRemoveFavoriteMutation
10
10
  } from '@akinon/next/data/client/wishlist';
11
+ import { Product } from '@akinon/next/types';
11
12
  import { Icon } from '@theme/components';
12
13
  import { pushAddToWishlist } from '@theme/utils/gtm';
13
14
  import { useToast, useLocalization } from '@akinon/next/hooks';
15
+ import { useFavouriteProductIds } from './favourite-product-ids-context';
14
16
 
15
17
  interface FavButtonProps {
16
18
  label?: string;
@@ -21,16 +23,25 @@ interface FavButtonProps {
21
23
 
22
24
  const FAV_TOAST_OPTIONS = { position: 'bottom-right' as const }
23
25
 
24
- const useFavButton = (productPk: number) => {
26
+ const useFavButton = (
27
+ productPk: number,
28
+ product?: Pick<Product, 'base_code' | 'name' | 'price' | 'currency_type'>
29
+ ) => {
25
30
  const { status } = useSession();
26
31
  const { t } = useLocalization();
27
32
  const { success, error: showError } = useToast();
28
33
 
34
+ // Inside a FavouriteProductIdsProvider (PLP) the favourite state comes from
35
+ // the batched favourite-product-ids request, so the full favourites list is
36
+ // only fetched when that flow is unavailable (no provider, or 404 fallback).
37
+ const favouriteProductIds = useFavouriteProductIds();
38
+ const useBatchFlow = Boolean(favouriteProductIds?.isActive);
39
+
29
40
  const { data: favorites } = useGetFavoritesQuery(
30
41
  {
31
42
  limit: 1000
32
43
  },
33
- { skip: status !== 'authenticated' }
44
+ { skip: status !== 'authenticated' || useBatchFlow }
34
45
  );
35
46
 
36
47
  const favoriteItem = useMemo(
@@ -41,7 +52,11 @@ const useFavButton = (productPk: number) => {
41
52
  [favorites, productPk]
42
53
  );
43
54
 
44
- const isActive = Boolean(favoriteItem);
55
+ const favouriteId = useBatchFlow
56
+ ? favouriteProductIds.getFavouriteId(productPk)
57
+ : favoriteItem?.pk;
58
+
59
+ const isActive = favouriteId !== undefined;
45
60
  const [isPushed, setIsPushed] = useState<boolean>(false);
46
61
 
47
62
  const [addFavorite] = useAddFavoriteMutation();
@@ -49,8 +64,8 @@ const useFavButton = (productPk: number) => {
49
64
 
50
65
  const handleClick = useCallback(async () => {
51
66
  try {
52
- if (favoriteItem) {
53
- await removeFavorite(favoriteItem.pk).unwrap();
67
+ if (favouriteId !== undefined) {
68
+ await removeFavorite(favouriteId).unwrap();
54
69
  success(t('product.removed_from_favorites'), FAV_TOAST_OPTIONS);
55
70
  } else {
56
71
  await addFavorite(productPk).unwrap();
@@ -60,19 +75,26 @@ const useFavButton = (productPk: number) => {
60
75
  } catch (err) {
61
76
  showError(t('product.favorites_error'), FAV_TOAST_OPTIONS);
62
77
  }
63
- }, [favoriteItem, productPk, addFavorite, removeFavorite, success, showError, t]);
78
+ }, [favouriteId, productPk, addFavorite, removeFavorite, success, showError, t]);
64
79
 
65
80
  useEffect(() => {
66
- if (favoriteItem && isPushed) {
81
+ if (!isActive || !isPushed) return;
82
+
83
+ // The batched flow returns ids only, so the analytics payload falls back
84
+ // to the product the card already renders.
85
+ const gtmProduct = favoriteItem?.product ?? product;
86
+
87
+ if (gtmProduct) {
67
88
  pushAddToWishlist({
68
- base_code: favoriteItem?.product?.base_code,
69
- name: favoriteItem?.product?.name,
70
- price: favoriteItem?.product?.price,
71
- currency_type: favoriteItem?.product?.currency_type
89
+ base_code: gtmProduct.base_code,
90
+ name: gtmProduct.name,
91
+ price: gtmProduct.price,
92
+ currency_type: gtmProduct.currency_type
72
93
  });
73
- setIsPushed(false);
74
94
  }
75
- }, [favoriteItem, isPushed]);
95
+
96
+ setIsPushed(false);
97
+ }, [isActive, favoriteItem, product, isPushed]);
76
98
 
77
99
  const FavButton = useMemo(() => {
78
100
  const View = (props: FavButtonProps) => (
@@ -0,0 +1,42 @@
1
+ import {
2
+ FAVOURITE_PRODUCT_IDS_RETRY_AFTER_MS,
3
+ isFavouriteProductIdsSuspended,
4
+ suspendFavouriteProductIds
5
+ } from '../favourite-product-ids-fallback';
6
+
7
+ const STORAGE_KEY = 'pz-favourite-product-ids-unavailable-until';
8
+
9
+ describe('favourite-product-ids-fallback', () => {
10
+ beforeEach(() => {
11
+ window.localStorage.clear();
12
+ });
13
+
14
+ it('is not suspended by default', () => {
15
+ expect(isFavouriteProductIdsSuspended()).toBe(false);
16
+ });
17
+
18
+ it('suspends after a 404 marker is written', () => {
19
+ suspendFavouriteProductIds();
20
+
21
+ expect(isFavouriteProductIdsSuspended()).toBe(true);
22
+
23
+ const until = Number(window.localStorage.getItem(STORAGE_KEY));
24
+ expect(until).toBeGreaterThan(Date.now());
25
+ expect(until).toBeLessThanOrEqual(
26
+ Date.now() + FAVOURITE_PRODUCT_IDS_RETRY_AFTER_MS
27
+ );
28
+ });
29
+
30
+ it('expires the suspension and clears the marker', () => {
31
+ window.localStorage.setItem(STORAGE_KEY, String(Date.now() - 1000));
32
+
33
+ expect(isFavouriteProductIdsSuspended()).toBe(false);
34
+ expect(window.localStorage.getItem(STORAGE_KEY)).toBeNull();
35
+ });
36
+
37
+ it('treats a corrupt marker as not suspended', () => {
38
+ window.localStorage.setItem(STORAGE_KEY, 'not-a-number');
39
+
40
+ expect(isFavouriteProductIdsSuspended()).toBe(false);
41
+ });
42
+ });
@@ -0,0 +1,51 @@
1
+ const STORAGE_KEY = 'pz-favourite-product-ids-unavailable-until';
2
+
3
+ // Not every brand's backend ships the favourite-product-ids endpoint at the
4
+ // same time, so a 404 suspends the new flow for this long before probing again.
5
+ export const FAVOURITE_PRODUCT_IDS_RETRY_AFTER_MS = 6 * 60 * 60 * 1000;
6
+
7
+ const getStorage = (): Storage | null => {
8
+ try {
9
+ // The property access itself is what throws when storage is blocked, so it
10
+ // has to be inside the try rather than guarded by a typeof check.
11
+ return typeof window === 'undefined' ? null : window.localStorage ?? null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ };
16
+
17
+ // Fails open: when storage is unavailable the endpoint is probed again, which
18
+ // at worst costs one extra 404 per page view.
19
+ export const isFavouriteProductIdsSuspended = (): boolean => {
20
+ const storage = getStorage();
21
+ if (!storage) return false;
22
+
23
+ try {
24
+ const raw = storage.getItem(STORAGE_KEY);
25
+ if (!raw) return false;
26
+
27
+ const until = Number(raw);
28
+ if (!Number.isFinite(until) || Date.now() >= until) {
29
+ storage.removeItem(STORAGE_KEY);
30
+ return false;
31
+ }
32
+
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ };
38
+
39
+ export const suspendFavouriteProductIds = (): void => {
40
+ const storage = getStorage();
41
+ if (!storage) return;
42
+
43
+ try {
44
+ storage.setItem(
45
+ STORAGE_KEY,
46
+ String(Date.now() + FAVOURITE_PRODUCT_IDS_RETRY_AFTER_MS)
47
+ );
48
+ } catch {
49
+ // Fail open; see isFavouriteProductIdsSuspended.
50
+ }
51
+ };
@@ -11,6 +11,7 @@ import { GetCategoryResponse } from '@akinon/next/types';
11
11
  import { useAppDispatch, useAppSelector } from '@akinon/next/redux/hooks';
12
12
  import { setFacets, setMenuOpen } from '@theme/redux/reducers/category';
13
13
  import CategoryActiveFilters from '@theme/views/category/category-active-filters';
14
+ import { FavouriteProductIdsProvider } from '@theme/hooks/favourite-product-ids-context';
14
15
  import { useLocalization } from '@akinon/next/hooks';
15
16
  import { Link, LoaderSpinner } from '@akinon/next/components';
16
17
  import { ROUTES } from '@theme/routes';
@@ -62,6 +63,11 @@ export default function ListPage(props: ListPageProps) {
62
63
 
63
64
  const { t } = useLocalization();
64
65
 
66
+ const productPks = useMemo(
67
+ () => data?.products?.map((product) => product.pk) ?? [],
68
+ [data?.products]
69
+ );
70
+
65
71
  useEffect(() => {
66
72
  dispatch(setFacets(data.facets));
67
73
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -105,23 +111,25 @@ export default function ListPage(props: ListPageProps) {
105
111
 
106
112
  {data.products?.length === 0 && page > 1 && <LoaderSpinner />}
107
113
 
108
- <div
109
- className={clsx('grid gap-x-4 gap-y-12 grid-cols-2', {
110
- 'md:grid-cols-3': layoutSize === 3,
111
- 'lg:grid-cols-2': layoutSize === 2,
112
- 'lg:grid-cols-3': layoutSize === 3
113
- })}
114
- >
115
- {data?.products?.map((product, index) => (
116
- <ProductItem
117
- key={product.pk}
118
- product={product}
119
- width={itemDimensions.width}
120
- height={itemDimensions.height}
121
- index={index}
122
- />
123
- ))}
124
- </div>
114
+ <FavouriteProductIdsProvider productIds={productPks}>
115
+ <div
116
+ className={clsx('grid gap-x-4 gap-y-12 grid-cols-2', {
117
+ 'md:grid-cols-3': layoutSize === 3,
118
+ 'lg:grid-cols-2': layoutSize === 2,
119
+ 'lg:grid-cols-3': layoutSize === 3
120
+ })}
121
+ >
122
+ {data?.products?.map((product, index) => (
123
+ <ProductItem
124
+ key={product.pk}
125
+ product={product}
126
+ width={itemDimensions.width}
127
+ height={itemDimensions.height}
128
+ index={index}
129
+ />
130
+ ))}
131
+ </div>
132
+ </FavouriteProductIdsProvider>
125
133
  {data?.products?.length > 0 && (
126
134
  <Pagination
127
135
  total={data.pagination.total_count}
@@ -22,7 +22,7 @@ export const ProductItem = (props: Props) => {
22
22
  // TODO: Static image will change (TR)
23
23
  const { product, width, height, index } = props;
24
24
  const [viewed, setViewed] = useState(false);
25
- const { FavButton } = useFavButton(product.pk);
25
+ const { FavButton } = useFavButton(product.pk, product);
26
26
  const { ref, inView } = useInView();
27
27
 
28
28
  const image_url = product.productimage_set[0]?.image;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/projectzero",
3
- "version": "2.0.78-rc.0",
3
+ "version": "2.0.78-rc.2",
4
4
  "private": false,
5
5
  "description": "CLI tool to manage your Project Zero Next project",
6
6
  "bin": {