@riosst100/pwa-marketplace 2.9.3 → 2.9.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@riosst100/pwa-marketplace",
3
3
  "author": "riosst100@gmail.com",
4
- "version": "2.9.3",
4
+ "version": "2.9.4",
5
5
  "main": "src/index.js",
6
6
  "pwa-studio": {
7
7
  "targets": {
@@ -25,7 +25,7 @@ const SubCategory = props => {
25
25
  const isActive = index == 0 ? true : false;
26
26
 
27
27
  const item = isActive ? (
28
- <span className={classes.item}><b>{text}</b></span>
28
+ <span key={index} className={classes.item}><b>{text}</b></span>
29
29
  ) : (
30
30
  <Link
31
31
  key={index}
@@ -295,6 +295,9 @@ export const useCheckoutPage = (props = {}) => {
295
295
  // When true, we intentionally hide the OrderConfirmationPage (orderNumber)
296
296
  // to allow external payment (Xendit) to complete first.
297
297
  const [suppressOrderConfirmation, setSuppressOrderConfirmation] = useState(false);
298
+ // Support returning from external payment with orderNumber in URL
299
+ const [externalOrderNumber, setExternalOrderNumber] = useState(null);
300
+ const [externalOrderDetails, setExternalOrderDetails] = useState(null);
298
301
 
299
302
  const handlePlaceOrder = useCallback(async () => {
300
303
  // Fetch order details and then use an effect to actually place the
@@ -352,6 +355,17 @@ export const useCheckoutPage = (props = {}) => {
352
355
  try {
353
356
  // Prevent success page from rendering; we'll redirect to Xendit first
354
357
  setSuppressOrderConfirmation(true);
358
+ // Persist a snapshot of order details for rendering confirmation after redirect
359
+ try {
360
+ if (orderDetailsData) {
361
+ const key = `orderConfirmationSnapshot:${orderNumber}`;
362
+ const payload = JSON.stringify(orderDetailsData);
363
+ globalThis.localStorage && globalThis.localStorage.setItem(key, payload);
364
+ }
365
+ } catch (e) {
366
+ // eslint-disable-next-line no-console
367
+ console.warn('Unable to persist order confirmation snapshot', e);
368
+ }
355
369
  const res = await xenditCreateInvoice({
356
370
  variables: { orderId: orderNumber }
357
371
  });
@@ -418,6 +432,33 @@ export const useCheckoutPage = (props = {}) => {
418
432
  isPlacingOrder
419
433
  ]);
420
434
 
435
+ // On first load, check for orderNumber in querystring to render confirmation
436
+ useEffect(() => {
437
+ try {
438
+ const { location } = globalThis;
439
+ if (!location) return;
440
+ const params = new URLSearchParams(location.search || '');
441
+ const qsOrder = params.get('orderNumber');
442
+ if (!qsOrder) return;
443
+
444
+ setExternalOrderNumber(qsOrder);
445
+ // Attempt to load the previously saved snapshot
446
+ try {
447
+ const key = `orderConfirmationSnapshot:${qsOrder}`;
448
+ const raw = globalThis.localStorage && globalThis.localStorage.getItem(key);
449
+ if (raw) {
450
+ const parsed = JSON.parse(raw);
451
+ setExternalOrderDetails(parsed);
452
+ }
453
+ } catch (e) {
454
+ // eslint-disable-next-line no-console
455
+ console.warn('Failed to restore order confirmation snapshot', e);
456
+ }
457
+ } catch (_) {
458
+ // ignore
459
+ }
460
+ }, []);
461
+
421
462
  useEffect(async () => {
422
463
  await setCheckoutState(false);
423
464
  }, [setCheckoutState]);
@@ -517,11 +558,11 @@ export const useCheckoutPage = (props = {}) => {
517
558
  isGuestCheckout: !isSignedIn,
518
559
  isLoading,
519
560
  isUpdating,
520
- orderDetailsData,
561
+ orderDetailsData: externalOrderDetails || orderDetailsData,
521
562
  orderDetailsLoading,
522
563
  orderNumber: suppressOrderConfirmation
523
564
  ? null
524
- : (placeOrderData && placeOrderData.placeOrder.order.order_number) || null,
565
+ : (externalOrderNumber || (placeOrderData && placeOrderData.placeOrder.order.order_number) || null),
525
566
  placeOrderLoading,
526
567
  placeOrderButtonClicked,
527
568
  setCheckoutStep,
@@ -34,7 +34,10 @@ export const useFilterSidebar = props => {
34
34
 
35
35
  const allActiveFilters = getFiltersFromSearch(search);
36
36
 
37
- const { data: introspectionData } = useQuery(getFilterInputsQuery);
37
+ const { data: introspectionData } = useQuery(getFilterInputsQuery, {
38
+ fetchPolicy: 'cache-and-network',
39
+ nextFetchPolicy: 'cache-first'
40
+ });
38
41
 
39
42
  // const {
40
43
  // called: subFilterItemsCalled,
@@ -120,6 +120,7 @@ export const useCategoryContent = props => {
120
120
  return masterAttributes;
121
121
  }, [masterAttributesData]);
122
122
 
123
+
123
124
 
124
125
  useEffect(() => {
125
126
  if (categoryId) {
@@ -132,6 +133,10 @@ export const useCategoryContent = props => {
132
133
  }
133
134
 
134
135
  const filters = getFiltersFromSearch(search);
136
+
137
+ // console.log('masterAttributes',masterAttributes)
138
+ // console.log('filters',filters)
139
+
135
140
 
136
141
  // Construct the filter arg object.
137
142
  const newFilters = {};
@@ -141,6 +146,8 @@ export const useCategoryContent = props => {
141
146
  }
142
147
  });
143
148
 
149
+ // console.log('newFilters',newFilters)
150
+
144
151
  getFilters({
145
152
  variables: {
146
153
  filters: newFilters
@@ -26,7 +26,9 @@ const Breadcrumbs = props => {
26
26
 
27
27
  const talonProps = useBreadcrumbs({ categoryId });
28
28
 
29
- const DELIMITER = <ArrowRight2 size={13} className='inline-flex text-gray-200' />
29
+ const location = useLocation(); // dipindah ke atas sebelum conditional return
30
+
31
+ const DELIMITER = <ArrowRight2 size={13} className='inline-flex text-gray-200' />;
30
32
 
31
33
  const {
32
34
  currentCategory,
@@ -39,13 +41,15 @@ const Breadcrumbs = props => {
39
41
 
40
42
  // For all links generate a fragment like "/ Text"
41
43
  const links = useMemo(() => {
42
- return normalizedData.map(({ text, path }) => {
44
+ return normalizedData.map(({ text, path }, index) => {
43
45
  if (text == "dCollectible Card Games") {
44
46
  return '';
45
47
  }
46
-
48
+
49
+ console.log('index', index);
50
+
47
51
  return (
48
- <Fragment key={text}>
52
+ <Fragment key={index}>
49
53
  <span className={classes.divider}>{DELIMITER}</span>
50
54
  <Link
51
55
  className={classes.link}
@@ -74,14 +78,11 @@ const Breadcrumbs = props => {
74
78
  );
75
79
  }
76
80
 
77
- const filterBreadcrumbsElement = [];
81
+ let filterBreadcrumbsElement = []; // ✅ pakai let agar bisa di-push
78
82
 
79
83
  const lastIndex = currentFilter ? currentFilter.length - 1 : 0;
80
- // const filterpath = ;
81
-
82
- const { search } = useLocation();
83
84
 
84
- // const previousSearch = useRef(search);
85
+ const { search } = location;
85
86
 
86
87
  const removeShopbyParam = (url) => {
87
88
  const urlParts = url.split("?"); // Pisahkan URL sebelum dan sesudah tanda tanya
@@ -98,78 +99,72 @@ const Breadcrumbs = props => {
98
99
 
99
100
  // Gabungkan URL dasar dengan query string yang telah dimodifikasi
100
101
  return newQueryString ? `${baseUrl}?${newQueryString}` : baseUrl;
101
- }
102
+ };
102
103
 
103
104
  const params = removeShopbyParam(search);
104
-
105
+
105
106
  currentFilter && currentFilter.length && currentFilter.map((filter, index) => {
106
107
  currentProduct ? (
107
108
  filterBreadcrumbsElement.push(
108
- <>
109
- <span className={classes.divider}>{DELIMITER}</span>
110
- <Link
111
- key={index}
112
- className={classes.link}
113
- to={resourceUrl('/'+currentCategoryPath)}
114
- onClick={handleClick}
115
- >
116
- {filter.label}
117
- </Link>
118
- </>
119
- )
120
- ) : (
121
- customPage ? (
122
- filterBreadcrumbsElement.push(
123
- <>
109
+ <span key={index}>
124
110
  <span className={classes.divider}>{DELIMITER}</span>
125
111
  <Link
126
- key={index}
127
112
  className={classes.link}
128
- to={resourceUrl('/'+currentCategoryPath+params)}
113
+ to={resourceUrl('/' + currentCategoryPath)}
129
114
  onClick={handleClick}
130
115
  >
131
116
  {filter.label}
132
117
  </Link>
133
- </>
118
+ </span>
119
+ )
120
+ ) : (
121
+ customPage ? (
122
+ filterBreadcrumbsElement.push(
123
+ <span key={index}>
124
+ <span className={classes.divider}>{DELIMITER}</span>
125
+ <Link
126
+ className={classes.link}
127
+ to={resourceUrl('/' + currentCategoryPath + params)}
128
+ onClick={handleClick}
129
+ >
130
+ {filter.label}
131
+ </Link>
132
+ </span>
134
133
  )
135
134
  ) : filterBreadcrumbsElement.push(
136
135
  index == lastIndex ?
137
- <>
138
- <span className={classes.divider}>{DELIMITER}</span>
139
- <span key={index} className={cn(classes.currentCategory, 'text-blue-700 font-medium')}>{filter.label}</span>
140
- </> : <>
141
- <span className={classes.divider}>{DELIMITER}</span>
142
- <Link
143
- key={index}
144
- className={classes.link}
145
- to={resourceUrl('/'+currentCategoryPath+params)}
146
- onClick={handleClick}
147
- >
148
- {filter.label}
149
- </Link>
150
- </>
136
+ <span key={index}>
137
+ <span className={classes.divider}>{DELIMITER}</span>
138
+ <span className={cn(classes.currentCategory, 'text-blue-700 font-medium')}>{filter.label}</span>
139
+ </span> : <span key={index}>
140
+ <span className={classes.divider}>{DELIMITER}</span>
141
+ <Link
142
+ className={classes.link}
143
+ to={resourceUrl('/' + currentCategoryPath + params)}
144
+ onClick={handleClick}
145
+ >
146
+ {filter.label}
147
+ </Link>
148
+ </span>
151
149
  )
152
150
  );
153
151
  });
154
152
 
155
- // If we have a "currentProduct" it means we're on a PDP so we want the last
156
- // category text to be a link. If we don't have a "currentProduct" we're on
157
- // a category page so it should be regular text.
158
153
  const currentCategoryLink = currentCategory == "dCollectible Card Games" ? '' : (customPage || currentProduct || currentFilter && currentFilter.length ? (
159
154
  <>
160
- <span className={classes.divider}>{DELIMITER}</span>
161
- <Link
162
- className={classes.link}
163
- to={'/'+resourceUrl(currentCategoryPath)}
164
- onClick={handleClick}
165
- >
166
- {currentCategory}
167
- </Link>
155
+ <span className={classes.divider}>{DELIMITER}</span>
156
+ <Link
157
+ className={classes.link}
158
+ to={'/' + resourceUrl(currentCategoryPath)}
159
+ onClick={handleClick}
160
+ >
161
+ {currentCategory}
162
+ </Link>
168
163
  </>
169
164
  ) : (
170
165
  <>
171
- <span className={classes.divider}>{DELIMITER}</span>
172
- <span className={cn(classes.currentCategory, 'text-blue-700 font-medium')}>{currentCategory}</span>
166
+ <span className={classes.divider}>{DELIMITER}</span>
167
+ <span className={cn(classes.currentCategory, 'text-blue-700 font-medium')}>{currentCategory}</span>
173
168
  </>
174
169
  ));
175
170
 
@@ -186,7 +181,7 @@ const Breadcrumbs = props => {
186
181
  <span className={classes.text}>{customPage}</span>
187
182
  </Fragment>
188
183
  ) : null;
189
-
184
+
190
185
  return (
191
186
  <div className={classes.root} aria-live="polite" aria-busy="false">
192
187
  <Link className={classes.link} to="/">
@@ -15,9 +15,27 @@ const OrderConfirmationPage = props => {
15
15
  const { data, orderNumber } = props;
16
16
  const { formatMessage } = useIntl();
17
17
 
18
- const talonProps = useOrderConfirmationPage({
19
- data
20
- });
18
+ // If data is not available (e.g., after external redirect without snapshot),
19
+ // render a minimal confirmation page.
20
+ const hasData =
21
+ data &&
22
+ data.cart &&
23
+ Array.isArray(data.cart.shipping_addresses) &&
24
+ data.cart.shipping_addresses.length > 0;
25
+
26
+ const talonProps = hasData
27
+ ? useOrderConfirmationPage({ data })
28
+ : { flatData: {
29
+ city: '',
30
+ country: '',
31
+ email: '',
32
+ firstname: '',
33
+ lastname: '',
34
+ postcode: '',
35
+ region: '',
36
+ shippingMethod: '',
37
+ street: []
38
+ }, isSignedIn: true };
21
39
 
22
40
  const { flatData, isSignedIn } = talonProps;
23
41
 
@@ -33,7 +51,7 @@ const OrderConfirmationPage = props => {
33
51
  street
34
52
  } = flatData;
35
53
 
36
- const streetRows = street.map((row, index) => {
54
+ const streetRows = (street || []).map((row, index) => {
37
55
  return (
38
56
  <span key={index} className={classes.addressStreet}>
39
57
  {row}
@@ -51,6 +69,13 @@ const OrderConfirmationPage = props => {
51
69
  behavior: 'smooth'
52
70
  });
53
71
  }
72
+ // Best-effort cleanup of stored snapshot after successful view
73
+ try {
74
+ if (orderNumber) {
75
+ const key = `orderConfirmationSnapshot:${orderNumber}`;
76
+ globalThis.localStorage && globalThis.localStorage.removeItem(key);
77
+ }
78
+ } catch (_) {}
54
79
  }, []);
55
80
 
56
81
  const createAccountForm = !isSignedIn ? (
@@ -109,42 +134,46 @@ const OrderConfirmationPage = props => {
109
134
  </Link>
110
135
  </div>
111
136
  </div>
112
- <div className={classes.detailsGrid}>
113
- <div className={classes.card}>
114
- <div
115
- data-cy="OrderConfirmationPage-shippingInfoHeading"
116
- className={classes.cardHeading}
117
- >
118
- <FormattedMessage
119
- id={'global.shippingInformation'}
120
- defaultMessage={'Shipping Information'}
121
- />
137
+ {hasData ? (
138
+ <>
139
+ <div className={classes.detailsGrid}>
140
+ <div className={classes.card}>
141
+ <div
142
+ data-cy="OrderConfirmationPage-shippingInfoHeading"
143
+ className={classes.cardHeading}
144
+ >
145
+ <FormattedMessage
146
+ id={'global.shippingInformation'}
147
+ defaultMessage={'Shipping Information'}
148
+ />
149
+ </div>
150
+ <div className={classes.shippingInfo}>
151
+ <span className={classes.email}>{email}</span>
152
+ <span className={classes.name}>{nameString}</span>
153
+ {streetRows}
154
+ <span className={classes.addressAdditional}>
155
+ {additionalAddressString}
156
+ </span>
157
+ </div>
158
+ </div>
159
+ <div className={classes.card}>
160
+ <div
161
+ data-cy="OrderConfirmationPage-shippingMethodHeading"
162
+ className={classes.cardHeading}
163
+ >
164
+ <FormattedMessage
165
+ id={'global.shippingMethod'}
166
+ defaultMessage={'Shipping Method'}
167
+ />
168
+ </div>
169
+ <div className={classes.shippingMethod}>{shippingMethod}</div>
170
+ </div>
122
171
  </div>
123
- <div className={classes.shippingInfo}>
124
- <span className={classes.email}>{email}</span>
125
- <span className={classes.name}>{nameString}</span>
126
- {streetRows}
127
- <span className={classes.addressAdditional}>
128
- {additionalAddressString}
129
- </span>
172
+ <div className={classes.itemsReview}>
173
+ <ItemsReview data={data} />
130
174
  </div>
131
- </div>
132
- <div className={classes.card}>
133
- <div
134
- data-cy="OrderConfirmationPage-shippingMethodHeading"
135
- className={classes.cardHeading}
136
- >
137
- <FormattedMessage
138
- id={'global.shippingMethod'}
139
- defaultMessage={'Shipping Method'}
140
- />
141
- </div>
142
- <div className={classes.shippingMethod}>{shippingMethod}</div>
143
- </div>
144
- </div>
145
- <div className={classes.itemsReview}>
146
- <ItemsReview data={data} />
147
- </div>
175
+ </>
176
+ ) : null}
148
177
  <div
149
178
  data-cy="OrderConfirmationPage-additionalText"
150
179
  className={classes.additionalText}
@@ -181,6 +210,6 @@ OrderConfirmationPage.propTypes = {
181
210
  additionalText: string,
182
211
  sidebarContainer: string
183
212
  }),
184
- data: object.isRequired,
213
+ data: object,
185
214
  orderNumber: string
186
215
  };
@@ -125,7 +125,7 @@ const CheckoutPage = props => {
125
125
  defaultMessage: 'Checkout'
126
126
  });
127
127
 
128
- if (orderNumber && orderDetailsData) {
128
+ if (orderNumber) {
129
129
  return (
130
130
  <OrderConfirmationPage
131
131
  data={orderDetailsData}
@@ -154,7 +154,12 @@ export const useShopBy = props => {
154
154
  return null;
155
155
  }
156
156
 
157
- let options = rawData.find(item => item.attribute_code === shopby).options;
157
+ let filter = rawData.find(item => item.attribute_code === shopby);
158
+ if (!filter) {
159
+ return null;
160
+ }
161
+
162
+ let options = filter.options;
158
163
 
159
164
  const mappingData = options.reduce((acc, item) => {
160
165
  let firstLetter = item.label.charAt(0).toUpperCase();
@@ -184,7 +189,12 @@ export const useShopBy = props => {
184
189
  return null;
185
190
  }
186
191
 
187
- let options = rawData.find(item => item.attribute_code === shopby).options;
192
+ let filter = rawData.find(item => item.attribute_code === shopby);
193
+ if (!filter) {
194
+ return null;
195
+ }
196
+
197
+ let options = filter.options;
188
198
 
189
199
  // const filteredSets = [];
190
200
 
@@ -1,4 +1,4 @@
1
- import { useQuery } from '@apollo/client';
1
+ import { useApolloClient, useQuery } from '@apollo/client';
2
2
  import { useCallback, useMemo } from 'react';
3
3
  import { useLocation } from 'react-router-dom';
4
4
  import { useDropdown } from '@magento/peregrine/lib/hooks/useDropdown';
@@ -6,6 +6,7 @@ import { useAwaitQuery } from '@magento/peregrine/lib/hooks/useAwaitQuery';
6
6
  import { BrowserPersistence } from '@magento/peregrine/lib/util';
7
7
  import mergeOperations from '@magento/peregrine/lib/util/shallowMerge';
8
8
  import DEFAULT_OPERATIONS from './websiteSwitcher.gql';
9
+ import { useCartContext } from '@magento/peregrine/lib/context/cart';
9
10
 
10
11
  const storage = new BrowserPersistence();
11
12
 
@@ -161,6 +162,13 @@ export const useWebsiteSwitcher = (props = {}) => {
161
162
  [pathname, fetchRouteData, internalRoutes]
162
163
  );
163
164
 
165
+ const apolloClient = useApolloClient();
166
+
167
+ const [
168
+ { cartId },
169
+ { createCart, removeCart, getCartDetails }
170
+ ] = useCartContext();
171
+
164
172
  const handleSwitchWebsite = useCallback(
165
173
  // Change store view code and currency to be used in Apollo link request headers
166
174
  async (storeCode, websiteCode) => {
@@ -190,13 +198,16 @@ export const useWebsiteSwitcher = (props = {}) => {
190
198
 
191
199
  const newPath = pathName ? `/${pathName}${newSearchParams}` : (accessBaseWebsite ? `/${pathName}${accessBaseWebsite}` : '');
192
200
 
201
+ await apolloClient.clearCacheData(apolloClient, 'cart');
202
+ await removeCart();
203
+
193
204
  if (newWebsiteCode) {
194
205
  globalThis.location.assign(`/${newWebsiteCode}${newPath || ''}`);
195
206
  } else {
196
207
  globalThis.location.assign(`${newPath || ''}`);
197
208
  }
198
209
  },
199
- [availableStores, getPathname, searchParams]
210
+ [availableStores, getPathname, searchParams, apolloClient, removeCart]
200
211
  );
201
212
 
202
213
  const handleTriggerClick = useCallback(() => {