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/components/ProtectCheck/ProtectCheckCard.tsx b/packages/ui/src/components/ProtectCheck/ProtectCheckCard.tsx new file mode 100644 index 00000000000..40746674e65 --- /dev/null +++ b/packages/ui/src/components/ProtectCheck/ProtectCheckCard.tsx @@ -0,0 +1,93 @@ +import { Card } from '@/ui/elements/Card'; +import { Header } from '@/ui/elements/Header'; + +import { + Box, + Button, + Col, + descriptors, + Flex, + Flow, + localizationKeys, + Spinner, + useLocalizations, +} from '../../customizables'; +import { useSpinDelay } from '../../hooks'; +import type { ProtectCheckRunnerState } 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: ProtectCheckRunnerState; +}; + +export const ProtectCheckCard = ({ flow, runner }: ProtectCheckCardProps) => { + const { containerRef, isRunning, isWidgetVisible, error, retry } = runner; + 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 card header alone carries the first ~300ms. + // The error and widget-visibility gates stay OUTSIDE the delay hook below: 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 ( + + + + + + + + {error} + + + {showSpinner && !error && !isWidgetVisible ? ( + + + + ) : null} + {error ? ( +