import {useCallback, useEffect} from 'react' import {ScrollView, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import { SupportCode, useCreateSupportLink, } from '#/lib/hooks/useCreateSupportLink' import {dateDiff, useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' import {useSessionApi} from '#/state/session' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {AgeAssuranceInitDialog} from '#/components/ageAssurance/AgeAssuranceInitDialog' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import * as Dialog from '#/components/Dialog' import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings' import {DeviceLocationRequestDialog} from '#/components/dialogs/DeviceLocationRequestDialog' import {Full as Logo} from '#/components/icons/Logo' import {ShieldCheck_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/icons/Shield' import {createStaticClick, SimpleInlineLinkText} from '#/components/Link' import {Outlet as PortalOutlet} from '#/components/Portal' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {BottomSheetOutlet} from '#/../modules/bottom-sheet' import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssuranceDataContext} from '#/ageAssurance/data' import {useComputeAgeAssuranceRegionAccess} from '#/ageAssurance/useComputeAgeAssuranceRegionAccess' import { isLegacyBirthdateBug, useAgeAssuranceRegionConfig, } from '#/ageAssurance/util' import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' import {useDeviceGeolocationApi} from '#/geolocation' const textStyles = [a.text_md, a.leading_snug] export function NoAccessScreen() { const t = useTheme() const {_} = useLingui() const ax = useAnalytics() const {gtPhone} = useBreakpoints() const insets = useSafeAreaInsets() const birthdateControl = useDialogControl() const {data} = useAgeAssuranceDataContext() const region = useAgeAssuranceRegionConfig() const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() const {logoutCurrentAccount} = useSessionApi() const createSupportLink = useCreateSupportLink() const aa = useAgeAssurance() const isBlocked = aa.state.status === aa.Status.Blocked const isAARegion = !!region const hasDeclaredAge = data?.declaredAge !== undefined const canUpdateBirthday = isBirthdateUpdateAllowed || isLegacyBirthdateBug(data?.birthdate || '') useEffect(() => { // just counting overall hits here ax.metric(`blockedGeoOverlay:shown`, {}) ax.metric(`ageAssurance:noAccessScreen:shown`, { accountCreatedAt: data?.accountCreatedAt || 'unknown', isAARegion, hasDeclaredAge, canUpdateBirthday, }) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const onPressLogout = useCallback(() => { if (IS_WEB) { // We're switching accounts, which remounts the entire app. // On mobile, this gets us Home, but on the web we also need reset the URL. // We can't change the URL via a navigate() call because the navigator // itself is about to unmount, and it calls pushState() too late. // So we change the URL ourselves. The navigator will pick it up on remount. history.pushState(null, '', '/') } logoutCurrentAccount('AgeAssuranceNoAccessScreen') }, [logoutCurrentAccount]) const orgAdmonition = ( For organizational accounts, use the birthdate of the person who is responsible for the account. ) const birthdateUpdateText = canUpdateBirthday ? ( <> If you believe your birthdate is incorrect, you can update it by{' '} { ax.metric('ageAssurance:noAccessScreen:openBirthdateDialog', {}) birthdateControl.open() })}> clicking here . {orgAdmonition} ) : ( If you believe your birthdate is incorrect, please{' '} contact our support team . ) return ( <> {hasDeclaredAge ? ( <> {isAARegion ? ( <> Hey there! You are accessing Bluesky from a region that legally requires us to verify your age before allowing you to access the app. {!aa.flags.isOverRegionMinAccessAge && ( Unfortunately, your declared age indicates that you are not old enough to access Bluesky in your region. )} {!isBlocked && birthdateUpdateText} {aa.flags.isOverRegionMinAccessAge && } ) : ( Unfortunately, the birthdate you have saved to your profile makes you too young to access Bluesky. {birthdateUpdateText} )} ) : ( Hi there! In order to provide an age-appropriate experience, we need to know your birthdate. This is a one-time thing, and your data will be kept private. Set your birthdate below and we'll get you back to posting and exploring in no time! {orgAdmonition} )} To log out,{' '} { onPressLogout() })}> click here . {/* * While this blocking overlay is up, other dialogs in the shell * are not mounted, so it _should_ be safe to use these here * without fear of other modals showing up. */} ) } function AccessSection() { const t = useTheme() const {_, i18n} = useLingui() const ax = useAnalytics() const control = useDialogControl() const appealControl = Dialog.useDialogControl() const locationControl = Dialog.useDialogControl() const getTimeAgo = useGetTimeAgo() const {setDeviceGeolocation} = useDeviceGeolocationApi() const computeAgeAssuranceRegionAccess = useComputeAgeAssuranceRegionAccess() const aa = useAgeAssurance() const {status, lastInitiatedAt} = aa.state const isBlocked = status === aa.Status.Blocked const hasInitiated = !!lastInitiatedAt const timeAgo = lastInitiatedAt ? getTimeAgo(lastInitiatedAt, new Date()) : null const diff = lastInitiatedAt ? dateDiff(lastInitiatedAt, new Date(), 'down') : null return ( <> {isBlocked ? ( You are currently unable to access Bluesky's Age Assurance flow. Please{' '} { appealControl.open() ax.metric('ageAssurance:appealDialogOpen', {}) })}> contact our moderation team {' '} if you believe this is an error. ) : ( <> {lastInitiatedAt && timeAgo && diff ? ( {diff.value === 0 ? ( Last initiated just now ) : ( Last initiated {timeAgo} ago )} ) : ( Age assurance only takes a few minutes )} )} {IS_NATIVE && ( <> Is your location not accurate?{' '} { locationControl.open() })}> Tap here to confirm your location. {' '} { const access = computeAgeAssuranceRegionAccess( props.geolocation, ) if (access !== aa.Access.Full) { props.disableDialogAction() props.setDialogError( _( msg`We're sorry, but based on your device's location, you are currently located in a region that requires age assurance.`, ), ) } else { props.closeDialog(() => { // set this after close! setDeviceGeolocation(props.geolocation) Toast.show(_(msg`Thanks! You're all set.`), { type: 'success', }) }) } }} /> )} ) }