mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View} from 'react-native'
3import {AppBskyLabelerDefs} from '@atproto/api'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6
7import {getLabelingServiceTitle} from '#/lib/moderation'
8import {ReportOption} from '#/lib/moderation/useReportOptions'
9import {useGate} from '#/lib/statsig/statsig'
10import {useAgent} from '#/state/session'
11import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
12import * as Toast from '#/view/com/util/Toast'
13import {atoms as a, native, useTheme} from '#/alf'
14import {Button, ButtonIcon, ButtonText} from '#/components/Button'
15import * as Dialog from '#/components/Dialog'
16import * as Toggle from '#/components/forms/Toggle'
17import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
18import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
19import {Loader} from '#/components/Loader'
20import {Text} from '#/components/Typography'
21import {ReportDialogProps} from './types'
22
23export function SubmitView({
24 params,
25 labelers,
26 selectedLabeler,
27 selectedReportOption,
28 goBack,
29 onSubmitComplete,
30}: ReportDialogProps & {
31 labelers: AppBskyLabelerDefs.LabelerViewDetailed[]
32 selectedLabeler: string
33 selectedReportOption: ReportOption
34 goBack: () => void
35 onSubmitComplete: () => void
36}) {
37 const t = useTheme()
38 const {_} = useLingui()
39 const agent = useAgent()
40 const gate = useGate()
41 const [details, setDetails] = React.useState<string>('')
42 const [submitting, setSubmitting] = React.useState<boolean>(false)
43 const [selectedServices, setSelectedServices] = React.useState<string[]>([
44 selectedLabeler,
45 ])
46 const [error, setError] = React.useState('')
47
48 const submit = React.useCallback(async () => {
49 setSubmitting(true)
50 setError('')
51
52 const $type =
53 params.type === 'account'
54 ? 'com.atproto.admin.defs#repoRef'
55 : 'com.atproto.repo.strongRef'
56 const report = {
57 reasonType: selectedReportOption.reason,
58 subject: {
59 $type,
60 ...params,
61 },
62 reason: details,
63 }
64 const results = await Promise.all(
65 selectedServices.map(did => {
66 if (gate('session_withproxy_fix')) {
67 return agent
68 .createModerationReport(report, {
69 encoding: 'application/json',
70 headers: {
71 'atproto-proxy': `${did}#atproto_labeler`,
72 },
73 })
74 .then(
75 _ => true,
76 _ => false,
77 )
78 } else {
79 return agent
80 .withProxy('atproto_labeler', did)
81 .createModerationReport(report)
82 .then(
83 _ => true,
84 _ => false,
85 )
86 }
87 }),
88 )
89
90 setSubmitting(false)
91
92 if (results.includes(true)) {
93 Toast.show(_(msg`Thank you. Your report has been sent.`))
94 onSubmitComplete()
95 } else {
96 setError(
97 _(
98 msg`There was an issue sending your report. Please check your internet connection.`,
99 ),
100 )
101 }
102 }, [
103 _,
104 params,
105 details,
106 selectedReportOption,
107 selectedServices,
108 onSubmitComplete,
109 setError,
110 agent,
111 gate,
112 ])
113
114 return (
115 <View style={[a.gap_2xl]}>
116 <Button
117 size="small"
118 variant="solid"
119 color="secondary"
120 shape="round"
121 label={_(msg`Go back to previous step`)}
122 onPress={goBack}>
123 <ButtonIcon icon={ChevronLeft} />
124 </Button>
125
126 <View
127 style={[
128 a.w_full,
129 a.flex_row,
130 a.align_center,
131 a.justify_between,
132 a.gap_lg,
133 a.p_md,
134 a.rounded_md,
135 a.border,
136 t.atoms.border_contrast_low,
137 ]}>
138 <View style={[a.flex_1, a.gap_xs]}>
139 <Text style={[a.text_md, a.font_bold]}>
140 {selectedReportOption.title}
141 </Text>
142 <Text style={[a.leading_tight, {maxWidth: 400}]}>
143 {selectedReportOption.description}
144 </Text>
145 </View>
146
147 <Check size="md" style={[a.pr_sm, t.atoms.text_contrast_low]} />
148 </View>
149
150 <View style={[a.gap_md]}>
151 <Text style={[t.atoms.text_contrast_medium]}>
152 <Trans>Select the moderation service(s) to report to</Trans>
153 </Text>
154
155 <Toggle.Group
156 label="Select mod services"
157 values={selectedServices}
158 onChange={setSelectedServices}>
159 <View style={[a.flex_row, a.gap_md, a.flex_wrap]}>
160 {labelers.map(labeler => {
161 const title = getLabelingServiceTitle({
162 displayName: labeler.creator.displayName,
163 handle: labeler.creator.handle,
164 })
165 return (
166 <Toggle.Item
167 key={labeler.creator.did}
168 name={labeler.creator.did}
169 label={title}>
170 <LabelerToggle title={title} />
171 </Toggle.Item>
172 )
173 })}
174 </View>
175 </Toggle.Group>
176 </View>
177 <View style={[a.gap_md]}>
178 <Text style={[t.atoms.text_contrast_medium]}>
179 <Trans>Optionally provide additional information below:</Trans>
180 </Text>
181
182 <View style={[a.relative, a.w_full]}>
183 <Dialog.Input
184 multiline
185 value={details}
186 onChangeText={setDetails}
187 label="Text field"
188 style={{paddingRight: 60}}
189 numberOfLines={6}
190 />
191
192 <View
193 style={[
194 a.absolute,
195 a.flex_row,
196 a.align_center,
197 a.pr_md,
198 a.pb_sm,
199 {
200 bottom: 0,
201 right: 0,
202 },
203 ]}>
204 <CharProgress count={details?.length || 0} />
205 </View>
206 </View>
207 </View>
208
209 <View style={[a.flex_row, a.align_center, a.justify_end, a.gap_lg]}>
210 {!selectedServices.length ||
211 (error && (
212 <Text
213 style={[
214 a.flex_1,
215 a.italic,
216 a.leading_snug,
217 t.atoms.text_contrast_medium,
218 ]}>
219 {error ? (
220 error
221 ) : (
222 <Trans>You must select at least one labeler for a report</Trans>
223 )}
224 </Text>
225 ))}
226
227 <Button
228 testID="sendReportBtn"
229 size="large"
230 variant="solid"
231 color="negative"
232 label={_(msg`Send report`)}
233 onPress={submit}
234 disabled={!selectedServices.length}>
235 <ButtonText>
236 <Trans>Send report</Trans>
237 </ButtonText>
238 {submitting && <ButtonIcon icon={Loader} />}
239 </Button>
240 </View>
241 </View>
242 )
243}
244
245function LabelerToggle({title}: {title: string}) {
246 const t = useTheme()
247 const ctx = Toggle.useItemContext()
248
249 return (
250 <View
251 style={[
252 a.flex_row,
253 a.align_center,
254 a.gap_md,
255 a.p_md,
256 a.pr_lg,
257 a.rounded_sm,
258 a.overflow_hidden,
259 t.atoms.bg_contrast_25,
260 ctx.selected && [t.atoms.bg_contrast_50],
261 ]}>
262 <Toggle.Checkbox />
263 <View
264 style={[
265 a.flex_row,
266 a.align_center,
267 a.justify_between,
268 a.gap_lg,
269 a.z_10,
270 ]}>
271 <Text
272 style={[
273 native({marginTop: 2}),
274 t.atoms.text_contrast_medium,
275 ctx.selected && t.atoms.text,
276 ]}>
277 {title}
278 </Text>
279 </View>
280 </View>
281 )
282}