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