Add appeal dialog

This commit is contained in:
Eric Bailey
2025-07-11 18:15:53 -05:00
parent 7ced82124b
commit 0a7750e642
3 changed files with 157 additions and 3 deletions
@@ -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 (
<>
<AgeAssuranceInitDialog control={control} />
<AgeAssuranceAppealDialog control={appealControl} />
<View style={style}>
<View
@@ -80,8 +85,15 @@ function Inner({style}: ViewStyleProp & {}) {
<Admonition type="warning">
<Trans>
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{' '}
<InlineLinkText
label={_(msg`Contact our moderation team`)}
{...createStaticClick(() => {
appealControl.open()
})}>
contact our moderation team
</InlineLinkText>{' '}
if you believe this is an error.
</Trans>
</Admonition>
</View>
@@ -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 (
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={_(msg`Contact our moderation team`)}
style={[web({maxWidth: 400})]}>
<Inner control={control} />
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
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 (
<View>
<View style={[a.align_start]}>
<AgeAssuranceBadge />
</View>
<Text style={[a.text_2xl, a.font_heavy, a.pt_md, a.leading_tight]}>
<Trans>Contact us</Trans>
</Text>
<Text style={[a.text_sm, a.pt_sm, a.leading_snug]}>
<Trans>
Please provide any additional details you feel moderators may need in
order to properly assess your Age Assurance status.
</Trans>
</Text>
<View style={[a.pt_md]}>
<Dialog.Input
multiline
isInvalid={isInvalid}
value={details}
onChangeText={details => {
setDetails(details)
}}
label={_(msg`Additional details (limit 1000 characters)`)}
numberOfLines={4}
onSubmitEditing={onSubmit}
/>
<View style={[a.pt_md, a.gap_sm, gtPhone && [a.flex_row_reverse]]}>
<Button
label={_(msg`Submit`)}
size="small"
variant="solid"
color="primary"
onPress={onSubmit}>
<ButtonText>
<Trans>Submit</Trans>
</ButtonText>
{isPending && <ButtonIcon icon={Loader} position="right" />}
</Button>
<Button
label={_(msg`Cancel`)}
size="small"
variant="solid"
color="secondary"
onPress={() => control.close()}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
</View>
</View>
</View>
)
}
+1 -1
View File
@@ -65,7 +65,7 @@ export function Provider({children}: {children: React.ReactNode}) {
// (() => ({
// data: {
// lastInitiatedAt: new Date().toISOString(),
// status: 'assured',
// status: 'blocked',
// } as AppBskyUnspeccedDefs.AgeAssuranceState,
// }))(),
// )