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