diff --git a/.eslintrc.js b/.eslintrc.js
index 726cc56074..59c1b4eb4f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -34,6 +34,7 @@ module.exports = {
'P',
'Admonition',
'Admonition.Admonition',
+ 'AgeAssuranceAdmonition',
'Span',
],
impliedTextProps: [],
diff --git a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx
index 1686bb8e1e..3eae2424fc 100644
--- a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx
+++ b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx
@@ -23,7 +23,7 @@ export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) {
const {isLoaded, assurance, declaredAge} = useAgeInfo()
if (!isLoaded) return null
- if (declaredAge < 18) return null
+ if (declaredAge && declaredAge < 18) return null
if (!assurance.isAgeRestricted) return null
return
diff --git a/src/components/ageAssurance/AgeAssuranceAdmonition.tsx b/src/components/ageAssurance/AgeAssuranceAdmonition.tsx
new file mode 100644
index 0000000000..a8ec7b043c
--- /dev/null
+++ b/src/components/ageAssurance/AgeAssuranceAdmonition.tsx
@@ -0,0 +1,100 @@
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {atoms as a, select, useTheme, type ViewStyleProp} from '#/alf'
+import {useDialogControl} from '#/components/ageAssurance/AgeAssuranceInitDialog'
+import {useAgeInfo} from '#/components/ageAssurance/useAgeInfo'
+import type * as Dialog from '#/components/Dialog'
+import {ShieldCheck_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/Shield'
+import {InlineLinkText} from '#/components/Link'
+import {Text} from '#/components/Typography'
+
+export function AgeAssuranceAdmonition({
+ children,
+ style,
+}: ViewStyleProp & {children: React.ReactNode}) {
+ const control = useDialogControl()
+ const {isLoaded, isUnderage, assurance} = useAgeInfo()
+
+ if (!isLoaded) return null
+ if (isUnderage) return null
+ if (!assurance.isAgeRestricted) return null
+
+ return (
+
+ {children}
+
+ )
+}
+
+function Inner({
+ children,
+ style,
+}: ViewStyleProp & {
+ children: React.ReactNode
+ control: Dialog.DialogControlProps
+}) {
+ const t = useTheme()
+ const {_} = useLingui()
+
+ return (
+ <>
+
+
+
+
+
+
+ {children}
+
+
+ Learn more in your{' '}
+
+ account settings.
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/src/components/ageAssurance/AgeAssuranceDismissableNotice.tsx b/src/components/ageAssurance/AgeAssuranceDismissableNotice.tsx
new file mode 100644
index 0000000000..03972a9e86
--- /dev/null
+++ b/src/components/ageAssurance/AgeAssuranceDismissableNotice.tsx
@@ -0,0 +1,58 @@
+import {View} from 'react-native'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
+import {atoms as a, type ViewStyleProp} from '#/alf'
+import {AgeAssuranceAdmonition} from '#/components/ageAssurance/AgeAssuranceAdmonition'
+import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
+import {useAgeInfo} from '#/components/ageAssurance/useAgeInfo'
+import {Button, ButtonIcon} from '#/components/Button'
+import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
+
+export function AgeAssuranceDismissableNotice({style}: ViewStyleProp & {}) {
+ const {_} = useLingui()
+ const {isLoaded, isUnderage, assurance} = useAgeInfo()
+ const {nux} = useNux(Nux.AgeAssurancePrompt)
+ const copy = useAgeAssuranceCopy()
+ const {mutate: save, variables} = useSaveNux()
+ const hidden = !!variables
+
+ if (!isLoaded) return null
+ if (isUnderage) return null
+ if (!assurance.isAgeRestricted) return null
+ if (assurance.lastInitiatedAt) return null
+ if (hidden) return null
+ if (nux && nux.completed) return null
+
+ return (
+
+
+ {copy.notice}
+
+
+
+
+ )
+}
diff --git a/src/components/ageAssurance/AgeAssurancePrompt.tsx b/src/components/ageAssurance/AgeAssurancePrompt.tsx
deleted file mode 100644
index e371bc17ff..0000000000
--- a/src/components/ageAssurance/AgeAssurancePrompt.tsx
+++ /dev/null
@@ -1,147 +0,0 @@
-import {View} from 'react-native'
-import {msg, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-
-import {useAgeAssuranceContext} from '#/state/age-assurance'
-import {Nux, useNux, useSaveNux} from '#/state/queries/nuxs'
-import {atoms as a, select, useTheme, type ViewStyleProp} from '#/alf'
-import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
-import {
- AgeAssuranceInitDialog,
- useDialogControl,
-} from '#/components/ageAssurance/AgeAssuranceInitDialog'
-import {IsAgeRestricted} from '#/components/ageAssurance/IsAgeRestricted'
-import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
-import {Button, ButtonIcon, ButtonText} from '#/components/Button'
-import type * as Dialog from '#/components/Dialog'
-import {Divider} from '#/components/Divider'
-import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
-import {Text} from '#/components/Typography'
-
-export function AgeAssurancePrompt({style}: ViewStyleProp & {}) {
- const control = useDialogControl()
- const {hasInitiated} = useAgeAssuranceContext()
- const {nux} = useNux(Nux.AgeAssurancePrompt)
-
- if (nux && nux.completed) return null
-
- return (
- <>
-
- {!hasInitiated && }
-
-
- >
- )
-}
-
-function Inner({
- style,
- control,
-}: ViewStyleProp & {control: Dialog.DialogControlProps}) {
- const t = useTheme()
- const {_} = useLingui()
- const copy = useAgeAssuranceCopy()
- const {mutate: save} = useSaveNux()
-
- return (
- <>
-
-
-
-
-
- {copy.notice}
-
-
-
-
-
- {copy.noticeSub}
-
-
-
-
-
-
-
-
- >
- )
-}
diff --git a/src/components/ageAssurance/AgeRestrictedScreen.tsx b/src/components/ageAssurance/AgeRestrictedScreen.tsx
index f30a5dc35f..5a7b28a7c5 100644
--- a/src/components/ageAssurance/AgeRestrictedScreen.tsx
+++ b/src/components/ageAssurance/AgeRestrictedScreen.tsx
@@ -1,147 +1,94 @@
-import {useMemo} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-import {atoms as a, select, useTheme} from '#/alf'
+import {atoms as a} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
-import {
- AgeAssuranceInitDialog,
- useDialogControl,
-} from '#/components/ageAssurance/AgeAssuranceInitDialog'
-import {IsAgeRestricted} from '#/components/ageAssurance/IsAgeRestricted'
import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
-import {Button, ButtonText} from '#/components/Button'
-import {Divider} from '#/components/Divider'
+import {useAgeInfo} from '#/components/ageAssurance/useAgeInfo'
+import {ButtonIcon, ButtonText} from '#/components/Button'
+import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import * as Layout from '#/components/Layout'
+import {Link} from '#/components/Link'
import {Text} from '#/components/Typography'
export function AgeRestrictedScreen({
children,
- fallback,
screenTitle,
infoText,
}: {
children: React.ReactNode
- fallback?: React.ReactNode
screenTitle?: string
infoText?: string
}) {
- const t = useTheme()
const {_} = useLingui()
const copy = useAgeAssuranceCopy()
- const control = useDialogControl()
- const screenFallback = useMemo(() => {
+ const {isLoaded, assurance} = useAgeInfo()
+
+ if (assurance.isExempt) return children
+ if (!isLoaded) {
return (
- fallback || (
-
-
-
-
-
-
-
-
-
- )
+
+
+
+
+
+
+
+
+
)
- }, [fallback])
+ }
+ if (!assurance.isAgeRestricted) return children
return (
- <>
-
-
-
-
-
-
- {screenTitle ?? Unavailable}
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {screenTitle ?? Unavailable}
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+ You must verify your age in order to access this screen.
+
+
-
- {copy.notice}
-
+
+ {copy.notice}
+
+
-
-
- You must complete age verification to access this screen.
-
-
+
+
+
+ Go to account settings
+
+
+
+
-
-
-
-
- {copy.noticeSub}
-
-
-
-
-
-
- {infoText && {infoText}}
-
-
-
-
-
-
- {children}
-
- >
+ {infoText && {infoText}}
+
+
+
)
}
diff --git a/src/components/ageAssurance/IsAgeRestricted.tsx b/src/components/ageAssurance/IsAgeRestricted.tsx
deleted file mode 100644
index 4bf8c9b105..0000000000
--- a/src/components/ageAssurance/IsAgeRestricted.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import {useAgeAssuranceContext} from '#/state/age-assurance'
-
-export function True({
- children,
- fallback,
-}: {
- children: React.ReactNode
- fallback?: React.ReactNode
-}) {
- const {isLoaded, isAgeRestricted, isExempt} = useAgeAssuranceContext()
- /**
- * For the true case, if the user is exempt, return nothing
- */
- if (isExempt) return null
-
- const isDefinitelyAgeRestricted = isLoaded && isAgeRestricted
- const notSureYet = isAgeRestricted
-
- return isDefinitelyAgeRestricted
- ? children
- : notSureYet
- ? fallback || null
- : null
-}
-
-export function False({
- children,
- fallback,
-}: {
- children: React.ReactNode
- fallback?: React.ReactNode
-}) {
- const {isLoaded, isAgeRestricted, isExempt} = useAgeAssuranceContext()
-
- /**
- * For the false case, if the user is exempt, return children
- */
- if (isExempt) return children
-
- const isDefinitelyNotAgeRestricted = isLoaded && !isAgeRestricted
- const notSureYet = !isAgeRestricted
-
- return isDefinitelyNotAgeRestricted
- ? children
- : notSureYet
- ? fallback || null
- : null
-}
-
-export const IsAgeRestricted = {
- True,
- False,
-}
diff --git a/src/components/ageAssurance/useAgeInfo.ts b/src/components/ageAssurance/useAgeInfo.ts
index b168100496..73eae5d3d9 100644
--- a/src/components/ageAssurance/useAgeInfo.ts
+++ b/src/components/ageAssurance/useAgeInfo.ts
@@ -7,12 +7,13 @@ export function useAgeInfo() {
const ctx = useAgeAssuranceContext()
const {isFetched: preferencesLoaded, data: preferences} =
usePreferencesQuery()
- const declaredAge = useMemo(() => preferences?.userAge || -1, [preferences])
+ const declaredAge = useMemo(() => preferences?.userAge, [preferences])
return useMemo(() => {
return {
isLoaded: ctx.isLoaded && preferencesLoaded,
declaredAge,
+ isUnderage: ctx.isAgeRestricted && (declaredAge || 0) < 18,
assurance: ctx,
}
}, [ctx, preferencesLoaded, declaredAge])
diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx
index adadcb22c8..200a6a1e44 100644
--- a/src/screens/Moderation/index.tsx
+++ b/src/screens/Moderation/index.tsx
@@ -23,7 +23,8 @@ import {isNonConfigurableModerationAuthority} from '#/state/session/additional-m
import {useSetMinimalShellMode} from '#/state/shell'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
-import {AgeAssurancePrompt} from '#/components/ageAssurance/AgeAssurancePrompt'
+import {AgeAssuranceAdmonition} from '#/components/ageAssurance/AgeAssuranceAdmonition'
+import {useAgeInfo} from '#/components/ageAssurance/useAgeInfo'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {BirthDateSettingsDialog} from '#/components/dialogs/BirthDateSettings'
@@ -86,8 +87,9 @@ export function ModerationScreen(
error: preferencesError,
data: preferences,
} = usePreferencesQuery()
+ const {isLoaded: isAgeInfoLoaded} = useAgeInfo()
- const isLoading = isPreferencesLoading
+ const isLoading = isPreferencesLoading || !isAgeInfoLoaded
const error = preferencesError
return (
@@ -160,6 +162,7 @@ export function ModerationScreenInner({
error: labelersError,
} = useMyLabelersQuery()
const {isAgeRestricted} = useAgeAssuranceContext()
+ const {declaredAge, isUnderage, assurance: ageAssurance} = useAgeInfo()
useFocusEffect(
useCallback(() => {
@@ -173,8 +176,6 @@ export function ModerationScreenInner({
(optimisticAdultContent && optimisticAdultContent.enabled) ||
(!optimisticAdultContent && preferences.moderationPrefs.adultContentEnabled)
)
- const ageNotSet = !preferences.userAge
- const isUnderage = (preferences.userAge || 0) < 18
const onToggleAdultContentEnabled = useCallback(
async (selected: boolean) => {
@@ -309,10 +310,15 @@ export function ModerationScreenInner({
Content filters
-
+
+
+ You must complete age verification in order to access the settings
+ below.
+
+
- {ageNotSet && (
+ {declaredAge === undefined && ageAssurance.isAgeRestricted && (
<>