@sonic-equipment/ui 202.0.0 → 203.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.
Files changed (56) hide show
  1. package/dist/breadcrumbs/breadcrumb.js +7 -16
  2. package/dist/breadcrumbs/breadcrumb.module.css.js +1 -1
  3. package/dist/buttons/add-to-cart-button/add-to-cart-button.d.ts +1 -1
  4. package/dist/buttons/add-to-cart-button/add-to-cart-button.js +12 -13
  5. package/dist/collapsables/accordion/accordion-item.js +6 -5
  6. package/dist/collapsables/accordion/accordion.module.css.js +1 -1
  7. package/dist/cookies/client-cookie-context.d.ts +3 -0
  8. package/dist/cookies/client-cookie-context.js +30 -0
  9. package/dist/cookies/cookie-context.d.ts +2 -0
  10. package/dist/cookies/cookie-context.js +6 -0
  11. package/dist/cookies/cookie-provider.d.ts +6 -0
  12. package/dist/cookies/cookie-provider.js +10 -0
  13. package/dist/cookies/readonly-cookie-reader.d.ts +2 -0
  14. package/dist/cookies/readonly-cookie-reader.js +18 -0
  15. package/dist/cookies/types.d.ts +14 -0
  16. package/dist/cookies/types.js +7 -0
  17. package/dist/{shared/hooks → cookies}/use-cookie.d.ts +1 -2
  18. package/dist/cookies/use-cookie.js +30 -0
  19. package/dist/country-select/hooks/use-countries.js +1 -1
  20. package/dist/country-selector/connected-country-selector.js +1 -1
  21. package/dist/country-selector/use-countries-languages.js +3 -11
  22. package/dist/exports.d.ts +6 -1
  23. package/dist/header/buttons/account/connected-account-button.js +1 -8
  24. package/dist/index.js +7 -2
  25. package/dist/media/image-grid/images-grid.d.ts +2 -1
  26. package/dist/media/image-grid/images-grid.js +2 -2
  27. package/dist/media/image-lightbox/image-lightbox.d.ts +2 -1
  28. package/dist/media/image-lightbox/image-lightbox.js +2 -2
  29. package/dist/pages/account/components/create-account-form/create-account-form.js +1 -1
  30. package/dist/pages/account/create-account-page/create-account-page.js +5 -3
  31. package/dist/pages/account/layouts/sign-in-page-layout/sign-in-page-layout.d.ts +2 -1
  32. package/dist/pages/account/layouts/sign-in-page-layout/sign-in-page-layout.js +2 -4
  33. package/dist/pages/account/sign-in-page/sign-in-page.js +2 -2
  34. package/dist/pages/checkout/cart-page/cart-page.js +1 -1
  35. package/dist/pages/checkout/order-confirmation-page/order-confirmation-page-content.js +1 -1
  36. package/dist/pages/my-sonic/widgets/connected-bill-to-address-widget.js +2 -1
  37. package/dist/pages/my-sonic/widgets/connected-ship-to-address-widget.js +2 -1
  38. package/dist/pages/product/product-details-page/components/product-details-images/product-detail-images.js +1 -3
  39. package/dist/pages/product/product-details-page/components/product-details-images/product-detail-images.module.css.js +1 -1
  40. package/dist/pages/product/product-details-page/product-details.js +5 -1
  41. package/dist/pages/product/product-listing-page/no-results/no-results.js +3 -1
  42. package/dist/shared/api/storefront/hooks/cart/use-fetch-current-cart-with-atp.js +6 -4
  43. package/dist/shared/api/storefront/hooks/product/use-mark-product-as-viewed.d.ts +5 -3
  44. package/dist/shared/api/storefront/hooks/product/use-mark-product-as-viewed.js +4 -8
  45. package/dist/shared/api/storefront/services/website-service.js +9 -0
  46. package/dist/shared/hooks/use-debounced-callback.d.ts +5 -1
  47. package/dist/shared/hooks/use-debounced-callback.js +10 -10
  48. package/dist/shared/providers/react-query-container.d.ts +8 -3
  49. package/dist/shared/providers/react-query-container.js +4 -4
  50. package/dist/shared/routing/route-provider.d.ts +1 -0
  51. package/dist/shared/routing/types.d.ts +1 -0
  52. package/dist/shared/routing/use-location.js +2 -2
  53. package/dist/sidebar/sidebar-provider.js +0 -2
  54. package/dist/styles.css +31 -1
  55. package/package.json +1 -1
  56. package/dist/shared/hooks/use-cookie.js +0 -34
@@ -5,32 +5,23 @@ import clsx from 'clsx';
5
5
  import { Link } from '../buttons/link/link.js';
6
6
  import { GlyphsChevronsSlimLeftIcon } from '../icons/glyph/glyphs-chevrons-slim-left-icon.js';
7
7
  import { SolidHomeIcon } from '../icons/solid/solid-home-icon.js';
8
- import { useIsBreakpoint } from '../shared/hooks/use-is-breakpoint.js';
9
8
  import styles from './breadcrumb.module.css.js';
10
9
 
