diff options
author | Eric Bailey <git@esb.lol> | 2025-07-16 13:58:07 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-07-16 13:58:07 -0500 |
commit | 1dbc331314278cb7a42ded9b190dac7038ad9878 (patch) | |
tree | b5d44e1ea75ea9d5343eec90425c8c7ac74df39f /src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx | |
parent | 712c3ad4211e2e68d0cdbcc480967c63aeaa6c0e (diff) | |
download | voidsky-1dbc331314278cb7a42ded9b190dac7038ad9878.tar.zst |
UI for age assurance compliance (#8652)
* Add geo prop * Add prelim fetch * Add geo debug * Pass in assurance state to notifications registration * Comments * Bump git index * Add some component utils, no design, gate chat * Disable mod prefs buttons, does not yet edit mod prefs * Add initial prompt component * Refine logic for showing prompt * Add send email dialog * Hook up dialog to fake mutation * Fix geo debug bug * Move provider inside query provider * Slightly better screen gater * Ok decent fallback with isExempt * Reorg * Wrap prompt in new logic * Override mod prefs * Use real endpoints, optimistic state * Add persistent card, add time-ago, warning to dialog * Add comment * No undefined query values * Fix case in import * Wait for AA to load before registering push * Override prefs in all locations * Small refactor of notifications registration * Register push after aa state * Add retries * Update blocked screens UI * Strengthen email validation * Add intent dialog * Do service auth for init * Rug refreshJwt * Update copy * Some mobile styles, add dev mode option * Fix links on native * Clean up intent dialog on native * Don't mutate existing session, only copy * Handle email validation error from server * Clarity is better * Moar clear * Fixes * Tweaks * Add country code * Gate it * Refresh state after redirect * Re-check on window focus * Remove todo * Enable in dev * Check for did match on redirect * Add blocked state * Add appeal dialog * Copy tweaks * Inset in blue well * Nux the prompt * Copy updates * Refetch just in case * Uppercase country code * Align copy, add notice to chat screens * Tweak copy * Add test code * Add debug code * Refactor AccountCard * Big refactor * Delay post-feed queries instead * Debug code * Clean up state * Reorg * Clean up copy * Comments * Reorg * UPdate URL * Cleanup * Remove todo * Update debug code * revert unneeded changes * UPdate nux name * Revert unneeded change * Updaet storage schema * Checkpoint: cleanup * Checkpoint: almost there * isLoaded -> isReady * Rename useAgeAssurance * isUnderage -> isDeclaredUnderage * Decompose, add docblocks * Refactor * UPdate debug * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Drop including Bluesky * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Apply suggestion from @surfdude29 Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Remove todo * Gate debug * Revert unneeded change * Fail closed * Comments * Comment * Comment * fix prettier * rm viewheader * bump sdk * prevent overlap in admonition * add age assurance intent route * Just meow Co-authored-by: Samuel Newman <mozzius@protonmail.com> * Nix callback * Fix spelling of dismissible lol * Don't compare translated string * Better KWS link labels * Hide DMs send options in menu * Add button * Fix order * Use only supported languages * Rm button * best-effort language mapping * improve typing --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx')
-rw-r--r-- | src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx new file mode 100644 index 000000000..41e706fee --- /dev/null +++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx @@ -0,0 +1,196 @@ +import {useEffect, useRef, useState} from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {retry} from '#/lib/async/retry' +import {wait} from '#/lib/async/wait' +import {isNative} from '#/platform/detection' +import {useAgeAssuranceAPIContext} from '#/state/ageAssurance' +import {useAgent} from '#/state/session' +import {atoms as a, useTheme, web} from '#/alf' +import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' +import {Loader} from '#/components/Loader' +import {Text} from '#/components/Typography' + +export type AgeAssuranceRedirectDialogState = { + result: 'success' | 'unknown' + actorDid: string +} + +/** + * Validate and parse the query parameters returned from the age assurance + * redirect. If not valid, returns `undefined` and the dialog will not open. + */ +export function parseAgeAssuranceRedirectDialogState( + state: { + result?: string + actorDid?: string + } = {}, +): AgeAssuranceRedirectDialogState | undefined { + let result: AgeAssuranceRedirectDialogState['result'] = 'unknown' + const actorDid = state.actorDid + + switch (state.result) { + case 'success': + result = 'success' + break + case 'unknown': + default: + result = 'unknown' + break + } + + if (result && actorDid) { + return { + result, + actorDid, + } + } +} + +export function useAgeAssuranceRedirectDialogControl() { + return useGlobalDialogsControlContext().ageAssuranceRedirectDialogControl +} + +export function AgeAssuranceRedirectDialog() { + const {_} = useLingui() + const control = useAgeAssuranceRedirectDialogControl() + + // TODO for testing + // Dialog.useAutoOpen(control.control, 3e3) + + return ( + <Dialog.Outer control={control.control}> + <Dialog.Handle /> + + <Dialog.ScrollableInner + label={_(msg`Verifying your age assurance status`)} + style={[web({maxWidth: 400})]}> + <Inner optimisticState={control.value} /> + </Dialog.ScrollableInner> + </Dialog.Outer> + ) +} + +export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { + const t = useTheme() + const {_} = useLingui() + const agent = useAgent() + const polling = useRef(false) + const unmounted = useRef(false) + const control = useAgeAssuranceRedirectDialogControl() + const [error, setError] = useState(false) + const {refetch: refreshAgeAssuranceState} = useAgeAssuranceAPIContext() + + useEffect(() => { + if (polling.current) return + + polling.current = true + + wait( + 3e3, + retry( + 5, + () => true, + async () => { + if (!agent.session) return + if (unmounted.current) return + + const {data} = await agent.app.bsky.unspecced.getAgeAssuranceState() + + if (data.status !== 'assured') { + throw new Error( + `Polling for age assurance state did not receive assured status`, + ) + } + + return data + }, + 1e3, + ), + ) + .then(async data => { + if (!data) return + if (!agent.session) return + if (unmounted.current) return + + // success! update state + await refreshAgeAssuranceState() + + control.clear() + control.control.close() + }) + .catch(() => { + if (unmounted.current) return + setError(true) + // try a refetch anyway + refreshAgeAssuranceState() + }) + + return () => { + unmounted.current = true + } + }, [agent, control, refreshAgeAssuranceState]) + + return ( + <> + <View style={[a.align_start, a.w_full]}> + <AgeAssuranceBadge /> + + <View + style={[ + a.flex_row, + a.justify_between, + a.align_center, + a.gap_sm, + a.pt_lg, + a.pb_md, + ]}> + {error && <ErrorIcon size="md" fill={t.palette.negative_500} />} + + <Text style={[a.text_xl, a.font_heavy]}> + {error ? <Trans>Connection issue</Trans> : <Trans>Verifying</Trans>} + </Text> + + {!error && <Loader size="md" />} + </View> + + <Text style={[a.text_md, a.leading_snug]}> + {error ? ( + <Trans> + We were unable to receive the verification due to a connection + issue. It may arrive later. If it does, your account will update + automatically. + </Trans> + ) : ( + <Trans> + We're confirming your status with our servers. This dialog should + close in a few seconds. + </Trans> + )} + </Text> + + {error && isNative && ( + <View style={[a.w_full, a.pt_lg]}> + <Button + label={_(msg`Close`)} + size="large" + variant="solid" + color="secondary"> + <ButtonText> + <Trans>Close</Trans> + </ButtonText> + </Button> + </View> + )} + </View> + + {error && <Dialog.Close />} + </> + ) +} |