Bluesky app fork with some witchin' additions 馃挮
1import {View} from 'react-native'
2import {msg} from '@lingui/core/macro'
3import {useLingui} from '@lingui/react'
4import {Trans} from '@lingui/react/macro'
5
6import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
7import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
8import {Admonition} from '#/components/Admonition'
9import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog'
10import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
11import {AgeAssuranceConfigUnavailableError} from '#/components/ageAssurance/AgeAssuranceErrors'
12import {
13 AgeAssuranceInitDialog,
14 useDialogControl,
15} from '#/components/ageAssurance/AgeAssuranceInitDialog'
16import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
17import {Button, ButtonText} from '#/components/Button'
18import * as Dialog from '#/components/Dialog'
19import {DeviceLocationRequestDialog} from '#/components/dialogs/DeviceLocationRequestDialog'
20import {Divider} from '#/components/Divider'
21import {createStaticClick, InlineLinkText} from '#/components/Link'
22import * as Toast from '#/components/Toast'
23import {Text} from '#/components/Typography'
24import {useAgeAssurance} from '#/ageAssurance'
25import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess'
26import {useAnalytics} from '#/analytics'
27import {IS_NATIVE} from '#/env'
28import {useDeviceGeolocationApi} from '#/geolocation'
29
30export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) {
31 const aa = useAgeAssurance()
32 if (aa.state.access === aa.Access.Full) return null
33 if (aa.state.error === 'config') {
34 return (
35 <View style={style}>
36 <AgeAssuranceConfigUnavailableError />
37 </View>
38 )
39 }
40 return <Inner style={style} />
41}
42
43function Inner({style}: ViewStyleProp & {}) {
44 const t = useTheme()
45 const {_, i18n} = useLingui()
46 const ax = useAnalytics()
47 const control = useDialogControl()
48 const appealControl = Dialog.useDialogControl()
49 const locationControl = Dialog.useDialogControl()
50 const getTimeAgo = useGetTimeAgo()
51 const {gtPhone} = useBreakpoints()
52 const {setDeviceGeolocation} = useDeviceGeolocationApi()
53 const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess()
54
55 const copy = useAgeAssuranceCopy()
56 const aa = useAgeAssurance()
57 const {status, lastInitiatedAt} = aa.state
58 const isBlocked = status === aa.Status.Blocked
59 const hasInitiated = !!lastInitiatedAt
60 const hasCompletedFlow = status === aa.Status.Assured
61 const timeAgo = lastInitiatedAt
62 ? getTimeAgo(lastInitiatedAt, new Date())
63 : null
64 const diff = lastInitiatedAt
65 ? dateDiff(lastInitiatedAt, new Date(), 'down')
66 : null
67
68 return (
69 <>
70 <AgeAssuranceInitDialog control={control} />
71 <AgeAssuranceAppealDialog control={appealControl} />
72
73 <View style={style}>
74 <View
75 style={[a.p_lg, a.rounded_md, a.border, t.atoms.border_contrast_low]}>
76 <View
77 style={[
78 a.flex_row,
79 a.justify_between,
80 a.align_center,
81 a.gap_lg,
82 a.pb_md,
83 a.z_10,
84 ]}>
85 <View style={[a.align_start]}>
86 <AgeAssuranceBadge />
87 </View>
88 </View>
89
90 <View style={[a.pb_md, a.gap_sm]}>
91 <Text style={[a.text_sm, a.leading_snug]}>{copy.notice}</Text>
92 {hasCompletedFlow && (
93 <Text style={[a.text_sm, a.leading_snug]}>
94 <Trans>
95 If you are 18 years of age or older and want to try again,
96 click the button below and use a different verification method
97 if one is available in your region. If you have questions or
98 concerns,{' '}
99 <InlineLinkText
100 label={_(msg`Contact our support team`)}
101 {...createStaticClick(() => {
102 appealControl.open()
103 })}>
104 our support team can help.
105 </InlineLinkText>
106 </Trans>
107 </Text>
108 )}
109
110 {IS_NATIVE && (
111 <>
112 <Text style={[a.text_sm, a.leading_snug]}>
113 <Trans>
114 Is your location not accurate?{' '}
115 <InlineLinkText
116 label={_(msg`Confirm your location`)}
117 {...createStaticClick(() => {
118 locationControl.open()
119 })}>
120 Tap here to confirm your location.
121 </InlineLinkText>{' '}
122 </Trans>
123 </Text>
124
125 <DeviceLocationRequestDialog
126 control={locationControl}
127 onLocationAcquired={props => {
128 const access = computeAgeAssuranceRegionAccess(
129 props.geolocation,
130 )
131 if (access !== aa.Access.Full) {
132 props.disableDialogAction()
133 props.setDialogError(
134 _(
135 msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`,
136 ),
137 )
138 } else {
139 props.closeDialog(() => {
140 // set this after close!
141 setDeviceGeolocation(props.geolocation)
142 Toast.show(_(msg`Thanks! You're all set.`), {
143 type: 'success',
144 })
145 })
146 }
147 }}
148 />
149 </>
150 )}
151 </View>
152
153 {isBlocked ? (
154 <Admonition type="warning">
155 <Trans>
156 You are currently unable to access Bluesky's Age Assurance flow.
157 Please{' '}
158 <InlineLinkText
159 label={_(msg`Contact our moderation team`)}
160 {...createStaticClick(() => {
161 appealControl.open()
162 ax.metric('ageAssurance:appealDialogOpen', {})
163 })}>
164 contact our moderation team
165 </InlineLinkText>{' '}
166 if you believe this is an error.
167 </Trans>
168 </Admonition>
169 ) : (
170 <>
171 <Divider />
172 <View
173 style={[
174 a.pt_md,
175 gtPhone
176 ? [
177 a.flex_row_reverse,
178 a.gap_xl,
179 a.justify_between,
180 a.align_center,
181 ]
182 : [a.gap_md],
183 ]}>
184 <Button
185 label={_(msg`Verify now`)}
186 size="small"
187 variant="solid"
188 color={hasInitiated ? 'secondary' : 'primary'}
189 onPress={() => {
190 control.open()
191 ax.metric('ageAssurance:initDialogOpen', {
192 hasInitiatedPreviously: hasInitiated,
193 })
194 }}>
195 <ButtonText>
196 {hasInitiated ? (
197 <Trans>Verify again</Trans>
198 ) : (
199 <Trans>Verify now</Trans>
200 )}
201 </ButtonText>
202 </Button>
203
204 {lastInitiatedAt && timeAgo && diff ? (
205 <Text
206 style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}
207 title={i18n.date(lastInitiatedAt, {
208 dateStyle: 'medium',
209 timeStyle: 'medium',
210 })}>
211 {diff.value === 0 ? (
212 <Trans>Last initiated just now</Trans>
213 ) : (
214 <Trans>Last initiated {timeAgo} ago</Trans>
215 )}
216 </Text>
217 ) : (
218 <Text
219 style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}>
220 <Trans>Age assurance only takes a few minutes</Trans>
221 </Text>
222 )}
223 </View>
224 </>
225 )}
226 </View>
227 </View>
228 </>
229 )
230}