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 {isNative} from '#/platform/detection'
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 {logger} from '#/ageAssurance'
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 {_} = useLingui()
85 const agent = useAgent()
86 const polling = useRef(false)
87 const unmounted = useRef(false)
88 const control = useAgeAssuranceRedirectDialogControl()
89 const [error, setError] = useState(false)
90 const [success, setSuccess] = useState(false)
91
92 useEffect(() => {
93 if (polling.current) return
94
95 polling.current = true
96
97 logger.metric('ageAssurance:redirectDialogOpen', {})
98
99 wait(
100 3e3,
101 retry(
102 5,
103 () => true,
104 async () => {
105 if (!agent.session) return
106 if (unmounted.current) return
107
108 const data = await refetchAgeAssuranceServerState({agent})
109
110 if (data?.state.status !== 'assured') {
111 throw new Error(
112 `Polling for age assurance state did not receive assured status`,
113 )
114 }
115
116 return data
117 },
118 1e3,
119 ),
120 )
121 .then(async data => {
122 if (!data) return
123 if (!agent.session) return
124 if (unmounted.current) return
125
126 setSuccess(true)
127
128 logger.metric('ageAssurance:redirectDialogSuccess', {})
129 })
130 .catch(() => {
131 if (unmounted.current) return
132 setError(true)
133 logger.metric('ageAssurance:redirectDialogFail', {})
134 })
135
136 return () => {
137 unmounted.current = true
138 }
139 }, [agent, control])
140
141 if (success) {
142 return (
143 <>
144 <View style={[a.align_start, a.w_full]}>
145 <AgeAssuranceBadge />
146
147 <View
148 style={[
149 a.flex_row,
150 a.justify_between,
151 a.align_center,
152 a.gap_sm,
153 a.pt_lg,
154 a.pb_md,
155 ]}>
156 <SuccessIcon size="sm" fill={t.palette.positive_500} />
157 <Text style={[a.text_xl, a.font_bold]}>
158 <Trans>Success</Trans>
159 </Text>
160 </View>
161
162 <Text style={[a.text_md, a.leading_snug]}>
163 <Trans>
164 We've confirmed your age assurance status. You can now close this
165 dialog.
166 </Trans>
167 </Text>
168
169 {isNative && (
170 <View style={[a.w_full, a.pt_lg]}>
171 <Button
172 label={_(msg`Close`)}
173 size="large"
174 variant="solid"
175 color="secondary"
176 onPress={() => control.control.close()}>
177 <ButtonText>
178 <Trans>Close</Trans>
179 </ButtonText>
180 </Button>
181 </View>
182 )}
183 </View>
184
185 <Dialog.Close />
186 </>
187 )
188 }
189
190 return (
191 <>
192 <View style={[a.align_start, a.w_full]}>
193 <AgeAssuranceBadge />
194
195 <View
196 style={[
197 a.flex_row,
198 a.justify_between,
199 a.align_center,
200 a.gap_sm,
201 a.pt_lg,
202 a.pb_md,
203 ]}>
204 {error && <ErrorIcon size="md" fill={t.palette.negative_500} />}
205
206 <Text style={[a.text_xl, a.font_bold]}>
207 {error ? <Trans>Connection issue</Trans> : <Trans>Verifying</Trans>}
208 </Text>
209
210 {!error && <Loader size="md" />}
211 </View>
212
213 <Text style={[a.text_md, a.leading_snug]}>
214 {error ? (
215 <Trans>
216 We were unable to receive the verification due to a connection
217 issue. It may arrive later. If it does, your account will update
218 automatically.
219 </Trans>
220 ) : (
221 <Trans>
222 We're confirming your age assurance status with our servers. This
223 should only take a few seconds.
224 </Trans>
225 )}
226 </Text>
227
228 {error && isNative && (
229 <View style={[a.w_full, a.pt_lg]}>
230 <Button
231 label={_(msg`Close`)}
232 size="large"
233 variant="solid"
234 color="secondary"
235 onPress={() => control.control.close()}>
236 <ButtonText>
237 <Trans>Close</Trans>
238 </ButtonText>
239 </Button>
240 </View>
241 )}
242 </View>
243
244 {error && <Dialog.Close />}
245 </>
246 )
247}