Skip to content

Commit 32e0c24

Browse files
feat: Add non-navigable alert modals
1 parent e784dfc commit 32e0c24

File tree

7 files changed

+306
-34
lines changed

7 files changed

+306
-34
lines changed

ui/components/app/alert-system/multiple-alert-modal/multiple-alert-modal.test.tsx

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('MultipleAlertModal', () => {
2323
const FROM_ALERT_KEY_MOCK = 'from';
2424
const CONTRACT_ALERT_KEY_MOCK = 'contract';
2525
const DATA_ALERT_KEY_MOCK = 'data';
26+
const HIDDEN_ALERT_KEY_MOCK = 'hidden';
2627
const onAcknowledgeClickMock = jest.fn();
2728
const onCloseMock = jest.fn();
2829

@@ -282,4 +283,98 @@ describe('MultipleAlertModal', () => {
282283
expect(getByText(alertsMock[1].message)).toBeInTheDocument();
283284
});
284285
});
286+
287+
describe('Alerts hidden from navigation', () => {
288+
const alertsWithHiddenNavigationMock = [
289+
{
290+
key: FROM_ALERT_KEY_MOCK,
291+
field: FROM_ALERT_KEY_MOCK,
292+
severity: Severity.Warning,
293+
message: 'Alert 1',
294+
reason: 'Reason 1',
295+
alertDetails: ['Detail 1', 'Detail 2'],
296+
},
297+
{
298+
key: HIDDEN_ALERT_KEY_MOCK,
299+
field: HIDDEN_ALERT_KEY_MOCK,
300+
severity: Severity.Danger,
301+
message: 'Hidden Alert',
302+
hideFromAlertNavigation: true,
303+
},
304+
{
305+
key: CONTRACT_ALERT_KEY_MOCK,
306+
field: CONTRACT_ALERT_KEY_MOCK,
307+
severity: Severity.Info,
308+
message: 'Alert 3',
309+
},
310+
];
311+
312+
const mockStoreWithHiddenNavigation = configureMockStore([])({
313+
...STATE_MOCK,
314+
confirmAlerts: {
315+
alerts: { [OWNER_ID_MOCK]: alertsWithHiddenNavigationMock },
316+
confirmed: {
317+
[OWNER_ID_MOCK]: {
318+
[FROM_ALERT_KEY_MOCK]: false,
319+
[HIDDEN_ALERT_KEY_MOCK]: false,
320+
[CONTRACT_ALERT_KEY_MOCK]: false,
321+
},
322+
},
323+
},
324+
});
325+
326+
const mockStoreWithHiddenNavigationConfirmed = configureMockStore([])({
327+
...STATE_MOCK,
328+
confirmAlerts: {
329+
alerts: { [OWNER_ID_MOCK]: alertsWithHiddenNavigationMock },
330+
confirmed: {
331+
[OWNER_ID_MOCK]: {
332+
[FROM_ALERT_KEY_MOCK]: true,
333+
[HIDDEN_ALERT_KEY_MOCK]: true,
334+
[CONTRACT_ALERT_KEY_MOCK]: true,
335+
},
336+
},
337+
},
338+
});
339+
340+
it('does not render navigation controls when the selected alert hides navigation', () => {
341+
const { queryByTestId } = renderWithProvider(
342+
<MultipleAlertModal
343+
{...defaultProps}
344+
alertKey={HIDDEN_ALERT_KEY_MOCK}
345+
/>,
346+
mockStoreWithHiddenNavigation,
347+
);
348+
349+
expect(queryByTestId('alert-modal-next-button')).toBeNull();
350+
expect(queryByTestId('alert-modal-back-button')).toBeNull();
351+
});
352+
353+
it('skips alerts hidden from navigation when cycling forward', () => {
354+
const { getByTestId, getByText, queryByText } = renderWithProvider(
355+
<MultipleAlertModal {...defaultProps} />,
356+
mockStoreWithHiddenNavigation,
357+
);
358+
359+
fireEvent.click(getByTestId('alert-modal-next-button'));
360+
361+
expect(queryByText('Hidden Alert')).not.toBeInTheDocument();
362+
expect(getByText('Alert 3')).toBeInTheDocument();
363+
});
364+
365+
it('acknowledges an alert that hides navigation without cycling', () => {
366+
onAcknowledgeClickMock.mockReset();
367+
const { getByTestId } = renderWithProvider(
368+
<MultipleAlertModal
369+
{...defaultProps}
370+
alertKey={HIDDEN_ALERT_KEY_MOCK}
371+
/>,
372+
mockStoreWithHiddenNavigationConfirmed,
373+
);
374+
375+
fireEvent.click(getByTestId('alert-modal-button'));
376+
377+
expect(onAcknowledgeClickMock).toHaveBeenCalledTimes(1);
378+
});
379+
});
285380
});

