@sonic-equipment/ui 131.0.0 → 132.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ import { Button } from '../button/button.js';
8
8
  import styles from './add-to-cart-button.module.css.js';
9
9
 
10
10
  function AddToCartButton({ initialState = 'initial', isDisabled = false, onChange, quantity, }) {
11
- const [currentState, setState] = useState(initialState);
11
+ const [currentState, setState] = useState(quantity > 0 ? 'spinner' : initialState);
12
12
  const [manualInputQuantity, setManualInputQuantity] = useState(null);
13
13
  useEffect(() => {
14
14
  setState(currentState => {
@@ -1,6 +1,7 @@
1
1
  export interface CartTotalsProps {
2
2
  deliveryDate?: string;
3
3
  fulfillmentMethod?: string;
4
+ isPayByInvoice?: boolean;
4
5
  orderNumber?: string;
5
6
  shippingCost: string;
6
7
  subtotal: string;
@@ -9,4 +10,4 @@ export interface CartTotalsProps {
9
10
  vatPercentage: number | undefined;
10
11
  }
11
12
  export declare const formatDisplayPriceToSymbolSpaceValue: (displayPrice: string) => string;
12
- export declare function CartTotals({ deliveryDate, fulfillmentMethod, orderNumber, shippingCost, subtotal, tax, total, vatPercentage, }: CartTotalsProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function CartTotals({ deliveryDate, fulfillmentMethod, isPayByInvoice, orderNumber, shippingCost, subtotal, tax, total, vatPercentage, }: CartTotalsProps): import("react/jsx-runtime").JSX.Element;
@@ -5,9 +5,9 @@ import { Heading } from '../typography/heading/heading.js';
5
5
  import styles from './cart-totals.module.css.js';
6
6
 
7
7
  const formatDisplayPriceToSymbolSpaceValue = (displayPrice) => displayPrice.replace(/^(\D)([\d,.]*$)/, (_, symbol, value) => `${symbol} ${value}`);
8
- function CartTotals({ deliveryDate, fulfillmentMethod, orderNumber, shippingCost, subtotal, tax, total, vatPercentage, }) {
8
+ function CartTotals({ deliveryDate, fulfillmentMethod, isPayByInvoice, orderNumber, shippingCost, subtotal, tax, total, vatPercentage, }) {
9
9
  const t = useFormattedMessage();
10
- return (jsxs("div", { className: styles['cart-totals'], children: [orderNumber && (jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Order number" }) }), jsx("div", { children: jsx("div", { className: styles.line, children: jsx("p", { className: styles.label, "data-test-selector": "orderConfirmation_orderNumber", children: orderNumber }) }) })] })), deliveryDate !== undefined && (jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Delivery date" }) }), jsx("div", { children: jsx("div", { className: styles.line, children: jsx("p", { className: styles.label, "data-test-selector": "deliveryDate", children: deliveryDate || t('As soon as possible') }) }) })] })), fulfillmentMethod && (jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Fulfillment method" }) }), jsx("div", { children: jsx("div", { className: styles.line, children: jsx("p", { className: styles.label, "data-test-selector": "fulfillmentMethod", children: fulfillmentMethod }) }) })] })), jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Cost overview" }) }), jsxs("div", { children: [jsxs("div", { className: styles.line, children: [jsxs("p", { className: styles.label, children: [jsx(FormattedMessage, { id: "Subtotal" }), ' ', jsx(FormattedMessage, { id: "Excl. VAT" })] }), jsx("p", { className: styles.value, "data-test-selector": "cartTotal_subTotal", children: formatDisplayPriceToSymbolSpaceValue(subtotal) })] }), jsxs("div", { className: styles.line, children: [jsx("p", { className: styles.label, children: jsx(FormattedMessage, { id: "Shipping and handling" }) }), jsx("p", { className: styles.value, "data-test-selector": "shippingCost", children: formatDisplayPriceToSymbolSpaceValue(shippingCost) })] }), jsxs("div", { className: styles.line, children: [jsxs("p", { className: styles.label, children: [jsx(FormattedMessage, { id: "VAT" }), ' ', vatPercentage ? `${vatPercentage}%` : ''] }), jsx("p", { className: styles.value, "data-test-selector": "vatAmount", children: formatDisplayPriceToSymbolSpaceValue(tax) })] })] })] }), jsx("section", { className: styles.totals, children: jsxs("div", { className: styles.line, children: [jsx("p", { className: styles.label, children: jsx(FormattedMessage, { id: "Total" }) }), jsx("p", { className: styles.value, "data-test-selector": "cartTotal_orderGrandTotalDisplay", children: formatDisplayPriceToSymbolSpaceValue(total) })] }) })] }));
10
+ return (jsxs("div", { className: styles['cart-totals'], children: [orderNumber && (jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Order number" }) }), jsx("div", { children: jsx("div", { className: styles.line, children: jsx("p", { className: styles.label, "data-test-selector": "orderConfirmation_orderNumber", children: orderNumber }) }) })] })), deliveryDate !== undefined && (jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Delivery date" }) }), jsx("div", { children: jsx("div", { className: styles.line, children: jsx("p", { className: styles.label, "data-test-selector": "deliveryDate", children: deliveryDate || t('As soon as possible') }) }) })] })), fulfillmentMethod && (jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Fulfillment method" }) }), jsx("div", { children: jsx("div", { className: styles.line, children: jsx("p", { className: styles.label, "data-test-selector": "fulfillmentMethod", children: fulfillmentMethod }) }) })] })), isPayByInvoice && (jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Payment method" }) }), jsx("div", { children: jsx("div", { className: styles.line, children: jsx("p", { className: styles.label, "data-test-selector": "paymentMethod", children: jsx(FormattedMessage, { id: "Pay by invoice" }) }) }) })] })), jsxs("section", { className: styles.section, children: [jsx(Heading, { className: styles['section-header'], size: "xxxs", tag: "h3", children: jsx(FormattedMessage, { id: "Cost overview" }) }), jsxs("div", { children: [jsxs("div", { className: styles.line, children: [jsxs("p", { className: styles.label, children: [jsx(FormattedMessage, { id: "Subtotal" }), ' ', jsx(FormattedMessage, { id: "Excl. VAT" })] }), jsx("p", { className: styles.value, "data-test-selector": "cartTotal_subTotal", children: formatDisplayPriceToSymbolSpaceValue(subtotal) })] }), jsxs("div", { className: styles.line, children: [jsx("p", { className: styles.label, children: jsx(FormattedMessage, { id: "Shipping and handling" }) }), jsx("p", { className: styles.value, "data-test-selector": "shippingCost", children: formatDisplayPriceToSymbolSpaceValue(shippingCost) })] }), jsxs("div", { className: styles.line, children: [jsxs("p", { className: styles.label, children: [jsx(FormattedMessage, { id: "VAT" }), ' ', vatPercentage ? `${vatPercentage}%` : ''] }), jsx("p", { className: styles.value, "data-test-selector": "vatAmount", children: formatDisplayPriceToSymbolSpaceValue(tax) })] })] })] }), jsx("section", { className: styles.totals, children: jsxs("div", { className: styles.line, children: [jsx("p", { className: styles.label, children: jsx(FormattedMessage, { id: "Total" }) }), jsx("p", { className: styles.value, "data-test-selector": "cartTotal_orderGrandTotalDisplay", children: formatDisplayPriceToSymbolSpaceValue(total) })] }) })] }));
11
11
  }
