@sonic-equipment/ui 259.0.4 → 259.0.6

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.
@@ -11,8 +11,6 @@ import { CheckoutPageLayout } from '../layouts/checkout-page-layout/checkout-pag
11
11
  import { Page } from '../../components/page/page.js';
12
12
  import { ErrorPage } from '../../error-page/error-page.js';
13
13
  import { LoadingPage } from '../../loading-page/loading-page.js';
14
- import { useSaveCartForLater } from '../../../shared/api/bff/hooks/cart/use-save-cart-for-later.js';
15
- import { useIsAuthenticated } from '../../../shared/api/storefront/hooks/authentication/use-is-authenticated.js';
16
14
  import { useDeleteCartLineById } from '../../../shared/api/storefront/hooks/cart/use-delete-cart-line-by-id.js';
17
15
  import { useDeleteCurrentCart } from '../../../shared/api/storefront/hooks/cart/use-delete-current-cart.js';
18
16
  import { useFetchCurrentCartLinesWithAtp } from '../../../shared/api/storefront/hooks/cart/use-fetch-current-cart-lines-with-atp.js';
@@ -27,22 +25,6 @@ function CartContent({ cartLines }) {
27
25
  const paths = usePaths();
28
26
  const { addToast } = useToast();
29
27
  const { data: currentCart } = useFetchCurrentCartWithAtp();
30
- const saveCartForLater = useSaveCartForLater({
31
- onError: () => {
32
- addToast({
33
- body: jsx(FormattedMessage, { id: "Unable to save cart for later." }),
34
- isUserDismissable: false,
35
- messageType: 'danger',
36
- });
37
- },
38
- onSuccess: () => {
39
- addToast({
40
- body: jsx(FormattedMessage, { id: "Saved cart for later." }),
41
- isUserDismissable: false,
42
- messageType: 'success',
43
- });
44
- },
45
- });
46
28
  const deleteCurrentCart = useDeleteCurrentCart({
47
29
  onError: () => {
48
30
  addToast({
@@ -75,7 +57,6 @@ function CartContent({ cartLines }) {
75
57
  });
76
58
  },
77
59
  });
78
- const isAuthenticated = useIsAuthenticated();
79
60
  if (!currentCart)
80
61
  return null;
81
62
  const currencyCode = getCurrencyCodeBySymbol(currentCart.currencySymbol);
@@ -83,9 +64,6 @@ function CartContent({ cartLines }) {
83
64
  throw new Error(`Currency code not found for symbol ${currentCart.currencySymbol}`);
84
65
  return (jsx(CheckoutPageLayout, { actions: {
85
66
  primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutShippingCartTotalContinueButton", href: paths.CHECKOUT_SHIPPING, children: jsx(FormattedMessage, { id: "Start checkout" }) })),
86
- secondary: isAuthenticated ? (jsx(Button, { color: "secondary", "data-test-selector": "saveCartForLaterButton", onClick: () => {
87
- saveCartForLater.mutate({ cart: currentCart });
88
- }, variant: "outline", children: jsx(FormattedMessage, { id: "Save order" }) })) : (jsx(Button, { color: "secondary", "data-test-selector": "saveCartForLaterButton", href: paths.SIGN_IN, variant: "outline", children: jsx(FormattedMessage, { id: "Save order" }) })),
89
67
  }, mobileSummary: jsx(CartTotalsSummary, { currencyCode: currencyCode, totalAmount: currentCart.orderGrandTotal }), overview: jsx(CartTotals, { currencyCode: currencyCode, shippingCost: currentCart.shippingAndHandling, subtotal: currentCart.orderSubTotal, tax: currentCart.totalTax, total: currentCart.orderGrandTotal, vatPercentage: cartLines[0]?.pricing?.vatRate || 0 }), children: jsx(OrderLineList, { onRemoveAll: () => deleteCurrentCart.mutate(), children: cartLines.map(cartLine => (jsx(ConnectedOrderLineCard, { deliveryDate: cartLine.atp?.date ?? null, href: cartLine.productUri, image: {
90
68
  fit: 'contain',
91
69
  image: {
@@ -1,7 +1,6 @@
1
1
  "use client";
2
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
3
  import { Button } from '../../../buttons/button/button.js';
4
- import { PrintButton } from '../../../buttons/print-button/print-button.js';
5
4
  import { OrderLineCard } from '../../../cards/orderline-card/orderline-card.js';
6
5
  import { CartTotals } from '../../../cart-totals/cart-totals.js';
7
6
  import { InfoDisplay } from '../../../display/info-display/info-display.js';
@@ -9,11 +8,9 @@ import { FormattedDate } from '../../../intl/formatted-date.js';
9
8
  import { FormattedMessage } from '../../../intl/formatted-message.js';
10
9
  import { useFormattedMessage } from '../../../intl/use-formatted-message.js';
11
10
  import { OrderLineList } from '../../../lists/orderline-list/orderline-list.js';
12
- import { useSaveCartForLater } from '../../../shared/api/bff/hooks/cart/use-save-cart-for-later.js';
13
11
  import { getCurrencyCodeBySymbol } from '../../../shared/model/currency.js';
14
12
  import { usePaths } from '../../../shared/routing/use-paths.js';
15
13
  import { ensureNumber } from '../../../shared/utils/number.js';
16
- import { useToast } from '../../../toast/use-toast.js';
17
14
  import { Page } from '../../components/page/page.js';
18
15
  import { BillingAndInvoiceInformation } from '../components/billing-and-invoice-information.js';
19
16
  import { CheckoutPageLayout } from '../layouts/checkout-page-layout/checkout-page-layout.js';
@@ -24,23 +21,6 @@ import styles from './order-confirmation-page.module.css.js';
24
21
  function OrderConfirmationPageContent({ cart, }) {
25
22
  const t = useFormattedMessage();
26
23
  const paths = usePaths();
27
- const { addToast } = useToast();
28
- const saveCartForLater = useSaveCartForLater({
29
- onError: () => {
30
- addToast({
31
- body: jsx(FormattedMessage, { id: "Unable to save cart for later." }),
32
- isUserDismissable: false,
33
- messageType: 'danger',
34
- });
35
- },
36
- onSuccess: () => {
37
- addToast({
38
- body: jsx(FormattedMessage, { id: "Saved cart for later." }),
39
- isUserDismissable: false,
40
- messageType: 'success',
41
- });
42
- },
43
- });
44
24
  const currencyCode = getCurrencyCodeBySymbol(cart.currencySymbol);
45
25
  if (!currencyCode)
46
26
  throw new Error(`Currency code not found for symbol ${cart.currencySymbol}`);
@@ -52,9 +32,6 @@ function OrderConfirmationPageContent({ cart, }) {
52
32
  },
53
33
  ], "data-test-selector": "orderConfirmationPage", title: t('Order confirmation'), children: jsx(CheckoutPageLayout, { actions: {
54
34
  primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutReviewAndSubmit_continueShopping", href: paths.HOME, children: jsx(FormattedMessage, { id: "Continue shopping" }) })),
55
- secondary: (jsxs(Fragment, { children: [cart.canSaveOrder && (jsx(Button, { color: "secondary", onClick: () => {
56
- saveCartForLater.mutate({ cart });
57
- }, variant: "outline", children: jsx(FormattedMessage, { id: "Save order" }) })), jsx(PrintButton, {})] })),
58
35
  }, overview: jsx(CartTotals, { currencyCode: currencyCode, fulfillmentMethod: cart.fulfillmentMethod, orderNumber: cart.orderNumber, shippingCost: cart.shippingAndHandling, subtotal: cart.orderSubTotal, tax: cart.totalTax, total: cart.orderGrandTotal, vatPercentage: cart.cartLines?.[0]?.pricing?.vatRate }), children: jsxs("div", { children: [jsx(CheckoutPageSection, { hasBorder: false, title: t('General'), children: jsx(CheckoutPageSectionContent, { children: jsxs("div", { className: styles['general-order-info'], children: [cart.orderDate && (jsx(InfoDisplay, { id: "order-date", label: t('Order date'), value: jsx(FormattedDate, { date: cart.orderDate }) })), cart.requestedDeliveryDateDisplay && (jsx(InfoDisplay, { id: "requested-delivery-date", label: t('Requested delivery date'), value: jsx(FormattedDate, { date: cart.requestedDeliveryDateDisplay }) })), cart.poNumber && (jsx(InfoDisplay, { id: "po-number", label: t('PO Number'), value: cart.poNumber }))] }) }) }), jsx(CheckoutPageSection, { hasBorder: false, title: t('Billing and shipping information'), children: jsx(CheckoutPageSectionContent, { children: jsx(BillingAndInvoiceInformation, { billToAddress: cart.billTo && {
59
36
  address1: cart.billTo.address1,
60
37
  address2: cart.billTo.address2,
@@ -6,6 +6,7 @@ import { useFetchSession } from '../../../shared/api/storefront/hooks/authentica
6
6
  import { usePatchSession } from '../../../shared/api/storefront/hooks/authentication/use-patch-session.js';
7
7
  import { useFetchCurrentCart } from '../../../shared/api/storefront/hooks/cart/use-fetch-current-cart.js';
8
8
  import { useFetchFulfillmentMethodsForCurrentCart } from '../../../shared/api/storefront/hooks/customer/use-fetch-fulfillment-methods-for-current-cart.js';
9
+ import { UnauthorizedRequestError, ForbiddenRequestError } from '../../../shared/fetch/request.js';
9
10
  import { useDataLayer } from '../../../shared/ga/use-data-layer.js';
10
11
  import { useDisclosure } from '../../../shared/hooks/use-disclosure.js';
11
12
  import { useNavigate } from '../../../shared/routing/use-navigate.js';
@@ -82,7 +83,7 @@ function ShippingPage() {
82
83
  }, [cart, createEcommerceEvent, dataLayer]);
83
84
  if (errorFetchCart)
84
85
  return jsx(ErrorPage, { error: errorFetchCart });
85
- if (isLoading || isNavigating || isError || isSuccess)
86
+ if (isLoading || isNavigating || isSuccess)
86
87
  return jsx(LoadingPage, {});
87
88
  if (hasNoBillToAddress && !countries)
88
89
  throw new Error('Countries are missing');
@@ -97,29 +98,38 @@ function ShippingPage() {
97
98
  async function handleSubmit({ address, cart, notes, }) {
98
99
  if (!cart.billTo)
99
100
  throw new Error('No billTo address found');
100
- await patchShippingDetails({
101
- billTo: {
102
- ...cart.billTo,
103
- address1: address.address1,
104
- address2: address.address2,
105
- address3: address.address3,
106
- attention: address.attention,
107
- city: address.city,
108
- companyName: address.companyName,
109
- country: { id: address.country.id },
110
- email: address.email,
111
- firstName: address.firstName,
112
- lastName: address.lastName,
113
- phone: address.phone,
114
- postalCode: address.postalCode,
115
- },
116
- cart,
117
- notes,
118
- });
119
- dataLayer.push(createEcommerceEvent({
120
- cart,
121
- event: { event: 'add_shipping_info' },
122
- }));
101
+ try {
102
+ await patchShippingDetails({
103
+ billTo: {
104
+ ...cart.billTo,
105
+ address1: address.address1,
106
+ address2: address.address2,
107
+ address3: address.address3,
108
+ attention: address.attention,
109
+ city: address.city,
110
+ companyName: address.companyName,
111
+ country: { id: address.country.id },
112
+ email: address.email,
113
+ firstName: address.firstName,
114
+ lastName: address.lastName,
115
+ phone: address.phone,
116
+ postalCode: address.postalCode,
117
+ },
118
+ cart,
119
+ notes,
120
+ });
121
+ dataLayer.push(createEcommerceEvent({
122
+ cart,
123
+ event: { event: 'add_shipping_info' },
124
+ }));
125
+ }
126
+ catch (error) {
127
+ if (error instanceof UnauthorizedRequestError ||
128
+ error instanceof ForbiddenRequestError)
129
+ throw error;
130
+ // Error is tracked by usePatchShippingDetails via isError/error state,
131
+ // surfaced to the UI via errorPatchBillingAddress
132
+ }
123
133
  }
124
134
  return (jsx(ShippingPageContent, { cart: cart,
125
135
  // TODO: Combine editAddress and readOnlyAddress into one section in order
@@ -157,28 +167,37 @@ function ShippingPage() {
157
167
  }, readOnlyAddress: jsx(ReadOnlyAddresses, { billTo: cart.billTo, cartId: cart.id, countries: countries || [], currentCountry: currentCountry, isLoading: isPatching, isPickup: isPickup, notes: cart.notes, onSubmit: async ({ cart: updatedCart, notes, shipTo }) => {
158
168
  if (!cart.billTo)
159
169
  return;
160
- if (notes ||
161
- cart.shipTo === null ||
162
- (shipTo && shipTo.id !== cart.shipTo?.id) ||
163
- (updatedCart &&
164
- (updatedCart.shipTo?.id !== cart.shipTo?.id ||
165
- updatedCart.shipTo === null))) {
166
- await patchShippingDetails({
167
- cart: {
168
- ...cart,
169
- ...updatedCart,
170
- },
171
- notes,
172
- shipTo,
173
- });
170
+ try {
171
+ if (notes ||
172
+ cart.shipTo === null ||
173
+ (shipTo && shipTo.id !== cart.shipTo?.id) ||
174
+ (updatedCart &&
175
+ (updatedCart.shipTo?.id !== cart.shipTo?.id ||
176
+ updatedCart.shipTo === null))) {
177
+ await patchShippingDetails({
178
+ cart: {
179
+ ...cart,
180
+ ...updatedCart,
181
+ },
182
+ notes,
183
+ shipTo,
184
+ });
185
+ }
186
+ else {
187
+ navigate(paths.REVIEW_AND_SUBMIT);
188
+ }
189
+ dataLayer.push(createEcommerceEvent({
190
+ cart,
191
+ event: { event: 'add_shipping_info' },
192
+ }));
174
193
  }
175
- else {
176
- navigate(paths.REVIEW_AND_SUBMIT);
194
+ catch (error) {
195
+ if (error instanceof UnauthorizedRequestError ||
196
+ error instanceof ForbiddenRequestError)
197
+ throw error;
198
+ // Error is tracked by usePatchShippingDetails via isError/error state,
199
+ // surfaced to the UI via errorPatchBillingAddress
177
200
  }
178
- dataLayer.push(createEcommerceEvent({
179
- cart,
180
- event: { event: 'add_shipping_info' },
181
- }));
182
201
  }, shipTo: cart.shipTo }) }));
183
202
  }
184
203
 
@@ -6,12 +6,15 @@ import { Details } from '../../display/details/details.js';
6
6
  import { StrokeRecentIcon } from '../../icons/stroke/stroke-recent-icon.js';
7
7
  import { useFormattedMessage } from '../../intl/use-formatted-message.js';
8
8
  import { Heading } from '../../typography/heading/heading.js';
9
+ import { isRequestError } from '../fetch/request.js';
9
10
  import { isProductionEnvironment } from '../utils/environment.js';
10
11
  import styles from './default-error-view.module.css.js';
11
12
 
12
13
  function DefaultErrorView({ className, error, errorInfo, resetError, resetLabel, showErrorDetails = !isProductionEnvironment, showReset, title, }) {
13
14
  const t = useFormattedMessage();
14
- return (jsxs("section", { className: clsx(styles['default-error-view'], className), children: [title && (jsx(Heading, { className: styles['title'], size: "s", tag: "h1", children: title || t('Something went wrong') })), jsx("p", { children: error.message || t('An unexpected error occured. Please try again.') }), showErrorDetails && (jsxs(Details, { className: styles['error-details'], lang: "en", summary: "Technical Details (only visible in development mode)", children: [jsx("output", { className: styles['error-kind'], children: error.toString() }), error.stack && (jsxs("div", { className: styles['error-info'], children: [jsx(Heading, { className: styles['error-info-title'], size: "xs", tag: "h2", children: "Stack:" }), jsx("pre", { className: styles['error-info-data'], children: error.stack })] })), errorInfo?.componentStack && (jsxs("div", { className: styles['component-info'], children: [jsx(Heading, { className: styles['error-info-title'], size: "xs", tag: "h2", children: "Component stack:" }), jsx("pre", { className: styles['error-info-data'], children: errorInfo.componentStack })] }))] })), showReset && resetError && (jsx(Button, { light: true, className: styles['reset-button'], color: "secondary", icon: jsx(StrokeRecentIcon, {}), onClick: resetError, size: "sm", type: "button", variant: "outline", children: resetLabel || t('Reset') }))] }));
15
+ return (jsxs("section", { className: clsx(styles['default-error-view'], className), children: [title && (jsx(Heading, { className: styles['title'], size: "s", tag: "h1", children: title || t('Something went wrong') })), jsx("p", { children: !isRequestError(error) && error.message
16
+ ? error.message
17
+ : t('An unexpected error occured. Please try again.') }), showErrorDetails && (jsxs(Details, { className: styles['error-details'], lang: "en", summary: "Technical Details (only visible in development mode)", children: [jsx("output", { className: styles['error-kind'], children: error.toString() }), error.stack && (jsxs("div", { className: styles['error-info'], children: [jsx(Heading, { className: styles['error-info-title'], size: "xs", tag: "h2", children: "Stack:" }), jsx("pre", { className: styles['error-info-data'], children: error.stack })] })), errorInfo?.componentStack && (jsxs("div", { className: styles['component-info'], children: [jsx(Heading, { className: styles['error-info-title'], size: "xs", tag: "h2", children: "Component stack:" }), jsx("pre", { className: styles['error-info-data'], children: errorInfo.componentStack })] }))] })), showReset && resetError && (jsx(Button, { light: true, className: styles['reset-button'], color: "secondary", icon: jsx(StrokeRecentIcon, {}), onClick: resetError, size: "sm", type: "button", variant: "outline", children: resetLabel || t('Reset') }))] }));
15
18
  }
16
19
 
17
20
  export { DefaultErrorView };
@@ -68,6 +68,19 @@ class RequestError extends Error {
68
68
  this.options = options?.options;
69
69
  // eslint-disable-next-line unicorn/custom-error-definition
70
70
  this.name = this.name || this.constructor.name || 'RequestError';
71
+ // Replace the non-serializable Response cause with a plain object so error
72
+ // reporting tools (e.g. Sentry) can capture full request/response context.
73
+ this.cause = {
74
+ request: {
75
+ method: this.options?.method ?? 'GET',
76
+ url: this.options?.url,
77
+ },
78
+ response: {
79
+ body: this.body,
80
+ status: this.status,
81
+ statusText: this.statusText,
82
+ },
83
+ };
71
84
  }
72
85
  toJSON() {
73
86
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonic-equipment/ui",
3
- "version": "259.0.4",
3
+ "version": "259.0.6",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "engines": {