forked from openedx/frontend-app-admin-console
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathToastManagerContext.tsx
More file actions
120 lines (102 loc) · 3.79 KB
/
ToastManagerContext.tsx
File metadata and controls
120 lines (102 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import {
createContext, useContext, useState, useMemo,
} from 'react';
import { logError } from '@edx/frontend-platform/logging';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Toast } from '@openedx/paragon';
import messages from './messages';
import { DEFAULT_TOAST_DELAY } from './constants';
type ToastType = 'success' | 'error' | 'error-retry';
export const ERROR_TOAST_MAP: Record<number | string, { type: ToastType; messageId: string }> = {
// Transient (retryable) server errors
500: { type: 'error-retry', messageId: 'library.authz.team.toast.500.error.message' },
502: { type: 'error-retry', messageId: 'library.authz.team.toast.502.error.message' },
503: { type: 'error-retry', messageId: 'library.authz.team.toast.503.error.message' },
408: { type: 'error-retry', messageId: 'library.authz.team.toast.408.error.message' },
// Generic fallback error
DEFAULT: { type: 'error-retry', messageId: 'library.authz.team.toast.default.error.message' },
};
export interface AppToast {
id: string;
message: string;
type: ToastType;
onRetry?: () => void;
delay?: number;
}
const Bold = (chunk: string) => <b>{chunk}</b>;
const Br = () => <br />;
type ToastManagerContextType = {
showToast: (toast: Omit<AppToast, 'id'>) => void;
showErrorToast: (error, retryFn?: () => void) => void;
Bold: (chunk: string) => JSX.Element;
Br: () => JSX.Element;
};
const ToastManagerContext = createContext<ToastManagerContextType | undefined>(undefined);
interface ToastManagerProviderProps {
children: React.ReactNode | React.ReactNode[];
}
export const ToastManagerProvider = ({ children }: ToastManagerProviderProps) => {
const intl = useIntl();
const [toasts, setToasts] = useState<(AppToast & { visible: boolean })[]>([]);
const showToast = (toast: Omit<AppToast, 'id'>) => {
const id = `toast-notification-${Date.now()}-${Math.floor(Math.random() * 1000000)}`;
const newToast = { ...toast, id, visible: true };
setToasts(prev => [...prev, newToast]);
};
const discardToast = (id: string) => {
setToasts(prev => prev.map(t => (t.id === id ? { ...t, visible: false } : t)));
setTimeout(() => {
setToasts(prev => prev.filter(t => t.id !== id));
}, 5000);
};
const value = useMemo<ToastManagerContextType>(() => {
const showErrorToast = (error, retryFn?: () => void) => {
logError(error);
const errorStatus = error?.customAttributes?.httpErrorStatus;
const toastConfig = ERROR_TOAST_MAP[errorStatus] || ERROR_TOAST_MAP.DEFAULT;
const message = intl.formatMessage(messages[toastConfig.messageId], { Bold, Br });
showToast({
message,
type: toastConfig.type,
onRetry: toastConfig.type === 'error-retry' && retryFn ? retryFn : undefined,
});
};
return ({
showToast,
showErrorToast,
Bold,
Br,
});
}, [intl]);
return (
<ToastManagerContext.Provider value={value}>
{children}
<div className="toast-container">
{toasts.map(toast => (
<Toast
key={toast.id}
show={toast.visible}
onClose={() => discardToast(toast.id)}
delay={toast.delay ?? DEFAULT_TOAST_DELAY}
action={toast.onRetry ? {
onClick: () => {
discardToast(toast.id);
toast.onRetry?.();
},
label: intl.formatMessage(messages['library.authz.team.toast.retry.label']),
} : undefined}
>
{toast.message}
</Toast>
))}
</div>
</ToastManagerContext.Provider>
);
};
export const useToastManager = (): ToastManagerContextType => {
const context = useContext(ToastManagerContext);
if (!context) {
throw new Error('useToastManager must be used within a ToastManagerProvider');
}
return context;
};