Migrate CaptchaDialog to AxoConfirmDialog

This commit is contained in:
Jamie
2026-09-08 11:53:47 -07:00
committed by GitHub
parent 694074d2f2
commit 62b1fccecb
8 changed files with 79 additions and 164 deletions
+4
View File
@@ -7471,6 +7471,10 @@
"messageformat": "After verifying, you can continue messaging. Any paused messages will automatically be sent.",
"description": "First paragraph in the captcha dialog"
},
"icu:CaptchaDialog__continue": {
"messageformat": "Continue",
"description": "Continue action in dialog"
},
"icu:CaptchaDialog--can-close__title": {
"messageformat": "Continue without verifying?",
"description": "Header in the captcha dialog that can be closed"
-53
View File
@@ -172,57 +172,4 @@
align-items: flex-end;
}
}
// Overrides for a modal with important message
&--important {
padding-block: 10px 16px;
padding-inline: 12px;
.module-Modal__header {
// Necessary because of the larger top margins for the title
align-items: start;
padding: 0;
}
.module-Modal__body {
padding-block: 0 4px !important;
padding-inline: 12px !important;
}
.module-Modal__body p {
margin-block: 0 20px;
margin-inline: 0;
}
.module-Modal__title {
@include mixins.font-title-2;
text-align: center;
margin-block: 10px 22px;
margin-inline: 0;
flex-shrink: 0;
&--with-x-button {
margin-block-start: 22px;
}
}
.module-Modal__button-footer {
justify-content: center;
margin-top: 27px;
flex-grow: 0;
flex-shrink: 0;
padding-block: 0 4px;
padding-inline: 12px;
.module-Button {
flex-grow: 1;
max-width: 152px;
&:not(:first-child) {
margin-inline-start: 16px;
}
}
}
}
}
+4 -1
View File
@@ -16,13 +16,16 @@ export namespace AxoConfirmDialog {
title: string | ReactElement;
description: string | ReactElement;
forceAlwaysBreakToSeparateLines?: boolean | null;
escape?: AxoAlertDialog.ContentEscape;
children?: ReactNode;
}>;
export const Root: FC<RootProps> = memo(props => {
return (
<AxoAlertDialog.Root open={props.open} onOpenChange={props.onOpenChange}>
<AxoAlertDialog.Content escape="cancel-is-destructive">
<AxoAlertDialog.Content
escape={props.escape ?? 'cancel-is-destructive'}
>
<AxoAlertDialog.Body>
<AxoAlertDialog.Title>{props.title}</AxoAlertDialog.Title>
<AxoAlertDialog.Description>
+14 -16
View File
@@ -2,9 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
import { useState, type JSX } from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta } from '@storybook/react';
import type { PropsType } from './CaptchaDialog.dom.tsx';
import { CaptchaDialog } from './CaptchaDialog.dom.tsx';
import { Button } from './Button.dom.tsx';
@@ -12,22 +10,22 @@ const { i18n } = window.SignalContext;
export default {
title: 'Components/CaptchaDialog',
argTypes: {
isPending: { control: { type: 'boolean' } },
},
args: {
i18n,
isPending: false,
onContinue: action('onContinue'),
},
} satisfies Meta<PropsType>;
} satisfies Meta;
export function Basic(args: PropsType): JSX.Element {
const [isSkipped, setIsSkipped] = useState(false);
export function Basic(): JSX.Element {
const [pending, setPending] = useState(false);
const [skipped, setSkipped] = useState(false);
if (isSkipped) {
return <Button onClick={() => setIsSkipped(false)}>Show again</Button>;
if (skipped) {
return <Button onClick={() => setSkipped(false)}>Show again</Button>;
}
return <CaptchaDialog {...args} onSkip={() => setIsSkipped(true)} />;
return (
<CaptchaDialog
i18n={i18n}
pending={pending}
onContinue={() => setPending(true)}
onSkip={() => setSkipped(true)}
/>
);
}
+49 -88
View File
@@ -1,112 +1,73 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { useRef, useState, type JSX, type MouseEvent } from 'react';
import { useCallback, useState, type JSX, type MouseEvent } from 'react';
import type { LocalizerType } from '../types/Util.std.ts';
import { Button, ButtonVariant } from './Button.dom.tsx';
import { Modal } from './Modal.dom.tsx';
import { Spinner } from './Spinner.dom.tsx';
import { AxoConfirmDialog } from '../axo/AxoConfirmDialog.dom.tsx';
export type PropsType = Readonly<{
export type CaptchaDialogProps = Readonly<{
i18n: LocalizerType;
isPending: boolean;
pending: boolean;
onContinue: () => void;
onSkip: () => void;
}>;
export function CaptchaDialog({
i18n,
isPending,
onSkip,
onContinue,
}: PropsType): JSX.Element {
const [isClosing, setIsClosing] = useState(false);
export function CaptchaDialog(props: CaptchaDialogProps): JSX.Element {
const { i18n, onContinue } = props;
const [closing, setClosing] = useState(false);
const buttonRef = useRef<HTMLButtonElement | null>(null);
const handleContinue = useCallback(
(event: MouseEvent) => {
event.preventDefault();
onContinue();
},
[onContinue]
);
const onCancelClick = (event: MouseEvent) => {
event.preventDefault();
setIsClosing(false);
};
const onSkipClick = (event: MouseEvent) => {
event.preventDefault();
onSkip();
};
if (isClosing && !isPending) {
const footer = (
<>
<Button onClick={onCancelClick} variant={ButtonVariant.Secondary}>
{i18n('icu:cancel')}
</Button>
<Button onClick={onSkipClick} variant={ButtonVariant.Destructive}>
{i18n('icu:CaptchaDialog--can_close__skip-verification')}
</Button>
</>
);
if (closing) {
return (
<Modal
modalName="CaptchaDialog"
moduleClassName="module-Modal"
i18n={i18n}
<AxoConfirmDialog.Root
title={i18n('icu:CaptchaDialog--can-close__title')}
onClose={() => setIsClosing(false)}
description={<p>{i18n('icu:CaptchaDialog--can-close__body')}</p>}
open
onOpenChange={() => setClosing(false)}
key="skip"
modalFooter={footer}
>
<section>
<p>{i18n('icu:CaptchaDialog--can-close__body')}</p>
</section>
</Modal>
<AxoConfirmDialog.Cancel />
<AxoConfirmDialog.Action
variant="strong-destructive"
onClick={props.onSkip}
>
{i18n('icu:CaptchaDialog--can_close__skip-verification')}
</AxoConfirmDialog.Action>
</AxoConfirmDialog.Root>
);
}
const onContinueClick = (event: MouseEvent) => {
event.preventDefault();
onContinue();
};
const updateButtonRef = (button: HTMLButtonElement): void => {
buttonRef.current = button;
if (button) {
button.focus();
}
};
const footer = (
<Button
disabled={isPending}
onClick={onContinueClick}
ref={updateButtonRef}
variant={ButtonVariant.Primary}
>
{isPending ? (
<Spinner size="22px" svgSize="small" direction="on-primary-button" />
) : (
'Continue'
)}
</Button>
);
return (
<Modal
modalName="CaptchaDialog.pending"
moduleClassName="module-Modal--important"
i18n={i18n}
<AxoConfirmDialog.Root
title={i18n('icu:CaptchaDialog__title')}
hasXButton
onClose={() => setIsClosing(true)}
key="primary"
modalFooter={footer}
description={
<>
<p>{i18n('icu:CaptchaDialog__first-paragraph')}</p>
<p>{i18n('icu:CaptchaDialog__second-paragraph')}</p>
</>
}
open
onOpenChange={() => setClosing(true)}
escape="cancel-is-noop"
>
<section>
<p>{i18n('icu:CaptchaDialog__first-paragraph')}</p>
<p>{i18n('icu:CaptchaDialog__second-paragraph')}</p>
</section>
</Modal>
<AxoConfirmDialog.Cancel>
{i18n('icu:CaptchaDialog--can_close__skip-verification')}
</AxoConfirmDialog.Cancel>
<AxoConfirmDialog.Action
variant="strong-primary"
pending={props.pending}
onClick={handleContinue}
autoFocus
>
{i18n('icu:CaptchaDialog__continue')}
</AxoConfirmDialog.Action>
</AxoConfirmDialog.Root>
);
}
+1 -1
View File
@@ -283,7 +283,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => {
renderCaptchaDialog: () => (
<CaptchaDialog
i18n={i18n}
isPending={overrideProps.challengeStatus === 'pending'}
pending={overrideProps.challengeStatus === 'pending'}
onContinue={action('onCaptchaContinue')}
onSkip={action('onCaptchaSkip')}
/>
+1 -1
View File
@@ -27,7 +27,7 @@ export const SmartCaptchaDialog = memo(function SmartCaptchaDialog({
return (
<CaptchaDialog
i18n={i18n}
isPending={isPending}
pending={isPending}
onSkip={onSkip}
onContinue={handleContinue}
/>
+6 -4
View File
@@ -14,6 +14,7 @@ import {
typeIntoInput,
waitForEnabledComposer,
} from '../helpers.node.ts';
import { expect } from 'playwright/test';
export const debug = createDebug('mock:test:challenge:receipts');
@@ -282,11 +283,12 @@ describe('challenge/receipts', function (this: Mocha.Suite) {
/** First, challenge returns 428 (try again) */
debug('Waiting for challenge');
const firstChallengeRequest = await app.waitForChallenge();
const challengeDialog = await window
.getByTestId('CaptchaDialog.pending')
.elementHandle();
const challengeDialog = window.getByRole('alertdialog', {
name: 'Verify to continue messaging',
});
await expect(challengeDialog).toBeVisible();
assert.exists(challengeDialog);
server.respondToChallengesWith(428);
debug('Solving challenge');