11
- function BreadcrumbShort({ links }) {
10
+ function Breadcrumb({ links }) {
11
+ if (links.length <= 1)
12
+ return null;
13
+ const linksWithoutFirst = links.slice(1);
12
14
  const homeLink = links[0];
13
15
  const previousLink = links.at(-2);
14
16
  const isHomeLink = previousLink === undefined || previousLink === homeLink;
15
17
  const href = previousLink?.href || homeLink?.href;
16
18
  const label = previousLink?.label || homeLink?.label;
17
- return (jsx(Breadcrumbs, { className: styles.breadcrumbs, children: jsx(Breadcrumb$1, { className: styles.breadcrumb, children: jsxs(Link, { className: styles.link, href: href, isDisabled: false, title: label, children: [jsx(GlyphsChevronsSlimLeftIcon, { className: styles.icon }), isHomeLink ? (jsx(SolidHomeIcon, { className: styles.icon })) : (jsx("span", { children: previousLink.label }))] }) }) }));
18
- }
19
- function BreadcrumbLongItem({ index, isDisabled, link, }) {
20
- return (jsx(Breadcrumb$1, { className: styles.breadcrumb, children: jsxs(Link, { className: styles.link, color: "secondary", href: link.href, isDisabled: isDisabled, title: link.label, children: [jsx(GlyphsChevronsSlimLeftIcon, { className: clsx(styles['previous-icon'], styles.icon) }), link.label] }) }, index));
21
- }
22
- function BreadcrumbLong({ links }) {
23
- const linksWithoutFirst = links.slice(1);
24
- const homeLink = links[0];
25
19
  if (!homeLink)
26
20
  return null;
27
- return (jsxs(Breadcrumbs, { className: styles.breadcrumbs, children: [jsx(Breadcrumb$1, { className: styles.breadcrumb, children: jsx(Link, { className: styles.link, href: homeLink.href, title: homeLink.label, children: jsx(SolidHomeIcon, { className: clsx(styles['home-icon'], styles.icon) }) }) }), linksWithoutFirst.map((link, index) => (jsx(BreadcrumbLongItem, { index: index, isDisabled: linksWithoutFirst.length - 1 === index, link: link }, link.href + link.label || index)))] }));
21
+ return (jsxs(Breadcrumbs, { className: styles.breadcrumbs, children: [jsx(Breadcrumb$1, { className: clsx(styles.breadcrumb, styles.short), children: jsxs(Link, { className: styles.link, href: href, isDisabled: false, title: label, children: [jsx(GlyphsChevronsSlimLeftIcon, { className: styles.icon }), isHomeLink ? (jsx(SolidHomeIcon, { className: styles.icon })) : (jsx("span", { children: previousLink.label }))] }) }), jsx(Breadcrumb$1, { className: clsx(styles.breadcrumb, styles.long), children: jsx(Link, { className: styles.link, href: homeLink.href, title: homeLink.label, children: jsx(SolidHomeIcon, { className: clsx(styles['home-icon'], styles.icon) }) }) }), linksWithoutFirst.map((link, index) => (jsx(BreadcrumbLongItem, { index: index, isDisabled: linksWithoutFirst.length - 1 === index, link: link }, link.href + link.label || index)))] }));
28
22
  }
29
- function Breadcrumb({ links }) {
30
- const isLg = useIsBreakpoint('lg');
31
- if (links.length <= 1)
32
- return null;
33
- return isLg ? BreadcrumbLong({ links }) : BreadcrumbShort({ links });
23
+ function BreadcrumbLongItem({ index, isDisabled, link, }) {
24
+ return (jsx(Breadcrumb$1, { className: clsx(styles.breadcrumb, styles.long), children: jsxs(Link, { className: styles.link, color: "secondary", href: link.href, isDisabled: isDisabled, title: link.label, children: [jsx(GlyphsChevronsSlimLeftIcon, { className: clsx(styles['previous-icon'], styles.icon) }), link.label] }) }, index));
34
25
  }
35
26
 
36
27
  export { Breadcrumb };
@@ -1,3 +1,3 @@
1
- var styles = {"breadcrumbs":"breadcrumb-module-CQGse","breadcrumb":"breadcrumb-module-hxhDY","link":"breadcrumb-module-fp2Q6","icon":"breadcrumb-module-uIn3w","previous-icon":"breadcrumb-module-K-wMJ"};
1
+ var styles = {"breadcrumbs":"breadcrumb-module-CQGse","breadcrumb":"breadcrumb-module-hxhDY","link":"breadcrumb-module-fp2Q6","icon":"breadcrumb-module-uIn3w","previous-icon":"breadcrumb-module-K-wMJ","short":"breadcrumb-module-ToeDB","long":"breadcrumb-module-np5GK"};
2
2
 
3
3
  export { styles as default };
@@ -3,5 +3,5 @@ interface AddToCartButtonProps {
3
3
  onChange: (quantity: number) => void;
4
4
  quantity: number;
5
5
  }
6
- export declare function AddToCartButton({ isDisabled, onChange, quantity, }: AddToCartButtonProps): import("react/jsx-runtime").JSX.Element;
6
+ export declare function AddToCartButton({ isDisabled, onChange: _onChange, quantity: _quantity, }: AddToCartButtonProps): import("react/jsx-runtime").JSX.Element;
7
7
  export {};
@@ -1,18 +1,16 @@
1
1
  "use client";
2
- import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
4
4
  import clsx from 'clsx';
5
5
  import { NumberField } from '../../forms/fields/number-field/number-field.js';
6
6
  import { SolidCartIcon } from '../../icons/solid/solid-cart-icon.js';
7
7
  import { useFormattedMessage } from '../../intl/use-formatted-message.js';
8
+ import { useDebouncedCallback } from '../../shared/hooks/use-debounced-callback.js';
8
9
  import { ensureNumber } from '../../shared/utils/number.js';
9
10
  import { Button } from '../button/button.js';
10
11
  import styles from './add-to-cart-button.module.css.js';
11
12
 
12
- function AddToCartButton({ isDisabled = false, onChange, quantity, }) {
13
- return (jsx(InternalAddToCartButton, { isDisabled: isDisabled, onChange: onChange, quantity: quantity }));
14
- }
15
- function InternalAddToCartButton({ isDisabled = false, onChange: _onChange, quantity: _quantity, }) {
13
+ function AddToCartButton({ isDisabled = false, onChange: _onChange, quantity: _quantity, }) {
16
14
  const [quantity, setQuantity] = useState(_quantity);
17
15
  const [currentState, setState] = useState(quantity > 0 ? 'input' : 'initial');
18
16
  const [hasFocus, setHasFocus] = useState(false);
@@ -60,7 +58,6 @@ function InitialState({ buttonRef, hasFocus, isDisabled, onAddToCart, onHasFocus
60
58
  onAddToCart();
61
59
  }, size: "md" }));
62
60
  }
63
- let debounced;
64
61
  /* input state: spinner buttons and manual input */
65
62
  function InputState({ hasFocus, inputRef, isDisabled, onChange: _onChange, onHasFocussed, quantity, }) {
66
63
  const t = useFormattedMessage();
@@ -68,6 +65,7 @@ function InputState({ hasFocus, inputRef, isDisabled, onChange: _onChange, onHas
68
65
  const [manualQuantity, setManualQuantity] = useState(null);
69
66
  const isManualInput = useMemo(() => manualQuantity !== null, [manualQuantity]);
70
67
  const isSpinnerInput = useMemo(() => manualQuantity === null, [manualQuantity]);
68
+ const onChangeDebounced = useDebouncedCallback((quantity) => _onChange(quantity), 1500);
71
69
  useEffect(() => {
72
70
  if (hasFocus) {
73
71
  inputRef?.current?.focus();
@@ -78,28 +76,29 @@ function InputState({ hasFocus, inputRef, isDisabled, onChange: _onChange, onHas
78
76
  useEffect(() => {
79
77
  // clear debounced when switching to manual input or when quantity is 0 (returning to initial state)
80
78
  if (isManualInput)
81
- clearTimeout(debounced);
82
- }, [isManualInput]);
79
+ onChangeDebounced.cancel();
80
+ }, [isManualInput, onChangeDebounced]);
83
81
  const onKeyUp = (e) => {
84
82
  if (isSpinnerInput)
85
83
  return;
86
84
  if (e.key === 'Enter')
87
85
  confirmManualQuantity();
88
86
  if (e.key === 'Escape') {
87
+ /* returns to spinner input and restarts a debounce timer for the internalQuantity on the field */
89
88
  setManualQuantity(null);
90
- setInternalQuantity(quantity);
89
+ onChangeDebounced(internalQuantity);
91
90
  }
92
91
  };
93
92
  const onSpinnerChange = (quantity) => {
94
- clearTimeout(debounced);
93
+ if (Number.isNaN(quantity))
94
+ return;
95
95
  setInternalQuantity(quantity);
96
96
  if (quantity === 0) {
97
+ onChangeDebounced.cancel();
97
98
  _onChange(0);
98
99
  }
99
100
  else {
100
- debounced = setTimeout(() => {
101
- _onChange(quantity);
102
- }, 1500);
101
+ onChangeDebounced(quantity);
103
102
  }
104
103
  };
105
104
  const onManualInput = (e) => {
@@ -20,14 +20,15 @@ function AccordionItem({ _pseudo = 'none', allowCollapse = true, allowToggle = t
20
20
  }
21
21
  }, [close, initialIsOpen, open]);
22
22
  const panelId = `panel-${id}`;
23
- return (jsxs("div", { className: clsx(className, ...ensureArray(borderType).map(type => styles[`border-type-${type}`]), styles['accordion-item'], isDisabled && styles.disabled, {
23
+ return (jsxs("div", { suppressHydrationWarning: true, className: clsx(className, ...ensureArray(borderType).map(type => styles[`border-type-${type}`]), styles['accordion-item'], isDisabled && styles.disabled, {
24
24
  [styles['is-open']]: isOpen,
25
- [styles['allow-collapse']]: allowCollapse,
26
25
  [styles['allow-toggle']]: allowCollapse && allowToggle,
27
- }), "data-test-selector": dataTestSelector, children: [jsx(Heading, { className: styles.heading, tag: "h3", children: allowCollapse ? (jsxs("button", { "aria-controls": panelId, "aria-expanded": isOpen, className: clsx(styles.button, styles[_pseudo]), disabled: isDisabled, id: id, onClick: () => {
28
- if (allowToggle)
26
+ }), "data-test-selector": dataTestSelector, children: [jsx(Heading, { className: styles.heading, tag: "h3", children: jsxs("button", { suppressHydrationWarning: true, "aria-controls": panelId, "aria-expanded": isOpen, className: clsx(styles.button, styles[_pseudo], {
27
+ [styles['disable-collapse']]: !allowCollapse,
28
+ }), disabled: isDisabled, id: id, onClick: () => {
29
+ if (allowToggle && allowCollapse)
29
30
  toggle();
30
- }, tabIndex: allowToggle ? 0 : -1, type: "button", children: [title, jsx("span", { className: styles.icon, children: size === 'lg' ? (jsx(GlyphsChevronsBoldDownIcon, {})) : (jsx(GlyphsChevronsSlimDownIcon, {})) })] })) : (jsx("span", { className: styles.button, children: title })) }), jsx("div", { "aria-labelledby": id, className: styles.panel, id: panelId, role: "region", children: jsx("div", { className: styles.content, children: children }) })] }));
31
+ }, tabIndex: allowToggle ? 0 : -1, type: "button", children: [title, jsx("span", { className: styles.icon, children: size === 'lg' ? (jsx(GlyphsChevronsBoldDownIcon, {})) : (jsx(GlyphsChevronsSlimDownIcon, {})) })] }) }), jsx("div", { "aria-labelledby": id, className: styles.panel, id: panelId, role: "region", children: jsx("div", { className: styles.content, children: children }) })] }));
31
32
  }
32
33
 
33
34
  export { AccordionItem };
@@ -1,3 +1,3 @@
1
- var styles = {"accordion":"accordion-module-9WvAH","indented":"accordion-module-6CcEH","white":"accordion-module-CaVdG","accordion-item":"accordion-module-lf9d-","lg":"accordion-module-0qnae","with-seperators":"accordion-module-yOLrW","heading":"accordion-module-d-B4T","button":"accordion-module--Rwpb","icon":"accordion-module-Y50uq","focus":"accordion-module-M4BZs","allow-toggle":"accordion-module-QEO2d","panel":"accordion-module-KZjMo","content":"accordion-module-ejMH3","border-type-bottom":"accordion-module-oTdZK","border-type-top":"accordion-module-0mrLq","border-type-middle":"accordion-module-aAr-R","is-open":"accordion-module-W0F1z","border-type-middle-accentuated":"accordion-module-OB98a","select":"accordion-module-SAbiG","disabled":"accordion-module-ogvYX"};
1
+ var styles = {"accordion":"accordion-module-9WvAH","indented":"accordion-module-6CcEH","white":"accordion-module-CaVdG","accordion-item":"accordion-module-lf9d-","lg":"accordion-module-0qnae","with-seperators":"accordion-module-yOLrW","heading":"accordion-module-d-B4T","button":"accordion-module--Rwpb","icon":"accordion-module-Y50uq","focus":"accordion-module-M4BZs","allow-toggle":"accordion-module-QEO2d","panel":"accordion-module-KZjMo","content":"accordion-module-ejMH3","border-type-bottom":"accordion-module-oTdZK","border-type-top":"accordion-module-0mrLq","border-type-middle":"accordion-module-aAr-R","is-open":"accordion-module-W0F1z","border-type-middle-accentuated":"accordion-module-OB98a","disable-collapse":"accordion-module-RnNRT","select":"accordion-module-SAbiG","disabled":"accordion-module-ogvYX"};
2
2
 
3
3
  export { styles as default };
@@ -0,0 +1,3 @@
1
+ import { CookieAttributes, CookieGetterSetter } from './types';
2
+ export declare const defaultCookieOptions: CookieAttributes;
3
+ export declare const clientCookieContextValue: CookieGetterSetter;
@@ -0,0 +1,30 @@
1
+ import Cookies from 'js-cookie';
2
+ import { config } from '../config.js';
3
+ import { TIME } from '../shared/utils/time.js';
4
+
5
+ const defaultCookieOptions = {
6
+ domain: config.COOKIE_DOMAIN,
7
+ expires: new Date(Date.now() + TIME.DAY * 356), // 365,
8
+ path: '/',
9
+ sameSite: 'none',
10
+ secure: true,
11
+ };
12
+ function get(name) {
13
+ if (name)
14
+ return Cookies.get(name);
15
+ return Cookies.get();
16
+ }
17
+ function set(name, value, options) {
18
+ if (value) {
19
+ Cookies.set(name, value, options);
20
+ }
21
+ else {
22
+ Cookies.remove(name);
23
+ }
24
+ }
25
+ const clientCookieContextValue = {
26
+ get,
27
+ set,
28
+ };
29
+
30
+ export { clientCookieContextValue, defaultCookieOptions };
@@ -0,0 +1,2 @@
1
+ import { CookieContextValue } from './types';
2
+ export declare const CookieContext: React.Context<CookieContextValue | undefined>;
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { createContext } from 'react';
3
+
4
+ const CookieContext = createContext(undefined);
5
+
6
+ export { CookieContext };
@@ -0,0 +1,6 @@
1
+ import { ReactNode } from 'react';
2
+ import { CookieContextValue } from './types';
3
+ export declare function CookieProvider({ children, value, }: {
4
+ children: ReactNode;
5
+ value: CookieContextValue;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { clientCookieContextValue } from './client-cookie-context.js';
4
+ import { CookieContext } from './cookie-context.js';
5
+
6
+ function CookieProvider({ children, value = clientCookieContextValue, }) {
7
+ return (jsx(CookieContext.Provider, { value: value, children: children }));
8
+ }
9
+
10
+ export { CookieProvider };
@@ -0,0 +1,2 @@
1
+ import { CookieGetterSetter } from './types';
2
+ export declare function createReadonlyCookieReader(cookies: Record<string, string>): CookieGetterSetter;
@@ -0,0 +1,18 @@
1
+ import { logger } from '../logging/logger.js';
2
+
3
+ function set(name, value, _options) {
4
+ logger.warn('Attempted to set a cookie on a readonly cookie reader', {
5
+ name,
6
+ value,
7
+ });
8
+ }
9
+ function createReadonlyCookieReader(cookies) {
10
+ function get(name) {
11
+ if (name)
12
+ return cookies[name];
13
+ return cookies;
14
+ }
15
+ return { get, set };
16
+ }
17
+
18
+ export { createReadonlyCookieReader };
@@ -0,0 +1,14 @@
1
+ export interface CookieAttributes {
2
+ domain?: string | undefined;
3
+ expires?: Date | undefined;
4
+ path?: string | undefined;
5
+ sameSite?: 'lax' | 'strict' | 'none' | undefined;
6
+ secure?: boolean | undefined;
7
+ }
8
+ export interface CookieGetterSetter {
9
+ get(): Record<string, string> | undefined;
10
+ get(name: string): string | undefined;
11
+ set(name: string, value: string | undefined, options?: CookieAttributes): void;
12
+ }
13
+ export type CookieContextValue = CookieGetterSetter | Record<string, string>;
14
+ export declare function isCookieGetterSetter(value: CookieContextValue): value is CookieGetterSetter;
@@ -0,0 +1,7 @@
1
+ function isCookieGetterSetter(value) {
2
+ return (typeof value.get === 'function' &&
3
+ typeof value.set === 'function' &&
4
+ Object.keys(value).length === 2);
5
+ }
6
+
7
+ export { isCookieGetterSetter };
@@ -1,5 +1,4 @@
1
- import { CookieAttributes } from 'js-cookie';
2
- export declare const defaultCookieOptions: CookieAttributes;
1
+ import { CookieAttributes } from './types';
3
2
  interface SetValueFn {
4
3
  (value: string | undefined): void;
5
4
  (fn: (value: string | undefined) => string | undefined): void;
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { useContext, useState } from 'react';
3
+ import { defaultCookieOptions, clientCookieContextValue } from './client-cookie-context.js';
4
+ import { CookieContext } from './cookie-context.js';
5
+ import { createReadonlyCookieReader } from './readonly-cookie-reader.js';
6
+ import { isCookieGetterSetter } from './types.js';
7
+
8
+ function useCookie(name, options = defaultCookieOptions) {
9
+ const context = useContext(CookieContext);
10
+ const Cookies = context
11
+ ? isCookieGetterSetter(context)
12
+ ? context
13
+ : createReadonlyCookieReader(context)
14
+ : clientCookieContextValue;
15
+ const cookieValue = Cookies.get()?.[name];
16
+ const [stateValue, setStateValue] = useState(cookieValue);
17
+ if (cookieValue !== stateValue) {
18
+ setStateValue(cookieValue);
19
+ }
20
+ function setValue(valueOrFn) {
21
+ setStateValue(oldValue => {
22
+ const newValue = typeof valueOrFn === 'function' ? valueOrFn(oldValue) : valueOrFn;
23
+ Cookies.set(name, newValue, options);
24
+ return newValue;
25
+ });
26
+ }
27
+ return [stateValue, setValue];
28
+ }
29
+
30
+ export { useCookie };
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
+ import { useCookie } from '../../cookies/use-cookie.js';
2
3
  import { isCountryCode, isCultureCode, isLanguageCode } from '../../intl/types.js';
3
4
  import { useFetchCountriesWithLanguages } from '../../shared/api/storefront/hooks/website/use-fetch-countries-with-languages.js';
4
- import { useCookie } from '../../shared/hooks/use-cookie.js';
5
5
  import { useSessionStorage } from '../../shared/hooks/use-session-storage.js';
6
6
 
7
7
  function useCountries({ enabled = true, } = {}) {
@@ -38,7 +38,7 @@ function ConnectedCountrySelector({ defaultCountryCode, defaultLanguageCode, onC
38
38
  logger.error('Error fetching countries and languages:', error);
39
39
  return null;
40
40
  }
41
- return (jsxs(Fragment, { children: [jsx(CountrySelectorTrigger, { onClick: open, selectedCountry: selectedCountry, selectedLanguage: selectedLanguage, showCountry: showCountry }), jsx(CountrySelectorDialog, { countries: countries, isDismissable: isDismissable, isOpen: isOpen, onOpenChange: open => {
41
+ return (jsxs(Fragment, { children: [jsx(CountrySelectorTrigger, { onClick: open, selectedCountry: selectedCountry || defaultCountry, selectedLanguage: selectedLanguage || defaultLanguage, showCountry: showCountry }), jsx(CountrySelectorDialog, { countries: countries, isDismissable: isDismissable, isOpen: isOpen, onOpenChange: open => {
42
42
  if (!isDismissable)
43
43
  return;
44
44
  setIsOpen(open);
@@ -1,24 +1,16 @@
1
1
  "use client";
2
2
  import { useEffect } from 'react';
3
- import { config } from '../config.js';
3
+ import { useCookie } from '../cookies/use-cookie.js';
4
4
  import { useFetchCountriesWithLanguages } from '../shared/api/storefront/hooks/website/use-fetch-countries-with-languages.js';
5
5
  import { updateLocale } from '../shared/api/storefront/services/website-service.js';
6
- import { useCookie } from '../shared/hooks/use-cookie.js';
7
6
  import { useSessionStorage } from '../shared/hooks/use-session-storage.js';
8
7
  import { isCountry } from '../shared/model/countries-languages.js';
9
8
 
10
- const cookieOptions = {
11
- domain: config.COOKIE_DOMAIN,
12
- expires: 365,
13
- path: '/',
14
- sameSite: 'None',
15
- secure: true,
16
- };
17
9
  function useCountriesLanguages({ defaultCountryCode, defaultLanguageCode, }) {
18
10
  const [sessionCountries, setSessionCountries] = useSessionStorage('countries-v1');
19
- const [currentCountryId] = useCookie('CurrentCountryId', cookieOptions);
11
+ const [currentCountryId] = useCookie('CurrentCountryId');
20
12
  // const [currentLanguageId] = ['be71b608-4876-420a-b1f5-adfe0093a72a'] // useCookie('CurrentLanguageId', cookieOptions)
21
- const [currentLanguageId] = useCookie('CurrentLanguageId', cookieOptions);
13
+ const [currentLanguageId] = useCookie('CurrentLanguageId');
22
14
  const hasSessionCountries = Boolean(sessionCountries?.length);
23
15
  const { data: apiCountries, error, isFetching, } = useFetchCountriesWithLanguages({
24
16
  enabled: !hasSessionCountries,
package/dist/exports.d.ts CHANGED
@@ -72,6 +72,12 @@ export * from './collapsables/show-all/show-all';
72
72
  export * from './collapsables/unmounter/unmounter';
73
73
  export * from './collapsables/unmounter/utils';
74
74
  export * from './config';
75
+ export * from './cookies/client-cookie-context';
76
+ export * from './cookies/cookie-context';
77
+ export * from './cookies/cookie-provider';
78
+ export * from './cookies/readonly-cookie-reader';
79
+ export * from './cookies/types';
80
+ export * from './cookies/use-cookie';
75
81
  export * from './country-select/country-select';
76
82
  export * from './country-select/hooks/use-countries';
77
83
  export * from './country-selector/connected-country-selector';
@@ -374,7 +380,6 @@ export * from './shared/ga/google-analytics-provider';
374
380
  export * from './shared/ga/types';
375
381
  export * from './shared/ga/use-data-layer';
376
382
  export * from './shared/hooks/use-breakpoint';
377
- export * from './shared/hooks/use-cookie';
378
383
  export * from './shared/hooks/use-cookiebot';
379
384
  export * from './shared/hooks/use-css-link';
380
385
  export * from './shared/hooks/use-debounced-callback';
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
  import { jsx } from 'react/jsx-runtime';
3
- import { useMemo } from 'react';
4
3
  import { IconButton } from '../../../buttons/icon-button/icon-button.js';
5
4
  import { useFormattedMessage } from '../../../intl/use-formatted-message.js';
6
5
  import { AccountIcon } from '../../../navigation/account-icon/account-icon.js';
@@ -12,13 +11,7 @@ function ConnectedAccountButton({ className, 'data-test-selector': dataTestSelec
12
11
  const paths = usePaths();
13
12
  const t = useFormattedMessage();
14
13
  const isAuthenticated = useIsAuthenticated();
15
- const _location = useLocation();
16
- const href = useMemo(() =>
17
- // TODO: Replace with relative URL when migrated away from Umbraco
18
- typeof location === 'undefined' ? undefined : location.href,
19
- // Using the location hook to detect url changes, but not using it directly
20
- // eslint-disable-next-line react-hooks/exhaustive-deps
21
- [_location]);
14
+ const { href } = useLocation();
22
15
  return (jsx(IconButton, { className: className, "data-authenticated": isAuthenticated ? true : false, "data-test-selector": dataTestSelector, href: isAuthenticated
23
16
  ? paths.ACCOUNT
24
17
  : `${paths.SIGN_IN}${href ? `?returnUrl=${encodeURIComponent(href)}` : ''}`, onClick: onClick, children: jsx(AccountIcon, { "aria-label": isAuthenticated ? t('My Sonic') : t('Sign in or create account'), isAuthenticated: isAuthenticated }) }));
package/dist/index.js CHANGED
@@ -76,6 +76,12 @@ export { ShowAll } from './collapsables/show-all/show-all.js';
76
76
  export { Unmounter, UnmounterContext, useUnmount } from './collapsables/unmounter/unmounter.js';
77
77
  export { createAddEndListener } from './collapsables/unmounter/utils.js';
78
78
  export { config, configPerEnvironment } from './config.js';
79
+ export { clientCookieContextValue, defaultCookieOptions } from './cookies/client-cookie-context.js';
80
+ export { CookieContext } from './cookies/cookie-context.js';
81
+ export { CookieProvider } from './cookies/cookie-provider.js';
82
+ export { createReadonlyCookieReader } from './cookies/readonly-cookie-reader.js';
83
+ export { isCookieGetterSetter } from './cookies/types.js';
84
+ export { useCookie } from './cookies/use-cookie.js';
79
85
  export { CountrySelect } from './country-select/country-select.js';
80
86
  export { useCountries } from './country-select/hooks/use-countries.js';
81
87
  export { ConnectedCountrySelector } from './country-selector/connected-country-selector.js';
@@ -375,7 +381,6 @@ export { GoogleAnalyticsProvider, InitializeGoogleAnalyticsProvider, useGoogleAn
375
381
  export { isGAEvent } from './shared/ga/types.js';
376
382
  export { useDataLayer } from './shared/ga/use-data-layer.js';
377
383
  export { useBreakpoint } from './shared/hooks/use-breakpoint.js';
378
- export { defaultCookieOptions, useCookie } from './shared/hooks/use-cookie.js';
379
384
  export { useCookiebot } from './shared/hooks/use-cookiebot.js';
380
385
  export { useCSSLink } from './shared/hooks/use-css-link.js';
381
386
  export { useDebouncedCallback } from './shared/hooks/use-debounced-callback.js';
@@ -403,7 +408,7 @@ export { isResponsiveImage } from './shared/model/image.js';
403
408
  export { CartProvider, useCartEvents } from './shared/providers/cart-provider.js';
404
409
  export { FavoriteProvider, useFavorite, useFavoriteProduct } from './shared/providers/favorite-provider.js';
405
410
  export { GlobalStateProvider, GlobalStateProviderContext, useGlobalState } from './shared/providers/global-state-provider.js';
406
- export { ReactQueryContainer } from './shared/providers/react-query-container.js';
411
+ export { ReactQueryContainer, globalQueryClient } from './shared/providers/react-query-container.js';
407
412
  export { RouteContext } from './shared/routing/route-context.js';
408
413
  export { RouteProvider } from './shared/routing/route-provider.js';
409
414
  export { buildHref } from './shared/routing/route-utils.js';
@@ -1,7 +1,8 @@
1
1
  import { ImageType } from '../../shared/model/image';
2
2
  interface ImagesGridProps {
3
+ className?: string;
3
4
  images: ImageType[];
4
5
  onSelectImage?: (image: ImageType, index: number) => void;
5
6
  }
6
- export declare function ImagesGrid({ images, onSelectImage }: ImagesGridProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function ImagesGrid({ className, images, onSelectImage, }: ImagesGridProps): import("react/jsx-runtime").JSX.Element;
7
8
  export {};
@@ -11,7 +11,7 @@ const mainImagePosition = {
11
11
  4: 0,
12
12
  5: 2,
13
13
  };
14
- function ImagesGrid({ images, onSelectImage }) {
14
+ function ImagesGrid({ className, images, onSelectImage, }) {
15
15
  const positionIndex = mainImagePosition[images.length] || 0;
16
16
  const imageList = [
17
17
  ...images.slice(1, positionIndex + 1),
@@ -24,7 +24,7 @@ function ImagesGrid({ images, onSelectImage }) {
24
24
  onSelectImage?.(image, images.indexOf(image));
25
25
  }
26
26
  }
27
- return (jsx("div", { className: styles['images-grid'], "data-count": images.length, children: imageList.map((image, index) => image && (jsx("div", { "aria-label": `Open image ${index}`, className: clsx(styles['grid-item'], {
27
+ return (jsx("div", { className: clsx(styles['images-grid'], className), "data-count": images.length, children: imageList.map((image, index) => image && (jsx("div", { "aria-label": `Open image ${index}`, className: clsx(styles['grid-item'], {
28
28
  [styles.clickable]: Boolean(onSelectImage),
29
29
  }), onClick: () => onSelectImage?.(image, index), onKeyDown: event => handleKeydown(event, image), role: "button", tabIndex: 0, children: jsx(Image, { className: styles.image, fit: "contain", image: image, title: image.altText }) }, index))) }));
30
30
  }
@@ -1,6 +1,7 @@
1
1
  import 'swiper/css';
2
2
  import { ImageType } from '../../shared/model/image';
3
3
  export interface ImageLightboxProps {
4
+ className?: string;
4
5
  images: ImageType[];
5
6
  initialSelectedIndex?: number;
6
7
  onZoom?: (args: {
@@ -9,4 +10,4 @@ export interface ImageLightboxProps {
9
10
  }) => void;
10
11
  variant?: 'sm' | 'lg';
11
12
  }
12
- export declare function ImageLightbox({ images, initialSelectedIndex, onZoom, variant, }: ImageLightboxProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function ImageLightbox({ className, images, initialSelectedIndex, onZoom, variant, }: ImageLightboxProps): import("react/jsx-runtime").JSX.Element;
@@ -10,7 +10,7 @@ import { CarouselPagination } from '../../carousel/pagination/pagination.js';
10
10
  import { Image } from '../image/image.js';
11
11
  import styles from './image-lightbox.module.css.js';
12
12
 
13
- function ImageLightbox({ images, initialSelectedIndex = 0, onZoom, variant = 'sm', }) {
13
+ function ImageLightbox({ className, images, initialSelectedIndex = 0, onZoom, variant = 'sm', }) {
14
14
  const [thumbsSwiper, setThumbsSwiper] = useState();
15
15
  const [currentIndex, setCurrentIndex] = useState(initialSelectedIndex);
16
16
  const nextEl = useRef(null);
@@ -26,7 +26,7 @@ function ImageLightbox({ images, initialSelectedIndex = 0, onZoom, variant = 'sm
26
26
  const scrollPercentage = clientY / height;
27
27
  onZoom?.({ index: currentIndex, scrollPercentage });
28
28
  }
29
- return (jsxs("div", { className: clsx(styles['image-lightbox'], styles[variant]), children: [jsx(Swiper, { watchSlidesProgress: true, className: styles['thumbs-swiper'], direction: variant === 'sm' ? 'horizontal' : 'vertical', modules: [Thumb], onSwiper: swiper => setThumbsSwiper(swiper), slidesPerView: "auto", spaceBetween: 8, children: images.map((image, index) => (
29
+ return (jsxs("div", { className: clsx(styles['image-lightbox'], styles[variant], className), children: [jsx(Swiper, { watchSlidesProgress: true, className: styles['thumbs-swiper'], direction: variant === 'sm' ? 'horizontal' : 'vertical', modules: [Thumb], onSwiper: swiper => setThumbsSwiper(swiper), slidesPerView: "auto", spaceBetween: 8, children: images.map((image, index) => (
30
30
  // eslint-disable-next-line @eslint-react/no-array-index-key
31
31
  jsx(SwiperSlide, { className: styles.slide, children: jsx("div", { className: styles.thumb, children: jsx(Image, { className: styles.image, fit: "contain", height: 80, image: {
32
32
  1: image['1'],
@@ -62,7 +62,7 @@ function CreateAccountForm({ errorType, isDisabled: _isDisabled = false, isPendi
62
62
  });
63
63
  };
64
64
  // form header
65
- const header = (jsx(Heading, { "data-test-selector": "PageTitle", italic: true, size: "m", tag: "h1", uppercase: true, children: title }));
65
+ const header = (jsx(Heading, { "data-test-selector": "pageTitle", italic: true, size: "m", tag: "h1", uppercase: true, children: title }));
66
66
  // form footer
67
67
  const footer = (jsx(FormSegment, { children: jsx(Button, { "data-test-selector": "createAccount_createButton", isDisabled: isDisabled, isLoading: isPendingCreateAccount && 'Creating account...', type: "submit", withArrow: true, children: t('create account') }) }));
68
68
  return (jsxs(Form, { "aria-label": title, autoComplete: true, className: styles['create-account-form'], errorMessage: errorType && errorMessages[errorType], footer: footer, header: header, onSubmit: handleSubmit, title: title, children: [jsxs(FormSegmentGroup, { children: [jsx(FormSegment, { children: jsx(TextField, { autoComplete: "username", "data-test-selector": "createAccount_email", inputMode: "email", isDisabled: isDisabled, isRequired: true, label: t('Email'), name: "email", type: "email", validate: value => {
@@ -20,8 +20,10 @@ function CreateAccountPage({ returnUrl } = {}) {
20
20
  const { error: errorCreateAccount, isPending: isPendingCreateAccount, isSuccess, mutate: createAccount, } = useCreateAccount();
21
21
  const isExistingAccount = errorCreateAccount instanceof ExistingAccountError;
22
22
  const isDisabled = isSuccess || isExistingAccount;
23
- const continuePath = returnUrl && returnUrl !== paths.ACCOUNT_CREATE ? returnUrl : paths.ACCOUNT;
24
- const isReturnToShipping = returnUrl === paths.CHECKOUT_SHIPPING;
23
+ const continuePath = returnUrl && decodeURIComponent(returnUrl) !== paths.ACCOUNT_CREATE
24
+ ? decodeURIComponent(returnUrl)
25
+ : paths.ACCOUNT;
26
+ const isReturnToShipping = returnUrl && decodeURIComponent(returnUrl) === paths.CHECKOUT_SHIPPING;
25
27
  const onSubmit = ({ data }) => {
26
28
  createAccount(data, {
27
29
  onSuccess() {
@@ -40,7 +42,7 @@ function CreateAccountPage({ returnUrl } = {}) {
40
42
  navigate(continuePath, { reload: true });
41
43
  return;
42
44
  }
43
- return (jsxs(Fragment, { children: [jsx(SignInPageLayout, { fullHeight: true, children: jsx(CreateAccountForm, { errorType: errorType, isDisabled: isDisabled, isPendingCreateAccount: isPendingCreateAccount, onSubmit: onSubmit }) }), jsx(Dialog, { footer: jsx(Button, { color: "primary", href: continuePath, route: { reload: true }, size: "md", withArrow: true, children: jsx(FormattedMessage, { id: "Continue" }) }), hasCloseButton: false, isDismissable: false, isKeyboardDismissDisabled: true, isOpen: isSuccess && !isReturnToShipping, title: "Account created", children: jsx("p", { children: jsx(FormattedMessage, { id: "Your new Sonic Equipment account was succesfully created. You should receive an email soon with further instructions on how to activate this account. If you do not receive this email, please contact Customer Support." }) }) }), jsx(Dialog, { footer: jsx(Button, { color: "primary", href: `${paths.SIGN_IN}${returnUrl ? `?returnUrl=${continuePath}` : ''}`, route: { reload: true }, size: "md", withArrow: true, children: jsx(FormattedMessage, { id: "Continue to sign in" }) }), hasCloseButton: false, isDismissable: false, isKeyboardDismissDisabled: true, isOpen: isExistingAccount, title: "Existing account", children: jsx("p", { children: jsx(FormattedMessage, { id: "The email address you entered is already associated with an existing account. Please sign in to this account or contact Customer Support." }) }) })] }));
45
+ return (jsxs(Fragment, { children: [jsx(SignInPageLayout, { fullHeight: true, "data-test-selector": "create-account-page", children: jsx(CreateAccountForm, { errorType: errorType, isDisabled: isDisabled, isPendingCreateAccount: isPendingCreateAccount, onSubmit: onSubmit }) }), jsx(Dialog, { footer: jsx(Button, { color: "primary", href: continuePath, route: { reload: true }, size: "md", withArrow: true, children: jsx(FormattedMessage, { id: "Continue" }) }), hasCloseButton: false, isDismissable: false, isKeyboardDismissDisabled: true, isOpen: isSuccess && !isReturnToShipping, title: "Account created", children: jsx("p", { children: jsx(FormattedMessage, { id: "Your new Sonic Equipment account was succesfully created. You should receive an email soon with further instructions on how to activate this account. If you do not receive this email, please contact Customer Support." }) }) }), jsx(Dialog, { footer: jsx(Button, { color: "primary", href: `${paths.SIGN_IN}${returnUrl ? `?returnUrl=${continuePath}` : ''}`, route: { reload: true }, size: "md", withArrow: true, children: jsx(FormattedMessage, { id: "Continue to sign in" }) }), hasCloseButton: false, isDismissable: false, isKeyboardDismissDisabled: true, isOpen: isExistingAccount, title: "Existing account", children: jsx("p", { children: jsx(FormattedMessage, { id: "The email address you entered is already associated with an existing account. Please sign in to this account or contact Customer Support." }) }) })] }));
44
46
  }
45
47
 
46
48
  export { CreateAccountPage };
@@ -2,7 +2,8 @@ import { ReactNode } from 'react';
2
2
  import { ImageType } from '../../../../shared/model/image';
3
3
  export interface SignInPageLayoutProps {
4
4
  children?: ReactNode;
5
+ 'data-test-selector': string;
5
6
  fullHeight?: boolean;
6
7
  image?: ImageType;
7
8
  }
8
- export declare function SignInPageLayout({ children, fullHeight, image, }: SignInPageLayoutProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function SignInPageLayout({ children, 'data-test-selector': dataTestSelector, fullHeight, image, }: SignInPageLayoutProps): import("react/jsx-runtime").JSX.Element;
@@ -1,14 +1,12 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import clsx from 'clsx';
4
- import { useIsBreakpoint } from '../../../../shared/hooks/use-is-breakpoint.js';
5
4
  import { Image } from '../../../../media/image/image.js';
6
5
  import { SIGN_IN_PAGE_BACKGROUND_IMAGE } from './sign-in-page-background-image.js';
7
6
  import styles from './sign-in-page-layout.module.css.js';
8
7
 
9
- function SignInPageLayout({ children, fullHeight, image = SIGN_IN_PAGE_BACKGROUND_IMAGE, }) {
10
- const isLg = useIsBreakpoint('lg');
11
- return (jsxs("div", { className: clsx(styles['sign-in-page-layout'], fullHeight && styles['full-height']), children: [jsx("section", { className: styles.main, children: children }), isLg && (jsx("div", { className: styles.side, children: jsx("div", { className: styles.image, children: jsx(Image, { image: image, title: "" }) }) }))] }));
8
+ function SignInPageLayout({ children, 'data-test-selector': dataTestSelector, fullHeight, image = SIGN_IN_PAGE_BACKGROUND_IMAGE, }) {
9
+ return (jsxs("div", { className: clsx(styles['sign-in-page-layout'], fullHeight && styles['full-height']), "data-test-selector": dataTestSelector, children: [jsx("section", { className: styles.main, children: children }), jsx("div", { className: styles.side, children: jsx("div", { className: styles.image, children: jsx(Image, { image: image, title: "" }) }) })] }));
12
10
  }
13
11
 
14
12
  export { SignInPageLayout };
@@ -37,7 +37,7 @@ function SignInPage({ returnUrl } = {}) {
37
37
  navigate(returnUrl || paths.HOME, { reload: true });
38
38
  };
39
39
  const allowGuestSignIn = returnUrl === paths.CHECKOUT_SHIPPING;
40
- const createAccountPath = `${paths.ACCOUNT_CREATE}${returnUrl ? `?returnUrl=${returnUrl}` : ''}`;
40
+ const createAccountPath = `${paths.ACCOUNT_CREATE}${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ''}`;
41
41
  const onSubmit = ({ data }) => {
42
42
  resetSignIn();
43
43
  resetCreateGuest();
@@ -57,7 +57,7 @@ function SignInPage({ returnUrl } = {}) {
57
57
  const onRecoverPasswordDialogOpen = () => {
58
58
  setRecoverPasswordDialogOpen(true);
59
59
  };
60
- return (jsxs(Fragment, { children: [jsx(SignInPageLayout, { fullHeight: true, children: jsx(SignInForm, { allowGuestSignIn: allowGuestSignIn, createAccountPath: createAccountPath, errorType: errorType, initialEmail: session?.isGuest ? '' : session?.email, initialRememberMe: session?.rememberMe, isDisabled: !session || isSuccess, isLoading: isLoading, isPendingGuestSignIn: isPendingCreateGuest, isPendingUserSignIn: isPendingSignIn, onRecoverPasswordDialogOpen: onRecoverPasswordDialogOpen, onSubmit: onSubmit }) }), jsx(RecoverPasswordDialog, { isOpen: isRecoverPasswordDialogOpen, onOpenChange: isOpen => setRecoverPasswordDialogOpen(isOpen) })] }));
60
+ return (jsxs(Fragment, { children: [jsx(SignInPageLayout, { fullHeight: true, "data-test-selector": "signInPage", children: jsx(SignInForm, { allowGuestSignIn: allowGuestSignIn, createAccountPath: createAccountPath, errorType: errorType, initialEmail: session?.isGuest ? '' : session?.email, initialRememberMe: session?.rememberMe, isDisabled: !session || isSuccess, isLoading: isLoading, isPendingGuestSignIn: isPendingCreateGuest, isPendingUserSignIn: isPendingSignIn, onRecoverPasswordDialogOpen: onRecoverPasswordDialogOpen, onSubmit: onSubmit }) }), jsx(RecoverPasswordDialog, { isOpen: isRecoverPasswordDialogOpen, onOpenChange: isOpen => setRecoverPasswordDialogOpen(isOpen) })] }));
61
61
  }
62
62
 
63
63
  export { SignInPage };
@@ -82,7 +82,7 @@ function CartContent({ cartLines }) {
82
82
  if (!currencyCode)
83
83
  throw new Error(`Currency code not found for symbol ${currentCart.currencySymbol}`);
84
84
  return (jsx(CheckoutPageLayout, { actions: {
85
- primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutShippingCartTotalContinueButton", href: "/CheckoutShipping", children: jsx(FormattedMessage, { id: "Start checkout" }) })),
85
+ primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutShippingCartTotalContinueButton", href: paths.CHECKOUT_SHIPPING, children: jsx(FormattedMessage, { id: "Start checkout" }) })),
86
86
  secondary: isAuthenticated ? (jsx(Button, { color: "secondary", "data-test-selector": "saveCartForLaterButton", onClick: () => {
87
87
  saveCartForLater.mutate({ cart: currentCart });
88
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" }) })),
@@ -51,7 +51,7 @@ function OrderConfirmationPageContent({ cart, }) {
51
51
  label: t('Order confirmation'),
52
52
  },
53
53
  ], "data-test-selector": "orderConfirmationPage", title: t('Order confirmation'), children: jsx(CheckoutPageLayout, { actions: {
54
- primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutReviewAndSubmit_continueShopping", href: "/", children: jsx(FormattedMessage, { id: "Continue shopping" }) })),
54
+ primary: (jsx(Button, { withArrow: true, "data-test-selector": "checkoutReviewAndSubmit_continueShopping", href: paths.HOME, children: jsx(FormattedMessage, { id: "Continue shopping" }) })),
55
55
  secondary: (jsxs(Fragment, { children: [cart.canSaveOrder && (jsx(Button, { color: "secondary", onClick: () => {
56
56
  saveCartForLater.mutate({ cart });
57
57
  }, variant: "outline", children: jsx(FormattedMessage, { id: "Save order" }) })), jsx(PrintButton, {})] })),
@@ -9,7 +9,8 @@ import { AddressDataCard } from './components/address-data-card.js';
9
9
 
10
10
  function ConnectedBillToAddressWidget({ billToId = 'current', } = {}) {
11
11
  const paths = usePaths();
12
- const { href } = useLocation();
12
+ const { pathname, search } = useLocation();
13
+ const href = `${pathname}${search}`;
13
14
  const editAddressUrl = `${paths.ACCOUNT_EDIT_BILL_TO_ADDRESS}/${billToId}?returnUrl=${encodeURIComponent(href)}`;
14
15
  const { data: billToAddress, error, isLoading, } = useFetchBillToAddress({ billToId });
15
16
  return (jsx(AddressDataCard, { actions: [
@@ -9,7 +9,8 @@ import { AddressDataCard } from './components/address-data-card.js';
9
9
 
10
10
  function ConnectedShipToAddressWidget({ billToId = 'current', shipToId = 'current', } = {}) {
11
11
  const paths = usePaths();
12
- const { href } = useLocation();
12
+ const { pathname, search } = useLocation();
13
+ const href = `${pathname}${search}`;
13
14
  const editAddressUrl = `${paths.ACCOUNT_EDIT_BILL_TO_ADDRESS}/${billToId}${paths.ACCOUNT_EDIT_SHIP_TO_ADDRESS}/${shipToId}?returnUrl=${encodeURIComponent(href)}`;
14
15
  const createAddressUrl = `${paths.ACCOUNT_EDIT_BILL_TO_ADDRESS}/${billToId}${paths.ACCOUNT_EDIT_SHIP_TO_ADDRESS}/new?returnUrl=${encodeURIComponent(href)}`;
15
16
  const { data: shipToAddress, error, isLoading, } = useFetchShipToAddress({
@@ -6,7 +6,6 @@ import { ImageLightbox } from '../../../../../media/image-lightbox/image-lightbo
6
6
  import { ZoomImage } from '../../../../../media/zoom-image/zoom-image.js';
7
7
  import { Modal } from '../../../../../modals/modal/modal.js';
8
8
  import { useDisclosure } from '../../../../../shared/hooks/use-disclosure.js';
9
- import { useIsBreakpoint } from '../../../../../shared/hooks/use-is-breakpoint.js';
10
9
  import styles from './product-detail-images.module.css.js';
11
10
 
12
11
  const MAX_IMAGES = 5;
@@ -16,7 +15,6 @@ function ProductDetailImages({ images }) {
16
15
  const [scrollPercentage, setScrollPercentage] = useState(0);
17
16
  const { isOpen: isOpenModal, open: openModal, toggle: toggleModal, } = useDisclosure();
18
17
  const { close: closeZoom, isOpen: isOpenZoom, open: openZoom, } = useDisclosure();
19
- const isXl = useIsBreakpoint('xl');
20
18
  if (images.length > MAX_IMAGES) {
21
19
  images = images.slice(0, MAX_IMAGES);
22
20
  }
@@ -29,7 +27,7 @@ function ProductDetailImages({ images }) {
29
27
  setScrollPercentage(scrollPercentage);
30
28
  openZoom();
31
29
  }
32
- return (jsxs(Fragment, { children: [isXl ? (jsxs(Fragment, { children: [jsx(ImagesGrid, { images: images, onSelectImage: (image, _index) => handleOpenImage(image) }), jsx(Modal, { hasCloseButton: true, isDismissable: true, isFullScreen: true, isKeyboardDismissDisabled: false, isOpen: isOpenModal, onOpenChange: toggleModal, shouldCloseOnInteractOutside: false, children: jsx("div", { className: styles['image-lightbox-modal'], children: jsx(ImageLightbox, { images: images, initialSelectedIndex: selectedImageIndex, onZoom: onZoom, variant: "lg" }) }) })] })) : (jsx(ImageLightbox, { images: images, onZoom: onZoom })), isOpenZoom && (jsx(ZoomImage, { currentImage: images[selectedZoomImageIndex], isZoomed: isOpenZoom, onClose: closeZoom, scrollFromTopPercentage: scrollPercentage }))] }));
30
+ return (jsxs(Fragment, { children: [jsx(ImagesGrid, { className: styles['images-grid'], images: images, onSelectImage: (image, _index) => handleOpenImage(image) }), isOpenModal && (jsx(Modal, { hasCloseButton: true, isDismissable: true, isFullScreen: true, isKeyboardDismissDisabled: false, isOpen: isOpenModal, onOpenChange: toggleModal, shouldCloseOnInteractOutside: false, children: jsx("div", { className: styles['image-lightbox-modal'], children: jsx(ImageLightbox, { images: images, initialSelectedIndex: selectedImageIndex, onZoom: onZoom, variant: "lg" }) }) })), jsx(ImageLightbox, { className: styles['image-lightbox'], images: images, onZoom: onZoom }), isOpenZoom && (jsx(ZoomImage, { currentImage: images[selectedZoomImageIndex], isZoomed: isOpenZoom, onClose: closeZoom, scrollFromTopPercentage: scrollPercentage }))] }));
33
31
  }
34
32
 
35
33
  export { ProductDetailImages };
@@ -1,3 +1,3 @@
1
- var styles = {"image-lightbox-modal":"product-detail-images-module-ERzjA"};
1
+ var styles = {"image-lightbox-modal":"product-detail-images-module-ERzjA","images-grid":"product-detail-images-module-cdIHn","image-lightbox":"product-detail-images-module-0udrk"};
2
2
 
3
3
  export { styles as default };
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
+ import { useEffect } from 'react';
3
4
  import { ConnectedProductCard } from '../../../cards/product-card/connected-product-card.js';
4
5
  import { CardCarousel } from '../../../carousel/card-carousel/card-carousel.js';
5
6
  import { ProductUSPCarousel } from '../../../carousel/usp-carousel/product-usp-carousel.js';
@@ -15,7 +16,10 @@ import { ProductDetailsRecentlyViewedSection } from './components/product-detail
15
16
 
16
17
  function ProductDetails({ data, priceComponent, }) {
17
18
  const { breadCrumb, included, page, product, usps } = data;
18
- useMarkProductAsRecentlyViewed({ productId: product.id });
19
+ const { mutate } = useMarkProductAsRecentlyViewed();
20
+ useEffect(() => {
21
+ mutate({ productId: product.id });
22
+ }, [mutate, product]);
19
23
  useDataLayer({
20
24
  event: {
21
25
  event: 'view_item',
@@ -3,12 +3,14 @@ import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { Button } from '../../../../buttons/button/button.js';
4
4
  import { FormattedMessage } from '../../../../intl/formatted-message.js';
5
5
  import { useIsBreakpoint } from '../../../../shared/hooks/use-is-breakpoint.js';
6
+ import { usePaths } from '../../../../shared/routing/use-paths.js';
6
7
  import { Heading } from '../../../../typography/heading/heading.js';
7
8
  import styles from './no-results.module.css.js';
8
9
 
9
10
  function NoResults({ content, title }) {
11
+ const paths = usePaths();
10
12
  const isLg = useIsBreakpoint('lg');
11
- return (jsxs("div", { className: styles['no-results'], children: [jsx(Heading, { bold: false, className: styles.title, size: isLg ? 's' : 'xs', tag: "h2", children: title }), jsx("p", { className: styles.body, children: content }), jsx("div", { className: styles.buttons, children: jsx(Button, { withArrow: true, "data-test-selector": "buttonContinueShopping", href: "/", size: "md", children: jsx(FormattedMessage, { id: "Continue shopping" }) }) })] }));
13
+ return (jsxs("div", { className: styles['no-results'], children: [jsx(Heading, { bold: false, className: styles.title, size: isLg ? 's' : 'xs', tag: "h2", children: title }), jsx("p", { className: styles.body, children: content }), jsx("div", { className: styles.buttons, children: jsx(Button, { withArrow: true, "data-test-selector": "buttonContinueShopping", href: paths.HOME, size: "md", children: jsx(FormattedMessage, { id: "Continue shopping" }) }) })] }));
12
14
  }
13
15
 
14
16
  export { NoResults };
@@ -12,10 +12,12 @@ function useFetchCurrentCartWithAtp({ forceRecalculation = true, select = cartMo
12
12
  select(atp
13
13
  ? {
14
14
  ...cartResult.data,
15
- cartLines: cartResult.data.cartLines?.map(line => ({
16
- ...line,
17
- atp: atp.find(a => a.productCode === line.erpNumber) || null,
18
- })),
15
+ cartLines: cartResult.data.cartLines?.map(line => {
16
+ const matchingAtp = atp.find(a => a.productCode === line.erpNumber);
17
+ if (!matchingAtp)
18
+ return line;
19
+ return { ...line, atp: matchingAtp };
20
+ }),
19
21
  }
20
22
  : cartResult.data),
21
23
  error: cartResult.error || error,
@@ -1,3 +1,5 @@
1
- export declare function useMarkProductAsRecentlyViewed({ productId, }: {
2
- productId?: string;
3
- }): import("@tanstack/react-query").UseQueryResult<void, Error>;
1
+ interface MarkProductAsRecentlyViewedParams {
2
+ productId: string;
3
+ }
4
+ export declare function useMarkProductAsRecentlyViewed(): import("@tanstack/react-query").UseMutationResult<void, Error, MarkProductAsRecentlyViewedParams, unknown>;
5
+ export {};
@@ -1,19 +1,15 @@
1
- import { useQueryClient, useQuery } from '@tanstack/react-query';
1
+ import { useQueryClient, useMutation } from '@tanstack/react-query';
2
2
  import { markProductAsRecentlyViewed } from '../../services/product-service.js';
3
3
 
4
- function useMarkProductAsRecentlyViewed({ productId, }) {
4
+ function useMarkProductAsRecentlyViewed() {
5
5
  const queryClient = useQueryClient();
6
- return useQuery({
7
- enabled: Boolean(productId),
8
- gcTime: 0,
9
- queryFn: () => {
6
+ return useMutation({
7
+ mutationFn: async ({ productId }) => {
10
8
  markProductAsRecentlyViewed({ productId: productId });
11
9
  queryClient.invalidateQueries({
12
10
  queryKey: ['products', 'recently-viewed'],
13
11
  });
14
12
  },
15
- queryKey: ['mark-product-as-recently-viewed', productId],
16
- staleTime: 0,
17
13
  });
18
14
  }
19
15
 
@@ -1,8 +1,13 @@
1
1
  import { config } from '../../../../config.js';
2
2
  import { request } from '../../../fetch/request.js';
3
+ import { TIME } from '../../../utils/time.js';
3
4
 
4
5
  async function fetchCountriesLanguages() {
5
6
  const { body } = await request({
7
+ next: {
8
+ revalidate: 1 * TIME.DAY,
9
+ tags: ['countries-languages'],
10
+ },
6
11
  url: `${config.SHOP_API_URL}/api/v1/websites/current?expand=languages%2Ccountries`,
7
12
  });
8
13
  return {
@@ -12,6 +17,10 @@ async function fetchCountriesLanguages() {
12
17
  }
13
18
  async function fetchCountriesWithLanguages() {
14
19
  const { body } = await request({
20
+ next: {
21
+ revalidate: 1 * TIME.DAY,
22
+ tags: ['countries-languages'],
23
+ },
15
24
  url: `${config.SHOP_API_URL}/api/v1/custom/countrylanguage`,
16
25
  });
17
26
  return body;
@@ -1 +1,5 @@
1
- export declare function useDebouncedCallback<T extends (...args: any[]) => any>(func: T, delay: number): (...args: Parameters<T>) => Promise<ReturnType<T>>;
1
+ export interface DebouncedCallback<T extends (...args: any[]) => any> {
2
+ (...args: Parameters<T>): Promise<ReturnType<T>>;
3
+ cancel: VoidFunction;
4
+ }
5
+ export declare function useDebouncedCallback<T extends (...args: any[]) => any>(func: T, delay: number): DebouncedCallback<T>;
@@ -4,16 +4,16 @@ import { useRef, useCallback } from 'react';
4
4
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
5
  function useDebouncedCallback(func, delay) {
6
6
  const timeoutId = useRef();
7
- return useCallback(function debounced(...args) {
8
- return new Promise(resolve => {
9
- if (typeof window === 'undefined')
10
- return resolve(func(...args));
11
- clearTimeout(timeoutId.current);
12
- timeoutId.current = setTimeout(() => {
13
- resolve(func(...args));
14
- }, delay);
15
- });
16
- }, [delay, func]);
7
+ const debounced = (...args) => new Promise(resolve => {
8
+ if (typeof window === 'undefined')
9
+ return resolve(func(...args));
10
+ clearTimeout(timeoutId.current);
11
+ timeoutId.current = setTimeout(() => {
12
+ resolve(func(...args));
13
+ }, delay);
14
+ });
15
+ debounced.cancel = () => clearTimeout(timeoutId.current);
16
+ return useCallback(debounced, [delay, func]);
17
17
  }
18
18
 
19
19
  export { useDebouncedCallback };
@@ -1,5 +1,10 @@
1
- import { ReactNode } from 'react';
2
- export declare function ReactQueryContainer({ children, enableDevTools, }: {
3
- children: ReactNode;
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { QueryClient } from '@tanstack/react-query';
3
+ export declare const globalQueryClient: QueryClient;
4
+ export declare function ReactQueryContainer({ children, enableDevTools, queryClient, }: {
5
+ children: ReactNode | ((args: {
6
+ queryClient: QueryClient;
7
+ }) => ReactNode | ReactElement);
4
8
  enableDevTools?: boolean;
9
+ queryClient?: QueryClient;
5
10
  }): import("react/jsx-runtime").JSX.Element;
@@ -5,7 +5,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
5
5
  import { environment } from '../utils/environment.js';
6
6
  import { TIME } from '../utils/time.js';
7
7
 
8
- const queryClient = new QueryClient({
8
+ const globalQueryClient = new QueryClient({
9
9
  defaultOptions: {
10
10
  queries: {
11
11
  /* Set gcTime and staleTime to 0 to disable react query cache */
@@ -15,8 +15,8 @@ const queryClient = new QueryClient({
15
15
  },
16
16
  },
17
17
  });
18
- function ReactQueryContainer({ children, enableDevTools = environment !== 'production', }) {
19
- return (jsxs(QueryClientProvider, { client: queryClient, children: [enableDevTools && jsx(ReactQueryDevtools, { initialIsOpen: false }), children] }));
18
+ function ReactQueryContainer({ children, enableDevTools = environment !== 'production', queryClient = globalQueryClient, }) {
19
+ return (jsxs(QueryClientProvider, { client: queryClient, children: [enableDevTools && jsx(ReactQueryDevtools, { initialIsOpen: false }), children instanceof Function ? children({ queryClient }) : children] }));
20
20
  }
21
21
 
22
- export { ReactQueryContainer };
22
+ export { ReactQueryContainer, globalQueryClient };
@@ -7,6 +7,7 @@ export interface RouteProviderProps {
7
7
  paths: Paths;
8
8
  url: {
9
9
  basePathname?: string;
10
+ href: string;
10
11
  pathname: string;
11
12
  search: string;
12
13
  };
@@ -24,6 +24,7 @@ export interface RouteContextValue {
24
24
  paths: Paths;
25
25
  url: {
26
26
  basePathname?: string;
27
+ href: string;
27
28
  pathname: string;
28
29
  search: string;
29
30
  };
@@ -3,12 +3,12 @@ import qs from 'query-string';
3
3
  import { useRouter } from './use-router.js';
4
4
 
5
5
  function useLocation() {
6
- const { url: { basePathname, pathname, search }, } = useRouter();
6
+ const { url: { basePathname, href, pathname, search }, } = useRouter();
7
7
  const query = qs.parse(search || '');
8
8
  return {
9
9
  basePathname,
10
10
  fullPathname: pathname,
11
- href: `${pathname}${search}`,
11
+ href,
12
12
  pathname: pathname.replace(new RegExp(`^${basePathname}`), ''),
13
13
  query,
14
14
  search,
@@ -41,8 +41,6 @@ function SidebarProvider({ children }) {
41
41
  const { close, isDocked, isOpen, transition } = state;
42
42
  return (jsxs("div", { className: clsx(styles['sidebar-container'], {
43
43
  [styles['transition']]: transition,
44
- [styles['docked']]: isDocked,
45
- [styles['open']]: isOpen,
46
44
  }), children: [jsx(SidebarDetectBreakpoint, {}), children, jsx(BackgroundOverlay, { isOpen: isDocked && isOpen, onClick: close })] }));
47
45
  }
48
46
 
package/dist/styles.css CHANGED
@@ -479,7 +479,7 @@ html {
479
479
  grid-template-rows: 1fr;
480
480
  }
481
481
 
482
- .accordion-module-9WvAH .accordion-module-lf9d-:has(.accordion-module--Rwpb[disabled]) {
482
+ .accordion-module-9WvAH .accordion-module-lf9d-:has(.accordion-module--Rwpb[disabled]):not(.accordion-module-RnNRT) {
483
483
  border-color: var(--color-brand-light-gray);
484
484
  }
485
485
 
@@ -2218,6 +2218,21 @@ html {
2218
2218
  content: '/';
2219
2219
  margin-inline: 10px;
2220
2220
  }
2221
+ .breadcrumb-module-CQGse .breadcrumb-module-hxhDY.breadcrumb-module-ToeDB {
2222
+ display: flex;
2223
+ }
2224
+ .breadcrumb-module-CQGse .breadcrumb-module-hxhDY.breadcrumb-module-np5GK {
2225
+ display: none;
2226
+ }
2227
+ @media (width >= 768px) {
2228
+ .breadcrumb-module-CQGse .breadcrumb-module-hxhDY.breadcrumb-module-ToeDB {
2229
+ display: none;
2230
+ }
2231
+
2232
+ .breadcrumb-module-CQGse .breadcrumb-module-hxhDY.breadcrumb-module-np5GK {
2233
+ display: flex;
2234
+ }
2235
+ }
2221
2236
  @media (width < 768px) {
2222
2237
  .breadcrumb-module-CQGse .breadcrumb-module-hxhDY::after {
2223
2238
  display: none;
@@ -3369,6 +3384,7 @@ html {
3369
3384
 
3370
3385
  .product-card-module-pLaiB .product-card-module-XzunM {
3371
3386
  z-index: var(--z-top);
3387
+ margin: 0;
3372
3388
  grid-area: sku;
3373
3389
  justify-self: start;
3374
3390
  -webkit-user-select: all;
@@ -8555,6 +8571,20 @@ button.swiper-pagination-bullet {
8555
8571
  padding: 64px 48px;
8556
8572
  }
8557
8573
 
8574
+ .product-detail-images-module-cdIHn {
8575
+ display: none;
8576
+ }
8577
+
8578
+ @media (width >= 1024px) {
8579
+ .product-detail-images-module-0udrk {
8580
+ display: none;
8581
+ }
8582
+
8583
+ .product-detail-images-module-cdIHn {
8584
+ display: grid;
8585
+ }
8586
+ }
8587
+
8558
8588
  .product-details-panel-module-MXfPm {
8559
8589
  display: flex;
8560
8590
  flex-direction: column;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonic-equipment/ui",
3
- "version": "202.0.0",
3
+ "version": "203.0.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "engines": {
@@ -1,34 +0,0 @@
1
- "use client";
2
- import { useState } from 'react';
3
- import Cookies from 'js-cookie';
4
- import { config } from '../../config.js';
5
-
6
- const defaultCookieOptions = {
7
- domain: config.COOKIE_DOMAIN,
8
- expires: 365,
9
- path: '/',
10
- sameSite: 'None',
11
- secure: true,
12
- };
13
- function useCookie(name, options = defaultCookieOptions) {
14
- const cookieValue = Cookies.get()?.[name];
15
- const [stateValue, setStateValue] = useState(cookieValue);
16
- if (cookieValue !== stateValue) {
17
- setStateValue(cookieValue);
18
- }
19
- function setValue(valueOrFn) {
20
- setStateValue(oldValue => {
21
- const newValue = typeof valueOrFn === 'function' ? valueOrFn(oldValue) : valueOrFn;
22
- if (newValue) {
23
- Cookies.set(name, newValue, options);
24
- }
25
- else {
26
- Cookies.remove(name);
27
- }
28
- return newValue;
29
- });
30
- }
31
- return [stateValue, setValue];
32
- }
33
-
34
- export { defaultCookieOptions, useCookie };