@sonic-equipment/ui 232.0.0 → 234.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
@@ -398,6 +398,12 @@ export * from './shared/data/cart.data';
398
398
  export * from './shared/data/countries-languages.data';
399
399
  export * from './shared/data/navigation';
400
400
  export * from './shared/data/order';
401
+ export * from './shared/error/basic-error-view';
402
+ export * from './shared/error/default-error-view';
403
+ export * from './shared/error/error-boundary';
404
+ export * from './shared/error/errors';
405
+ export * from './shared/error/global-unhandled-error-handler';
406
+ export * from './shared/error/types';
401
407
  export * from './shared/feature-flags/use-feature-flags';
402
408
  export * from './shared/fetch/request';
403
409
  export * from './shared/ga/data-layer';
package/dist/index.js CHANGED
@@ -397,6 +397,11 @@ export { cart } from './shared/data/cart.data.js';
397
397
  export { countries, dutch, english, france, french, german, germany, languages, netherlands, switzerland, unitedKingdom } from './shared/data/countries-languages.data.js';
398
398
  export { navigationData } from './shared/data/navigation.js';
399
399
  export { orderModel } from './shared/data/order.js';
400
+ export { BasicErrorView } from './shared/error/basic-error-view.js';
401
+ export { DefaultErrorView } from './shared/error/default-error-view.js';
402
+ export { ErrorBoundary } from './shared/error/error-boundary.js';
403
+ export { AuthenticationError, FatalError, NetworkError, RetryableError } from './shared/error/errors.js';
404
+ export { GlobalUnhandledErrorHandler } from './shared/error/global-unhandled-error-handler.js';
400
405
  export { useFeatureFlags } from './shared/feature-flags/use-feature-flags.js';
401
406
  export { BadRequestError, ForbiddenRequestError, InternalServerErrorRequestError, NotFoundRequestError, RequestError, TimeoutRequestError, UnauthorizedRequestError, UnprocessableContentRequestError, isFormData, isJsonBody, isRequestError, request } from './shared/fetch/request.js';
402
407
  export { dataLayer } from './shared/ga/data-layer.js';
@@ -7,7 +7,7 @@ async function fetchTranslations(languageCode) {
7
7
  credentials: 'include',
8
8
  next: {
9
9
  revalidate: Infinity,
10
- tags: [`translations-${languageCode}`],
10
+ tags: [`translations`, `translations-${languageCode}`],
11
11
  },
12
12
  url: `${config.SHOP_API_URL}/api/v1/translationdictionaries?page=1&pageSize=9999&languageCode=${languageCode}`,
13
13
  });
