Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 2d8e855e25e8722ee37c90a5413acd7a36d43318 355 lines 13 kB view raw
1import {useCallback, useEffect} from 'react' 2import {ScrollView, View} from 'react-native' 3import {useSafeAreaInsets} from 'react-native-safe-area-context' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import { 8 SupportCode, 9 useCreateSupportLink, 10} from '#/lib/hooks/useCreateSupportLink' 11import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' 12import {logger} from '#/logger' 13import {isWeb} from '#/platform/detection' 14import {isNative} from '#/platform/detection' 15import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' 16import {useSessionApi} from '#/state/session' 17import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 18import {Admonition} from '#/components/Admonition' 19import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' 20import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' 21import {AgeAssuranceInitDialog} from '#/components/ageAssurance/AgeAssuranceInitDialog' 22import {Button, ButtonIcon, ButtonText} from '#/components/Button' 23import {useDialogControl} from '#/components/Dialog' 24import * as Dialog from '#/components/Dialog' 25import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' 26import {DeviceLocationRequestDialog} from '#/components/dialogs/DeviceLocationRequestDialog' 27import {Full as Logo} from '#/components/icons/Logo' 28import {ShieldCheck_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/icons/Shield' 29import {createStaticClick, SimpleInlineLinkText} from '#/components/Link' 30import {Outlet as PortalOutlet} from '#/components/Portal' 31import * as Toast from '#/components/Toast' 32import {Text} from '#/components/Typography' 33import {BottomSheetOutlet} from '#/../modules/bottom-sheet' 34import {useAgeAssurance} from '#/ageAssurance' 35import {useAgeAssuranceDataContext} from '#/ageAssurance/data' 36import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' 37import { 38 isLegacyBirthdateBug, 39 useAgeAssuranceRegionConfig, 40} from '#/ageAssurance/util' 41import {useDeviceGeolocationApi} from '#/geolocation' 42 43const textStyles = [a.text_md, a.leading_snug] 44 45export function NoAccessScreen() { 46 const t = useTheme() 47 const {_} = useLingui() 48 const {gtPhone} = useBreakpoints() 49 const insets = useSafeAreaInsets() 50 const birthdateControl = useDialogControl() 51 const {data} = useAgeAssuranceDataContext() 52 const region = useAgeAssuranceRegionConfig() 53 const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() 54 const {logoutCurrentAccount} = useSessionApi() 55 const createSupportLink = useCreateSupportLink() 56 57 const aa = useAgeAssurance() 58 const isBlocked = aa.state.status === aa.Status.Blocked 59 const isAARegion = !!region 60 const hasDeclaredAge = data?.declaredAge !== undefined 61 const canUpdateBirthday = 62 isBirthdateUpdateAllowed || isLegacyBirthdateBug(data?.birthdate || '') 63 64 useEffect(() => { 65 // just counting overall hits here 66 logger.metric(`blockedGeoOverlay:shown`, {}) 67 }, []) 68 69 const onPressLogout = useCallback(() => { 70 if (isWeb) { 71 // We're switching accounts, which remounts the entire app. 72 // On mobile, this gets us Home, but on the web we also need reset the URL. 73 // We can't change the URL via a navigate() call because the navigator 74 // itself is about to unmount, and it calls pushState() too late. 75 // So we change the URL ourselves. The navigator will pick it up on remount. 76 history.pushState(null, '', '/') 77 } 78 logoutCurrentAccount('AgeAssuranceNoAccessScreen') 79 }, [logoutCurrentAccount]) 80 81 const birthdateUpdateText = canUpdateBirthday ? ( 82 <Text style={[textStyles]}> 83 <Trans> 84 If you believe your birthdate is incorrect, you can update it by{' '} 85 <SimpleInlineLinkText 86 label={_(msg`Click here to update your birthdate`)} 87 style={[textStyles]} 88 {...createStaticClick(() => { 89 birthdateControl.open() 90 })}> 91 clicking here 92 </SimpleInlineLinkText> 93 . 94 </Trans> 95 </Text> 96 ) : ( 97 <Text style={[textStyles]}> 98 <Trans> 99 If you believe your birthdate is incorrect, please{' '} 100 <SimpleInlineLinkText 101 to={createSupportLink({code: SupportCode.AA_BIRTHDATE})} 102 label={_(msg`Click here to contact our support team`)} 103 style={[textStyles]}> 104 contact our support team 105 </SimpleInlineLinkText> 106 . 107 </Trans> 108 </Text> 109 ) 110 111 return ( 112 <> 113 <ScrollView 114 contentContainerStyle={[ 115 a.px_2xl, 116 { 117 paddingTop: isWeb ? a.p_5xl.padding : insets.top + a.p_2xl.padding, 118 paddingBottom: 100, 119 }, 120 ]}> 121 <View 122 style={[ 123 a.mx_auto, 124 a.w_full, 125 web({ 126 maxWidth: 380, 127 paddingTop: gtPhone ? '8vh' : undefined, 128 }), 129 { 130 gap: 32, 131 }, 132 ]}> 133 <View style={[a.align_start]}> 134 <AgeAssuranceBadge /> 135 </View> 136 137 {hasDeclaredAge ? ( 138 <> 139 {isAARegion ? ( 140 <> 141 <View style={[a.gap_lg]}> 142 <Text style={[textStyles]}> 143 <Trans> 144 You are accessing Bluesky from a region that legally 145 requires us to verify your age before allowing you to 146 access the app. 147 </Trans> 148 </Text> 149 150 {!isBlocked && birthdateUpdateText} 151 </View> 152 153 <AccessSection /> 154 </> 155 ) : ( 156 <View style={[a.gap_lg]}> 157 <Text style={[textStyles]}> 158 <Trans> 159 Unfortunately, the birthdate you have saved to your 160 profile makes you too young to access Bluesky. 161 </Trans> 162 </Text> 163 164 {birthdateUpdateText} 165 </View> 166 )} 167 </> 168 ) : ( 169 <View style={[a.gap_lg]}> 170 <Text style={[textStyles]}> 171 <Trans> 172 It looks like you haven't added your birthdate. You must 173 provide an accurate date of birth to use Bluesky. 174 </Trans> 175 </Text> 176 <Button 177 color="primary" 178 size="large" 179 label={_(msg`Click here to update your birthdate`)} 180 onPress={() => birthdateControl.open()}> 181 <ButtonText> 182 <Trans>Add your birthdate</Trans> 183 </ButtonText> 184 </Button> 185 </View> 186 )} 187 188 <View style={[a.pt_lg, a.gap_xl]}> 189 <Logo width={120} textFill={t.atoms.text.color} /> 190 <Text style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}> 191 <Trans> 192 To log out,{' '} 193 <SimpleInlineLinkText 194 label={_(msg`Click here to log out`)} 195 {...createStaticClick(() => { 196 onPressLogout() 197 })}> 198 click here 199 </SimpleInlineLinkText> 200 . 201 </Trans> 202 </Text> 203 </View> 204 </View> 205 </ScrollView> 206 207 <BirthDateSettingsDialog control={birthdateControl} /> 208 209 {/* 210 * While this blocking overlay is up, other dialogs in the shell 211 * are not mounted, so it _should_ be safe to use these here 212 * without fear of other modals showing up. 213 */} 214 <BottomSheetOutlet /> 215 <PortalOutlet /> 216 </> 217 ) 218} 219 220function AccessSection() { 221 const t = useTheme() 222 const {_, i18n} = useLingui() 223 const control = useDialogControl() 224 const appealControl = Dialog.useDialogControl() 225 const locationControl = Dialog.useDialogControl() 226 const getTimeAgo = useGetTimeAgo() 227 const {setDeviceGeolocation} = useDeviceGeolocationApi() 228 const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() 229 230 const aa = useAgeAssurance() 231 const {status, lastInitiatedAt} = aa.state 232 const isBlocked = status === aa.Status.Blocked 233 const hasInitiated = !!lastInitiatedAt 234 const timeAgo = lastInitiatedAt 235 ? getTimeAgo(lastInitiatedAt, new Date()) 236 : null 237 const diff = lastInitiatedAt 238 ? dateDiff(lastInitiatedAt, new Date(), 'down') 239 : null 240 241 return ( 242 <> 243 <AgeAssuranceInitDialog control={control} /> 244 <AgeAssuranceAppealDialog control={appealControl} /> 245 246 <View style={[a.gap_xl]}> 247 {isBlocked ? ( 248 <Admonition type="warning"> 249 <Trans> 250 You are currently unable to access Bluesky's Age Assurance flow. 251 Please{' '} 252 <SimpleInlineLinkText 253 label={_(msg`Contact our moderation team`)} 254 {...createStaticClick(() => { 255 appealControl.open() 256 logger.metric('ageAssurance:appealDialogOpen', {}) 257 })}> 258 contact our moderation team 259 </SimpleInlineLinkText>{' '} 260 if you believe this is an error. 261 </Trans> 262 </Admonition> 263 ) : ( 264 <> 265 <View style={[a.gap_md]}> 266 <Button 267 label={_(msg`Verify now`)} 268 size="large" 269 color={hasInitiated ? 'secondary' : 'primary'} 270 onPress={() => { 271 control.open() 272 logger.metric('ageAssurance:initDialogOpen', { 273 hasInitiatedPreviously: hasInitiated, 274 }) 275 }}> 276 <ButtonIcon icon={ShieldIcon} /> 277 <ButtonText> 278 {hasInitiated ? ( 279 <Trans>Verify again</Trans> 280 ) : ( 281 <Trans>Verify now</Trans> 282 )} 283 </ButtonText> 284 </Button> 285 286 {lastInitiatedAt && timeAgo && diff ? ( 287 <Text 288 style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]} 289 title={i18n.date(lastInitiatedAt, { 290 dateStyle: 'medium', 291 timeStyle: 'medium', 292 })}> 293 {diff.value === 0 ? ( 294 <Trans>Last initiated just now</Trans> 295 ) : ( 296 <Trans>Last initiated {timeAgo} ago</Trans> 297 )} 298 </Text> 299 ) : ( 300 <Text 301 style={[a.text_sm, a.italic, t.atoms.text_contrast_medium]}> 302 <Trans>Age assurance only takes a few minutes</Trans> 303 </Text> 304 )} 305 </View> 306 </> 307 )} 308 309 <View style={[a.gap_xs]}> 310 {isNative && ( 311 <> 312 <Admonition> 313 <Trans> 314 Is your location not accurate?{' '} 315 <SimpleInlineLinkText 316 label={_(msg`Confirm your location`)} 317 {...createStaticClick(() => { 318 locationControl.open() 319 })}> 320 Tap here to confirm your location. 321 </SimpleInlineLinkText>{' '} 322 </Trans> 323 </Admonition> 324 325 <DeviceLocationRequestDialog 326 control={locationControl} 327 onLocationAcquired={props => { 328 const access = computeAgeAssuranceRegionAccess( 329 props.geolocation, 330 ) 331 if (access !== aa.Access.Full) { 332 props.disableDialogAction() 333 props.setDialogError( 334 _( 335 msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`, 336 ), 337 ) 338 } else { 339 props.closeDialog(() => { 340 // set this after close! 341 setDeviceGeolocation(props.geolocation) 342 Toast.show(_(msg`Thanks! You're all set.`), { 343 type: 'success', 344 }) 345 }) 346 } 347 }} 348 /> 349 </> 350 )} 351 </View> 352 </View> 353 </> 354 ) 355}