forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useEffect, useRef, useState} from 'react'
2import {View} from 'react-native'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5
6import {retry} from '#/lib/async/retry'
7import {wait} from '#/lib/async/wait'
8import {useAgent} from '#/state/session'
9import {atoms as a, useTheme, web} from '#/alf'
10import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
11import {Button, ButtonText} from '#/components/Button'
12import * as Dialog from '#/components/Dialog'
13import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
14import {CheckThick_Stroke2_Corner0_Rounded as SuccessIcon} from '#/components/icons/Check'
15import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
16import {Loader} from '#/components/Loader'
17import {Text} from '#/components/Typography'
18import {refetchAgeAssuranceServerState} from '#/ageAssurance'
19import {useAnalytics} from '#/analytics'
20import {IS_NATIVE} from '#/env'
21
22export type AgeAssuranceRedirectDialogState = {
23 result: 'success' | 'unknown'
24 actorDid: string
25}
26
27/**
28 * Validate and parse the query parameters returned from the age assurance
29 * redirect. If not valid, returns `undefined` and the dialog will not open.
30 */
31export function parseAgeAssuranceRedirectDialogState(
32 state: {
33 result?: string
34 actorDid?: string
35 } = {},
36): AgeAssuranceRedirectDialogState | undefined {
37 let result: AgeAssuranceRedirectDialogState['result'] = 'unknown'
38 const actorDid = state.actorDid
39
40 switch (state.result) {
41 case 'success':
42 result = 'success'
43 break
44 case 'unknown':
45 default:
46 result = 'unknown'
47 break
48 }
49
50 if (result && actorDid) {
51 return {
52 result,
53 actorDid,
54 }
55 }
56}
57
58export function useAgeAssuranceRedirectDialogControl() {
59 return useGlobalDialogsControlContext().ageAssuranceRedirectDialogControl
60}
61
62export function AgeAssuranceRedirectDialog() {
63 const {_} = useLingui()
64 const control = useAgeAssuranceRedirectDialogControl()
65
66 // for testing
67 // Dialog.useAutoOpen(control.control, 3e3)
68
69 return (
70 <Dialog.Outer control={control.control} onClose={() => control.clear()}>
71 <Dialog.Handle />
72
73 <Dialog.ScrollableInner
74 label={_(msg`Verifying your age assurance status`)}
75 style={[web({maxWidth: 400})]}>
76 <Inner optimisticState={control.value} />
77 </Dialog.ScrollableInner>
78 </Dialog.Outer>
79 )
80}
81
82export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
83 const t = useTheme()
84 const ax = useAnalytics()
85 const {_} = useLingui()
86 const agent = useAgent()
87 const polling = useRef(false)
88 const unmounted = useRef(false)
89 const control = useAgeAssuranceRedirectDialogControl()
90 const [error, setError] = useState(false)
91 const [success, setSuccess] = useState(false)
92
93 useEffect(() => {
94 if (polling.current) return
95
96 polling.current = true
97
98 ax.metric('ageAssurance:redirectDialogOpen', {})
99
100 wait(
101 3e3,
102 retry(
103 5,
104 () => true,
105 async () => {
106 if (!agent.session) return
107 if (unmounted.current) return
108
109 const data = await refetchAgeAssuranceServerState({agent})
110
111 if (data?.state.status !== 'assured') {
112 throw new Error(
113 `Polling for age assurance state did not receive assured status`,
114 )
115 }
116
117 return data
118 },
119 1e3,
120 ),
121 )
122 .then(async data => {
123 if (!data) return
124 if (!agent.session) return
125 if (unmounted.current) return
126
127 setSuccess(true)
128
129 ax.metric('ageAssurance:redirectDialogSuccess', {})
130 })
131 .catch(() => {
132 if (unmounted.current) return
133 setError(true)
134 ax.metric('ageAssurance:redirectDialogFail', {})
135 })
136
137 return () => {
138 unmounted.current = true
139 }
140 }, [ax, agent, control])
141
142 if (success) {
143 return (
144 <>
145 <View style={[a.align_start, a.w_full]}>
146 <AgeAssuranceBadge />
147
148 <View
149 style={[
150 a.flex_row,
151 a.justify_between,
152 a.align_center,
153 a.gap_sm,
154 a.pt_lg,
155 a.pb_md,
156 ]}>
157 <SuccessIcon size="sm" fill={t.palette.positive_500} />
158 <Text style={[a.text_xl, a.font_bold]}>
159 <Trans>Success</Trans>
160 </Text>
161 </View>
162
163 <Text style={[a.text_md, a.leading_snug]}>
164 <Trans>
165 We've confirmed your age assurance status. You can now close this
166 dialog.
167 </Trans>
168 </Text>
169
170 {IS_NATIVE && (
171 <View style={[a.w_full, a.pt_lg]}>
172 <Button
173 label={_(msg`Close`)}
174 size="large"
175 variant="solid"
176 color="secondary"
177 onPress={() => control.control.close()}>
178 <ButtonText>
179 <Trans>Close</Trans>
180 </ButtonText>
181 </Button>
182 </View>
183 )}
184 </View>
185
186 <Dialog.Close />
187 </>
188 )
189 }
190
191 return (
192 <>
193 <View style={[a.align_start, a.w_full]}>
194 <AgeAssuranceBadge />
195
196 <View
197 style={[
198 a.flex_row,
199 a.justify_between,
200 a.align_center,
201 a.gap_sm,
202 a.pt_lg,
203 a.pb_md,
204 ]}>
205 {error && <ErrorIcon size="md" fill={t.palette.negative_500} />}
206
207 <Text style={[a.text_xl, a.font_bold]}>
208 {error ? <Trans>Connection issue</Trans> : <Trans>Verifying</Trans>}
209 </Text>
210
211 {!error && <Loader size="md" />}
212 </View>
213
214 <Text style={[a.text_md, a.leading_snug]}>
215 {error ? (
216 <Trans>
217 We were unable to receive the verification due to a connection
218 issue. It may arrive later. If it does, your account will update
219 automatically.
220 </Trans>
221 ) : (
222 <Trans>
223 We're confirming your age assurance status with our servers. This
224 should only take a few seconds.
225 </Trans>
226 )}
227 </Text>
228
229 {error && IS_NATIVE && (
230 <View style={[a.w_full, a.pt_lg]}>
231 <Button
232 label={_(msg`Close`)}
233 size="large"
234 variant="solid"
235 color="secondary"
236 onPress={() => control.control.close()}>
237 <ButtonText>
238 <Trans>Close</Trans>
239 </ButtonText>
240 </Button>
241 </View>
242 )}
243 </View>
244
245 {error && <Dialog.Close />}
246 </>
247 )
248}