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, // }))(), // )