From 62b1fccecb862a7664ebbecdf0ccba11893cbe0d Mon Sep 17 00:00:00 2001 From: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:53:47 -0700 Subject: [PATCH] Migrate CaptchaDialog to AxoConfirmDialog --- _locales/en/messages.json | 4 + stylesheets/components/Modal.scss | 53 -------- ts/axo/AxoConfirmDialog.dom.tsx | 5 +- ts/components/CaptchaDialog.dom.stories.tsx | 30 ++--- ts/components/CaptchaDialog.dom.tsx | 137 +++++++------------- ts/components/LeftPane.dom.stories.tsx | 2 +- ts/state/smart/CaptchaDialog.preload.tsx | 2 +- ts/test-mock/rate-limit/viewed_test.node.ts | 10 +- 8 files changed, 79 insertions(+), 164 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index d9cc0dce92..f8968355ab 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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" diff --git a/stylesheets/components/Modal.scss b/stylesheets/components/Modal.scss index 1ce82e850e..81030457ea 100644 --- a/stylesheets/components/Modal.scss +++ b/stylesheets/components/Modal.scss @@ -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; - } - } - } - } } diff --git a/ts/axo/AxoConfirmDialog.dom.tsx b/ts/axo/AxoConfirmDialog.dom.tsx index 1f2224f8cd..a937286d42 100644 --- a/ts/axo/AxoConfirmDialog.dom.tsx +++ b/ts/axo/AxoConfirmDialog.dom.tsx @@ -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 = memo(props => { return ( - + {props.title} diff --git a/ts/components/CaptchaDialog.dom.stories.tsx b/ts/components/CaptchaDialog.dom.stories.tsx index b15fcd1d53..c35a4e9da6 100644 --- a/ts/components/CaptchaDialog.dom.stories.tsx +++ b/ts/components/CaptchaDialog.dom.stories.tsx @@ -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; +} 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 ; + if (skipped) { + return ; } - return setIsSkipped(true)} />; + return ( + setPending(true)} + onSkip={() => setSkipped(true)} + /> + ); } diff --git a/ts/components/CaptchaDialog.dom.tsx b/ts/components/CaptchaDialog.dom.tsx index 34731420d5..41448ee25f 100644 --- a/ts/components/CaptchaDialog.dom.tsx +++ b/ts/components/CaptchaDialog.dom.tsx @@ -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(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 = ( - <> - - - - ); + if (closing) { return ( - setIsClosing(false)} + description={

{i18n('icu:CaptchaDialog--can-close__body')}

} + open + onOpenChange={() => setClosing(false)} key="skip" - modalFooter={footer} > -
-

{i18n('icu:CaptchaDialog--can-close__body')}

-
-
+ + + {i18n('icu:CaptchaDialog--can_close__skip-verification')} + + ); } - const onContinueClick = (event: MouseEvent) => { - event.preventDefault(); - - onContinue(); - }; - - const updateButtonRef = (button: HTMLButtonElement): void => { - buttonRef.current = button; - if (button) { - button.focus(); - } - }; - - const footer = ( - - ); - return ( - setIsClosing(true)} - key="primary" - modalFooter={footer} + description={ + <> +

{i18n('icu:CaptchaDialog__first-paragraph')}

+

{i18n('icu:CaptchaDialog__second-paragraph')}

+ + } + open + onOpenChange={() => setClosing(true)} + escape="cancel-is-noop" > -
-

{i18n('icu:CaptchaDialog__first-paragraph')}

-

{i18n('icu:CaptchaDialog__second-paragraph')}

-
-
+ + {i18n('icu:CaptchaDialog--can_close__skip-verification')} + + + {i18n('icu:CaptchaDialog__continue')} + + ); } diff --git a/ts/components/LeftPane.dom.stories.tsx b/ts/components/LeftPane.dom.stories.tsx index e18f6b5ce0..8eface38a8 100644 --- a/ts/components/LeftPane.dom.stories.tsx +++ b/ts/components/LeftPane.dom.stories.tsx @@ -283,7 +283,7 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => { renderCaptchaDialog: () => ( diff --git a/ts/state/smart/CaptchaDialog.preload.tsx b/ts/state/smart/CaptchaDialog.preload.tsx index bdb64a7a9b..8001c3b6f6 100644 --- a/ts/state/smart/CaptchaDialog.preload.tsx +++ b/ts/state/smart/CaptchaDialog.preload.tsx @@ -27,7 +27,7 @@ export const SmartCaptchaDialog = memo(function SmartCaptchaDialog({ return ( diff --git a/ts/test-mock/rate-limit/viewed_test.node.ts b/ts/test-mock/rate-limit/viewed_test.node.ts index f367838296..a83feea56c 100644 --- a/ts/test-mock/rate-limit/viewed_test.node.ts +++ b/ts/test-mock/rate-limit/viewed_test.node.ts @@ -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');