ui/components/app/alert-system/multiple-alert-modal/multiple-alert-modal.tsx

Lines changed: 99 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -165,73 +165,146 @@ export function MultipleAlertModal({
165165
showCloseIcon = true,
166166
skipAlertNavigation = false,
167167
}: MultipleAlertModalProps) {
168-
const { isAlertConfirmed, fieldAlerts, alerts } = useAlerts(ownerId);
168+
const {
169+
alerts: alertsFromHook,
170+
fieldAlerts: fieldAlertsFromHook,
171+
isAlertConfirmed,
172+
navigableAlerts: navigableAlertsFromHook,
173+
navigableFieldAlerts: navigableFieldAlertsFromHook,
174+
} = useAlerts(ownerId);
175+
176+
const alerts = alertsFromHook ?? [];
177+
const fieldAlerts = fieldAlertsFromHook ?? [];
178+
const navigableAlerts = navigableAlertsFromHook ?? alerts;
179+
const navigableFieldAlerts = navigableFieldAlertsFromHook ?? fieldAlerts;
180+
169181
const alertsToDisplay = displayAllAlerts ? alerts : fieldAlerts;
182+
const navigableAlertsToDisplay = displayAllAlerts
183+
? navigableAlerts
184+
: navigableFieldAlerts;
170185

171-
const initialAlertIndex = alertsToDisplay.findIndex(
172-
(alert: Alert) => alert.key === alertKey,
173-
);
186+
const initialAlertKey =
187+
alertsToDisplay.find((alert) => alert.key === alertKey)?.key ??
188+
navigableAlertsToDisplay[0]?.key ??
189+
alertsToDisplay[0]?.key;
174190

175-
const [selectedIndex, setSelectedIndex] = useState(
176-
initialAlertIndex === -1 ? 0 : initialAlertIndex,
191+
const [currentAlertKey, setCurrentAlertKey] = useState<string | undefined>(
192+
initialAlertKey,
177193
);
178194

179-
// If the selected alert is not found, default to the first alert
180-
const selectedAlert = alertsToDisplay[selectedIndex] ?? alertsToDisplay[0];
195+
const selectedAlert =
196+
alertsToDisplay.find((alert) => alert.key === currentAlertKey) ??
197+
alertsToDisplay[0];
198+
199+
const currentNavigableIndex = navigableAlertsToDisplay.findIndex(
200+
(alert) => alert.key === (selectedAlert?.key ?? currentAlertKey),
201+
);
181202

182203
const hasUnconfirmedAlerts = alerts.some(
183204
(alert: Alert) =>
184205
!isAlertConfirmed(alert.key) && alert.severity === Severity.Danger,
185206
);
186207

187208
const handleBackButtonClick = useCallback(() => {
188-
setSelectedIndex((prevIndex: number) =>
189-
prevIndex > 0 ? prevIndex - 1 : prevIndex,
209+
const activeAlertKey = selectedAlert?.key ?? currentAlertKey;
210+
const newIndex = navigableAlertsToDisplay.findIndex(
211+
(alert) => alert.key === activeAlertKey,
190212
);
191-
}, []);
213+
214+
if (newIndex > 0) {
215+
setCurrentAlertKey(navigableAlertsToDisplay[newIndex - 1]?.key);
216+
}
217+
}, [currentAlertKey, navigableAlertsToDisplay, selectedAlert]);
192218

193219
const handleNextButtonClick = useCallback(() => {
194-
setSelectedIndex((prevIndex: number) => prevIndex + 1);
195-
}, []);
220+
const activeAlertKey = selectedAlert?.key ?? currentAlertKey;
221+
const newIndex = navigableAlertsToDisplay.findIndex(
222+
(alert) => alert.key === activeAlertKey,
223+
);
224+
225+
if (
226+
newIndex !== -1 &&
227+
newIndex + 1 < navigableAlertsToDisplay.length &&
228+
navigableAlertsToDisplay[newIndex + 1]?.key
229+
) {
230+
setCurrentAlertKey(navigableAlertsToDisplay[newIndex + 1]?.key);
231+
}
232+
}, [currentAlertKey, navigableAlertsToDisplay, selectedAlert]);
196233

