250 lines
6.6 KiB
TypeScript
250 lines
6.6 KiB
TypeScript
import {useEffect, useRef, useState} from 'react'
|
|
import {View} from 'react-native'
|
|
import {msg} from '@lingui/core/macro'
|
|
import {useLingui} from '@lingui/react'
|
|
import {Trans} from '@lingui/react/macro'
|
|
|
|
import {retry} from '#/lib/async/retry'
|
|
import {wait} from '#/lib/async/wait'
|
|
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 {CheckThick_Stroke2_Corner0_Rounded as SuccessIcon} from '#/components/icons/Check'
|
|
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
|
|
import {Loader} from '#/components/Loader'
|
|
import {Text} from '#/components/Typography'
|
|
import {refetchAgeAssuranceServerState} from '#/ageAssurance'
|
|
import {useAnalytics} from '#/analytics'
|
|
import {IS_NATIVE} from '#/env'
|
|
|
|
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()
|
|
|
|
// for testing
|
|
// Dialog.useAutoOpen(control.control, 3e3)
|
|
|
|
return (
|
|
<Dialog.Outer control={control.control} onClose={() => control.clear()}>
|
|
<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 ax = useAnalytics()
|
|
const {_} = useLingui()
|
|
const agent = useAgent()
|
|
const polling = useRef(false)
|
|
const unmounted = useRef(false)
|
|
const control = useAgeAssuranceRedirectDialogControl()
|
|
const [error, setError] = useState(false)
|
|
const [success, setSuccess] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (polling.current) return
|
|
|
|
polling.current = true
|
|
|
|
ax.metric('ageAssurance:redirectDialogOpen', {})
|
|
|
|
wait(
|
|
3e3,
|
|
retry(
|
|
5,
|
|
() => true,
|
|
async () => {
|
|
if (!agent.session) return
|
|
if (unmounted.current) return
|
|
|
|
const data = await refetchAgeAssuranceServerState({agent})
|
|
|
|
if (data?.state.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
|
|
|
|
setSuccess(true)
|
|
|
|
ax.metric('ageAssurance:redirectDialogSuccess', {})
|
|
})
|
|
.catch(() => {
|
|
if (unmounted.current) return
|
|
setError(true)
|
|
ax.metric('ageAssurance:redirectDialogFail', {})
|
|
})
|
|
|
|
return () => {
|
|
unmounted.current = true
|
|
}
|
|
}, [ax, agent, control])
|
|
|
|
if (success) {
|
|
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,
|
|
]}>
|
|
<SuccessIcon size="sm" fill={t.palette.positive_500} />
|
|
<Text style={[a.text_xl, a.font_bold]}>
|
|
<Trans>Success</Trans>
|
|
</Text>
|
|
</View>
|
|
|
|
<Text style={[a.text_md, a.leading_snug]}>
|
|
<Trans>
|
|
We've confirmed your age assurance status. You can now close this
|
|
dialog.
|
|
</Trans>
|
|
</Text>
|
|
|
|
{IS_NATIVE && (
|
|
<View style={[a.w_full, a.pt_lg]}>
|
|
<Button
|
|
label={_(msg`Close`)}
|
|
size="large"
|
|
variant="solid"
|
|
color="secondary"
|
|
onPress={() => control.control.close()}>
|
|
<ButtonText>
|
|
<Trans>Close</Trans>
|
|
</ButtonText>
|
|
</Button>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
<Dialog.Close />
|
|
</>
|
|
)
|
|
}
|
|
|
|
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_bold]}>
|
|
{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 age assurance status with our servers. This
|
|
should only take a few seconds.
|
|
</Trans>
|
|
)}
|
|
</Text>
|
|
|
|
{error && IS_NATIVE && (
|
|
<View style={[a.w_full, a.pt_lg]}>
|
|
<Button
|
|
label={_(msg`Close`)}
|
|
size="large"
|
|
variant="solid"
|
|
color="secondary"
|
|
onPress={() => control.control.close()}>
|
|
<ButtonText>
|
|
<Trans>Close</Trans>
|
|
</ButtonText>
|
|
</Button>
|
|
</View>
|
|
)}
|
|
</View>
|
|
|
|
{error && <Dialog.Close />}
|
|
</>
|
|
)
|
|
}
|