@sonic-equipment/ui 235.0.0 → 236.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.
package/dist/exports.d.ts CHANGED
@@ -471,6 +471,7 @@ export * from './shared/utils/merge';
471
471
  export * from './shared/utils/number';
472
472
  export * from './shared/utils/price';
473
473
  export * from './shared/utils/promise';
474
+ export * from './shared/utils/query-client';
474
475
  export * from './shared/utils/random';
475
476
  export * from './shared/utils/refs';
476
477
  export * from './shared/utils/scrolling';
package/dist/index.js CHANGED
@@ -466,6 +466,7 @@ export { clone, deepMerge, isPlainObject, default as main, merge } from './share
466
466
  export { ensureNumber } from './shared/utils/number.js';
467
467
  export { formatCurrency, getCurrencyByCountryCode, parseCurrency } from './shared/utils/price.js';
468
468
  export { isPromise, wait } from './shared/utils/promise.js';
469
+ export { createQueryClient } from './shared/utils/query-client.js';
469
470
  export { random, randomInt } from './shared/utils/random.js';
470
471
  export { multiRef } from './shared/utils/refs.js';
471
472
  export { scrollIntoViewRef, scrollToTop } from './shared/utils/scrolling.js';
@@ -2,5 +2,5 @@ import { ReactNode } from 'react';
2
2
  interface MissingTranslationProviderProps {
3
3
  children: ReactNode;
4
4
  }
5
- export declare function MissingTranslationProvider({ children, }: MissingTranslationProviderProps): import("react/jsx-runtime").JSX.Element | null;
5
+ export declare function MissingTranslationProvider({ children, }: MissingTranslationProviderProps): string | number | boolean | import("react/jsx-runtime").JSX.Element | Iterable<ReactNode> | null | undefined;
6
6
  export {};
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { useCallback, useMemo } from 'react';
4
+ import { logger } from '../logging/logger.js';
4
5
  import { useFetchTranslations } from '../shared/api/storefront/hooks/translation/use-fetch-translations.js';
5
6
  import { useFeatureFlags } from '../shared/feature-flags/use-feature-flags.js';
6
7
  import { IntlContext } from './intl-context.js';
@@ -10,7 +11,7 @@ import { getLanguageCodeFromCultureCode } from './utils.js';
10
11
  function MissingTranslationProvider({ children, }) {
11
12
  const { missing, translations: showTranslations } = useFeatureFlags();
12
13
  const intlProps = useIntl();
13
- const { data: translations, isLoading } = useFetchTranslations(getLanguageCodeFromCultureCode(intlProps.cultureCode), { enabled: missing });
14
+ const { data: translations, error, isLoading, } = useFetchTranslations(getLanguageCodeFromCultureCode(intlProps.cultureCode), { enabled: missing });
14
15
  const formattedMessage = useCallback((id, options) => {
15
16
  const message = intlProps.formattedMessage(id, options);
16
17
  if (!missing && !showTranslations)
@@ -32,6 +33,10 @@ function MissingTranslationProvider({ children, }) {
32
33
  }), [intlProps, formattedMessage]);
33
34
  if (!missing && !showTranslations)
34
35
  return jsx(Fragment, { children: children });
36
+ if (error) {
37
+ logger.error('MissingTranslationProvider::Error fetching translations', error);
38
+ return children;
39
+ }
35
40
  if (missing && (isLoading || !translations))
36
41
  return null;
37
42
  return jsx(IntlContext.Provider, { value: value, children: children });
@@ -1,11 +1,9 @@
1
1
  "use client";
2
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { jsx } from 'react/jsx-runtime';
3
3
  import { useEffect } from 'react';
4
4
  import { logger } from '../../logging/logger.js';
5
- import { isRequestError } from '../../shared/fetch/request.js';
5
+ import { DefaultErrorView } from '../../shared/error/default-error-view.js';
6
6
  import { usePaths } from '../../shared/routing/use-paths.js';
7
- import { environment } from '../../shared/utils/environment.js';
8
- import { Heading } from '../../typography/heading/heading.js';
9
7
  import { Page } from '../components/page/page.js';
10
8
 
11
9
  function ErrorPage({ error }) {
@@ -16,7 +14,7 @@ function ErrorPage({ error }) {
16
14
  return (jsx(Page, { breadcrumb: [
17
15
  { href: paths.HOME, label: 'Home' },
18
16
  { href: paths.HOME, label: 'Error' },
19
- ], "data-test-selector": "errorPage", title: "Something went wrong", children: environment !== 'production' && (jsxs(Fragment, { children: [jsx(Heading, { size: "l", children: isRequestError(error) ? (jsxs(Fragment, { children: [error.status, " - ", error.statusText || 'Unknown error'] })) : (jsx(Fragment, { children: error.message })) }), isRequestError(error) && (jsxs(Fragment, { children: [jsx(Heading, { size: "xs", children: "Error details" }), jsx(Heading, { size: "xxs", children: "Body" }), jsx("pre", { children: JSON.stringify(error.body, null, 2) }), jsx(Heading, { size: "xxs", children: "Options" }), jsx("pre", { children: JSON.stringify(error.options, null, 2) })] }))] })) }));
17
+ ], "data-test-selector": "errorPage", title: "Something went wrong", children: jsx(DefaultErrorView, { error: error }) }));
20
18
  }