12
12
 
13
13
  export { CartTotals, formatDisplayPriceToSymbolSpaceValue };
@@ -28,6 +28,7 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
28
28
  const { isLoading: isPlacingCart, mutate: placeOrder } = usePlaceOrder();
29
29
  const dropinRef = useRef(null);
30
30
  const [paymentError, setPaymentError] = useState();
31
+ const [apiError, setAPIError] = useState();
31
32
  const invalidateAdyen = useInvalidateAdyen();
32
33
  const [isValidatingVAT, setIsValidatingVAT] = useState(false);
33
34
  const t = useFormattedMessage();
@@ -95,54 +96,83 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
95
96
  setIsValidatingVAT(false);
96
97
  }
97
98
  }, 500);
99
+ const isAdyenPayment = cart.paymentMethod?.name !== 'PBI' &&
100
+ cart.paymentOptions &&
101
+ cart.billTo?.id &&
102
+ countryCode;
98
103
  async function onSubmit(e) {
99
104
  const formData = new FormData(e.currentTarget);
100
105
  const cart = cartRef.current;
101
106
  setValidationErrors(prev => prev); // NOTE: This makes sure the form is revalidated on submit
102
107
  if (Object.keys(validationErrors).length > 0)
103
108
  return;
104
- if (!dropinRef.current) {
105
- console.warn('Adyen Dropin not ready');
106
- return;
109
+ if (isAdyenPayment) {
110
+ /* Adyen Payment */
111
+ if (!dropinRef.current) {
112
+ console.warn('Adyen Dropin not ready');
113
+ return;
114
+ }
115
+ dropinRef.current.showValidation();
116
+ if (!dropinRef.current.isValid)
117
+ return;
118
+ try {
119
+ await patchCart({
120
+ cart: {
121
+ ...cart,
122
+ customerVatNumber: formData.get('vatNumber')?.toString(),
123
+ poNumber: formData.get('poNumber')?.toString(),
124
+ properties: {
125
+ ...cart.properties,
126
+ industry: formData.get('industry')?.toString() || '',
127
+ },
128
+ requestedDeliveryDate: formData.get('deliveryDate')?.toString(),
129
+ },
130
+ });
131
+ }
132
+ catch (error) {
133
+ console.error(error);
134
+ setAPIError(error);
135
+ }
136
+ try {
137
+ dropinRef.current.submit();
138
+ }
139
+ catch (error) {
140
+ console.error(error);
141
+ setPaymentError(error);
142
+ }
107
143
  }
108
- dropinRef.current.showValidation();
109
- if (!dropinRef.current.isValid)
110
- return;
144
+ else {
145
+ /* Pay by Invoice */
146
+ const cart = cartRef.current;
147
+ try {
148
+ await placeOrder({ cart });
149
+ }
150
+ catch (error) {
151
+ console.error(error);
152
+ setAPIError(error);
153
+ }
154
+ return onPaymentComplete();
155
+ }
156
+ }
157
+ const onComplete = useCallback(async (result) => {
158
+ const cart = cartRef.current;
111
159
  try {
112
- // TODO: try catch handle failed patch, display error on top of form
113
- await patchCart({
160
+ await placeOrder({
114
161
  cart: {
115
162
  ...cart,
116
- customerVatNumber: formData.get('vatNumber')?.toString(),
117
- poNumber: formData.get('poNumber')?.toString(),
118
- properties: {
119
- ...cart.properties,
120
- industry: formData.get('industry')?.toString() || '',
163
+ paymentMethod: null,
164
+ paymentOptions: {
165
+ ...cart.paymentOptions, // Hack needed to make B2B happy
166
+ adyenPspReference: result.pspReference,
167
+ isAdyenDropIn: true,
121
168
  },
122
- requestedDeliveryDate: formData.get('deliveryDate')?.toString(),
123
169
  },
124
170
  });
125
- dropinRef.current.submit();
126
171
  }
127
172
  catch (error) {
128
173
  console.error(error);
129
- setPaymentError(error);
174
+ setAPIError(error);
130
175
  }
131
- }
132
- const onComplete = useCallback(async (result) => {
133
- const cart = cartRef.current;
134
- // TODO: try catch handle error, display error on top
135
- await placeOrder({
136
- cart: {
137
- ...cart,
138
- paymentMethod: null,
139
- paymentOptions: {
140
- ...cart.paymentOptions, // Hack needed to make B2B happy
141
- adyenPspReference: result.pspReference,
142
- isAdyenDropIn: true,
143
- },
144
- },
145
- });
146
176
  return onPaymentComplete();
147
177
  }, [onPaymentComplete, placeOrder]);
148
178
  const onError = useCallback((error, result) => {
@@ -155,7 +185,7 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
155
185
  return (jsxs(Form, { className: styles['payment-form'], "data-test-selector": "paymentForm", id: form, onSubmit: e => {
156
186
  e.preventDefault();
157
187
  onSubmit(e);
158
- }, validationErrors: validationErrors, children: [hasAtp && (jsxs("div", { className: styles['delivery-date'], children: [jsx(Select, { showLabel: true, "data-test-selector": "deliveryDateSelect", isDisabled: hasReturnedFromAdyen || asSoonAsPossible, isRequired: !asSoonAsPossible, label: t('Select a desired delivery date'), name: "deliveryDate", onChange: setDeliveryDate, options: atpSelectOptions, selectedOption: deliveryDate, variant: "solid" }, String(asSoonAsPossible)), jsxs("div", { className: styles['asap-checkbox'], children: [jsx(Checkbox, { "data-test-selector": "asapCheckbox", isDisabled: hasReturnedFromAdyen || !hasAtp, isSelected: asSoonAsPossible, onChange: checked => {
188
+ }, validationErrors: validationErrors, children: [Boolean(apiError) && (jsx("div", { className: styles['error-message'], children: jsx(FormattedMessage, { id: "An unexpected error occured" }) })), hasAtp && (jsxs("div", { className: styles['delivery-date'], children: [jsx(Select, { showLabel: true, "data-test-selector": "deliveryDateSelect", isDisabled: hasReturnedFromAdyen || asSoonAsPossible, isRequired: !asSoonAsPossible, label: t('Select a desired delivery date'), name: "deliveryDate", onChange: setDeliveryDate, options: atpSelectOptions, selectedOption: deliveryDate, variant: "solid" }, String(asSoonAsPossible)), jsxs("div", { className: styles['asap-checkbox'], children: [jsx(Checkbox, { "data-test-selector": "asapCheckbox", isDisabled: hasReturnedFromAdyen || !hasAtp, isSelected: asSoonAsPossible, onChange: checked => {
159
189
  setAsSoonAsPossible(checked);
160
190
  if (checked)
161
191
  setDeliveryDate('');
@@ -171,13 +201,10 @@ function Payment({ atp, cart: _cart, form, onError: _onError, onPaymentComplete,
171
201
  MA: 'Maritime',
172
202
  OT: 'Other',
173
203
  /* eslint-enable sort-keys-fix/sort-keys-fix */
174
- }, variant: "solid" }), jsx(TextField, { showLabel: true, defaultValue: cart.customerVatNumber, isDisabled: hasReturnedFromAdyen || isProcessing, label: t('VAT Number'), name: "vatNumber", onInput: e => debouncedValidator(e.target.value), validate: () => validationErrors.vatNumber ?? true }), jsx(TextField, { showLabel: true, defaultValue: cart.poNumber, isDisabled: hasReturnedFromAdyen || isProcessing, label: t('PO Number'), name: "poNumber" }), paymentMethodOptions && (jsx(Select, { "data-test-selector": "paymentMethodSelect", defaultSelectedOption: cart.paymentOptions?.paymentMethods?.[0]?.name || 'ADY', isDisabled: (hasReturnedFromAdyen ||
204
+ }, variant: "solid" }), jsx(TextField, { showLabel: true, defaultValue: cart.customerVatNumber, isDisabled: hasReturnedFromAdyen || isProcessing, label: t('VAT Number'), name: "vatNumber", onInput: e => debouncedValidator(e.target.value), validate: () => validationErrors.vatNumber ?? true }), jsx(TextField, { showLabel: true, defaultValue: cart.poNumber, isDisabled: hasReturnedFromAdyen || isProcessing, label: t('PO Number'), name: "poNumber" }), paymentMethodOptions && Object.keys(paymentMethodOptions).length > 1 && (jsx(Select, { "data-test-selector": "paymentMethodSelect", defaultSelectedOption: cart.paymentOptions?.paymentMethods?.[0]?.name || 'ADY', isDisabled: (hasReturnedFromAdyen ||
175
205
  (cart.paymentOptions?.paymentMethods &&
176
206
  cart.paymentOptions.paymentMethods.length <= 1)) ??
177
- true, label: t('Payment method'), name: "paymentMethod", options: paymentMethodOptions, variant: "solid" })), cart.paymentMethod?.name !== 'PBI' &&
178
- cart.paymentOptions &&
179
- cart.billTo?.id &&
180
- countryCode && (jsx(AdyenPayment, { amount: cart.orderGrandTotal, cartId: cart.trackId, countryCode: countryCode, currencyCode: currencySymbolToISO[cart.currencySymbol], customerId: cart.billTo.id, dropinRef: dropinRef, environment: "test", onComplete: onComplete, onError: onError, orderAmount: cart.orderGrandTotal, returnUrl: typeof window === 'undefined'
207
+ true, label: t('Payment method'), name: "paymentMethod", options: paymentMethodOptions, variant: "solid" })), isAdyenPayment && cart.billTo && (jsx(AdyenPayment, { amount: cart.orderGrandTotal, cartId: cart.trackId, countryCode: countryCode, currencyCode: currencySymbolToISO[cart.currencySymbol], customerId: cart.billTo.id, dropinRef: dropinRef, environment: "test", onComplete: onComplete, onError: onError, orderAmount: cart.orderGrandTotal, returnUrl: typeof window === 'undefined'
181
208
  ? ''
182
209
  : // eslint-disable-next-line ssr-friendly/no-dom-globals-in-react-fc
183
210
  `https://sonicequipment.commerce.insitesandbox.com${window.location.pathname}${window.location.search ? `${window.location.search}` : ''}` })), Boolean(paymentError) && (jsx("div", { className: styles['error-message'], children: jsx(FormattedMessage, { id: "An error occurred while processing your payment. Please try again." }) })), jsxs("div", { className: styles['invoice-and-shipping'], children: [jsx(Label, { children: jsx(FormattedMessage, { id: "Billing and shipping address" }) }), jsx(Accordion, { variant: "select", children: jsx(AccordionItem, { id: "invoice-and-shipping", title: "Factuur- en verzendinformatie", children: jsxs("div", { className: styles['content'], children: [jsx("section", { className: styles.section, children: jsxs("div", { children: [jsx("p", { className: styles.label, children: jsx(FormattedMessage, { id: "Billing address" }) }), jsx(Address, { address1: cart.billTo?.address1, address2: cart.billTo?.address2, address3: cart.billTo?.address3, city: cart.billTo?.city, companyName: cart.billTo?.companyName, country: cart.billTo?.country?.name, email: cart.billTo?.email, phone: cart.billTo?.phone, postalCode: cart.billTo?.postalCode })] }) }), jsx("section", { className: styles.section, children: jsxs("div", { children: [jsx("p", { className: styles.label, children: jsx(FormattedMessage, { id: "Shipping address" }) }), jsx(Address, { address1: cart.shipTo?.address1, address2: cart.shipTo?.address2, address3: cart.shipTo?.address3, city: cart.shipTo?.city, companyName: cart.shipTo?.companyName, country: cart.shipTo?.country?.name, email: cart.shipTo?.email, phone: cart.shipTo?.phone, postalCode: cart.shipTo?.postalCode })] }) })] }) }) })] })] }));
@@ -1 +1 @@
1
- export type TranslationId = "'{0}' in all products" | "Try 'Search' and try to find the product you're looking for" | "Unfortnately, We found no articles for your search '{0}'" | ' to your account to manage your lists.' | 'Add order notes' | 'Add to list' | 'Address' | 'Amount: {0}' | 'An error occurred while processing your payment. Please try again.' | 'An unexpected error occured' | 'Are you looking for information about our service? Please visit our customer support page' | 'Are you sure you want to remove all items from your cart?' | 'Are you sure you want to remove this item from your cart?' | 'article' | 'articles' | 'As soon as possible' | 'Attention' | 'Billing address' | 'Billing and shipping address' | 'Cancel' | 'Cart' | 'Changing your address is currently not possible. Please contact customer support to change your address.' | 'Chosen filters' | 'City' | 'Clear filters' | 'Clear' | 'Click the button below to continue shopping.' | 'Company name' | 'Continue shopping' | 'Continue' | 'Cost overview' | 'Country' | 'create account' | 'Create new list' | 'Delivery expected in {0} {1}' | 'Delivery date' | 'Double check your spelling' | 'Downloads' | 'Easily add your favorite products' | 'Edit billing address' | 'Edit shipping address' | 'Email' | 'Excl. VAT' | 'Explore by categories' | 'Exploring our products by category' | 'facet.categories' | 'facet.height' | 'facet.weight' | 'Features' | 'First name' | 'Fulfillment method' | 'Hide filters' | 'Home' | 'Incl. VAT' | 'Includes' | 'Industry' | 'Language' | 'Last name' | 'List name already exists' | 'New list name' | 'of' | 'Order number' | 'Order' | 'Pay' | 'Payment method' | 'Payment' | 'pc' | 'Phone' | 'Pick up' | 'Pickup address' | 'Please Sign In' | 'PO Number' | 'Popular searches' | 'Postal Code' | 'Print' | 'Processing' | 'Product Features' | 'Product' | 'Products' | 'Quick access' | 'Recent searches' | 'Recently viewed' | 'Remove all' | 'Review and payment' | 'Save order' | 'Save' | 'Saved cart for later.' | 'Search' | 'Searching again using more general terms' | 'See all results' | 'Select a desired delivery date' | 'Select a list' | 'Selecting As Soon As Possible will enable us to send the products to you as they become available.' | 'Share your favorite list with others' | 'Ship' | 'Shipping address' | 'Shipping and handling' | 'Shipping details' | 'Shop more efficiently and quicker with a favorites list' | 'Show all' | 'Show filters' | 'Show less' | 'Show' | 'sign in' | 'Sonic address' | 'Sorry, there are no products found' | 'Sorry, we could not find matches for' | 'Sort by' | 'sort.newest' | 'sort.price_asc' | 'sort.price_desc' | 'sort.relevance' | 'Specifications' | 'Submit' | 'Subtotal' | 'Suggestions' | 'tag.limited' | 'tag.new' | 'The product has been added to your cart.' | 'The product has been removed from your cart.' | 'The product has been updated in your cart.' | 'There are no products in your shopping cart.' | 'Total amount is' | 'Total' | 'Total' | 'Try another search' | 'Try another search' | 'Unable to add the product to your cart.' | 'Unable to add the product to your cart.' | 'Unable to empty your cart.' | 'Unable to empty your cart.' | 'Unable to remove the product from your cart.' | 'Unable to remove the product from your cart.' | 'Unable to save cart for later.' | 'Unable to save cart for later.' | 'Unable to update the product in your cart.' | 'Unable to update the product in your cart.' | 'Updating address' | 'Use billing address' | 'Use fewer keywords' | 'Validating' | 'validation.badInput' | 'validation.customError' | 'validation.invalid' | 'validation.patternMismatch' | 'validation.rangeOverflow' | 'validation.rangeUnderflow' | 'validation.stepMismatch' | 'validation.tooLong' | 'validation.tooShort' | 'validation.typeMismatch' | 'validation.valid' | 'validation.valueMissing' | 'VAT Number' | 'VAT' | 'Welcome to Sonic Equipment. Please choose your country and language below.' | 'What are you searching for?' | 'You could try checking the spelling of your search query' | 'You could try exploring our products by category' | 'You could try' | 'You must ' | 'Your cart has been emptied.' | 'Your favorites are available on multiple devices' | 'Your shopping cart is still empty';
1
+ export type TranslationId = "'{0}' in all products" | "Try 'Search' and try to find the product you're looking for" | "Unfortnately, We found no articles for your search '{0}'" | ' to your account to manage your lists.' | 'Add order notes' | 'Add to list' | 'Address' | 'Amount: {0}' | 'An error occurred while processing your payment. Please try again.' | 'An unexpected error occured' | 'Are you looking for information about our service? Please visit our customer support page' | 'Are you sure you want to remove all items from your cart?' | 'Are you sure you want to remove this item from your cart?' | 'article' | 'articles' | 'As soon as possible' | 'Attention' | 'Billing address' | 'Billing and shipping address' | 'Cancel' | 'Cart' | 'Changing your address is currently not possible. Please contact customer support to change your address.' | 'Chosen filters' | 'City' | 'Clear filters' | 'Clear' | 'Click the button below to continue shopping.' | 'Company name' | 'Continue shopping' | 'Continue' | 'Cost overview' | 'Country' | 'create account' | 'Create new list' | 'Delivery expected in {0} {1}' | 'Delivery date' | 'Double check your spelling' | 'Downloads' | 'Easily add your favorite products' | 'Edit billing address' | 'Edit shipping address' | 'Email' | 'Excl. VAT' | 'Explore by categories' | 'Exploring our products by category' | 'facet.categories' | 'facet.height' | 'facet.weight' | 'Features' | 'First name' | 'Fulfillment method' | 'Hide filters' | 'Home' | 'Incl. VAT' | 'Includes' | 'Industry' | 'Language' | 'Last name' | 'List name already exists' | 'New list name' | 'of' | 'Order number' | 'Order' | 'Pay by invoice' | 'Pay' | 'Payment method' | 'Payment' | 'pc' | 'Phone' | 'Pick up' | 'Pickup address' | 'Please Sign In' | 'PO Number' | 'Popular searches' | 'Postal Code' | 'Print' | 'Processing' | 'Product Features' | 'Product' | 'Products' | 'Quick access' | 'Recent searches' | 'Recently viewed' | 'Remove all' | 'Review and payment' | 'Save order' | 'Save' | 'Saved cart for later.' | 'Search' | 'Searching again using more general terms' | 'See all results' | 'Select a desired delivery date' | 'Select a list' | 'Selecting As Soon As Possible will enable us to send the products to you as they become available.' | 'Share your favorite list with others' | 'Ship' | 'Shipping address' | 'Shipping and handling' | 'Shipping details' | 'Shop more efficiently and quicker with a favorites list' | 'Show all' | 'Show filters' | 'Show less' | 'Show' | 'sign in' | 'Sonic address' | 'Sorry, there are no products found' | 'Sorry, we could not find matches for' | 'Sort by' | 'sort.newest' | 'sort.price_asc' | 'sort.price_desc' | 'sort.relevance' | 'Specifications' | 'Submit' | 'Subtotal' | 'Suggestions' | 'tag.limited' | 'tag.new' | 'The product has been added to your cart.' | 'The product has been removed from your cart.' | 'The product has been updated in your cart.' | 'There are no products in your shopping cart.' | 'Total amount is' | 'Total' | 'Total' | 'Try another search' | 'Try another search' | 'Unable to add the product to your cart.' | 'Unable to add the product to your cart.' | 'Unable to empty your cart.' | 'Unable to empty your cart.' | 'Unable to remove the product from your cart.' | 'Unable to remove the product from your cart.' | 'Unable to save cart for later.' | 'Unable to save cart for later.' | 'Unable to update the product in your cart.' | 'Unable to update the product in your cart.' | 'Updating address' | 'Use billing address' | 'Use fewer keywords' | 'Validating' | 'validation.badInput' | 'validation.customError' | 'validation.invalid' | 'validation.patternMismatch' | 'validation.rangeOverflow' | 'validation.rangeUnderflow' | 'validation.stepMismatch' | 'validation.tooLong' | 'validation.tooShort' | 'validation.typeMismatch' | 'validation.valid' | 'validation.valueMissing' | 'VAT Number' | 'VAT' | 'Welcome to Sonic Equipment. Please choose your country and language below.' | 'What are you searching for?' | 'You could try checking the spelling of your search query' | 'You could try exploring our products by category' | 'You could try' | 'You must ' | 'Your cart has been emptied.' | 'Your favorites are available on multiple devices' | 'Your shopping cart is still empty';
@@ -68,7 +68,8 @@ function PaymentPage() {
68
68
  { href: '/CheckoutReviewAndSubmit', label: t('Review and payment') },
69
69
  ], title: t('Review and payment'), children: jsxs(CheckoutPageLayout, { actions: {
70
70
  primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutShippingCartTotalContinueButton", form: PAYMENT_FORM_ID, isDisabled: isProcessing, isLoading: isProcessing ? (jsx(FormattedMessage, { id: "Processing" })) : isValidating ? (jsx(FormattedMessage, { id: "Validating" })) : (false), type: "submit", children: jsx(FormattedMessage, { id: "Pay" }) })),
71
- }, mobileSummary: jsx(CartTotalsSummary, { totalAmount: cart.orderGrandTotalDisplay }), overview: jsx(CartTotals, { deliveryDate: hasAtp ? undefined : cart.requestedDeliveryDate, fulfillmentMethod: cart.fulfillmentMethod, shippingCost: cart.shippingAndHandlingDisplay, subtotal: cart.orderSubTotalDisplay, tax: cart.totalTaxDisplay, total: cart.orderGrandTotalDisplay, vatPercentage: cart.cartLines[0]?.pricing?.vatRate || 0 }), children: [jsx(CheckoutPageSection, { hasBorder: false, title: jsx(FormattedMessage, { id: "Payment" }), children: jsx(CheckoutPageSectionContent, { children: jsx(Payment, { atp: atp, cart: cart, form: PAYMENT_FORM_ID, onPaymentComplete: onPaymentComplete, onProcessing: setIsProcessing, onValidating: setIsValidating }) }) }), jsx(CheckoutPageSection, { hasBorder: false, title: jsx(FormattedMessage, { id: "Order" }), children: jsx(CheckoutPageSectionContent, { stretch: true, children: jsx(OrderLineList, { children: cart.cartLines.map(cartLine => (jsx(OrderLineCard, { deliveryDate: cartLine.atp?.date, href: cartLine.productUri, image: {
71
+ }, mobileSummary: jsx(CartTotalsSummary, { totalAmount: cart.orderGrandTotalDisplay }), overview: jsx(CartTotals, { deliveryDate: hasAtp ? undefined : cart.requestedDeliveryDate, fulfillmentMethod: cart.fulfillmentMethod, isPayByInvoice: (cart.paymentOptions?.paymentMethods?.length || 1) <= 1 &&
72
+ cart.paymentMethod?.name === 'PBI', shippingCost: cart.shippingAndHandlingDisplay, subtotal: cart.orderSubTotalDisplay, tax: cart.totalTaxDisplay, total: cart.orderGrandTotalDisplay, vatPercentage: cart.cartLines[0]?.pricing?.vatRate || 0 }), children: [jsx(CheckoutPageSection, { hasBorder: false, title: jsx(FormattedMessage, { id: "Payment" }), children: jsx(CheckoutPageSectionContent, { children: jsx(Payment, { atp: atp, cart: cart, form: PAYMENT_FORM_ID, onPaymentComplete: onPaymentComplete, onProcessing: setIsProcessing, onValidating: setIsValidating }) }) }), jsx(CheckoutPageSection, { hasBorder: false, title: jsx(FormattedMessage, { id: "Order" }), children: jsx(CheckoutPageSectionContent, { stretch: true, children: jsx(OrderLineList, { children: cart.cartLines.map(cartLine => (jsx(OrderLineCard, { deliveryDate: cartLine.atp?.date, href: cartLine.productUri, image: {
72
73
  fit: 'contain',
73
74
  image: {
74
75
  '1': cartLine.smallImagePath,
@@ -1,19 +1,20 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useMemo, useEffect, useCallback } from 'react';
2
+ import { useMemo, useEffect } from 'react';
3
3
  import { Button } from '../../../buttons/button/button.js';
4
4
  import { CartTotals } from '../../../cart-totals/cart-totals.js';
5
5
  import { CartTotalsSummary } from '../../../cart-totals/cart-totals-summary.js';
6
6
  import { Select } from '../../../forms/select/select.js';
7
7
  import { FormattedMessage } from '../../../intl/formatted-message.js';
8
8
  import { useFormattedMessage } from '../../../intl/use-formatted-message.js';
9
+ import { useAwaitableMutation } from '../../../shared/api/shared/hooks/use-awaitable-mutation.js';
9
10
  import { useFetchSession } from '../../../shared/api/storefront/hooks/authentication/use-fetch-session.js';
10
11
  import { useIsAuthenticated } from '../../../shared/api/storefront/hooks/authentication/use-is-authenticated.js';
11
12
  import { usePatchSession } from '../../../shared/api/storefront/hooks/authentication/use-patch-session.js';
12
13
  import { useFetchCurrentCart } from '../../../shared/api/storefront/hooks/cart/use-fetch-current-cart.js';
13
- import { usePatchCart } from '../../../shared/api/storefront/hooks/cart/use-patch-cart.js';
14
14
  import { useFetchFulfillmentMethodsForCurrentCart } from '../../../shared/api/storefront/hooks/customer/use-fetch-fulfillment-methods-for-current-cart.js';
15
- import { usePatchBillToAddress } from '../../../shared/api/storefront/hooks/customer/use-patch-bill-to-address.js';
16
15
  import { useFetchCountries } from '../../../shared/api/storefront/hooks/website/use-fetch-countries.js';
16
+ import { patchCart } from '../../../shared/api/storefront/services/cart-service.js';
17
+ import { patchBillToAddress } from '../../../shared/api/storefront/services/customer-service.js';
17
18
  import { useNavigate } from '../../../shared/routing/route-provider.js';
18
19
  import { hasNo } from '../../../shared/utils/types.js';
19
20
  import { Page } from '../../components/page/page.js';
@@ -28,23 +29,19 @@ import styles from './shipping-page.module.css.js';
28
29
 
29
30
  const REVIEW_AND_SUBMIT_PATH = '/CheckoutReviewAndSubmit';
30
31
  function usePatchCartAndBillingAddress() {
31
- const { isError: isPatchingBillToAddressError, isLoading: isPatchBillToAddress, isSuccess: isPatchingBillToAddressSuccess, mutate: patchBillToAddress, } = usePatchBillToAddress();
32
- const { isError: isPatchCartError, isLoading: isPatchingCart, isSuccess: isPatchCartSuccess, mutate: patchCart, } = usePatchCart();
33
- const mutate = useCallback(async ({ billTo, cart, notes, }) => {
34
- await Promise.all([
35
- billTo &&
36
- patchBillToAddress({
37
- billTo,
38
- }),
39
- patchCart({ cart: { ...cart, notes } }),
40
- ].filter(Boolean));
41
- }, [patchBillToAddress, patchCart]);
42
- return {
43
- isError: isPatchingBillToAddressError || isPatchCartError,
44
- isPatching: isPatchBillToAddress || isPatchingCart,
45
- isSuccess: isPatchingBillToAddressSuccess || isPatchCartSuccess,
46
- mutate,
47
- };
32
+ return useAwaitableMutation({
33
+ mutationFn: async ({ billTo, cart, notes, }) => {
34
+ await Promise.all([
35
+ billTo && patchBillToAddress({ billTo }),
36
+ patchCart({ cart: { ...cart, notes } }),
37
+ ].filter(Boolean));
38
+ },
39
+ onSuccess: ({ queryClient }) => {
40
+ queryClient.removeQueries({ queryKey: ['customer'] });
41
+ queryClient.removeQueries({ queryKey: ['carts'] });
42
+ queryClient.removeQueries({ queryKey: ['session'] });
43
+ },
44
+ });
48
45
  }
49
46
  function ShippingPage() {
50
47
  const isAuthenticated = useIsAuthenticated();
@@ -55,7 +52,7 @@ function ShippingPage() {
55
52
  const { data: session } = useFetchSession();
56
53
  const { data: fulfillmentMethods, isLoading: isLoadingFulfillmentMethods } = useFetchFulfillmentMethodsForCurrentCart();
57
54
  const { isLoading: isPatchingSession, mutate: patchSession } = usePatchSession();
58
- const { isError, isPatching, isSuccess, mutate: patchCartAndBillingAddress, } = usePatchCartAndBillingAddress();
55
+ const { isError, isLoading: isPatching, isSuccess, mutate: patchCartAndBillingAddress, } = usePatchCartAndBillingAddress();
59
56
  const isLoading = isLoadingCart || isLoadingCountries || isLoadingFulfillmentMethods;
60
57
  const t = useFormattedMessage();
61
58
  const { isNavigating, navigate } = useNavigate();
@@ -67,7 +64,9 @@ function ShippingPage() {
67
64
  ...acc,
68
65
  [method]: t(`fulfillmentMethod.${method}`),
69
66
  }), {});
67
+ const isPickup = cart?.fulfillmentMethod === 'PickUp';
70
68
  useEffect(() => {
69
+ /* Initial guards. When these are not met, we should not proceed */
71
70
  if (isNavigating)
72
71
  return;
73
72
  if (isAuthenticated === undefined)
@@ -82,6 +81,7 @@ function ShippingPage() {
82
81
  return navigate('/signin?returnUrl=/CheckoutShipping');
83
82
  }, [cart, navigate, isAuthenticated, isNavigating]);
84
83
  useEffect(() => {
84
+ /* Guards for when the bill to address is saved and we should navigate to the next page */
85
85
  if (isPatching)
86
86
  return;
87
87
  if (!isSuccess)
@@ -90,11 +90,13 @@ function ShippingPage() {
90
90
  return;
91
91
  if (isError)
92
92
  return location?.reload();
93
- navigate(REVIEW_AND_SUBMIT_PATH);
94
- }, [isSuccess, navigate, isNavigating, isPatching, isError]);
93
+ if (hasNo(cart?.billTo?.address1))
94
+ return;
95
+ return navigate(REVIEW_AND_SUBMIT_PATH);
96
+ }, [isSuccess, cart, navigate, isNavigating, isPatching, isError]);
95
97
  if (error)
96
98
  return jsx(ErrorPage, { error: error });
97
- if (isLoading || isNavigating || isError)
99
+ if (isLoading || isNavigating || isError || isSuccess)
98
100
  return jsx(LoadingPage, {});
99
101
  if (!isAuthenticated ||
100
102
  hasNo(cart) ||
@@ -118,12 +120,11 @@ function ShippingPage() {
118
120
  },
119
121
  });
120
122
  await refetchCart();
121
- }, options: fulfillmentMethodOptions || {}, variant: "solid" }) }) }) })), hasBillToAddress ? (jsx(ReadOnlyAddresses, { billTo: cart.billTo, isLoading: isPatching, isPickup: cart.fulfillmentMethod === 'PickUp', notes: cart.notes, onSubmit: async ({ notes }) => {
123
+ }, options: fulfillmentMethodOptions || {}, variant: "solid" }) }) }) })), hasBillToAddress ? (jsx(ReadOnlyAddresses, { billTo: cart.billTo, isLoading: isPatching, isPickup: isPickup, notes: cart.notes, onSubmit: async ({ notes }) => {
122
124
  if (!cart.billTo)
123
125
  return;
124
126
  await patchCartAndBillingAddress({ cart, notes });
125
- navigate(REVIEW_AND_SUBMIT_PATH);
126
- }, shipTo: cart.shipTo })) : (jsx(EditAddresses, { countries: countries || [], isLoading: isPatching, isPickup: cart.fulfillmentMethod === 'PickUp', onSubmit: async ({ address, notes }) => {
127
+ }, shipTo: cart.shipTo })) : (jsx(EditAddresses, { countries: countries || [], isLoading: isPatching, isPickup: isPickup, onSubmit: async ({ address, notes }) => {
127
128
  if (!cart.billTo)
128
129
  return;
129
130
  await patchCartAndBillingAddress({
@@ -131,7 +132,6 @@ function ShippingPage() {
131
132
  cart,
132
133
  notes,
133
134
  });
134
- navigate(REVIEW_AND_SUBMIT_PATH);
135
135
  } }))] }) }) }));
136
136
  }
137
137
 
@@ -1,4 +1,6 @@
1
- export declare function usePatchBillToAddress(): {
1
+ export declare function usePatchBillToAddress({ skipInvalidation, }?: {
2
+ skipInvalidation?: boolean;
3
+ }): {
2
4
  error: unknown;
3
5
  isError: boolean;
4
6
  isLoading: boolean;
@@ -1,10 +1,12 @@
1
1
  import { patchBillToAddress } from '../../services/customer-service.js';
2
2
  import { useAwaitableMutation } from '../../../shared/hooks/use-awaitable-mutation.js';
3
3
 
4
- function usePatchBillToAddress() {
4
+ function usePatchBillToAddress({ skipInvalidation = false, } = {}) {
5
5
  return useAwaitableMutation({
6
6
  mutationFn: patchBillToAddress,
7
7
  onComplete({ queryClient }) {
8
+ if (skipInvalidation)
9
+ return;
8
10
  queryClient.invalidateQueries({ queryKey: ['customer'] });
9
11
  queryClient.invalidateQueries({ queryKey: ['carts'] });
10
12
  queryClient.invalidateQueries({ queryKey: ['session'] });
@@ -21,7 +21,7 @@ function useDataLayer(eventOrArgs) {
21
21
  quantity: cartLine.qtyOrdered ?? 0,
22
22
  })) ?? [],
23
23
  },
24
- value: event.value ?? cart.orderSubTotal ?? 0, // TODO: Why not grandTotal?
24
+ value: event.value ?? cart.orderSubTotal ?? 0,
25
25
  };
26
26
  }
27
27
  else if (product) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonic-equipment/ui",
3
- "version": "131.0.0",
3
+ "version": "132.0.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -19,16 +19,15 @@
19
19
  "main": "dist/index.js",
20
20
  "types": "./dist/index.d.ts",
21
21
  "scripts": {
22
- "build": "rollup --config",
23
- "build:export": "pnpm generate:exports:save && rollup --config",
22
+ "build": "pnpm generate:exports:save && rollup --config",
24
23
  "rebuild": "rm -rf dist && pnpm run build",
25
24
  "build-storybook": "storybook build",
26
25
  "check-updates": "npx npm-check-updates",
27
26
  "clean": "rimraf node_modules dist",
28
27
  "generate:icons": "node src/utils/icons/import-icons.js",
29
28
  "generate:icons:watch": "nodemon src/utils/icons/import-icons.js",
30
- "generate:exports": "find ./src -type f | grep -e 'ts[x]*$' | grep -vE \"stories|icons|exports|.model.ts|test|local-storage|src/index|\\.d\\.ts$\" | sed -e \"s/\\.ts[x]*//\" | sed -e \"s/.\\/src/./\" | awk '{ print \"export * from \\\"\" $1 \"\\\"\" }' | sed -e \"s/\\\"/'/g\" | sort",
31
- "generate:exports:save": "echo '/* eslint-disable simple-import-sort/exports */' > src/exports.ts && pnpm run --silent generate:exports >> src/exports.ts",
29
+ "generate:exports": "node src/utils/generate-exports.js",
30
+ "generate:exports:save": "pnpm run --silent generate:exports > src/exports.ts",
32
31
  "reinstall": "pnpm clean; pnpm install && pnpm build",
33
32
  "dev": "storybook dev -p 6006",
34
33
  "dev:https": "storybook dev --https --ssl-cert src/proxy/server.cert --ssl-key src/proxy/server.key -p 6006",