diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx index 1c815a755b..9a0a9c9d51 100644 --- a/src/ageAssurance/index.tsx +++ b/src/ageAssurance/index.tsx @@ -3,6 +3,7 @@ import {createContext, useCallback, useContext, useEffect, useMemo} from 'react' import {useGetAndRegisterPushToken} from '#/lib/notifications/notifications' import {Provider as RedirectOverlayProvider} from '#/ageAssurance/components/RedirectOverlay' import {AgeAssuranceDataProvider} from '#/ageAssurance/data' +import {useAgeAssuranceDataContext} from '#/ageAssurance/data' import {logger} from '#/ageAssurance/logger' import { useAgeAssuranceState, @@ -13,6 +14,7 @@ import { type AgeAssuranceState, AgeAssuranceStatus, } from '#/ageAssurance/types' +import {isUserUnderAdultAge} from '#/ageAssurance/util' export { prefetchConfig as prefetchAgeAssuranceConfig, @@ -27,6 +29,10 @@ const AgeAssuranceStateContext = createContext<{ Access: typeof AgeAssuranceAccess Status: typeof AgeAssuranceStatus state: AgeAssuranceState + flags: { + adultContentDisabled: boolean + chatDisabled: boolean + } }>({ Access: AgeAssuranceAccess, Status: AgeAssuranceStatus, @@ -35,6 +41,10 @@ const AgeAssuranceStateContext = createContext<{ status: AgeAssuranceStatus.Unknown, access: AgeAssuranceAccess.Full, }, + flags: { + adultContentDisabled: false, + chatDisabled: false, + }, }) /** @@ -58,6 +68,7 @@ export function Provider({children}: {children: React.ReactNode}) { function InnerProvider({children}: {children: React.ReactNode}) { const state = useAgeAssuranceState() + const {data} = useAgeAssuranceDataContext() const getAndRegisterPushToken = useGetAndRegisterPushToken() const handleAccessUpdate = useCallback( @@ -76,14 +87,23 @@ function InnerProvider({children}: {children: React.ReactNode}) { return ( ({ + value={useMemo(() => { + const chatDisabled = state.access !== AgeAssuranceAccess.Full + const isUnderage = data?.birthdate + ? isUserUnderAdultAge(data.birthdate) + : true + const adultContentDisabled = + state.access !== AgeAssuranceAccess.Full || isUnderage + return { Access: AgeAssuranceAccess, Status: AgeAssuranceStatus, state, - }), - [state], - )}> + flags: { + adultContentDisabled, + chatDisabled, + }, + } + }, [state, data])}> {children} ) diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index bf1248fc19..1043283305 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -82,3 +82,7 @@ export function isLegacyBirthdateBug(birthDate: string) { export function isUserUnderMinimumAge(birthDate: string) { return getAge(new Date(birthDate)) < DEFAULT_MIN_AGE } + +export function isUserUnderAdultAge(birthDate: string) { + return getAge(new Date(birthDate)) < 18 +} diff --git a/src/components/ageAssurance/useAgeAssuranceCopy.ts b/src/components/ageAssurance/useAgeAssuranceCopy.ts index f773349167..e75f84feed 100644 --- a/src/components/ageAssurance/useAgeAssuranceCopy.ts +++ b/src/components/ageAssurance/useAgeAssuranceCopy.ts @@ -2,22 +2,14 @@ import {useMemo} from 'react' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useAgeAssurance} from '#/ageAssurance' - export function useAgeAssuranceCopy() { const {_} = useLingui() - const aa = useAgeAssurance() return useMemo(() => { return { - notice: - aa.state.access === aa.Access.Safe - ? _( - msg`Due to laws in your region, certain features on Bluesky are currently restricted until you're able to verify you're an adult.`, - ) - : _( - msg`The laws in your location require you to verify you're an adult before accessing certain features on Bluesky, like adult content and direct messaging.`, - ), + notice: _( + msg`Due to laws in your region, certain features on Bluesky are currently restricted until you're able to verify you're an adult.`, + ), banner: _( msg`The laws in your location require you to verify you're an adult to access certain features. Tap to learn more.`, ), @@ -25,5 +17,5 @@ export function useAgeAssuranceCopy() { msg`Don't worry! All existing messages and settings are saved and will be available after you verify you're an adult.`, ), } - }, [_, aa]) + }, [_]) } diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 6fed53c510..7791a9eb8d 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -12,6 +12,7 @@ import { } from '#/lib/routes/types' import {logger} from '#/logger' import {isIOS} from '#/platform/detection' +import {useIsBirthdateUpdateAllowed} from '#/state/birthdate' import { useMyLabelersQuery, usePreferencesQuery, @@ -21,7 +22,9 @@ import { import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities' import {useSetMinimalShellMode} from '#/state/shell' import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf' +import {Admonition} from '#/components/Admonition' import {AgeAssuranceAdmonition} from '#/components/ageAssurance/AgeAssuranceAdmonition' +import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' import {Button} from '#/components/Button' import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' import {Divider} from '#/components/Divider' @@ -164,6 +167,8 @@ export function ModerationScreenInner({ error: labelersError, } = useMyLabelersQuery() const aa = useAgeAssurance() + const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() + const aaCopy = useAgeAssuranceCopy() useFocusEffect( useCallback(() => { @@ -173,10 +178,17 @@ export function ModerationScreenInner({ const {mutateAsync: setAdultContentPref, variables: optimisticAdultContent} = usePreferencesSetAdultContentMutation() - const adultContentEnabled = !!( + let adultContentEnabled = !!( (optimisticAdultContent && optimisticAdultContent.enabled) || (!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled) ) + const adultContentUIDisabledOnIOS = isIOS && !adultContentEnabled + let adultContentUIDisabled = adultContentUIDisabledOnIOS + + if (aa.flags.adultContentDisabled) { + adultContentEnabled = false + adultContentUIDisabled = true + } const onToggleAdultContentEnabled = useCallback( async (selected: boolean) => { @@ -193,10 +205,26 @@ export function ModerationScreenInner({ [setAdultContentPref], ) - const disabledOnIOS = isIOS && !adultContentEnabled - return ( + {aa.flags.adultContentDisabled && isBirthdateUpdateAllowed && ( + + + + Your declared age is under 18. Some settings below may be + disabled. If this was a mistake, you may edit your birthdate in + your{' '} + + account settings + + . + + + + )} + - - You must complete age assurance in order to access content filters. - + {aaCopy.notice} @@ -339,14 +365,14 @@ export function ModerationScreenInner({ a.flex_row, a.align_center, a.justify_between, - disabledOnIOS && {opacity: 0.5}, + adultContentUIDisabled && {opacity: 0.5}, ]}> Enable adult content @@ -362,7 +388,7 @@ export function ModerationScreenInner({ - {disabledOnIOS && ( + {adultContentUIDisabledOnIOS && (