21
19
 
22
20
  export { ErrorPage };
@@ -10,11 +10,11 @@ import { Message } from '../../../../message/message.js';
10
10
  import { usePatchCurrentAccount } from '../../../../shared/api/storefront/hooks/account/use-patch-current-account.js';
11
11
  import { usePatchSession } from '../../../../shared/api/storefront/hooks/authentication/use-patch-session.js';
12
12
  import { usePostShipToAddress } from '../../../../shared/api/storefront/hooks/customer/use-post-ship-to-address.js';
13
+ import { DefaultErrorView } from '../../../../shared/error/default-error-view.js';
13
14
  import { NotFoundRequestError } from '../../../../shared/fetch/request.js';
14
15
  import { useNavigate } from '../../../../shared/routing/use-navigate.js';
15
16
  import { usePaths } from '../../../../shared/routing/use-paths.js';
16
17
  import { Heading } from '../../../../typography/heading/heading.js';
17
- import { ErrorPage } from '../../../error-page/error-page.js';
18
18
  import { LoadingPage } from '../../../loading-page/loading-page.js';
19
19
  import formStyles from '../../../../forms/partials/address-form/address-form.module.css.js';
20
20
  import styles from './create-ship-to-address.module.css.js';
@@ -32,9 +32,9 @@ function ConnectedCreateShipToAddressForm({ billToId, returnUrl, }) {
32
32
  if (errorShipToAddress && errorShipToAddress instanceof NotFoundRequestError)
33
33
  return (jsx(Heading, { size: "l", children: jsx(FormattedMessage, { id: "Address not found" }) }));
34
34
  if (errorCountries)
35
- return jsx(ErrorPage, { error: errorCountries });
35
+ return jsx(DefaultErrorView, { error: errorCountries });
36
36
  if (!countries) {
37
- return jsx(ErrorPage, { error: new Error('No countries data available.') });
37
+ return (jsx(DefaultErrorView, { error: new Error('No countries data available.') }));
38
38
  }
39
39
  return (jsxs(Form, { className: formStyles.form, "data-test-selector": "shipToAddressForm", onSubmit: async ({ formData }) => {
40
40
  const countryFormValue = formData.get('countrySelect')?.toString();
@@ -9,10 +9,10 @@ import { FormattedMessage } from '../../../../intl/formatted-message.js';
9
9
  import { Message } from '../../../../message/message.js';
10
10
  import { useFetchBillToAddress } from '../../../../shared/api/storefront/hooks/customer/use-fetch-bill-to-address.js';
11
11
  import { usePatchBillToAddress } from '../../../../shared/api/storefront/hooks/customer/use-patch-bill-to-address.js';
12
+ import { DefaultErrorView } from '../../../../shared/error/default-error-view.js';
12
13
  import { NotFoundRequestError } from '../../../../shared/fetch/request.js';
13
14
  import { useNavigate } from '../../../../shared/routing/use-navigate.js';
14
15
  import { Heading } from '../../../../typography/heading/heading.js';
15
- import { ErrorPage } from '../../../error-page/error-page.js';
16
16
  import { LoadingPage } from '../../../loading-page/loading-page.js';
17
17
  import formStyles from '../../../../forms/partials/address-form/address-form.module.css.js';
18
18
  import styles from './edit-bill-to-address.module.css.js';
@@ -61,11 +61,11 @@ function ConnectedEditBillToAddressForm({ billToId, returnUrl, }) {
61
61
  if (errorBillToAddress && errorBillToAddress instanceof NotFoundRequestError)
62
62
  return (jsx(Heading, { size: "l", children: jsx(FormattedMessage, { id: "Address not found" }) }));
63
63
  if (errorBillToAddress)
64
- return jsx(ErrorPage, { error: errorBillToAddress });
64
+ return jsx(DefaultErrorView, { error: errorBillToAddress });
65
65
  if (errorCountries)
66
- return jsx(ErrorPage, { error: errorCountries });
66
+ return jsx(DefaultErrorView, { error: errorCountries });
67
67
  if (!billToAddress || !countries) {
68
- return (jsx(ErrorPage, { error: new Error('No bill to address or countries data available.') }));
68
+ return (jsx(DefaultErrorView, { error: new Error('No bill to address or countries data available.') }));
69
69
  }
70
70
  return (jsxs(Form, { className: formStyles.form, "data-test-selector": "billToAddressForm", onSubmit: ({ formData }) => {
71
71
  const countryFormValue = formData.get('countrySelect')?.toString();
@@ -12,10 +12,10 @@ import { usePatchCurrentAccount } from '../../../../shared/api/storefront/hooks/
12
12
  import { usePatchSession } from '../../../../shared/api/storefront/hooks/authentication/use-patch-session.js';
13
13
  import { useFetchShipToAddress } from '../../../../shared/api/storefront/hooks/customer/use-fetch-ship-to-address.js';
14
14
  import { usePatchShipToAddress } from '../../../../shared/api/storefront/hooks/customer/use-patch-ship-to-address.js';
15
+ import { DefaultErrorView } from '../../../../shared/error/default-error-view.js';
15
16
  import { NotFoundRequestError } from '../../../../shared/fetch/request.js';
16
17
  import { useNavigate } from '../../../../shared/routing/use-navigate.js';
17
18
  import { Heading } from '../../../../typography/heading/heading.js';
18
- import { ErrorPage } from '../../../error-page/error-page.js';
19
19
  import { LoadingPage } from '../../../loading-page/loading-page.js';
20
20
  import formStyles from '../../../../forms/partials/address-form/address-form.module.css.js';
21
21
  import styles from './edit-ship-to-address.module.css.js';
@@ -66,11 +66,11 @@ function ConnectedEditShipToAddressForm({ billToId, returnUrl, shipToId, }) {
66
66
  if (errorShipToAddress && errorShipToAddress instanceof NotFoundRequestError)
67
67
  return (jsx(Heading, { size: "l", children: jsx(FormattedMessage, { id: "Address not found" }) }));
68
68
  if (errorShipToAddress)
69
- return jsx(ErrorPage, { error: errorShipToAddress });
69
+ return jsx(DefaultErrorView, { error: errorShipToAddress });
70
70
  if (errorCountries)
71
- return jsx(ErrorPage, { error: errorCountries });
71
+ return jsx(DefaultErrorView, { error: errorCountries });
72
72
  if (!shipToAddress || !countries) {
73
- return (jsx(ErrorPage, { error: new Error('No ship to address or countries data available.') }));
73
+ return (jsx(DefaultErrorView, { error: new Error('No ship to address or countries data available.') }));
74
74
  }
75
75
  return (jsxs(Form, { className: formStyles.form, "data-test-selector": "shipToAddressForm", onSubmit: async ({ formData }) => {
76
76
  const countryFormValue = formData.get('countrySelect')?.toString();
@@ -1,2 +1,2 @@
1
1
  import { ErrorViewProps } from './types';
2
- export declare function DefaultErrorView({ error, errorInfo, resetError, retryError, showErrorDetails, showReset, showRetry, }: ErrorViewProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function DefaultErrorView({ error, errorInfo, resetError, showErrorDetails, showReset, }: ErrorViewProps): import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,8 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { environment } from '../utils/environment.js';
3
4
 
4
- function DefaultErrorView({ error, errorInfo, resetError, retryError, showErrorDetails, showReset, showRetry, }) {
5
+ function DefaultErrorView({ error, errorInfo, resetError, showErrorDetails = environment !== 'production', showReset, }) {
5
6
  return (jsxs("div", { style: {
6
7
  backgroundColor: '#fef2f2',
7
8
  border: '2px solid #ef4444',
@@ -20,23 +21,15 @@ function DefaultErrorView({ error, errorInfo, resetError, retryError, showErrorD
20
21
  overflow: 'auto',
21
22
  padding: '1rem',
22
23
  whiteSpace: 'pre-wrap',
23
- }, children: [jsxs("div", { style: { marginBottom: '1rem' }, children: [jsx("strong", { children: "Error:" }), " ", error.toString()] }), error.stack && (jsxs("div", { style: { marginBottom: '1rem' }, children: [jsx("strong", { children: "Stack:" }), jsx("pre", { style: { margin: '0.5rem 0 0 0' }, children: error.stack })] })), errorInfo?.componentStack && (jsxs("div", { children: [jsx("strong", { children: "Component Stack:" }), jsx("pre", { style: { margin: '0.5rem 0 0 0' }, children: errorInfo.componentStack })] }))] })] })), jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }, children: [showRetry && (jsx("button", { onClick: retryError, style: {
24
- backgroundColor: '#3b82f6',
25
- border: 'none',
26
- borderRadius: '4px',
27
- color: 'white',
28
- cursor: 'pointer',
29
- fontWeight: '500',
30
- padding: '0.5rem 1rem',
31
- }, type: "button", children: "\uD83D\uDD04 Try again" })), showReset && (jsx("button", { onClick: resetError, style: {
32
- backgroundColor: '#6b7280',
33
- border: 'none',
34
- borderRadius: '4px',
35
- color: 'white',
36
- cursor: 'pointer',
37
- fontWeight: '500',
38
- padding: '0.5rem 1rem',
39
- }, type: "button", children: "\u21BA Reset" }))] })] }));
24
+ }, children: [jsxs("div", { style: { marginBottom: '1rem' }, children: [jsx("strong", { children: "Error:" }), " ", error.toString()] }), error.stack && (jsxs("div", { style: { marginBottom: '1rem' }, children: [jsx("strong", { children: "Stack:" }), jsx("pre", { style: { margin: '0.5rem 0 0 0' }, children: error.stack })] })), errorInfo?.componentStack && (jsxs("div", { children: [jsx("strong", { children: "Component Stack:" }), jsx("pre", { style: { margin: '0.5rem 0 0 0' }, children: errorInfo.componentStack })] }))] })] })), jsx("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }, children: showReset && resetError && (jsx("button", { onClick: resetError, style: {
25
+ backgroundColor: '#6b7280',
26
+ border: 'none',
27
+ borderRadius: '4px',
28
+ color: 'white',
29
+ cursor: 'pointer',
30
+ fontWeight: '500',
31
+ padding: '0.5rem 1rem',
32
+ }, type: "button", children: "\u21BA Reset" })) })] }));
40
33
  }
