diff --git a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx
index 42c9fa8a74..cccf385692 100644
--- a/src/components/ageAssurance/AgeAssuranceAccountCard.tsx
+++ b/src/components/ageAssurance/AgeAssuranceAccountCard.tsx
@@ -6,6 +6,7 @@ import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
import {useAgeAssuranceContext} from '#/state/age-assurance'
import {atoms as a, useBreakpoints, useTheme, type ViewStyleProp} from '#/alf'
import {Admonition} from '#/components/Admonition'
+import {AgeAssuranceAppealDialog} from '#/components/ageAssurance/AgeAssuranceAppealDialog'
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
import {
AgeAssuranceInitDialog,
@@ -13,6 +14,8 @@ import {
} from '#/components/ageAssurance/AgeAssuranceInitDialog'
import {IsAgeRestricted} from '#/components/ageAssurance/IsAgeRestricted'
import {Button, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import {createStaticClick, InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
export function AgeAssuranceAccountCard({style}: ViewStyleProp & {}) {
@@ -29,6 +32,7 @@ function Inner({style}: ViewStyleProp & {}) {
const {status, isAgeRestricted, hasInitiated, lastInitiatedAt} =
useAgeAssuranceContext()
const control = useDialogControl()
+ const appealControl = Dialog.useDialogControl()
const getTimeAgo = useGetTimeAgo()
const {gtPhone} = useBreakpoints()
@@ -42,6 +46,7 @@ function Inner({style}: ViewStyleProp & {}) {
return (
<>
+
You are currently unable to access Bluesky's Age Assurance
- flow. Please contact our moderation team if you believe this
- is an error.
+ flow. Please{' '}
+ {
+ appealControl.open()
+ })}>
+ contact our moderation team
+ {' '}
+ if you believe this is an error.
diff --git a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx
new file mode 100644
index 0000000000..2d20f3667f
--- /dev/null
+++ b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx
@@ -0,0 +1,142 @@
+import React from 'react'
+import {View} from 'react-native'
+import {BSKY_LABELER_DID, ComAtprotoModerationDefs} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useMutation} from '@tanstack/react-query'
+
+import {logger} from '#/logger'
+import {useAgent, useSession} from '#/state/session'
+import * as Toast from '#/view/com/util/Toast'
+import {atoms as a, useBreakpoints, web} from '#/alf'
+import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import * as Dialog from '#/components/Dialog'
+import {Loader} from '#/components/Loader'
+import {Text} from '#/components/Typography'
+
+export function AgeAssuranceAppealDialog({
+ control,
+}: {
+ control: Dialog.DialogControlProps
+}) {
+ const {_} = useLingui()
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+function Inner({control}: {control: Dialog.DialogControlProps}) {
+ const {_} = useLingui()
+ const {currentAccount} = useSession()
+ const {gtPhone} = useBreakpoints()
+ const agent = useAgent()
+
+ const [details, setDetails] = React.useState('')
+ const isInvalid = details.length > 1000
+
+ const {mutate, isPending} = useMutation({
+ mutationFn: async () => {
+ await agent.createModerationReport(
+ {
+ reasonType: ComAtprotoModerationDefs.REASONAPPEAL,
+ subject: {
+ $type: 'com.atproto.admin.defs#repoRef',
+ did: currentAccount?.did,
+ },
+ reason: `AGE_ASSURANCE_INQUIRY: ` + details,
+ },
+ {
+ encoding: 'application/json',
+ headers: {
+ 'atproto-proxy': `${BSKY_LABELER_DID}#atproto_labeler`,
+ },
+ },
+ )
+ },
+ onError: err => {
+ logger.error('AgeAssuranceAppealDialog failed', {safeMessage: err})
+ Toast.show(
+ _(msg`Age assurance inquiry failed to send, please try again.`),
+ 'xmark',
+ )
+ },
+ onSuccess: () => {
+ control.close()
+ Toast.show(
+ _(
+ msg({
+ message: 'Age assurance inquiry was submitted',
+ context: 'toast',
+ }),
+ ),
+ )
+ },
+ })
+
+ const onSubmit = React.useCallback(() => mutate(), [mutate])
+
+ return (
+
+
+
+
+
+
+ Contact us
+
+
+
+
+ Please provide any additional details you feel moderators may need in
+ order to properly assess your Age Assurance status.
+
+
+
+
+ {
+ setDetails(details)
+ }}
+ label={_(msg`Additional details (limit 1000 characters)`)}
+ numberOfLines={4}
+ onSubmitEditing={onSubmit}
+ />
+
+
+
+
+
+
+ )
+}
diff --git a/src/state/age-assurance/index.tsx b/src/state/age-assurance/index.tsx
index e959c69c4f..b7273368f0 100644
--- a/src/state/age-assurance/index.tsx
+++ b/src/state/age-assurance/index.tsx
@@ -65,7 +65,7 @@ export function Provider({children}: {children: React.ReactNode}) {
// (() => ({
// data: {
// lastInitiatedAt: new Date().toISOString(),
- // status: 'assured',
+ // status: 'blocked',
// } as AppBskyUnspeccedDefs.AgeAssuranceState,
// }))(),
// )