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