41
34
 
42
35
  export { DefaultErrorView };
@@ -3,7 +3,6 @@ import { ErrorContext, ErrorViewProps } from './types';
3
3
  export interface ErrorBoundaryProps {
4
4
  ErrorView?: ComponentType<ErrorViewProps>;
5
5
  allowReset?: boolean;
6
- allowRetry?: boolean;
7
6
  children: ReactNode;
8
7
  context?: ErrorContext;
9
8
  ignoreGlobalUnhandledErrors?: boolean;
@@ -2,7 +2,6 @@
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
  import { Component } from 'react';
4
4
  import { DefaultErrorView } from './default-error-view.js';
5
- import { NetworkError } from './errors.js';
6
5
  import { GlobalUnhandledErrorHandler } from './global-unhandled-error-handler.js';
7
6
 
8
7
  class InternalErrorBoundary extends Component {
@@ -25,28 +24,7 @@ class InternalErrorBoundary extends Component {
25
24
  errorCount: 0,
26
25
  errorInfo: null,
27
26
  hasError: false,
28
- });
29
- if (onReset) {
30
- onReset();
31
- }
32
- }
33
- });
34
- Object.defineProperty(this, "retryErrorBoundary", {
35
- enumerable: true,
36
- configurable: true,
37
- writable: true,
38
- value: () => {
39
- const { error } = this.state;
40
- if (error &&
41
- 'retryDelay' in error &&
42
- typeof error.retryDelay === 'number') {
43
- this.resetTimeoutId = window?.setTimeout(() => {
44
- this.resetErrorBoundary();
45
- }, error.retryDelay);
46
- }
47
- else {
48
- this.resetErrorBoundary();
49
- }
27
+ }, onReset);
50
28
  }
