ui phase 11
This commit is contained in:
@@ -41,4 +41,58 @@ describe('<ConfirmDialog/>', () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Cancel' }));
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('renders the typed-confirmation field when requireTypedConfirmation is set', () => {
|
||||
wrap(
|
||||
<ConfirmDialog
|
||||
open
|
||||
onConfirm={jest.fn()}
|
||||
{...base}
|
||||
requireTypedConfirmation={['CONFIRM', '150000']}
|
||||
typedConfirmationLabel="Type to confirm"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByLabelText('Type to confirm')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('keeps confirm disabled until the typed value matches, case/whitespace-insensitively', () => {
|
||||
const onConfirm = jest.fn();
|
||||
wrap(
|
||||
<ConfirmDialog
|
||||
open
|
||||
onConfirm={onConfirm}
|
||||
{...base}
|
||||
requireTypedConfirmation={['CONFIRM', '150000']}
|
||||
typedConfirmationLabel="Type to confirm"
|
||||
/>,
|
||||
);
|
||||
const confirmBtn = screen.getByRole('button', { name: 'Run' });
|
||||
const field = screen.getByLabelText('Type to confirm');
|
||||
|
||||
expect(confirmBtn).toBeDisabled();
|
||||
|
||||
fireEvent.change(field, { target: { value: 'nope' } });
|
||||
expect(confirmBtn).toBeDisabled();
|
||||
|
||||
fireEvent.change(field, { target: { value: ' confirm ' } });
|
||||
expect(confirmBtn).not.toBeDisabled();
|
||||
|
||||
fireEvent.click(confirmBtn);
|
||||
expect(onConfirm).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it('enables confirm on an exact match against a different allowed value (e.g. an amount)', () => {
|
||||
wrap(
|
||||
<ConfirmDialog
|
||||
open
|
||||
onConfirm={jest.fn()}
|
||||
{...base}
|
||||
requireTypedConfirmation={['CONFIRM', '150000']}
|
||||
typedConfirmationLabel="Type to confirm"
|
||||
/>,
|
||||
);
|
||||
const confirmBtn = screen.getByRole('button', { name: 'Run' });
|
||||
fireEvent.change(screen.getByLabelText('Type to confirm'), { target: { value: '150000' } });
|
||||
expect(confirmBtn).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,15 @@ export interface ConfirmDialogProps {
|
||||
requireReason?: boolean;
|
||||
reasonLabel?: string;
|
||||
reasonPlaceholder?: string;
|
||||
/**
|
||||
* When set, confirm stays disabled until the typed value case-insensitively matches ONE of these
|
||||
* (e.g. the literal word «تایید» OR the exact amount digit-string) — the guard for an irreversible
|
||||
* action like running a payout batch. A gate, not a payload: `onConfirm`'s signature is unchanged, the
|
||||
* typed value itself is never passed to the caller.
|
||||
*/
|
||||
requireTypedConfirmation?: string[];
|
||||
typedConfirmationLabel?: string;
|
||||
typedConfirmationPlaceholder?: string;
|
||||
/** MUI color for the confirm button — `error` for a destructive action. */
|
||||
confirmColor?: 'primary' | 'error' | 'secondary';
|
||||
}
|
||||
@@ -52,21 +61,31 @@ const ConfirmDialog: FunctionComponent<ConfirmDialogProps> = ({
|
||||
requireReason = false,
|
||||
reasonLabel,
|
||||
reasonPlaceholder,
|
||||
requireTypedConfirmation,
|
||||
typedConfirmationLabel,
|
||||
typedConfirmationPlaceholder,
|
||||
confirmColor = 'primary',
|
||||
}) => {
|
||||
const [reason, setReason] = useState('');
|
||||
const [typedValue, setTypedValue] = useState('');
|
||||
|
||||
const close = () => {
|
||||
setReason('');
|
||||
setTypedValue('');
|
||||
onClose();
|
||||
};
|
||||
|
||||
const confirm = () => {
|
||||
onConfirm(requireReason ? reason.trim() : undefined);
|
||||
setReason('');
|
||||
setTypedValue('');
|
||||
};
|
||||
|
||||
const confirmDisabled = loading || (requireReason && reason.trim().length === 0);
|
||||
const confirmDisabled =
|
||||
loading ||
|
||||
(requireReason && reason.trim().length === 0) ||
|
||||
(requireTypedConfirmation != null &&
|
||||
!requireTypedConfirmation.some((v) => v.trim().toLowerCase() === typedValue.trim().toLowerCase()));
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={loading ? undefined : close} fullWidth maxWidth="xs">
|
||||
@@ -92,6 +111,17 @@ const ConfirmDialog: FunctionComponent<ConfirmDialogProps> = ({
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
) : null}
|
||||
{requireTypedConfirmation ? (
|
||||
<TextField
|
||||
autoFocus
|
||||
fullWidth
|
||||
value={typedValue}
|
||||
onChange={(e) => setTypedValue(e.target.value)}
|
||||
label={typedConfirmationLabel}
|
||||
placeholder={typedConfirmationPlaceholder}
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
) : null}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<AppButton variant="text" color="inherit" onClick={close} disabled={loading}>
|
||||
|
||||
Reference in New Issue
Block a user