diff --git a/__e2e__/flows/report-dialog/post:default.yml b/__e2e__/flows/report-dialog/post.default.yml
similarity index 100%
rename from __e2e__/flows/report-dialog/post:default.yml
rename to __e2e__/flows/report-dialog/post.default.yml
diff --git a/__e2e__/flows/report-dialog/post:edit-reason.yml b/__e2e__/flows/report-dialog/post.edit-reason.yml
similarity index 100%
rename from __e2e__/flows/report-dialog/post:edit-reason.yml
rename to __e2e__/flows/report-dialog/post.edit-reason.yml
diff --git a/__e2e__/flows/report-dialog/post:reason-other.yml b/__e2e__/flows/report-dialog/post.reason-other.yml
similarity index 100%
rename from __e2e__/flows/report-dialog/post:reason-other.yml
rename to __e2e__/flows/report-dialog/post.reason-other.yml
diff --git a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx
index a189d9af29..08fd5f84fc 100644
--- a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx
+++ b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx
@@ -134,16 +134,18 @@ function Inner() {
setSuccess(true)
} catch (e) {
+ let error: React.ReactNode = _(
+ msg`Something went wrong, please try again`,
+ )
+
if (e instanceof XRPCError) {
if (e.error === 'InvalidEmail') {
- setError(
- _(
- msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`,
- ),
+ error = _(
+ msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`,
)
logger.metric('ageAssurance:initDialogError', {code: 'InvalidEmail'})
} else if (e.error === 'DidTooLong') {
- setError(
+ error = (
<>
We're having issues initializing the age assurance process for
@@ -155,15 +157,19 @@ function Inner() {
{' '}
for assistance.
- >,
+ >
)
logger.metric('ageAssurance:initDialogError', {code: 'DidTooLong'})
+ } else {
+ logger.metric('ageAssurance:initDialogError', {code: 'other'})
}
} else {
const {clean, raw} = cleanError(e)
- setError(clean || raw || _(msg`Something went wrong, please try again`))
+ error = clean || raw || error
logger.metric('ageAssurance:initDialogError', {code: 'other'})
}
+
+ setError(error)
}
}
@@ -188,7 +194,7 @@ function Inner() {
<>
- We use{' '}
+ We have partnered with{' '}
{' '}
to verify that you’re an adult. When you click "Begin" below,
- KWS will email you instructions for verifying your age. When
- you’re done, you'll be brought back to continue using Bluesky.
+ KWS will check if you have previously verified your age using
+ this email address for other games/services powered by KWS
+ technology. If not, KWS will email you instructions for
+ verifying your age. When you’re done, you'll be brought back
+ to continue using Bluesky.
diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx
index ff2e0bfd0f..b1c287e1b3 100644
--- a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx
+++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx
@@ -14,6 +14,7 @@ import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useGlobalDialogsControlContext} from '#/components/dialogs/Context'
+import {CheckThick_Stroke2_Corner0_Rounded as SuccessIcon} from '#/components/icons/Check'
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
@@ -66,7 +67,7 @@ export function AgeAssuranceRedirectDialog() {
// Dialog.useAutoOpen(control.control, 3e3)
return (
-
+ control.clear()}>
{
@@ -125,8 +127,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
// success! update state
await refreshAgeAssuranceState()
- control.clear()
- control.control.close()
+ setSuccess(true)
logger.metric('ageAssurance:redirectDialogSuccess', {})
})
@@ -143,6 +144,55 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
}
}, [agent, control, refreshAgeAssuranceState])
+ if (success) {
+ return (
+ <>
+
+
+
+
+
+
+ Success
+
+
+
+
+
+ We've confirmed your age assurance status. You can now close this
+ dialog.
+
+
+
+ {isNative && (
+
+
+
+ )}
+
+
+
+ >
+ )
+ }
+
return (
<>
@@ -175,8 +225,8 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
) : (
- We're confirming your status with our servers. This dialog should
- close in a few seconds.
+ We're confirming your age assurance status with our servers. This
+ should only take a few seconds.
)}
@@ -187,7 +237,8 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
label={_(msg`Close`)}
size="large"
variant="solid"
- color="secondary">
+ color="secondary"
+ onPress={() => control.control.close()}>
Close
diff --git a/src/components/ageAssurance/useAgeAssuranceCopy.ts b/src/components/ageAssurance/useAgeAssuranceCopy.ts
index f8a0edd79c..c861f8336b 100644
--- a/src/components/ageAssurance/useAgeAssuranceCopy.ts
+++ b/src/components/ageAssurance/useAgeAssuranceCopy.ts
@@ -11,7 +11,7 @@ export function useAgeAssuranceCopy() {
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.`,
),
banner: _(
- msg`The laws in your location require you to verify you're an adult. Tap to learn more.`,
+ msg`The laws in your location require you to verify you're an adult to access certain features. Tap to learn more.`,
),
chatsInfoText: _(
msg`Don't worry! All existing messages and settings are saved and will be available after you verify you're an adult.`,
diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx
index 6fc77142ee..a67b025425 100644
--- a/src/screens/Profile/Header/Metrics.tsx
+++ b/src/screens/Profile/Header/Metrics.tsx
@@ -1,10 +1,10 @@
import {View} from 'react-native'
-import {AppBskyActorDefs} from '@atproto/api'
+import {type AppBskyActorDefs} from '@atproto/api'
import {msg, plural} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {makeProfileLink} from '#/lib/routes/links'
-import {Shadow} from '#/state/cache/types'
+import {type Shadow} from '#/state/cache/types'
import {formatCount} from '#/view/com/util/numeric/format'
import {atoms as a, useTheme} from '#/alf'
import {InlineLinkText} from '#/components/Link'
@@ -36,7 +36,7 @@ export function ProfileHeaderMetrics({
testID="profileHeaderFollowersButton"
style={[a.flex_row, t.atoms.text]}
to={makeProfileLink(profile, 'followers')}
- label={`${followers} ${pluralizedFollowers}`}>
+ label={`${profile.followersCount || 0} ${pluralizedFollowers}`}>
{followers}
{pluralizedFollowers}
@@ -46,7 +46,7 @@ export function ProfileHeaderMetrics({
testID="profileHeaderFollowsButton"
style={[a.flex_row, t.atoms.text]}
to={makeProfileLink(profile, 'follows')}
- label={_(msg`${following} following`)}>
+ label={_(msg`${profile.followsCount || 0} following`)}>
{following}
{pluralizedFollowings}
diff --git a/src/state/ageAssurance/index.tsx b/src/state/ageAssurance/index.tsx
index eded747732..3451b1139d 100644
--- a/src/state/ageAssurance/index.tsx
+++ b/src/state/ageAssurance/index.tsx
@@ -1,4 +1,4 @@
-import {createContext, useContext, useMemo} from 'react'
+import {createContext, useContext, useMemo, useState} from 'react'
import {type AppBskyUnspeccedDefs} from '@atproto/api'
import {useQuery} from '@tanstack/react-query'
@@ -46,6 +46,7 @@ export function Provider({children}: {children: React.ReactNode}) {
const {geolocation} = useGeolocation()
const isAgeAssuranceEnabled = useIsAgeAssuranceEnabled()
const getAndRegisterPushToken = useGetAndRegisterPushToken()
+ const [refetchWhilePending, setRefetchWhilePending] = useState(false)
const {data, isFetched, refetch} = useQuery({
/**
@@ -56,6 +57,7 @@ export function Provider({children}: {children: React.ReactNode}) {
* However, it only needs to run if AA is enabled.
*/
enabled: isAgeAssuranceEnabled,
+ refetchOnWindowFocus: refetchWhilePending,
queryKey: createAgeAssuranceQueryKey(agent.session?.did ?? 'never'),
async queryFn() {
if (!agent.session) return null
@@ -109,6 +111,24 @@ export function Provider({children}: {children: React.ReactNode}) {
return ctx
}, [isFetched, data, isAgeAssuranceEnabled])
+ if (
+ !!ageAssuranceContext.lastInitiatedAt &&
+ ageAssuranceContext.status === 'pending' &&
+ !refetchWhilePending
+ ) {
+ /*
+ * If we have a pending state, we want to refetch on window focus to ensure
+ * that we get the latest state when the user returns to the app.
+ */
+ setRefetchWhilePending(true)
+ } else if (
+ !!ageAssuranceContext.lastInitiatedAt &&
+ ageAssuranceContext.status !== 'pending' &&
+ refetchWhilePending
+ ) {
+ setRefetchWhilePending(false)
+ }
+
const ageAssuranceAPIContext = useMemo(
() => ({
refetch,