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