mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at rn-bug 299 lines 8.7 kB view raw
1import React from 'react' 2import {View} from 'react-native' 3import {ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {useMutation} from '@tanstack/react-query' 7 8import {useLabelInfo} from '#/lib/moderation/useLabelInfo' 9import {makeProfileLink} from '#/lib/routes/links' 10import {sanitizeHandle} from '#/lib/strings/handles' 11import {logger} from '#/logger' 12import {useAgent, useSession} from '#/state/session' 13import * as Toast from '#/view/com/util/Toast' 14import {atoms as a, useBreakpoints, useTheme} from '#/alf' 15import {Button, ButtonIcon, ButtonText} from '#/components/Button' 16import * as Dialog from '#/components/Dialog' 17import {KeyboardPadding} from '#/components/KeyboardPadding' 18import {InlineLinkText} from '#/components/Link' 19import {Text} from '#/components/Typography' 20import {Divider} from '../Divider' 21import {Loader} from '../Loader' 22export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog' 23 24type Subject = 25 | { 26 uri: string 27 cid: string 28 } 29 | { 30 did: string 31 } 32 33export interface LabelsOnMeDialogProps { 34 control: Dialog.DialogOuterProps['control'] 35 subject: Subject 36 labels: ComAtprotoLabelDefs.Label[] 37} 38 39export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) { 40 return ( 41 <Dialog.Outer control={props.control}> 42 <Dialog.Handle /> 43 44 <LabelsOnMeDialogInner {...props} /> 45 </Dialog.Outer> 46 ) 47} 48 49function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { 50 const {_} = useLingui() 51 const {currentAccount} = useSession() 52 const [appealingLabel, setAppealingLabel] = React.useState< 53 ComAtprotoLabelDefs.Label | undefined 54 >(undefined) 55 const {subject, labels} = props 56 const isAccount = 'did' in subject 57 const containsSelfLabel = React.useMemo( 58 () => labels.some(l => l.src === currentAccount?.did), 59 [currentAccount?.did, labels], 60 ) 61 62 return ( 63 <Dialog.ScrollableInner 64 label={ 65 isAccount 66 ? _(msg`The following labels were applied to your account.`) 67 : _(msg`The following labels were applied to your content.`) 68 }> 69 {appealingLabel ? ( 70 <AppealForm 71 label={appealingLabel} 72 subject={subject} 73 control={props.control} 74 onPressBack={() => setAppealingLabel(undefined)} 75 /> 76 ) : ( 77 <> 78 <Text style={[a.text_2xl, a.font_bold, a.pb_xs, a.leading_tight]}> 79 {isAccount ? ( 80 <Trans>Labels on your account</Trans> 81 ) : ( 82 <Trans>Labels on your content</Trans> 83 )} 84 </Text> 85 <Text style={[a.text_md, a.leading_snug]}> 86 {containsSelfLabel ? ( 87 <Trans> 88 You may appeal non-self labels if you feel they were placed in 89 error. 90 </Trans> 91 ) : ( 92 <Trans> 93 You may appeal these labels if you feel they were placed in 94 error. 95 </Trans> 96 )} 97 </Text> 98 99 <View style={[a.py_lg, a.gap_md]}> 100 {labels.map(label => ( 101 <Label 102 key={`${label.val}-${label.src}`} 103 label={label} 104 isSelfLabel={label.src === currentAccount?.did} 105 control={props.control} 106 onPressAppeal={setAppealingLabel} 107 /> 108 ))} 109 </View> 110 </> 111 )} 112 <Dialog.Close /> 113 <KeyboardPadding /> 114 </Dialog.ScrollableInner> 115 ) 116} 117 118function Label({ 119 label, 120 isSelfLabel, 121 control, 122 onPressAppeal, 123}: { 124 label: ComAtprotoLabelDefs.Label 125 isSelfLabel: boolean 126 control: Dialog.DialogOuterProps['control'] 127 onPressAppeal: (label: ComAtprotoLabelDefs.Label) => void 128}) { 129 const t = useTheme() 130 const {_} = useLingui() 131 const {labeler, strings} = useLabelInfo(label) 132 return ( 133 <View 134 style={[ 135 a.border, 136 t.atoms.border_contrast_low, 137 a.rounded_sm, 138 a.overflow_hidden, 139 ]}> 140 <View style={[a.p_md, a.gap_sm, a.flex_row]}> 141 <View style={[a.flex_1, a.gap_xs]}> 142 <Text style={[a.font_bold, a.text_md]}>{strings.name}</Text> 143 <Text style={[t.atoms.text_contrast_medium, a.leading_snug]}> 144 {strings.description} 145 </Text> 146 </View> 147 {!isSelfLabel && ( 148 <View> 149 <Button 150 variant="solid" 151 color="secondary" 152 size="small" 153 label={_(msg`Appeal`)} 154 onPress={() => onPressAppeal(label)}> 155 <ButtonText> 156 <Trans>Appeal</Trans> 157 </ButtonText> 158 </Button> 159 </View> 160 )} 161 </View> 162 163 <Divider /> 164 165 <View style={[a.px_md, a.py_sm, t.atoms.bg_contrast_25]}> 166 <Text style={[t.atoms.text_contrast_medium]}> 167 {isSelfLabel ? ( 168 <Trans>This label was applied by you.</Trans> 169 ) : ( 170 <Trans> 171 Source:{' '} 172 <InlineLinkText 173 to={makeProfileLink( 174 labeler ? labeler.creator : {did: label.src, handle: ''}, 175 )} 176 onPress={() => control.close()}> 177 {labeler 178 ? sanitizeHandle(labeler.creator.handle, '@') 179 : label.src} 180 </InlineLinkText> 181 </Trans> 182 )} 183 </Text> 184 </View> 185 </View> 186 ) 187} 188 189function AppealForm({ 190 label, 191 subject, 192 control, 193 onPressBack, 194}: { 195 label: ComAtprotoLabelDefs.Label 196 subject: Subject 197 control: Dialog.DialogOuterProps['control'] 198 onPressBack: () => void 199}) { 200 const {_} = useLingui() 201 const {labeler, strings} = useLabelInfo(label) 202 const {gtMobile} = useBreakpoints() 203 const [details, setDetails] = React.useState('') 204 const isAccountReport = 'did' in subject 205 const agent = useAgent() 206 207 const {mutate, isPending} = useMutation({ 208 mutationFn: async () => { 209 const $type = !isAccountReport 210 ? 'com.atproto.repo.strongRef' 211 : 'com.atproto.admin.defs#repoRef' 212 await agent 213 .withProxy('atproto_labeler', label.src) 214 .createModerationReport({ 215 reasonType: ComAtprotoModerationDefs.REASONAPPEAL, 216 subject: { 217 $type, 218 ...subject, 219 }, 220 reason: details, 221 }) 222 }, 223 onError: err => { 224 logger.error('Failed to submit label appeal', {message: err}) 225 Toast.show(_(msg`Failed to submit appeal, please try again.`)) 226 }, 227 onSuccess: () => { 228 control.close() 229 Toast.show(_(msg`Appeal submitted`)) 230 }, 231 }) 232 233 const onSubmit = React.useCallback(() => mutate(), [mutate]) 234 235 return ( 236 <> 237 <Text style={[a.text_2xl, a.font_bold, a.pb_xs, a.leading_tight]}> 238 <Trans>Appeal "{strings.name}" label</Trans> 239 </Text> 240 <Text style={[a.text_md, a.leading_snug]}> 241 <Trans> 242 This appeal will be sent to{' '} 243 <InlineLinkText 244 to={makeProfileLink( 245 labeler ? labeler.creator : {did: label.src, handle: ''}, 246 )} 247 onPress={() => control.close()} 248 style={[a.text_md, a.leading_snug]}> 249 {labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src} 250 </InlineLinkText> 251 . 252 </Trans> 253 </Text> 254 <View style={[a.my_md]}> 255 <Dialog.Input 256 label={_(msg`Text input field`)} 257 placeholder={_( 258 msg`Please explain why you think this label was incorrectly applied by ${ 259 labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src 260 }`, 261 )} 262 value={details} 263 onChangeText={setDetails} 264 autoFocus={true} 265 numberOfLines={3} 266 multiline 267 maxLength={300} 268 /> 269 </View> 270 271 <View 272 style={ 273 gtMobile 274 ? [a.flex_row, a.justify_between] 275 : [{flexDirection: 'column-reverse'}, a.gap_sm] 276 }> 277 <Button 278 testID="backBtn" 279 variant="solid" 280 color="secondary" 281 size="medium" 282 onPress={onPressBack} 283 label={_(msg`Back`)}> 284 <ButtonText>{_(msg`Back`)}</ButtonText> 285 </Button> 286 <Button 287 testID="submitBtn" 288 variant="solid" 289 color="primary" 290 size="medium" 291 onPress={onSubmit} 292 label={_(msg`Submit`)}> 293 <ButtonText>{_(msg`Submit`)}</ButtonText> 294 {isPending && <ButtonIcon icon={Loader} />} 295 </Button> 296 </View> 297 </> 298 ) 299}