Refactor useActorStatus, add disablement and appeal dialog
This commit is contained in:
@@ -0,0 +1,144 @@
|
|||||||
|
import {useCallback, useState} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {type AppBskyActorDefs, ToolsOzoneReportDefs} from '@atproto/api'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useMutation} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {BLUESKY_MOD_SERVICE_HEADERS} from '#/lib/constants'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {useAgent} from '#/state/session'
|
||||||
|
import {atoms as a, web} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import * as Toast from '#/components/Toast'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function GoLiveDisabledDialog({
|
||||||
|
control,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
status: AppBskyActorDefs.StatusView
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
<DialogInner control={control} status={status} />
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DialogInner({
|
||||||
|
control,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
status: AppBskyActorDefs.StatusView
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const agent = useAgent()
|
||||||
|
const [details, setDetails] = useState('')
|
||||||
|
|
||||||
|
const {mutate, isPending} = useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
if (!agent.session?.did) {
|
||||||
|
throw new Error('Not logged in')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
logger.info('Submitting go live appeal', {
|
||||||
|
details,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
await agent.createModerationReport(
|
||||||
|
{
|
||||||
|
reasonType: ToolsOzoneReportDefs.REASONAPPEAL,
|
||||||
|
subject: {
|
||||||
|
$type: 'com.atproto.repo.strongRef',
|
||||||
|
uri: status.uri!,
|
||||||
|
cid: status.cid!,
|
||||||
|
},
|
||||||
|
reason: details,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
encoding: 'application/json',
|
||||||
|
headers: BLUESKY_MOD_SERVICE_HEADERS,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
Toast.show(_(msg`Failed to submit appeal, please try again.`), {
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
control.close()
|
||||||
|
Toast.show(_(msg({message: 'Appeal submitted', context: 'toast'})), {
|
||||||
|
type: 'success',
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSubmit = useCallback(() => mutate(), [mutate])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={_(msg`Appeal livestream suspension`)}
|
||||||
|
style={[web({maxWidth: 400})]}>
|
||||||
|
<View style={[a.gap_lg]}>
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.flex_1,
|
||||||
|
a.text_2xl,
|
||||||
|
a.font_semi_bold,
|
||||||
|
a.leading_snug,
|
||||||
|
a.pr_4xl,
|
||||||
|
]}>
|
||||||
|
<Trans>Going live is currently disabled for your account</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_md, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
You are currently blocked from using the Go Live feature. To
|
||||||
|
appeal this moderation decision, please submit the form below.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_md, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
This appeal will be sent to Bluesky's moderation service.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={[a.gap_md]}>
|
||||||
|
<Dialog.Input
|
||||||
|
label={_(msg`Text input field`)}
|
||||||
|
placeholder={_(
|
||||||
|
msg`Please explain why you think your Go Live access was incorrectly disabled.`,
|
||||||
|
)}
|
||||||
|
value={details}
|
||||||
|
onChangeText={setDetails}
|
||||||
|
autoFocus={true}
|
||||||
|
numberOfLines={3}
|
||||||
|
multiline
|
||||||
|
maxLength={300}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
testID="submitBtn"
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
onPress={onSubmit}
|
||||||
|
label={_(msg`Submit`)}>
|
||||||
|
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
||||||
|
{isPending && <ButtonIcon icon={Loader} />}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Dialog.Close />
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
)
|
||||||
|
}
|
||||||
+20
-7
@@ -19,23 +19,36 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
|
|||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
tick! // revalidate every minute
|
tick! // revalidate every minute
|
||||||
|
|
||||||
if (
|
if (shadowed && 'status' in shadowed && shadowed.status) {
|
||||||
shadowed &&
|
const isValid = validateStatus(shadowed.did, shadowed.status, config)
|
||||||
'status' in shadowed &&
|
const isDisabled = shadowed.status.isDisabled || false
|
||||||
shadowed.status &&
|
const isActive = isStatusStillActive(shadowed.status.expiresAt)
|
||||||
validateStatus(shadowed.did, shadowed.status, config) &&
|
if (isValid && !isDisabled && isActive) {
|
||||||
isStatusStillActive(shadowed.status.expiresAt)
|
|
||||||
) {
|
|
||||||
return {
|
return {
|
||||||
|
uri: shadowed.status.uri,
|
||||||
|
cid: shadowed.status.cid,
|
||||||
|
isDisabled: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
status: 'app.bsky.actor.status#live',
|
status: 'app.bsky.actor.status#live',
|
||||||
embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this
|
embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this
|
||||||
expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this
|
expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this
|
||||||
record: shadowed.status.record,
|
record: shadowed.status.record,
|
||||||
} satisfies AppBskyActorDefs.StatusView
|
} satisfies AppBskyActorDefs.StatusView
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
uri: shadowed.status.uri,
|
||||||
|
cid: shadowed.status.cid,
|
||||||
|
isDisabled,
|
||||||
|
isActive: false,
|
||||||
|
status: 'app.bsky.actor.status#live',
|
||||||
|
embed: shadowed.status.embed as $Typed<AppBskyEmbedExternal.View>, // temp_isStatusValid asserts this
|
||||||
|
expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this
|
||||||
|
record: shadowed.status.record,
|
||||||
|
} satisfies AppBskyActorDefs.StatusView
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
status: '',
|
status: '',
|
||||||
|
isDisabled: false,
|
||||||
isActive: false,
|
isActive: false,
|
||||||
record: {},
|
record: {},
|
||||||
} satisfies AppBskyActorDefs.StatusView
|
} satisfies AppBskyActorDefs.StatusView
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/
|
|||||||
import {StarterPack} from '#/components/icons/StarterPack'
|
import {StarterPack} from '#/components/icons/StarterPack'
|
||||||
import {EditLiveDialog} from '#/components/live/EditLiveDialog'
|
import {EditLiveDialog} from '#/components/live/EditLiveDialog'
|
||||||
import {GoLiveDialog} from '#/components/live/GoLiveDialog'
|
import {GoLiveDialog} from '#/components/live/GoLiveDialog'
|
||||||
|
import {GoLiveDisabledDialog} from '#/components/live/GoLiveDisabledDialog'
|
||||||
import * as Menu from '#/components/Menu'
|
import * as Menu from '#/components/Menu'
|
||||||
import {
|
import {
|
||||||
ReportDialog,
|
ReportDialog,
|
||||||
@@ -79,6 +80,7 @@ let ProfileMenu = ({
|
|||||||
const [devModeEnabled] = useDevMode()
|
const [devModeEnabled] = useDevMode()
|
||||||
const verification = useFullVerificationState({profile})
|
const verification = useFullVerificationState({profile})
|
||||||
const canGoLive = useCanGoLive(currentAccount?.did)
|
const canGoLive = useCanGoLive(currentAccount?.did)
|
||||||
|
const status = useActorStatus(profile)
|
||||||
|
|
||||||
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
|
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
|
||||||
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
|
||||||
@@ -90,6 +92,7 @@ let ProfileMenu = ({
|
|||||||
const blockPromptControl = Prompt.usePromptControl()
|
const blockPromptControl = Prompt.usePromptControl()
|
||||||
const loggedOutWarningPromptControl = Prompt.usePromptControl()
|
const loggedOutWarningPromptControl = Prompt.usePromptControl()
|
||||||
const goLiveDialogControl = useDialogControl()
|
const goLiveDialogControl = useDialogControl()
|
||||||
|
const goLiveDisabledDialogControl = useDialogControl()
|
||||||
const addToStarterPacksDialogControl = useDialogControl()
|
const addToStarterPacksDialogControl = useDialogControl()
|
||||||
|
|
||||||
const showLoggedOutWarning = React.useMemo(() => {
|
const showLoggedOutWarning = React.useMemo(() => {
|
||||||
@@ -220,8 +223,6 @@ let ProfileMenu = ({
|
|||||||
return v.issuer === currentAccount?.did
|
return v.issuer === currentAccount?.did
|
||||||
}) ?? []
|
}) ?? []
|
||||||
|
|
||||||
const status = useActorStatus(profile)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EventStopper onKeyDown={false}>
|
<EventStopper onKeyDown={false}>
|
||||||
<Menu.Root>
|
<Menu.Root>
|
||||||
@@ -330,13 +331,21 @@ let ProfileMenu = ({
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
testID="profileHeaderDropdownListAddRemoveBtn"
|
testID="profileHeaderDropdownListAddRemoveBtn"
|
||||||
label={
|
label={
|
||||||
status.isActive
|
status.isDisabled
|
||||||
|
? _(msg`Go live is disabled`)
|
||||||
|
: status.isActive
|
||||||
? _(msg`Edit live status`)
|
? _(msg`Edit live status`)
|
||||||
: _(msg`Go live`)
|
: _(msg`Go live`)
|
||||||
}
|
}
|
||||||
onPress={goLiveDialogControl.open}>
|
onPress={
|
||||||
|
status.isDisabled
|
||||||
|
? goLiveDisabledDialogControl.open
|
||||||
|
: goLiveDialogControl.open
|
||||||
|
}>
|
||||||
<Menu.ItemText>
|
<Menu.ItemText>
|
||||||
{status.isActive ? (
|
{status.isDisabled ? (
|
||||||
|
<Trans>Go live is disabled</Trans>
|
||||||
|
) : status.isActive ? (
|
||||||
<Trans>Edit live status</Trans>
|
<Trans>Edit live status</Trans>
|
||||||
) : (
|
) : (
|
||||||
<Trans>Go live</Trans>
|
<Trans>Go live</Trans>
|
||||||
@@ -517,7 +526,12 @@ let ProfileMenu = ({
|
|||||||
verifications={currentAccountVerifications}
|
verifications={currentAccountVerifications}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{status.isActive ? (
|
{status.isDisabled ? (
|
||||||
|
<GoLiveDisabledDialog
|
||||||
|
control={goLiveDisabledDialogControl}
|
||||||
|
status={status}
|
||||||
|
/>
|
||||||
|
) : status.isActive ? (
|
||||||
<EditLiveDialog
|
<EditLiveDialog
|
||||||
control={goLiveDialogControl}
|
control={goLiveDialogControl}
|
||||||
status={status}
|
status={status}
|
||||||
|
|||||||
Reference in New Issue
Block a user