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