From c0f0bfaeb7996f9d8faf5a775ac8a36e5704870e Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 28 Sep 2026 09:16:17 -0700 Subject: [PATCH 1/2] refactor(ui): share one ProtectCheckCard between the sign-in and sign-up protect check cards SignInProtectCheck and SignUpProtectCheck carried the same card markup, differing only in the sign-in or sign-up localization keys. ProtectCheckCard in common/ now renders it from the runner's state and the flow. Each card keeps its runner call and continuation, its stale-visit guard, and its blocked card. --- .changeset/protect-check-card.md | 2 + packages/ui/src/common/ProtectCheckCard.tsx | 93 +++++++++++++++++++ packages/ui/src/common/index.ts | 1 + .../components/SignIn/SignInProtectCheck.tsx | 71 ++------------ .../components/SignUp/SignUpProtectCheck.tsx | 71 ++------------ 5 files changed, 108 insertions(+), 130 deletions(-) create mode 100644 .changeset/protect-check-card.md create mode 100644 packages/ui/src/common/ProtectCheckCard.tsx diff --git a/.changeset/protect-check-card.md b/.changeset/protect-check-card.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/protect-check-card.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/ui/src/common/ProtectCheckCard.tsx b/packages/ui/src/common/ProtectCheckCard.tsx new file mode 100644 index 00000000000..51f6bc1d82d --- /dev/null +++ b/packages/ui/src/common/ProtectCheckCard.tsx @@ -0,0 +1,93 @@ +import { + Box, + Button, + Col, + descriptors, + Flex, + Flow, + localizationKeys, + Spinner, + useLocalizations, +} from '../customizables'; +import { Card } from '../elements/Card'; +import { useCardState } from '../elements/contexts'; +import { Header } from '../elements/Header'; +import { useSpinDelay } from '../hooks'; +import type { ProtectCheckRunner } from '../hooks/useProtectCheckRunner'; + +const localizationKeysByFlow = { + signIn: { + title: localizationKeys('signIn.protectCheck.title'), + subtitle: localizationKeys('signIn.protectCheck.subtitle'), + loading: localizationKeys('signIn.protectCheck.loading'), + retryButton: localizationKeys('signIn.protectCheck.retryButton'), + }, + signUp: { + title: localizationKeys('signUp.protectCheck.title'), + subtitle: localizationKeys('signUp.protectCheck.subtitle'), + loading: localizationKeys('signUp.protectCheck.loading'), + retryButton: localizationKeys('signUp.protectCheck.retryButton'), + }, +}; + +type ProtectCheckCardProps = { + flow: 'signIn' | 'signUp'; + runner: ProtectCheckRunner; +}; + +export const ProtectCheckCard = ({ flow, runner }: ProtectCheckCardProps) => { + const { containerRef, isRunning, isWidgetVisible, hasError, retry } = runner; + const card = useCardState(); + const { t } = useLocalizations(); + const keys = localizationKeysByFlow[flow]; + + // Debounce the spinner's entrance so a near-instant check (or a script that signals its + // widget immediately) never flashes it. The error and widget-visibility gates stay outside + // the delay hook: its minimum visible duration must never outrank the handshake's "spinner is + // gone when the promise resolves" guarantee, nor keep a spinner next to the retry button. + const showSpinner = useSpinDelay(isRunning, { delay: 300 }); + + return ( + + + + + + + + {card.error} + + + {showSpinner && !hasError && !isWidgetVisible ? ( + + + + ) : null} + {hasError ? ( +