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}