Checkpoint: almost there

This commit is contained in:
Eric Bailey
2025-07-15 17:00:33 -05:00
parent b1b86e2c60
commit 13db34c590
12 changed files with 62 additions and 69 deletions
@@ -20,11 +20,11 @@ import {createStaticClick, InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) { export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) {
const {isLoaded, mustCompleteAgeAssurance, isUnderage} = useAgeInfo() const {isLoaded, isAgeRestricted, isUnderage} = useAgeInfo()
if (!isLoaded) return null if (!isLoaded) return null
if (isUnderage) return null if (isUnderage) return null
if (!mustCompleteAgeAssurance) return null if (!isAgeRestricted) return null
return <Inner style={style} /> return <Inner style={style} />
} }
@@ -15,11 +15,11 @@ export function AgeAssuranceAdmonition({
style, style,
}: ViewStyleProp & {children: React.ReactNode}) { }: ViewStyleProp & {children: React.ReactNode}) {
const control = useDialogControl() const control = useDialogControl()
const {isLoaded, isUnderage, mustCompleteAgeAssurance} = useAgeInfo() const {isLoaded, isUnderage, isAgeRestricted} = useAgeInfo()
if (!isLoaded) return null if (!isLoaded) return null
if (isUnderage) return null if (isUnderage) return null
if (!mustCompleteAgeAssurance) return null if (!isAgeRestricted) return null
return ( return (
<Inner style={style} control={control}> <Inner style={style} control={control}>
@@ -12,8 +12,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
export function AgeAssuranceDismissableNotice({style}: ViewStyleProp & {}) { export function AgeAssuranceDismissableNotice({style}: ViewStyleProp & {}) {
const {_} = useLingui() const {_} = useLingui()
const {isLoaded, isUnderage, mustCompleteAgeAssurance, assurance} = const {isLoaded, isUnderage, isAgeRestricted, assurance} = useAgeInfo()
useAgeInfo()
const {nux} = useNux(Nux.AgeAssuranceDismissableNotice) const {nux} = useNux(Nux.AgeAssuranceDismissableNotice)
const copy = useAgeAssuranceCopy() const copy = useAgeAssuranceCopy()
const {mutate: save, variables} = useSaveNux() const {mutate: save, variables} = useSaveNux()
@@ -21,7 +20,7 @@ export function AgeAssuranceDismissableNotice({style}: ViewStyleProp & {}) {
if (!isLoaded) return null if (!isLoaded) return null
if (isUnderage) return null if (isUnderage) return null
if (!mustCompleteAgeAssurance) return null if (!isAgeRestricted) return null
if (assurance.lastInitiatedAt) return null if (assurance.lastInitiatedAt) return null
if (hidden) return null if (hidden) return null
if (nux && nux.completed) return null if (nux && nux.completed) return null
@@ -24,7 +24,7 @@ export function AgeRestrictedScreen({
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const copy = useAgeAssuranceCopy() const copy = useAgeAssuranceCopy()
const {isLoaded, mustCompleteAgeAssurance} = useAgeInfo() const {isLoaded, isAgeRestricted} = useAgeInfo()
if (!isLoaded) { if (!isLoaded) {
return ( return (
@@ -39,7 +39,7 @@ export function AgeRestrictedScreen({
</Layout.Screen> </Layout.Screen>
) )
} }
if (!mustCompleteAgeAssurance) return children if (!isAgeRestricted) return children
return ( return (
<Layout.Screen> <Layout.Screen>
+9 -11
View File
@@ -8,8 +8,7 @@ import debounce from 'lodash.debounce'
import {PUBLIC_APPVIEW_DID, PUBLIC_STAGING_APPVIEW_DID} from '#/lib/constants' import {PUBLIC_APPVIEW_DID, PUBLIC_STAGING_APPVIEW_DID} from '#/lib/constants'
import {logger as notyLogger} from '#/lib/notifications/util' import {logger as notyLogger} from '#/lib/notifications/util'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useAgeAssuranceContext} from '#/state/ageAssurance' import {useIsAgeRestricted} from '#/state/ageAssurance/useIsAgeRestricted'
import {useMustCompleteAgeAssurance} from '#/state/ageAssurance/useMustCompleteAgeAssurance'
import {type SessionAccount, useAgent, useSession} from '#/state/session' import {type SessionAccount, useAgent, useSession} from '#/state/session'
import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler'
@@ -123,7 +122,7 @@ async function getPushToken() {
* @see https://github.com/bluesky-social/social-app/pull/4467 * @see https://github.com/bluesky-social/social-app/pull/4467
*/ */
export function useGetAndRegisterPushToken() { export function useGetAndRegisterPushToken() {
const mustCompleteAgeAssurance = useMustCompleteAgeAssurance() const {isAgeRestricted} = useIsAgeRestricted()
const registerPushToken = useRegisterPushToken() const registerPushToken = useRegisterPushToken()
return useCallback( return useCallback(
async ({ async ({
@@ -150,13 +149,13 @@ export function useGetAndRegisterPushToken() {
*/ */
registerPushToken({ registerPushToken({
token, token,
isAgeRestricted: isAgeRestrictedOverride ?? mustCompleteAgeAssurance, isAgeRestricted: isAgeRestrictedOverride ?? isAgeRestricted,
}) })
} }
return token return token
}, },
[registerPushToken, mustCompleteAgeAssurance], [registerPushToken, isAgeRestricted],
) )
} }
@@ -171,15 +170,14 @@ export function useNotificationsRegistration() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const registerPushToken = useRegisterPushToken() const registerPushToken = useRegisterPushToken()
const getAndRegisterPushToken = useGetAndRegisterPushToken() const getAndRegisterPushToken = useGetAndRegisterPushToken()
const {isLoaded: isAgeAssuranceLoaded} = useAgeAssuranceContext() const {isReady: isAgeRestrictionReady, isAgeRestricted} = useIsAgeRestricted()
const mustCompleteAgeAssurance = useMustCompleteAgeAssurance()
useEffect(() => { useEffect(() => {
/** /**
* We want this to init right away _after_ we have a logged in user, and * We want this to init right away _after_ we have a logged in user, and
* _after_ we've loaded their age assurance state. * _after_ we've loaded their age assurance state.
*/ */
if (!currentAccount || !isAgeAssuranceLoaded) return if (!currentAccount || !isAgeRestrictionReady) return
notyLogger.debug(`useNotificationsRegistration`) notyLogger.debug(`useNotificationsRegistration`)
@@ -201,7 +199,7 @@ export function useNotificationsRegistration() {
* @see https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener * @see https://docs.expo.dev/versions/latest/sdk/notifications/#addpushtokenlistenerlistener
*/ */
const subscription = Notifications.addPushTokenListener(async token => { const subscription = Notifications.addPushTokenListener(async token => {
registerPushToken({token, isAgeRestricted: mustCompleteAgeAssurance}) registerPushToken({token, isAgeRestricted: isAgeRestricted})
notyLogger.debug(`addPushTokenListener callback`, {token}) notyLogger.debug(`addPushTokenListener callback`, {token})
}) })
@@ -212,8 +210,8 @@ export function useNotificationsRegistration() {
currentAccount, currentAccount,
getAndRegisterPushToken, getAndRegisterPushToken,
registerPushToken, registerPushToken,
isAgeAssuranceLoaded, isAgeRestrictionReady,
mustCompleteAgeAssurance, isAgeRestricted,
]) ])
} }
+3 -3
View File
@@ -160,7 +160,7 @@ export function ModerationScreenInner({
data: labelers, data: labelers,
error: labelersError, error: labelersError,
} = useMyLabelersQuery() } = useMyLabelersQuery()
const {declaredAge, isUnderage, mustCompleteAgeAssurance} = useAgeInfo() const {declaredAge, isUnderage, isAgeRestricted} = useAgeInfo()
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -345,7 +345,7 @@ export function ModerationScreenInner({
a.overflow_hidden, a.overflow_hidden,
t.atoms.bg_contrast_25, t.atoms.bg_contrast_25,
]}> ]}>
{!isUnderage && !mustCompleteAgeAssurance && ( {!isUnderage && !isAgeRestricted && (
<> <>
<View <View
style={[ style={[
@@ -414,7 +414,7 @@ export function ModerationScreenInner({
</> </>
)} )}
<GlobalLabelPreference <GlobalLabelPreference
disabled={isUnderage || mustCompleteAgeAssurance} disabled={isUnderage || isAgeRestricted}
labelDefinition={LABELS.nudity} labelDefinition={LABELS.nudity}
/> />
</View> </View>
+3 -9
View File
@@ -13,7 +13,6 @@ import {
type AgeAssuranceContextType, type AgeAssuranceContextType,
} from '#/state/ageAssurance/types' } from '#/state/ageAssurance/types'
import {useGeolocation} from '#/state/geolocation' import {useGeolocation} from '#/state/geolocation'
import {STALE} from '#/state/queries'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
const logger = Logger.create(Logger.Context.AgeAssurance) const logger = Logger.create(Logger.Context.AgeAssurance)
@@ -26,7 +25,6 @@ const DEFAULT_AGE_ASSURANCE_STATE: AppBskyUnspeccedDefs.AgeAssuranceState = {
const AgeAssuranceContext = createContext<AgeAssuranceContextType>({ const AgeAssuranceContext = createContext<AgeAssuranceContextType>({
status: 'unknown', status: 'unknown',
isLoaded: false, isLoaded: false,
isAgeRestricted: false,
lastInitiatedAt: undefined, lastInitiatedAt: undefined,
}) })
const AgeAssuranceAPIContext = createContext<AgeAssuranceAPIContextType>({ const AgeAssuranceAPIContext = createContext<AgeAssuranceAPIContextType>({
@@ -54,8 +52,6 @@ export function Provider({children}: {children: React.ReactNode}) {
*/ */
enabled: true, enabled: true,
queryKey: createAgeAssuranceQueryKey(agent.session?.did ?? 'never'), queryKey: createAgeAssuranceQueryKey(agent.session?.did ?? 'never'),
staleTime: STALE.MINUTES.ONE,
refetchOnWindowFocus: geolocation?.isAgeRestrictedGeo === true,
async queryFn() { async queryFn() {
if (!agent.session) return null if (!agent.session) return null
@@ -78,11 +74,13 @@ export function Provider({children}: {children: React.ReactNode}) {
account: agent.session?.did, account: agent.session?.did,
}) })
if (gate('age_assurance')) {
await getAndRegisterPushToken({ await getAndRegisterPushToken({
isAgeRestricted: Boolean( isAgeRestricted: Boolean(
isAgeRestrictedGeo && data.status !== 'assured', isAgeRestrictedGeo && data.status !== 'assured',
), ),
}) })
}
return data return data
} catch (e) { } catch (e) {
@@ -99,19 +97,15 @@ export function Provider({children}: {children: React.ReactNode}) {
* Derive state, or fall back to defaults * Derive state, or fall back to defaults
*/ */
const ageAssuranceContext = useMemo<AgeAssuranceContextType>(() => { const ageAssuranceContext = useMemo<AgeAssuranceContextType>(() => {
const enabled = __DEV__ || gate('age_assurance')
const {status, lastInitiatedAt} = data || DEFAULT_AGE_ASSURANCE_STATE const {status, lastInitiatedAt} = data || DEFAULT_AGE_ASSURANCE_STATE
const ctx: AgeAssuranceContextType = { const ctx: AgeAssuranceContextType = {
isLoaded: isFetched, isLoaded: isFetched,
status, status,
lastInitiatedAt, lastInitiatedAt,
isAgeRestricted: isAgeRestrictedGeo && status !== 'assured' && enabled,
} }
logger.debug(`context`, ctx) logger.debug(`context`, ctx)
return ctx return ctx
}, [gate, isAgeRestrictedGeo, isFetched, data]) }, [isFetched, data])
const ageAssuranceAPIContext = useMemo<AgeAssuranceAPIContextType>( const ageAssuranceAPIContext = useMemo<AgeAssuranceAPIContextType>(
() => ({ () => ({
+1 -6
View File
@@ -10,12 +10,7 @@ export type AgeAssuranceContextType = {
/** /**
* The last time the age assurance state was attempted by the user. * The last time the age assurance state was attempted by the user.
*/ */
lastInitiatedAt: string | undefined lastInitiatedAt: AppBskyUnspeccedDefs.AgeAssuranceState['lastInitiatedAt']
/**
* Whether the current user is age-restricted based on their geolocation and
* age assurance state retrieved from the server.
*/
isAgeRestricted: boolean
} }
export type AgeAssuranceAPIContextType = { export type AgeAssuranceAPIContextType = {
+5 -5
View File
@@ -2,7 +2,7 @@ import {useMemo} from 'react'
import {Logger} from '#/logger' import {Logger} from '#/logger'
import {useAgeAssuranceContext} from '#/state/ageAssurance' import {useAgeAssuranceContext} from '#/state/ageAssurance'
import {useMustCompleteAgeAssurance} from '#/state/ageAssurance/useMustCompleteAgeAssurance' import {useIsAgeRestricted} from '#/state/ageAssurance/useIsAgeRestricted'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
const logger = Logger.create(Logger.Context.AgeAssurance) const logger = Logger.create(Logger.Context.AgeAssurance)
@@ -11,7 +11,7 @@ type AgeInfo = {
isLoaded: boolean isLoaded: boolean
declaredAge: number | undefined declaredAge: number | undefined
isUnderage: boolean isUnderage: boolean
mustCompleteAgeAssurance: boolean isAgeRestricted: boolean
assurance: ReturnType<typeof useAgeAssuranceContext> assurance: ReturnType<typeof useAgeAssuranceContext>
} }
@@ -21,8 +21,8 @@ type AgeInfo = {
* more user-friendly interface. * more user-friendly interface.
*/ */
export function useAgeInfo(): AgeInfo { export function useAgeInfo(): AgeInfo {
const mustCompleteAgeAssurance = useMustCompleteAgeAssurance()
const ctx = useAgeAssuranceContext() const ctx = useAgeAssuranceContext()
const {isAgeRestricted} = useIsAgeRestricted()
const {isFetched: preferencesLoaded, data: preferences} = const {isFetched: preferencesLoaded, data: preferences} =
usePreferencesQuery() usePreferencesQuery()
const declaredAge = preferences?.userAge const declaredAge = preferences?.userAge
@@ -34,11 +34,11 @@ export function useAgeInfo(): AgeInfo {
isLoaded, isLoaded,
declaredAge, declaredAge,
isUnderage, isUnderage,
mustCompleteAgeAssurance, isAgeRestricted,
assurance: ctx, assurance: ctx,
} }
logger.debug(`useAgeInfo`, info) logger.debug(`useAgeInfo`, info)
return info return info
}, [ctx, preferencesLoaded, declaredAge, mustCompleteAgeAssurance]) }, [ctx, preferencesLoaded, declaredAge, isAgeRestricted])
} }
@@ -0,0 +1,24 @@
import {useMemo} from 'react'
import {useGate} from '#/lib/statsig/statsig'
import {useAgeAssuranceContext} from '#/state/ageAssurance'
import {useGeolocation} from '#/state/geolocation'
export function useIsAgeRestricted() {
const {isLoaded, status} = useAgeAssuranceContext()
const {geolocation} = useGeolocation()
const gate = useGate()
return useMemo(() => {
if (!gate('age_assurance') || !geolocation?.isAgeRestrictedGeo) {
return {
isReady: true,
isAgeRestricted: false,
}
}
return {
isReady: isLoaded,
isAgeRestricted: status !== 'assured',
}
}, [isLoaded, status, geolocation, gate])
}
@@ -1,17 +0,0 @@
import {useMemo} from 'react'
import {useGate} from '#/lib/statsig/statsig'
import {useAgeAssuranceContext} from '#/state/ageAssurance'
import {useGeolocation} from '#/state/geolocation'
export function useMustCompleteAgeAssurance() {
const gate = useGate()
const {geolocation} = useGeolocation()
const {isLoaded, status: ageAssuranceStatus} = useAgeAssuranceContext()
return useMemo(() => {
if (!gate('age_assurance')) return false
if (!geolocation?.isAgeRestrictedGeo) return false
if (!isLoaded) return false
return ageAssuranceStatus !== 'assured'
}, [isLoaded, gate, geolocation, ageAssuranceStatus])
}
+4 -4
View File
@@ -11,7 +11,7 @@ import {replaceEqualDeep} from '#/lib/functions'
import {getAge} from '#/lib/strings/time' import {getAge} from '#/lib/strings/time'
import {logger} from '#/logger' import {logger} from '#/logger'
import {AGE_RESTRICTED_MODERATION_PREFS} from '#/state/ageAssurance/const' import {AGE_RESTRICTED_MODERATION_PREFS} from '#/state/ageAssurance/const'
import {useMustCompleteAgeAssurance} from '#/state/ageAssurance/useMustCompleteAgeAssurance' import {useIsAgeRestricted} from '#/state/ageAssurance/useIsAgeRestricted'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import { import {
DEFAULT_HOME_FEED_PREFS, DEFAULT_HOME_FEED_PREFS,
@@ -34,7 +34,7 @@ export const preferencesQueryKey = [preferencesQueryKeyRoot]
export function usePreferencesQuery() { export function usePreferencesQuery() {
const agent = useAgent() const agent = useAgent()
const mustCompleteAgeAssurance = useMustCompleteAgeAssurance() const {isReady: isAgeRestrictionReady, isAgeRestricted} = useIsAgeRestricted()
return useQuery({ return useQuery({
staleTime: STALE.SECONDS.FIFTEEN, staleTime: STALE.SECONDS.FIFTEEN,
@@ -76,12 +76,12 @@ export function usePreferencesQuery() {
select: useCallback( select: useCallback(
(data: UsePreferencesQueryResponse) => { (data: UsePreferencesQueryResponse) => {
const isUnderage = (data.userAge || 0) < 18 const isUnderage = (data.userAge || 0) < 18
if (isUnderage || mustCompleteAgeAssurance) { if (isUnderage || (isAgeRestrictionReady && isAgeRestricted)) {
data.moderationPrefs = AGE_RESTRICTED_MODERATION_PREFS data.moderationPrefs = AGE_RESTRICTED_MODERATION_PREFS
} }
return data return data
}, },
[mustCompleteAgeAssurance], [isAgeRestrictionReady, isAgeRestricted],
), ),
}) })
} }