Skip to content

Commit b6bcd68

Browse files
fix(Tablet): correctly process error in dialog action (#758)
1 parent 799a05f commit b6bcd68

File tree

7 files changed

+196
-141
lines changed

7 files changed

+196
-141
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import {useState, type ReactNode} from 'react';
2+
3+
import {Button, type ButtonProps} from '@gravity-ui/uikit';
4+
5+
import {CriticalActionDialog} from '../CriticalActionDialog';
6+
7+
interface ButtonWithConfirmDialogProps<T, K> {
8+
children: ReactNode;
9+
onConfirmAction: () => Promise<T>;
10+
onConfirmActionSuccess?: (() => Promise<K>) | VoidFunction;
11+
dialogContent: string;
12+
buttonDisabled?: ButtonProps['disabled'];
13+
buttonView?: ButtonProps['view'];
14+
buttonClassName?: ButtonProps['className'];
15+
}
16+
17+
export function ButtonWithConfirmDialog<T, K>({
18+
children,
19+
onConfirmAction,
20+
onConfirmActionSuccess,
21+
dialogContent,
22+
buttonDisabled = false,
23+
buttonView = 'action',
24+
buttonClassName,
25+
}: ButtonWithConfirmDialogProps<T, K>) {
26+
const [isConfirmDialogVisible, setIsConfirmDialogVisible] = useState(false);
27+
const [buttonLoading, setButtonLoading] = useState(false);
28+
29+
const handleConfirmAction = async () => {
30+
setButtonLoading(true);
31+
await onConfirmAction();
32+
setButtonLoading(false);
33+
};
34+
35+
const handleConfirmActionSuccess = async () => {
36+
if (onConfirmActionSuccess) {
37+
setButtonLoading(true);
38+
39+
try {
40+
await onConfirmActionSuccess();
41+
} catch {
42+
} finally {
43+
setButtonLoading(false);
44+
}
45+
}
46+
};
47+
48+
const handleConfirmActionError = () => {
49+
setButtonLoading(false);
50+
};
51+
52+
return (
53+
<>
54+
<CriticalActionDialog
55+
visible={isConfirmDialogVisible}
56+
text={dialogContent}
57+
onConfirm={handleConfirmAction}
58+
onConfirmActionSuccess={handleConfirmActionSuccess}
59+
onConfirmActionError={handleConfirmActionError}
60+
onClose={() => {
61+
setIsConfirmDialogVisible(false);
62+
}}
63+
/>
64+
<Button
65+
onClick={() => setIsConfirmDialogVisible(true)}
66+
view={buttonView}
67+
disabled={buttonDisabled}
68+
loading={!buttonDisabled && buttonLoading}
69+
className={buttonClassName}
70+
>
71+
{children}
72+
</Button>
73+
</>
74+
);
75+
}
Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
11
.ydb-critical-dialog {
2-
width: 252px !important;
2+
width: 400px;
33

44
&__warning-icon {
55
margin-right: 16px;
66
}
77

8+
&__error-icon {
9+
height: 24px;
10+
margin-right: 16px;
11+
12+
color: var(--ydb-color-status-red);
13+
}
14+
815
&__body {
916
display: flex;
1017
align-items: center;
1118

1219
padding: 16px 16px 0;
1320
}
14-
15-
& .yc-dialog-footer {
16-
padding: 20px 4px 4px;
17-
}
18-
19-
& .yc-dialog-footer__children {
20-
display: none;
21-
}
22-
23-
& .yc-dialog-footer__button {
24-
box-sizing: border-box;
25-
min-width: 120px;
26-
margin: 0;
27-
}
28-
29-
& .yc-dialog-footer__button_action_cancel {
30-
margin-right: 4px;
31-
}
3221
}
Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,87 @@
11
import {FormEvent, useState} from 'react';
22
import cn from 'bem-cn-lite';
33
import {Dialog} from '@gravity-ui/uikit';
4+
import {CircleXmarkFill} from '@gravity-ui/icons';
45

6+
import type {IResponseError} from '../../types/api/error';
57
import {Icon} from '../Icon';
8+
import {criticalActionDialogKeyset} from './i18n';
69

710
import './CriticalActionDialog.scss';
811

912
const b = cn('ydb-critical-dialog');
1013

11-
interface CriticalActionDialogProps {
14+
const parseError = (error: IResponseError) => {
15+
if (error.status === 403) {
16+
return criticalActionDialogKeyset('no-rights-error');
17+
}
18+
if (error.statusText) {
19+
return error.statusText;
20+
}
21+
22+
return criticalActionDialogKeyset('default-error');
23+
};
24+
25+
interface CriticalActionDialogProps<T> {
1226
visible: boolean;
1327
text: string;
1428
onClose: VoidFunction;
15-
onConfirm: () => Promise<unknown>;
16-
onConfirmActionFinish: VoidFunction;
29+
onConfirm: () => Promise<T>;
30+
onConfirmActionSuccess: VoidFunction;
31+
onConfirmActionError: VoidFunction;
1732
}
1833

19-
export const CriticalActionDialog = ({
34+
export function CriticalActionDialog<T>({
2035
visible,
2136
text,
2237
onClose,
2338
onConfirm,
24-
onConfirmActionFinish,
25-
}: CriticalActionDialogProps) => {
39+
onConfirmActionSuccess,
40+
onConfirmActionError,
41+
}: CriticalActionDialogProps<T>) {
2642
const [isLoading, setIsLoading] = useState(false);
43+
const [error, setError] = useState<IResponseError>();
2744

2845
const onSubmit = async (e: FormEvent) => {
2946
e.preventDefault();
3047
setIsLoading(true);
3148

32-
return onConfirm().then(() => {
33-
onConfirmActionFinish();
34-
setIsLoading(false);
35-
onClose();
36-
});
49+
return onConfirm()
50+
.then(() => {
51+
onConfirmActionSuccess();
52+
onClose();
53+
})
54+
.catch((err) => {
55+
onConfirmActionError();
56+
setError(err);
57+
})
58+
.finally(() => {
59+
setIsLoading(false);
60+
});
3761
};
3862

39-
return (
40-
<Dialog open={visible} hasCloseButton={false} className={b()} size="s" onClose={onClose}>
63+
const renderDialogContent = () => {
64+
if (error) {
65+
return (
66+
<>
67+
<Dialog.Body className={b('body')}>
68+
<span className={b('error-icon')}>
69+
<CircleXmarkFill width="24" height="22" />
70+
</span>
71+
{parseError(error)}
72+
</Dialog.Body>
73+
74+
<Dialog.Footer
75+
loading={false}
76+
preset="default"
77+
textButtonCancel={criticalActionDialogKeyset('button-close')}
78+
onClickButtonCancel={onClose}
79+
/>
80+
</>
81+
);
82+
}
83+
84+
return (
4185
<form onSubmit={onSubmit}>
4286
<Dialog.Body className={b('body')}>
4387
<span className={b('warning-icon')}>
@@ -49,13 +93,26 @@ export const CriticalActionDialog = ({
4993
<Dialog.Footer
5094
loading={isLoading}
5195
preset="default"
52-
textButtonApply="Confirm"
53-
textButtonCancel="Cancel"
96+
textButtonApply={criticalActionDialogKeyset('button-confirm')}
97+
textButtonCancel={criticalActionDialogKeyset('button-cancel')}
5498
propsButtonApply={{type: 'submit'}}
5599
onClickButtonCancel={onClose}
56100
onClickButtonApply={() => {}}
57101
/>
58102
</form>
103+
);
104+
};
105+
106+
return (
107+
<Dialog
108+
open={visible}
109+
hasCloseButton={false}
110+
className={b()}
111+
size="s"
112+
onClose={onClose}
113+
onTransitionExited={() => setError(undefined)}
114+
>
115+
{renderDialogContent()}
59116
</Dialog>
60117
);
61-
};
118+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"default-error": "Something went wrong, action cannot be completed",
3+
"no-rights-error": "You don't have enough rights to complete the operation",
4+
5+
"button-confirm": "Confirm",
6+
"button-cancel": "Cancel",
7+
"button-close": "Close"
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'ydb-critical-action-dialog';
6+
7+
export const criticalActionDialogKeyset = registerKeysets(COMPONENT, {en});

src/containers/Tablet/Tablet.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export const Tablet = () => {
7373
}, [dispatch, tablet]);
7474

7575
const fetchData = useCallback(() => {
76-
dispatch(getTablet(id));
76+
return dispatch(getTablet(id));
7777
}, [dispatch, id]);
7878

7979
useAutofetcher(fetchData, [fetchData], true);

0 commit comments

Comments
 (0)