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

Configure Feed

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

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