Bluesky app fork with some witchin' additions 馃挮
witchsky.app
bluesky
fork
client
1import {memo, useState} from 'react'
2import {View} from 'react-native'
3import {type AppBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api'
4import {msg} from '@lingui/core/macro'
5import {useLingui} from '@lingui/react'
6import {Trans} from '@lingui/react/macro'
7import {StackActions, useNavigation} from '@react-navigation/native'
8import type React from 'react'
9
10import {type NavigationProp} from '#/lib/routes/types'
11import {useProfileShadow} from '#/state/cache/profile-shadow'
12import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
13import {
14 useProfileBlockMutationQueue,
15 useProfileQuery,
16} from '#/state/queries/profile'
17import * as Toast from '#/view/com/util/Toast'
18import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf'
19import {Button, ButtonText} from '#/components/Button'
20import * as Dialog from '#/components/Dialog'
21import * as Toggle from '#/components/forms/Toggle'
22import {Loader} from '#/components/Loader'
23import {Text} from '#/components/Typography'
24import {IS_NATIVE} from '#/env'
25
26type ReportDialogParams = {
27 convoId: string
28 message: ChatBskyConvoDefs.MessageView
29}
30
31/**
32 * Dialog shown after a report is submitted, allowing the user to block the
33 * reporter and/or leave the conversation.
34 */
35export const AfterReportDialog = memo(function BlockOrDeleteDialogInner({
36 control,
37 params,
38 currentScreen,
39}: {
40 control: Dialog.DialogControlProps
41 params: ReportDialogParams
42 currentScreen: 'list' | 'conversation'
43}): React.ReactNode {
44 const {_} = useLingui()
45 return (
46 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
47 <Dialog.Handle />
48 <Dialog.ScrollableInner
49 label={_(
50 msg`Would you like to block this user and/or delete this conversation?`,
51 )}
52 style={[web({maxWidth: 400})]}>
53 <DialogInner params={params} currentScreen={currentScreen} />
54 <Dialog.Close />
55 </Dialog.ScrollableInner>
56 </Dialog.Outer>
57 )
58})
59
60function DialogInner({
61 params,
62 currentScreen,
63}: {
64 params: ReportDialogParams
65 currentScreen: 'list' | 'conversation'
66}) {
67 const t = useTheme()
68 const {_} = useLingui()
69 const control = Dialog.useDialogContext()
70 const {
71 data: profile,
72 isPending,
73 isError,
74 } = useProfileQuery({
75 did: params.message.sender.did,
76 })
77
78 return isPending ? (
79 <View style={[a.w_full, a.py_5xl, a.align_center]}>
80 <Loader size="lg" />
81 </View>
82 ) : isError || !profile ? (
83 <View style={[a.w_full, a.gap_lg]}>
84 <View style={[a.justify_center, a.gap_sm]}>
85 <Text style={[a.text_2xl, a.font_semi_bold]}>
86 <Trans>Report submitted</Trans>
87 </Text>
88 <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
89 <Trans>Our moderation team has received your report.</Trans>
90 </Text>
91 </View>
92
93 <Button
94 label={_(msg`Close`)}
95 onPress={() => control.close()}
96 size={platform({native: 'small', web: 'large'})}
97 color="secondary">
98 <ButtonText>
99 <Trans>Close</Trans>
100 </ButtonText>
101 </Button>
102 </View>
103 ) : (
104 <DoneStep
105 convoId={params.convoId}
106 currentScreen={currentScreen}
107 profile={profile}
108 />
109 )
110}
111
112function DoneStep({
113 convoId,
114 currentScreen,
115 profile,
116}: {
117 convoId: string
118 currentScreen: 'list' | 'conversation'
119 profile: AppBskyActorDefs.ProfileViewDetailed
120}) {
121 const {_} = useLingui()
122 const navigation = useNavigation<NavigationProp>()
123 const control = Dialog.useDialogContext()
124 const {gtMobile} = useBreakpoints()
125 const t = useTheme()
126 const [actions, setActions] = useState<string[]>(['block', 'leave'])
127 const shadow = useProfileShadow(profile)
128 const [queueBlock] = useProfileBlockMutationQueue(shadow)
129
130 const {mutate: leaveConvo} = useLeaveConvo(convoId, {
131 onMutate: () => {
132 if (currentScreen === 'conversation') {
133 navigation.dispatch(
134 StackActions.replace('Messages', IS_NATIVE ? {animation: 'pop'} : {}),
135 )
136 }
137 },
138 onError: () => {
139 Toast.show(_(msg`Could not leave chat`), 'xmark')
140 },
141 })
142
143 let btnText = _(msg`Done`)
144 let toastMsg: string | undefined
145 if (actions.includes('leave') && actions.includes('block')) {
146 btnText = _(msg`Block and Delete`)
147 toastMsg = _(msg({message: 'Conversation deleted', context: 'toast'}))
148 } else if (actions.includes('leave')) {
149 btnText = _(msg`Delete Conversation`)
150 toastMsg = _(msg({message: 'Conversation deleted', context: 'toast'}))
151 } else if (actions.includes('block')) {
152 btnText = _(msg`Block User`)
153 toastMsg = _(msg({message: 'User blocked', context: 'toast'}))
154 }
155
156 const onPressPrimaryAction = () => {
157 control.close(() => {
158 if (actions.includes('block')) {
159 queueBlock()
160 }
161 if (actions.includes('leave')) {
162 leaveConvo()
163 }
164 if (toastMsg) {
165 Toast.show(toastMsg, 'check')
166 }
167 })
168 }
169
170 return (
171 <View style={a.gap_2xl}>
172 <View style={[a.justify_center, gtMobile ? a.gap_sm : a.gap_xs]}>
173 <Text style={[a.text_2xl, a.font_semi_bold]}>
174 <Trans>Report submitted</Trans>
175 </Text>
176 <Text style={[a.text_md, t.atoms.text_contrast_medium]}>
177 <Trans>Our moderation team has received your report.</Trans>
178 </Text>
179 </View>
180 <Toggle.Group
181 label={_(msg`Block user and/or delete this conversation`)}
182 values={actions}
183 onChange={setActions}>
184 <View style={[a.gap_md]}>
185 <Toggle.Item name="block" label={_(msg`Block user`)}>
186 <Toggle.Checkbox />
187 <Toggle.LabelText style={[a.text_md]}>
188 <Trans>Block user</Trans>
189 </Toggle.LabelText>
190 </Toggle.Item>
191 <Toggle.Item name="leave" label={_(msg`Delete conversation`)}>
192 <Toggle.Checkbox />
193 <Toggle.LabelText style={[a.text_md]}>
194 <Trans>Delete conversation</Trans>
195 </Toggle.LabelText>
196 </Toggle.Item>
197 </View>
198 </Toggle.Group>
199
200 <View style={[a.gap_sm]}>
201 <Button
202 label={btnText}
203 onPress={onPressPrimaryAction}
204 size="large"
205 color={actions.length > 0 ? 'negative' : 'primary'}>
206 <ButtonText>{btnText}</ButtonText>
207 </Button>
208 <Button
209 label={_(msg`Close`)}
210 onPress={() => control.close()}
211 size="large"
212 color="secondary">
213 <ButtonText>
214 <Trans>Close</Trans>
215 </ButtonText>
216 </Button>
217 </View>
218 </View>
219 )
220}