197234
const handleAcknowledgeClick = useCallback(() => {
198-
if (skipAlertNavigation) {
235+
const activeAlertKey = selectedAlert?.key ?? currentAlertKey;
236+
const activeAlertHidesNavigation =
237+
selectedAlert?.hideFromAlertNavigation === true;
238+
239+
if (skipAlertNavigation || !activeAlertKey) {
240+
onFinalAcknowledgeClick();
241+
return;
242+
}
243+
244+
if (activeAlertHidesNavigation) {
199245
onFinalAcknowledgeClick();
200246
return;
201247
}
202248

203-
if (selectedIndex + 1 === alertsToDisplay.length) {
249+
const navigableIndex = navigableAlertsToDisplay.findIndex(
250+
(alert) => alert.key === activeAlertKey,
251+
);
252+
253+
if (navigableIndex === -1) {
254+
onFinalAcknowledgeClick();
255+
return;
256+
}
257+
258+
const isLastNavigableAlert =
259+
navigableIndex === navigableAlertsToDisplay.length - 1;
260+
261+
if (isLastNavigableAlert) {
204262
if (!hasUnconfirmedAlerts) {
205263
onFinalAcknowledgeClick();
206264
return;
207265
}
208266

209-
setSelectedIndex(0);
267+
const firstNavigableKey = navigableAlertsToDisplay[0]?.key;
268+
if (firstNavigableKey) {
269+
setCurrentAlertKey(firstNavigableKey);
270+
}
210271
return;
211272
}
212-
handleNextButtonClick();
273+
274+
const nextAlertKey = navigableAlertsToDisplay[navigableIndex + 1]?.key;
275+
if (nextAlertKey) {
276+
setCurrentAlertKey(nextAlertKey);
277+
}
213278
}, [
279+
currentAlertKey,
214280
onFinalAcknowledgeClick,
215-
handleNextButtonClick,
216-
selectedIndex,
217-
alertsToDisplay.length,
218281
hasUnconfirmedAlerts,
282+
navigableAlertsToDisplay,
283+
selectedAlert,
219284
skipAlertNavigation,
220285
]);
221286

287+
const showNavigationButtons =
288+
!skipAlertNavigation &&
289+
selectedAlert?.hideFromAlertNavigation !== true &&
290+
currentNavigableIndex !== -1 &&
291+
navigableAlertsToDisplay.length > 1;
292+
222293
return (
223294
<AlertModal
224295
ownerId={ownerId}
225296
onAcknowledgeClick={handleAcknowledgeClick}
226297
alertKey={selectedAlert?.key}
227298
onClose={onClose}
228299
headerStartAccessory={
229-
<PageNavigation
230-
alerts={alertsToDisplay}
231-
onBackButtonClick={handleBackButtonClick}
232-
onNextButtonClick={handleNextButtonClick}
233-
selectedIndex={selectedIndex}
234-
/>
300+
showNavigationButtons ? (
301+
<PageNavigation
302+
alerts={navigableAlertsToDisplay}
303+
onBackButtonClick={handleBackButtonClick}
304+
onNextButtonClick={handleNextButtonClick}
305+
selectedIndex={currentNavigableIndex}
306+
/>
307+
) : null
235308
}
236309
showCloseIcon={showCloseIcon}
237310
/>

ui/components/app/confirm/info/row/alert-row/alert-row.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,15 @@ export const ConfirmInfoAlertRow = ({
5454
const { getFieldAlerts } = useAlerts(ownerId);
5555
const fieldAlerts = getFieldAlerts(alertKey);
5656
const hasFieldAlert = fieldAlerts.length > 0;
57-
const selectedAlertSeverity = fieldAlerts[0]?.severity;
58-
const selectedAlertKey = fieldAlerts[0]?.key;
59-
const selectedAlertShowArrow = fieldAlerts[0]?.showArrow;
60-
const selectedAlertInlineAlertText = fieldAlerts[0]?.inlineAlertText;
57+
const selectedAlert = fieldAlerts[0];
58+
const selectedAlertSeverity = selectedAlert?.severity;
59+
const selectedAlertKey = selectedAlert?.key;
60+
const selectedAlertShowArrow = selectedAlert?.showArrow;
61+
const selectedAlertInlineAlertText = selectedAlert?.inlineAlertText;
6162
const selectedAlertIsOpenModalOnClick =
62-
fieldAlerts[0]?.isOpenModalOnClick ?? true;
63+
selectedAlert?.isOpenModalOnClick ?? true;
64+
const selectedAlertHideFromAlertNavigation =
65+
selectedAlert?.hideFromAlertNavigation ?? false;
6366

6467
const [alertModalVisible, setAlertModalVisible] = useState<boolean>(false);
6568

@@ -139,7 +142,7 @@ export const ConfirmInfoAlertRow = ({
139142
onFinalAcknowledgeClick={handleModalClose}
140143
onClose={handleModalClose}
141144
showCloseIcon={false}
142-
skipAlertNavigation={true}
145+
skipAlertNavigation={selectedAlertHideFromAlertNavigation}
143146
/>
144147
)}
145148
{confirmInfoRow}

ui/ducks/confirm-alerts/confirm-alerts.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ export type Alert = {
6868
*/
6969
severity: AlertSeverity;
7070

71+
/**
72+
* Whether this alert should be excluded from navigation controls.
73+
*/
74+
hideFromAlertNavigation?: boolean;
75+
7176
/**
7277
* Whether to show the arrow icon on the inline alert.
7378
*/

0 commit comments

Comments
 (0)