51
29
  });
52
30
  this.state = {
@@ -95,28 +73,12 @@ class InternalErrorBoundary extends Component {
95
73
  window?.clearTimeout(this.resetTimeoutId);
96
74
  }
97
75
  }
98
- shouldShowRetry() {
99
- const { allowRetry } = this.props;
100
- const { error } = this.state;
101
- if (allowRetry === true)
102
- return true;
103
- if (allowRetry === false)
104
- return false;
105
- if (error && 'isRetryable' in error) {
106
- return true;
107
- }
108
- if (error instanceof NetworkError) {
109
- return true;
110
- }
111
- return false;
112
- }
113
76
  render() {
114
77
  const { error, errorInfo, hasError } = this.state;
115
78
  const { allowReset, children, context, ErrorView = DefaultErrorView, showErrorDetails, } = this.props;
116
79
  if (!hasError || !error)
117
80
  return children;
118
- const showRetry = this.shouldShowRetry();
119
- return (jsx(ErrorView, { context: context, error: error, errorInfo: errorInfo || undefined, resetError: this.resetErrorBoundary, retryError: this.retryErrorBoundary, showErrorDetails: Boolean(showErrorDetails), showReset: allowReset !== false, showRetry: showRetry }));
81
+ return (jsx(ErrorView, { context: context, error: error, errorInfo: errorInfo || undefined, resetError: this.resetErrorBoundary, showErrorDetails: Boolean(showErrorDetails), showReset: allowReset }));
120
82
  }
