Checkpoint: cleanup

This commit is contained in:
Eric Bailey
2025-07-15 16:15:38 -05:00
parent d481d4530f
commit b1b86e2c60
11 changed files with 76 additions and 57 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, assurance, declaredAge} = useAgeInfo() const {isLoaded, mustCompleteAgeAssurance, isUnderage} = useAgeInfo()
if (!isLoaded) return null if (!isLoaded) return null
if (declaredAge && declaredAge < 18) return null if (isUnderage) return null
if (!assurance.isAgeRestricted) return null if (!mustCompleteAgeAssurance) 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, assurance} = useAgeInfo() const {isLoaded, isUnderage, mustCompleteAgeAssurance} = useAgeInfo()
if (!isLoaded) return null if (!isLoaded) return null
if (isUnderage) return null if (isUnderage) return null
if (!assurance.isAgeRestricted) return null if (!mustCompleteAgeAssurance) return null
return ( return (
<Inner style={style} control={control}> <Inner style={style} control={control}>
@@ -12,7 +12,8 @@ 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, assurance} = useAgeInfo() const {isLoaded, isUnderage, mustCompleteAgeAssurance, assurance} =
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()
@@ -20,7 +21,7 @@ export function AgeAssuranceDismissableNotice({style}: ViewStyleProp & {}) {
if (!isLoaded) return null if (!isLoaded) return null
if (isUnderage) return null if (isUnderage) return null
if (!assurance.isAgeRestricted) return null if (!mustCompleteAgeAssurance) 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, assurance} = useAgeInfo() const {isLoaded, mustCompleteAgeAssurance} = useAgeInfo()
if (!isLoaded) { if (!isLoaded) {
return ( return (
@@ -39,7 +39,7 @@ export function AgeRestrictedScreen({
</Layout.Screen> </Layout.Screen>
) )
} }
if (!assurance.isAgeRestricted) return children if (!mustCompleteAgeAssurance) return children
return ( return (
<Layout.Screen> <Layout.Screen>
+8 -7
View File
@@ -9,6 +9,7 @@ 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 {useAgeAssuranceContext} from '#/state/ageAssurance'
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'
@@ -122,7 +123,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 {isAgeRestricted} = useAgeAssuranceContext() const mustCompleteAgeAssurance = useMustCompleteAgeAssurance()
const registerPushToken = useRegisterPushToken() const registerPushToken = useRegisterPushToken()
return useCallback( return useCallback(
async ({ async ({
@@ -149,13 +150,13 @@ export function useGetAndRegisterPushToken() {
*/ */
registerPushToken({ registerPushToken({
token, token,
isAgeRestricted: isAgeRestrictedOverride ?? isAgeRestricted, isAgeRestricted: isAgeRestrictedOverride ?? mustCompleteAgeAssurance,
}) })
} }
return token return token
}, },
[registerPushToken, isAgeRestricted], [registerPushToken, mustCompleteAgeAssurance],
) )
} }
@@ -170,8 +171,8 @@ export function useNotificationsRegistration() {
const {currentAccount} = useSession() const {currentAccount} = useSession()
const registerPushToken = useRegisterPushToken() const registerPushToken = useRegisterPushToken()
const getAndRegisterPushToken = useGetAndRegisterPushToken() const getAndRegisterPushToken = useGetAndRegisterPushToken()
const {isLoaded: isAgeAssuranceLoaded, isAgeRestricted} = const {isLoaded: isAgeAssuranceLoaded} = useAgeAssuranceContext()
useAgeAssuranceContext() const mustCompleteAgeAssurance = useMustCompleteAgeAssurance()
useEffect(() => { useEffect(() => {
/** /**
@@ -200,7 +201,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}) registerPushToken({token, isAgeRestricted: mustCompleteAgeAssurance})
notyLogger.debug(`addPushTokenListener callback`, {token}) notyLogger.debug(`addPushTokenListener callback`, {token})
}) })
@@ -212,7 +213,7 @@ export function useNotificationsRegistration() {
getAndRegisterPushToken, getAndRegisterPushToken,
registerPushToken, registerPushToken,
isAgeAssuranceLoaded, isAgeAssuranceLoaded,
isAgeRestricted, mustCompleteAgeAssurance,
]) ])
} }
+6 -6
View File
@@ -160,7 +160,7 @@ export function ModerationScreenInner({
data: labelers, data: labelers,
error: labelersError, error: labelersError,
} = useMyLabelersQuery() } = useMyLabelersQuery()
const {declaredAge, isUnderage, assurance: ageAssurance} = useAgeInfo() const {declaredAge, isUnderage, mustCompleteAgeAssurance} = useAgeInfo()
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
@@ -316,7 +316,7 @@ export function ModerationScreenInner({
</AgeAssuranceAdmonition> </AgeAssuranceAdmonition>
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
{declaredAge === undefined && ageAssurance.isAgeRestricted && ( {declaredAge === undefined && (
<> <>
<Button <Button
label={_(msg`Confirm your birthdate`)} label={_(msg`Confirm your birthdate`)}
@@ -345,7 +345,7 @@ export function ModerationScreenInner({
a.overflow_hidden, a.overflow_hidden,
t.atoms.bg_contrast_25, t.atoms.bg_contrast_25,
]}> ]}>
{!isUnderage && ( {!isUnderage && !mustCompleteAgeAssurance && (
<> <>
<View <View
style={[ style={[
@@ -361,7 +361,7 @@ export function ModerationScreenInner({
</Text> </Text>
<Toggle.Item <Toggle.Item
label={_(msg`Toggle to enable or disable adult content`)} label={_(msg`Toggle to enable or disable adult content`)}
disabled={disabledOnIOS || ageAssurance.isAgeRestricted} disabled={disabledOnIOS}
name="adultContent" name="adultContent"
value={adultContentEnabled} value={adultContentEnabled}
onChange={onToggleAdultContentEnabled}> onChange={onToggleAdultContentEnabled}>
@@ -377,7 +377,7 @@ export function ModerationScreenInner({
</View> </View>
</Toggle.Item> </Toggle.Item>
</View> </View>
{disabledOnIOS && !ageAssurance.isAgeRestricted && ( {disabledOnIOS && (
<View style={[a.pb_lg, a.px_lg]}> <View style={[a.pb_lg, a.px_lg]}>
<Text> <Text>
<Trans> <Trans>
@@ -414,7 +414,7 @@ export function ModerationScreenInner({
</> </>
)} )}
<GlobalLabelPreference <GlobalLabelPreference
disabled={ageAssurance.isAgeRestricted} disabled={isUnderage || mustCompleteAgeAssurance}
labelDefinition={LABELS.nudity} labelDefinition={LABELS.nudity}
/> />
</View> </View>
+2 -2
View File
@@ -55,7 +55,7 @@ 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, staleTime: STALE.MINUTES.ONE,
refetchOnWindowFocus: true, refetchOnWindowFocus: geolocation?.isAgeRestrictedGeo === true,
async queryFn() { async queryFn() {
if (!agent.session) return null if (!agent.session) return null
@@ -68,7 +68,7 @@ export function Provider({children}: {children: React.ReactNode}) {
// (() => ({ // (() => ({
// data: { // data: {
// lastInitiatedAt: undefined,//new Date().toISOString(), // lastInitiatedAt: undefined,//new Date().toISOString(),
// status: 'unknown', // status: 'pending',
// } as AppBskyUnspeccedDefs.AgeAssuranceState, // } as AppBskyUnspeccedDefs.AgeAssuranceState,
// }))(), // }))(),
// ) // )
+25 -6
View File
@@ -1,25 +1,44 @@
import {useMemo} from 'react' import {useMemo} from 'react'
import {Logger} from '#/logger'
import {useAgeAssuranceContext} from '#/state/ageAssurance' import {useAgeAssuranceContext} from '#/state/ageAssurance'
import {useMustCompleteAgeAssurance} from '#/state/ageAssurance/useMustCompleteAgeAssurance'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
const logger = Logger.create(Logger.Context.AgeAssurance)
type AgeInfo = {
isLoaded: boolean
declaredAge: number | undefined
isUnderage: boolean
mustCompleteAgeAssurance: boolean
assurance: ReturnType<typeof useAgeAssuranceContext>
}
/** /**
* Computed age information based on age assurance status and the user's * Computed age information based on age assurance status and the user's
* declared age. Use this instead of {@link useAgeAssuranceContext} to get a * declared age. Use this instead of {@link useAgeAssuranceContext} to get a
* more user-friendly interface. * more user-friendly interface.
*/ */
export function useAgeInfo() { export function useAgeInfo(): AgeInfo {
const mustCompleteAgeAssurance = useMustCompleteAgeAssurance()
const ctx = useAgeAssuranceContext() const ctx = useAgeAssuranceContext()
const {isFetched: preferencesLoaded, data: preferences} = const {isFetched: preferencesLoaded, data: preferences} =
usePreferencesQuery() usePreferencesQuery()
const declaredAge = useMemo(() => preferences?.userAge, [preferences]) const declaredAge = preferences?.userAge
return useMemo(() => { return useMemo(() => {
return { const isLoaded = ctx.isLoaded && preferencesLoaded
isLoaded: ctx.isLoaded && preferencesLoaded, const isUnderage = (declaredAge || 0) < 18
const info: AgeInfo = {
isLoaded,
declaredAge, declaredAge,
isUnderage: ctx.isAgeRestricted && (declaredAge || 0) < 18, isUnderage,
mustCompleteAgeAssurance,
assurance: ctx, assurance: ctx,
} }
}, [ctx, preferencesLoaded, declaredAge]) logger.debug(`useAgeInfo`, info)
return info
}, [ctx, preferencesLoaded, declaredAge, mustCompleteAgeAssurance])
} }
@@ -1,23 +0,0 @@
import {useCallback} from 'react'
import {type ModerationPrefs} from '@atproto/api'
import {useAgeAssuranceContext} from '#/state/ageAssurance'
import {AGE_RESTRICTED_MODERATION_PREFS} from '#/state/ageAssurance/const'
/**
* Hook to conditionally apply age-restricted moderation preferences, if
* needed. If not needed, the mod prefs passed to the callback will be used.
*/
export function useMaybeApplyAgeRestrictedModerationPrefs() {
const state = useAgeAssuranceContext()
return useCallback(
(prev: ModerationPrefs) => {
if (state.isAgeRestricted) {
return AGE_RESTRICTED_MODERATION_PREFS
}
return prev
},
[state],
)
}
@@ -0,0 +1,17 @@
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])
}
+8 -4
View File
@@ -10,7 +10,8 @@ import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {replaceEqualDeep} from '#/lib/functions' 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 {useMaybeApplyAgeRestrictedModerationPrefs} from '#/state/ageAssurance/useMaybeApplyAgeRestrictedModerationPrefs' import {AGE_RESTRICTED_MODERATION_PREFS} from '#/state/ageAssurance/const'
import {useMustCompleteAgeAssurance} from '#/state/ageAssurance/useMustCompleteAgeAssurance'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import { import {
DEFAULT_HOME_FEED_PREFS, DEFAULT_HOME_FEED_PREFS,
@@ -33,7 +34,7 @@ export const preferencesQueryKey = [preferencesQueryKeyRoot]
export function usePreferencesQuery() { export function usePreferencesQuery() {
const agent = useAgent() const agent = useAgent()
const maybeOverrideModPrefs = useMaybeApplyAgeRestrictedModerationPrefs() const mustCompleteAgeAssurance = useMustCompleteAgeAssurance()
return useQuery({ return useQuery({
staleTime: STALE.SECONDS.FIFTEEN, staleTime: STALE.SECONDS.FIFTEEN,
@@ -74,10 +75,13 @@ export function usePreferencesQuery() {
}, },
select: useCallback( select: useCallback(
(data: UsePreferencesQueryResponse) => { (data: UsePreferencesQueryResponse) => {
data.moderationPrefs = maybeOverrideModPrefs(data.moderationPrefs) const isUnderage = (data.userAge || 0) < 18
if (isUnderage || mustCompleteAgeAssurance) {
data.moderationPrefs = AGE_RESTRICTED_MODERATION_PREFS
}
return data return data
}, },
[maybeOverrideModPrefs], [mustCompleteAgeAssurance],
), ),
}) })
} }