@@ -0,0 +1 @@
1
+ export declare function BasicErrorView(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import { Heading } from '../../typography/heading/heading.js';
4
+
5
+ function BasicErrorView() {
6
+ return (jsx("html", { lang: "en", children: jsx("body", { children: jsxs("div", { style: {
7
+ alignItems: 'center',
8
+ display: 'flex',
9
+ flexDirection: 'column',
10
+ height: '100vh',
11
+ justifyContent: 'center',
12
+ padding: '1rem',
13
+ textAlign: 'center',
14
+ }, children: [jsx(Heading, { size: "m", children: "Something went wrong" }), jsx("p", { children: "An unexpected error occurred. Please try refreshing the page, or come back later." }), jsx("a", { href: "/", children: "Go to homepage" })] }) }) }));
15
+ }
16
+
17
+ export { BasicErrorView };
@@ -0,0 +1,2 @@
1
+ import { ErrorViewProps } from './types';
2
+ export declare function DefaultErrorView({ error, errorInfo, resetError, retryError, showErrorDetails, showReset, showRetry, }: ErrorViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,42 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+
4
+ function DefaultErrorView({ error, errorInfo, resetError, retryError, showErrorDetails, showReset, showRetry, }) {
5
+ return (jsxs("div", { style: {
6
+ backgroundColor: '#fef2f2',
7
+ border: '2px solid #ef4444',
8
+ borderRadius: '8px',
9
+ color: '#991b1b',
10
+ margin: '1rem',
11
+ padding: '2rem',
12
+ }, children: [jsx("h2", { style: { fontSize: '1.5rem', fontWeight: 'bold', marginTop: 0 }, children: "\u26A0\uFE0F Something went wrong" }), jsx("p", { style: { marginBottom: '1rem' }, children: error.message || 'An unexpected error occurred.' }), showErrorDetails && (jsxs("details", { style: { marginBottom: '1rem' }, children: [jsx("summary", { style: { cursor: 'pointer', fontWeight: 'bold' }, children: "Technical Details (only visible in development mode)" }), jsxs("div", { style: {
13
+ backgroundColor: '#fff',
14
+ border: '1px solid #fca5a5',
15
+ borderRadius: '4px',
16
+ fontFamily: 'monospace',
17
+ fontSize: '0.875rem',
18
+ marginTop: '0.5rem',
19
+ maxHeight: '300px',
20
+ overflow: 'auto',
21
+ padding: '1rem',
22
+ 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" }))] })] }));
40
+ }
41
+
42
+ export { DefaultErrorView };
@@ -0,0 +1,16 @@
1
+ import { ComponentType, ErrorInfo, ReactNode } from 'react';
2
+ import { ErrorContext, ErrorViewProps } from './types';
3
+ export interface ErrorBoundaryProps {
4
+ ErrorView?: ComponentType<ErrorViewProps>;
5
+ allowReset?: boolean;
6
+ allowRetry?: boolean;
7
+ children: ReactNode;
8
+ context?: ErrorContext;
9
+ ignoreGlobalUnhandledErrors?: boolean;
10
+ onError?: (error: Error, errorInfo: ErrorInfo, context: ErrorContext | undefined) => void;
11
+ onReset?: () => void;
12
+ showErrorDetails?: boolean;
13
+ }
14
+ export declare function ErrorBoundary({ children, persistError, ...props }: ErrorBoundaryProps & {
15
+ persistError?: boolean;
16
+ }): ReactNode;
@@ -0,0 +1,122 @@
1
+ "use client";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { Component } from 'react';
4
+ import { DefaultErrorView } from './default-error-view.js';
5
+ import { NetworkError } from './errors.js';
6
+ import { GlobalUnhandledErrorHandler } from './global-unhandled-error-handler.js';
7
+
8
+ class InternalErrorBoundary extends Component {
9
+ constructor(props) {
10
+ super(props);
11
+ Object.defineProperty(this, "resetTimeoutId", {
12
+ enumerable: true,
13
+ configurable: true,
14
+ writable: true,
15
+ value: null
16
+ });
17
+ Object.defineProperty(this, "resetErrorBoundary", {
18
+ enumerable: true,
19
+ configurable: true,
20
+ writable: true,
21
+ value: () => {
22
+ const { onReset } = this.props;
23
+ this.setState({
24
+ error: null,
25
+ errorCount: 0,
26
+ errorInfo: null,
27
+ 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
+ }
50
+ }
51
+ });
52
+ this.state = {
53
+ error: null,
54
+ errorCount: 0,
55
+ errorInfo: null,
56
+ hasError: false,
57
+ };
58
+ }
59
+ static getDerivedStateFromError(error) {
60
+ return {
61
+ error,
62
+ hasError: true,
63
+ };
64
+ }
65
+ componentDidCatch(error, errorInfo) {
66
+ const { context, onError, showErrorDetails } = this.props;
67
+ this.setState(prevState => ({
68
+ errorCount: prevState.errorCount + 1,
69
+ errorInfo,
70
+ }));
71
+ if (onError)
72
+ onError(error, errorInfo, context);
73
+ if (showErrorDetails) {
74
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
75
+ if (context) {
76
+ console.error('Error context:', context);
77
+ }
78
+ }
79
+ }
80
+ componentDidUpdate() {
81
+ const { hasError } = this.state;
82
+ }
83
+ componentWillUnmount() {
84
+ if (this.resetTimeoutId) {
85
+ window?.clearTimeout(this.resetTimeoutId);
86
+ }
87
+ }
88
+ shouldShowRetry() {
89
+ const { allowRetry } = this.props;
90
+ const { error } = this.state;
91
+ if (allowRetry === true)
92
+ return true;
93
+ if (allowRetry === false)
94
+ return false;
95
+ if (error && 'isRetryable' in error) {
96
+ return true;
97
+ }
98
+ if (error instanceof NetworkError) {
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ render() {
104
+ const { error, errorInfo, hasError } = this.state;
105
+ const { allowReset, children, context, ErrorView = DefaultErrorView, showErrorDetails, } = this.props;
106
+ if (!hasError || !error)
107
+ return children;
108
+ const showRetry = this.shouldShowRetry();
109
+ return (jsx(ErrorView, { context: context, error: error, errorInfo: errorInfo || undefined, resetError: this.resetErrorBoundary, retryError: this.retryErrorBoundary, showErrorDetails: Boolean(showErrorDetails), showReset: allowReset !== false, showRetry: showRetry }));
110
+ }
111
+ }
112
+ Object.defineProperty(InternalErrorBoundary, "displayName", {
113
+ enumerable: true,
114
+ configurable: true,
115
+ writable: true,
116
+ value: 'SonicErrorBoundary'
117
+ });
118
+ function ErrorBoundary({ children, persistError = false, ...props }) {
119
+ return (jsx(InternalErrorBoundary, { ...props, children: props.ignoreGlobalUnhandledErrors ? (children) : (jsx(GlobalUnhandledErrorHandler, { context: props.context, children: children })) }, persistError ? undefined : Math.random().toString()));
120
+ }
121
+
122
+ export { ErrorBoundary };
@@ -0,0 +1,17 @@
1
+ export declare class RetryableError extends Error {
2
+ readonly isRetryable = true;
3
+ readonly retryDelay?: number;
4
+ constructor(message: string, retryDelay?: number);
5
+ }
6
+ export declare class FatalError extends Error {
7
+ readonly isFatal = true;
8
+ constructor(message: string);
9
+ }
10
+ export declare class NetworkError extends RetryableError {
11
+ readonly statusCode?: number;
12
+ constructor(message: string, statusCode?: number, retryDelay?: number);
13
+ }
14
+ export declare class AuthenticationError extends Error {
15
+ readonly isAuthError = true;
16
+ constructor(message?: string);
17
+ }
@@ -0,0 +1,62 @@
1
+ class RetryableError extends Error {
2
+ constructor(message, retryDelay) {
3
+ super(message);
4
+ Object.defineProperty(this, "isRetryable", {
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true,
8
+ value: true
9
+ });
10
+ Object.defineProperty(this, "retryDelay", {
11
+ enumerable: true,
12
+ configurable: true,
13
+ writable: true,
14
+ value: void 0
15
+ });
16
+ this.name = 'RetryableError';
17
+ this.retryDelay = retryDelay;
18
+ Object.setPrototypeOf(this, RetryableError.prototype);
19
+ }
20
+ }
21
+ class FatalError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ Object.defineProperty(this, "isFatal", {
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ value: true
29
+ });
30
+ this.name = 'FatalError';
31
+ Object.setPrototypeOf(this, FatalError.prototype);
32
+ }
33
+ }
34
+ class NetworkError extends RetryableError {
35
+ constructor(message, statusCode, retryDelay = 3000) {
36
+ super(message, retryDelay);
37
+ Object.defineProperty(this, "statusCode", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: void 0
42
+ });
43
+ this.name = 'NetworkError';
44
+ this.statusCode = statusCode;
45
+ Object.setPrototypeOf(this, NetworkError.prototype);
46
+ }
47
+ }
48
+ class AuthenticationError extends Error {
49
+ constructor(message = 'Authentication required') {
50
+ super(message);
51
+ Object.defineProperty(this, "isAuthError", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: true
56
+ });
57
+ this.name = 'AuthenticationError';
58
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
59
+ }
60
+ }
61
+
62
+ export { AuthenticationError, FatalError, NetworkError, RetryableError };
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ import { ErrorContext } from './types';
3
+ interface GlobalUnhandledErrorHandlerProps {
4
+ captureUnhandledRejections?: boolean;
5
+ captureWindowErrors?: boolean;
6
+ context?: ErrorContext;
7
+ ignore?: boolean;
8
+ onErrorCaught?: (error: Error, source: 'unhandledrejection' | 'error', context?: ErrorContext) => void;
9
+ }
10
+ export declare function GlobalUnhandledErrorHandler({ children, ...props }: GlobalUnhandledErrorHandlerProps & {
11
+ children?: ReactNode;
12
+ }): import("react/jsx-runtime").JSX.Element;
13
+ export {};
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { createContext, useContext, useEffect, useState, useMemo } from 'react';
4
+
5
+ function InternalGlobalUnhandledErrorHandler({ captureUnhandledRejections = true, captureWindowErrors = true, context, ignore, onErrorCaught, }) {
6
+ const [error, setError] = useState(null);
7
+ useEffect(() => {
8
+ if (typeof window === 'undefined' || ignore)
9
+ return;
10
+ const handleUnhandledRejection = (event) => {
11
+ event.preventDefault();
12
+ const error = event.reason instanceof Error
13
+ ? event.reason
14
+ : new Error(String(event.reason));
15
+ if (onErrorCaught)
16
+ onErrorCaught(error, 'unhandledrejection', context);
17
+ setError(error);
18
+ };
19
+ const handleWindowError = (event, _source, _lineno, _colno, error) => {
20
+ const actualError = error ||
21
+ (typeof event === 'string'
22
+ ? new Error(event)
23
+ : new Error(event.message));
24
+ if (onErrorCaught)
25
+ onErrorCaught(actualError, 'error', context);
26
+ setError(actualError);
27
+ return true;
28
+ };
29
+ if (captureUnhandledRejections)
30
+ window.addEventListener('unhandledrejection', handleUnhandledRejection);
31
+ if (captureWindowErrors)
32
+ window.addEventListener('error', handleWindowError);
33
+ return () => {
34
+ if (typeof window === 'undefined')
35
+ return;
36
+ if (captureUnhandledRejections)
37
+ window.removeEventListener('unhandledrejection', handleUnhandledRejection);
38
+ if (captureWindowErrors)
39
+ window.removeEventListener('error', handleWindowError);
40
+ };
41
+ }, [
42
+ onErrorCaught,
43
+ captureWindowErrors,
44
+ captureUnhandledRejections,
45
+ context,
46
+ ignore,
47
+ ]);
48
+ // Throw for the error boundary to catch
49
+ if (error)
50
+ throw error;
51
+ return null;
52
+ }
53
+ const ErrorBoundaryContext = createContext(undefined);
54
+ function GlobalUnhandledErrorHandler({ children, ...props }) {
55
+ const context = useContext(ErrorBoundaryContext);
56
+ useEffect(() => {
57
+ if (!context)
58
+ return;
59
+ context.hasChild(true);
60
+ return () => context.hasChild(false);
61
+ }, [context]);
62
+ const [ignore, setIgnore] = useState(props.ignore ?? false);
63
+ const value = useMemo(() => ({ hasChild: setIgnore }), [setIgnore]);
64
+ return (jsxs(ErrorBoundaryContext.Provider, { value: value, children: [jsx(InternalGlobalUnhandledErrorHandler, { ...props, ignore: ignore }, ignore.toString()), children] }));
65
+ }
66
+
67
+ export { GlobalUnhandledErrorHandler };
@@ -0,0 +1,15 @@
1
+ import { ErrorInfo } from 'react';
2
+ export interface ErrorViewProps {
3
+ context?: ErrorContext;
4
+ error: Error;
5
+ errorInfo?: ErrorInfo;
6
+ resetError: () => void;
7
+ retryError: () => void;
8
+ showErrorDetails?: boolean;
9
+ showReset?: boolean;
10
+ showRetry?: boolean;
11
+ }
12
+ export interface ErrorContext {
13
+ [key: string]: unknown;
14
+ feature?: string;
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sonic-equipment/ui",
3
- "version": "232.0.0",
3
+ "version": "234.0.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "engines": {