121
83
  }
122
84
  Object.defineProperty(InternalErrorBoundary, "displayName", {
@@ -14,7 +14,9 @@ function InternalGlobalUnhandledErrorHandler({ captureUnhandledRejections = true
14
14
  : new Error(String(event.reason));
15
15
  if (onErrorCaught)
16
16
  onErrorCaught(error, 'unhandledrejection', context);
17
- setError(error);
17
+ queueMicrotask(() => {
18
+ setError(error);
19
+ });
18
20
  };
19
21
  const handleWindowError = (event, _source, _lineno, _colno, error) => {
20
22
  const actualError = error ||
@@ -23,7 +25,9 @@ function InternalGlobalUnhandledErrorHandler({ captureUnhandledRejections = true
23
25
  : new Error(event.message));
24
26
  if (onErrorCaught)
25
27
  onErrorCaught(actualError, 'error', context);
26
- setError(actualError);
28
+ queueMicrotask(() => {
29
+ setError(actualError);
30
+ });
27
31
  return true;
28
32
  };
29
33
  if (captureUnhandledRejections)
@@ -3,11 +3,9 @@ export interface ErrorViewProps {
3
3
  context?: ErrorContext;
4
4
  error: Error;
5
5
  errorInfo?: ErrorInfo;
6
- resetError: () => void;
7
- retryError: () => void;
6
+ resetError?: () => void;
8
7
  showErrorDetails?: boolean;
9
8
  showReset?: boolean;
10
- showRetry?: boolean;
11
9
  }
12
10
  export interface ErrorContext {
13
11
  [key: string]: unknown;
@@ -1,23 +1,11 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
3
  import { useMemo } from 'react';
4
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { QueryClientProvider } from '@tanstack/react-query';
5
5
  import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
6
6
  import { environment } from '../utils/environment.js';
7
- import { TIME } from '../utils/time.js';
7
+ import { createQueryClient } from '../utils/query-client.js';
8
8
 
9
- function createQueryClient() {
10
- return new QueryClient({
11
- defaultOptions: {
12
- queries: {
13
- /* Set gcTime and staleTime to 0 to disable react query cache */
14
- gcTime: 1 * TIME.DAY,
15
- retry: false,
16
- staleTime: 1 * TIME.DAY,
17
- },
18
- },
19
- });
20
- }
21
9
  const globalQueryClient = createQueryClient();
22
10
  function ReactQueryContainer({ children, enableDevTools = environment !== 'production', queryClient: _queryClient, }) {
23
11
  const queryClient = useMemo(() => _queryClient || createQueryClient(), [_queryClient]);
@@ -0,0 +1,4 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ export declare function createQueryClient({ throwOnError, }?: {
3
+ throwOnError?: (error: unknown) => boolean;
4
+ }): QueryClient;
@@ -0,0 +1,33 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ import { TIME } from './time.js';
3
+
4
+ /* Example of how to handle specific errors globally
5
+ * an have react query re-throw them to be caught by an error boundary
6
+
7
+ throwOnError: _error => {
8
+ return (
9
+ error instanceof UnauthorizedRequestError ||
10
+ error instanceof ForbiddenRequestError
11
+ )
12
+ },
13
+
14
+ */
15
+ function createQueryClient({ throwOnError, } = {}) {
16
+ return new QueryClient({
17
+ defaultOptions: {
18
+ mutations: {
19
+ retry: false,
20
+ throwOnError,
21
+ },
22
+ queries: {
23
+ /* Set gcTime and staleTime to 0 to disable react query cache */
24
+ gcTime: 1 * TIME.DAY,
25
+ retry: false,
26
+ staleTime: 1 * TIME.DAY,
27
+ throwOnError,
28
+ },
29
+ },
30
+ });
31
+ }
32
+
33
+ export { createQueryClient };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonic-equipment/ui",
3
- "version": "235.0.0",
3
+ "version": "236.0.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "engines": {