mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
fork

Configure Feed

Select the types of activity you want to include in your feed.

Delete some old dialogs (#8512)

* Delete old EditProfile dialog

* Delete old email verification dialogs

authored by

Eric Bailey and committed by
GitHub
619fa0d0 9cf457ac

-954
-259
src/components/dialogs/ChangeEmailDialog.tsx
··· 1 - import {useState} from 'react' 2 - import {View} from 'react-native' 3 - import {msg, Trans} from '@lingui/macro' 4 - import {useLingui} from '@lingui/react' 5 - 6 - import {cleanError} from '#/lib/strings/errors' 7 - import {useAgent, useSession} from '#/state/session' 8 - import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 9 - import {atoms as a, useBreakpoints, web} from '#/alf' 10 - import {Button, ButtonText} from '#/components/Button' 11 - import * as Dialog from '#/components/Dialog' 12 - import * as TextField from '#/components/forms/TextField' 13 - import {Loader} from '#/components/Loader' 14 - import {Text} from '#/components/Typography' 15 - 16 - export function ChangeEmailDialog({ 17 - control, 18 - verifyEmailControl, 19 - }: { 20 - control: Dialog.DialogControlProps 21 - verifyEmailControl: Dialog.DialogControlProps 22 - }) { 23 - return ( 24 - <Dialog.Outer control={control}> 25 - <Dialog.Handle /> 26 - <Inner verifyEmailControl={verifyEmailControl} /> 27 - </Dialog.Outer> 28 - ) 29 - } 30 - 31 - export function Inner({ 32 - verifyEmailControl, 33 - }: { 34 - verifyEmailControl: Dialog.DialogControlProps 35 - }) { 36 - const {_} = useLingui() 37 - const {currentAccount} = useSession() 38 - const agent = useAgent() 39 - const control = Dialog.useDialogContext() 40 - const {gtMobile} = useBreakpoints() 41 - 42 - const [currentStep, setCurrentStep] = useState< 43 - 'StepOne' | 'StepTwo' | 'StepThree' 44 - >('StepOne') 45 - const [email, setEmail] = useState('') 46 - const [confirmationCode, setConfirmationCode] = useState('') 47 - const [isProcessing, setIsProcessing] = useState(false) 48 - const [error, setError] = useState('') 49 - 50 - const currentEmail = currentAccount?.email || '(no email)' 51 - const uiStrings = { 52 - StepOne: { 53 - title: _(msg`Change Your Email`), 54 - message: '', 55 - }, 56 - StepTwo: { 57 - title: _(msg`Security Step Required`), 58 - message: _( 59 - msg`An email has been sent to your previous address, ${currentEmail}. It includes a confirmation code which you can enter below.`, 60 - ), 61 - }, 62 - StepThree: { 63 - title: _(msg`Email Updated!`), 64 - message: _( 65 - msg`Your email address has been updated but it is not yet verified. As a next step, please verify your new email.`, 66 - ), 67 - }, 68 - } 69 - 70 - const onRequestChange = async () => { 71 - if (email === currentAccount?.email) { 72 - setError( 73 - _( 74 - msg`The email address you entered is the same as your current email address.`, 75 - ), 76 - ) 77 - return 78 - } 79 - setError('') 80 - setIsProcessing(true) 81 - try { 82 - const res = await agent.com.atproto.server.requestEmailUpdate() 83 - if (res.data.tokenRequired) { 84 - setCurrentStep('StepTwo') 85 - } else { 86 - await agent.com.atproto.server.updateEmail({email: email.trim()}) 87 - await agent.resumeSession(agent.session!) 88 - setCurrentStep('StepThree') 89 - } 90 - } catch (e) { 91 - setError(cleanError(String(e))) 92 - } finally { 93 - setIsProcessing(false) 94 - } 95 - } 96 - 97 - const onConfirm = async () => { 98 - setError('') 99 - setIsProcessing(true) 100 - try { 101 - await agent.com.atproto.server.updateEmail({ 102 - email: email.trim(), 103 - token: confirmationCode.trim(), 104 - }) 105 - await agent.resumeSession(agent.session!) 106 - setCurrentStep('StepThree') 107 - } catch (e) { 108 - setError(cleanError(String(e))) 109 - } finally { 110 - setIsProcessing(false) 111 - } 112 - } 113 - 114 - const onVerify = async () => { 115 - control.close(() => { 116 - verifyEmailControl.open() 117 - }) 118 - } 119 - 120 - return ( 121 - <Dialog.ScrollableInner 122 - label={_(msg`Verify email dialog`)} 123 - style={web({maxWidth: 450})}> 124 - <Dialog.Close /> 125 - <View style={[a.gap_xl]}> 126 - <View style={[a.gap_sm]}> 127 - <Text style={[a.font_heavy, a.text_2xl]}> 128 - {uiStrings[currentStep].title} 129 - </Text> 130 - {error ? ( 131 - <View style={[a.rounded_sm, a.overflow_hidden]}> 132 - <ErrorMessage message={error} /> 133 - </View> 134 - ) : null} 135 - {currentStep === 'StepOne' ? ( 136 - <View> 137 - <TextField.LabelText> 138 - <Trans>Enter your new email address below.</Trans> 139 - </TextField.LabelText> 140 - <TextField.Root> 141 - <TextField.Input 142 - label={_(msg`New email address`)} 143 - placeholder={_(msg`alice@example.com`)} 144 - defaultValue={email} 145 - onChangeText={setEmail} 146 - keyboardType="email-address" 147 - autoComplete="email" 148 - /> 149 - </TextField.Root> 150 - </View> 151 - ) : ( 152 - <Text style={[a.text_md, a.leading_snug]}> 153 - {uiStrings[currentStep].message} 154 - </Text> 155 - )} 156 - </View> 157 - {currentStep === 'StepTwo' ? ( 158 - <View> 159 - <TextField.LabelText> 160 - <Trans>Confirmation code</Trans> 161 - </TextField.LabelText> 162 - <TextField.Root> 163 - <TextField.Input 164 - label={_(msg`Confirmation code`)} 165 - placeholder="XXXXX-XXXXX" 166 - onChangeText={setConfirmationCode} 167 - /> 168 - </TextField.Root> 169 - </View> 170 - ) : null} 171 - <View style={[a.gap_sm, gtMobile && [a.flex_row_reverse, a.ml_auto]]}> 172 - {currentStep === 'StepOne' ? ( 173 - <> 174 - <Button 175 - label={_(msg`Request change`)} 176 - variant="solid" 177 - color="primary" 178 - size="large" 179 - disabled={isProcessing} 180 - onPress={onRequestChange}> 181 - <ButtonText> 182 - <Trans>Request change</Trans> 183 - </ButtonText> 184 - {isProcessing ? ( 185 - <Loader size="sm" style={[{color: 'white'}]} /> 186 - ) : null} 187 - </Button> 188 - <Button 189 - label={_(msg`I have a code`)} 190 - variant="solid" 191 - color="secondary" 192 - size="large" 193 - disabled={isProcessing} 194 - onPress={() => setCurrentStep('StepTwo')}> 195 - <ButtonText> 196 - <Trans>I have a code</Trans> 197 - </ButtonText> 198 - </Button> 199 - </> 200 - ) : currentStep === 'StepTwo' ? ( 201 - <> 202 - <Button 203 - label={_(msg`Confirm`)} 204 - variant="solid" 205 - color="primary" 206 - size="large" 207 - disabled={isProcessing} 208 - onPress={onConfirm}> 209 - <ButtonText> 210 - <Trans>Confirm</Trans> 211 - </ButtonText> 212 - {isProcessing ? ( 213 - <Loader size="sm" style={[{color: 'white'}]} /> 214 - ) : null} 215 - </Button> 216 - <Button 217 - label={_(msg`Resend email`)} 218 - variant="solid" 219 - color="secondary" 220 - size="large" 221 - disabled={isProcessing} 222 - onPress={() => { 223 - setConfirmationCode('') 224 - setCurrentStep('StepOne') 225 - }}> 226 - <ButtonText> 227 - <Trans>Resend email</Trans> 228 - </ButtonText> 229 - </Button> 230 - </> 231 - ) : currentStep === 'StepThree' ? ( 232 - <> 233 - <Button 234 - label={_(msg`Verify email`)} 235 - variant="solid" 236 - color="primary" 237 - size="large" 238 - onPress={onVerify}> 239 - <ButtonText> 240 - <Trans>Verify email</Trans> 241 - </ButtonText> 242 - </Button> 243 - <Button 244 - label={_(msg`Close`)} 245 - variant="solid" 246 - color="secondary" 247 - size="large" 248 - onPress={() => control.close()}> 249 - <ButtonText> 250 - <Trans>Close</Trans> 251 - </ButtonText> 252 - </Button> 253 - </> 254 - ) : null} 255 - </View> 256 - </View> 257 - </Dialog.ScrollableInner> 258 - ) 259 - }
-360
src/components/dialogs/VerifyEmailDialog.tsx
··· 1 - import {useState} from 'react' 2 - import {View} from 'react-native' 3 - import {msg, Trans} from '@lingui/macro' 4 - import {useLingui} from '@lingui/react' 5 - 6 - import {cleanError} from '#/lib/strings/errors' 7 - import {logger} from '#/logger' 8 - import {useAgent, useSession} from '#/state/session' 9 - import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' 10 - import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 11 - import {Button, ButtonText} from '#/components/Button' 12 - import * as Dialog from '#/components/Dialog' 13 - import * as TextField from '#/components/forms/TextField' 14 - import {Envelope_Filled_Stroke2_Corner0_Rounded as EnvelopeIcon} from '#/components/icons/Envelope' 15 - import {InlineLinkText} from '#/components/Link' 16 - import {Loader} from '#/components/Loader' 17 - import {Text} from '#/components/Typography' 18 - import {ChangeEmailDialog} from './ChangeEmailDialog' 19 - 20 - export function VerifyEmailDialog({ 21 - control, 22 - onCloseWithoutVerifying, 23 - onCloseAfterVerifying, 24 - reasonText, 25 - changeEmailControl, 26 - reminder, 27 - }: { 28 - control: Dialog.DialogControlProps 29 - onCloseWithoutVerifying?: () => void 30 - onCloseAfterVerifying?: () => void 31 - reasonText?: string 32 - /** 33 - * if a changeEmailControl for a ChangeEmailDialog is not provided, 34 - * this component will create one for you. Using this prop 35 - * helps reduce duplication, since these dialogs are often used together. 36 - */ 37 - changeEmailControl?: Dialog.DialogControlProps 38 - reminder?: boolean 39 - }) { 40 - const agent = useAgent() 41 - const fallbackChangeEmailControl = Dialog.useDialogControl() 42 - 43 - const [didVerify, setDidVerify] = useState(false) 44 - 45 - return ( 46 - <> 47 - <Dialog.Outer 48 - control={control} 49 - onClose={async () => { 50 - if (!didVerify) { 51 - onCloseWithoutVerifying?.() 52 - return 53 - } 54 - 55 - try { 56 - await agent.resumeSession(agent.session!) 57 - onCloseAfterVerifying?.() 58 - } catch (e: unknown) { 59 - logger.error(String(e)) 60 - return 61 - } 62 - }}> 63 - <Dialog.Handle /> 64 - <Inner 65 - setDidVerify={setDidVerify} 66 - reasonText={reasonText} 67 - changeEmailControl={changeEmailControl ?? fallbackChangeEmailControl} 68 - reminder={reminder} 69 - /> 70 - </Dialog.Outer> 71 - {!changeEmailControl && ( 72 - <ChangeEmailDialog 73 - control={fallbackChangeEmailControl} 74 - verifyEmailControl={control} 75 - /> 76 - )} 77 - </> 78 - ) 79 - } 80 - 81 - export function Inner({ 82 - setDidVerify, 83 - reasonText, 84 - changeEmailControl, 85 - reminder, 86 - }: { 87 - setDidVerify: (value: boolean) => void 88 - reasonText?: string 89 - changeEmailControl: Dialog.DialogControlProps 90 - reminder?: boolean 91 - }) { 92 - const control = Dialog.useDialogContext() 93 - const {_} = useLingui() 94 - const {currentAccount} = useSession() 95 - const agent = useAgent() 96 - const {gtMobile} = useBreakpoints() 97 - const t = useTheme() 98 - 99 - const [currentStep, setCurrentStep] = useState< 100 - 'Reminder' | 'StepOne' | 'StepTwo' | 'StepThree' 101 - >(reminder ? 'Reminder' : 'StepOne') 102 - const [confirmationCode, setConfirmationCode] = useState('') 103 - const [isProcessing, setIsProcessing] = useState(false) 104 - const [error, setError] = useState('') 105 - 106 - const uiStrings = { 107 - Reminder: { 108 - title: _(msg`Please Verify Your Email`), 109 - message: _( 110 - msg`Your email has not yet been verified. This is an important security step which we recommend.`, 111 - ), 112 - }, 113 - StepOne: { 114 - title: _(msg`Verify Your Email`), 115 - message: '', 116 - }, 117 - StepTwo: { 118 - title: _(msg`Enter Code`), 119 - message: _( 120 - msg`An email has been sent! Please enter the confirmation code included in the email below.`, 121 - ), 122 - }, 123 - StepThree: { 124 - title: _(msg`Success!`), 125 - message: _(msg`Thank you! Your email has been successfully verified.`), 126 - }, 127 - } 128 - 129 - const onSendEmail = async () => { 130 - setError('') 131 - setIsProcessing(true) 132 - try { 133 - await agent.com.atproto.server.requestEmailConfirmation() 134 - setCurrentStep('StepTwo') 135 - } catch (e: unknown) { 136 - setError(cleanError(e)) 137 - } finally { 138 - setIsProcessing(false) 139 - } 140 - } 141 - 142 - const onVerifyEmail = async () => { 143 - setError('') 144 - setIsProcessing(true) 145 - try { 146 - await agent.com.atproto.server.confirmEmail({ 147 - email: (currentAccount?.email || '').trim(), 148 - token: confirmationCode.trim(), 149 - }) 150 - } catch (e: unknown) { 151 - setError(cleanError(String(e))) 152 - setIsProcessing(false) 153 - return 154 - } 155 - 156 - setIsProcessing(false) 157 - setDidVerify(true) 158 - setCurrentStep('StepThree') 159 - } 160 - 161 - return ( 162 - <Dialog.ScrollableInner 163 - label={_(msg`Verify email dialog`)} 164 - style={web({maxWidth: 450})}> 165 - <View style={[a.gap_xl]}> 166 - {currentStep === 'Reminder' && ( 167 - <View 168 - style={[ 169 - a.rounded_sm, 170 - a.align_center, 171 - a.justify_center, 172 - {height: 150}, 173 - t.atoms.bg_contrast_100, 174 - ]}> 175 - <EnvelopeIcon width={64} fill="white" /> 176 - </View> 177 - )} 178 - <View style={[a.gap_sm]}> 179 - <Text style={[a.font_heavy, a.text_2xl]}> 180 - {uiStrings[currentStep].title} 181 - </Text> 182 - {error ? ( 183 - <View style={[a.rounded_sm, a.overflow_hidden]}> 184 - <ErrorMessage message={error} /> 185 - </View> 186 - ) : null} 187 - {currentStep === 'StepOne' ? ( 188 - <View> 189 - {reasonText ? ( 190 - <View style={[a.gap_sm]}> 191 - <Text style={[a.text_md, a.leading_snug]}>{reasonText}</Text> 192 - <Text style={[a.text_md, a.leading_snug]}> 193 - Don't have access to{' '} 194 - <Text style={[a.text_md, a.leading_snug, a.font_bold]}> 195 - {currentAccount?.email} 196 - </Text> 197 - ?{' '} 198 - <InlineLinkText 199 - to="#" 200 - label={_(msg`Change email address`)} 201 - style={[a.text_md, a.leading_snug]} 202 - onPress={e => { 203 - e.preventDefault() 204 - control.close(() => { 205 - changeEmailControl.open() 206 - }) 207 - return false 208 - }}> 209 - <Trans>Change your email address</Trans> 210 - </InlineLinkText> 211 - . 212 - </Text> 213 - </View> 214 - ) : ( 215 - <Text style={[a.text_md, a.leading_snug]}> 216 - <Trans> 217 - You'll receive an email at{' '} 218 - <Text style={[a.text_md, a.leading_snug, a.font_bold]}> 219 - {currentAccount?.email} 220 - </Text>{' '} 221 - to verify it's you. 222 - </Trans>{' '} 223 - <InlineLinkText 224 - to="#" 225 - label={_(msg`Change email address`)} 226 - style={[a.text_md, a.leading_snug]} 227 - onPress={e => { 228 - e.preventDefault() 229 - control.close(() => { 230 - changeEmailControl.open() 231 - }) 232 - return false 233 - }}> 234 - <Trans>Need to change it?</Trans> 235 - </InlineLinkText> 236 - </Text> 237 - )} 238 - </View> 239 - ) : ( 240 - <Text style={[a.text_md, a.leading_snug]}> 241 - {uiStrings[currentStep].message} 242 - </Text> 243 - )} 244 - </View> 245 - {currentStep === 'StepTwo' ? ( 246 - <View> 247 - <TextField.LabelText> 248 - <Trans>Confirmation Code</Trans> 249 - </TextField.LabelText> 250 - <TextField.Root> 251 - <TextField.Input 252 - label={_(msg`Confirmation code`)} 253 - placeholder="XXXXX-XXXXX" 254 - onChangeText={setConfirmationCode} 255 - /> 256 - </TextField.Root> 257 - </View> 258 - ) : null} 259 - <View style={[a.gap_sm, gtMobile && [a.flex_row_reverse, a.ml_auto]]}> 260 - {currentStep === 'Reminder' ? ( 261 - <> 262 - <Button 263 - label={_(msg`Get started`)} 264 - variant="solid" 265 - color="primary" 266 - size="large" 267 - onPress={() => setCurrentStep('StepOne')}> 268 - <ButtonText> 269 - <Trans>Get started</Trans> 270 - </ButtonText> 271 - </Button> 272 - <Button 273 - label={_(msg`Maybe later`)} 274 - accessibilityHint={_(msg`Snoozes the reminder`)} 275 - variant="ghost" 276 - color="secondary" 277 - size="large" 278 - disabled={isProcessing} 279 - onPress={() => control.close()}> 280 - <ButtonText> 281 - <Trans>Maybe later</Trans> 282 - </ButtonText> 283 - </Button> 284 - </> 285 - ) : currentStep === 'StepOne' ? ( 286 - <> 287 - <Button 288 - label={_(msg`Send confirmation email`)} 289 - variant="solid" 290 - color="primary" 291 - size="large" 292 - disabled={isProcessing} 293 - onPress={onSendEmail}> 294 - <ButtonText> 295 - <Trans>Send confirmation</Trans> 296 - </ButtonText> 297 - {isProcessing ? ( 298 - <Loader size="sm" style={[{color: 'white'}]} /> 299 - ) : null} 300 - </Button> 301 - <Button 302 - label={_(msg`I have a code`)} 303 - variant="solid" 304 - color="secondary" 305 - size="large" 306 - disabled={isProcessing} 307 - onPress={() => setCurrentStep('StepTwo')}> 308 - <ButtonText> 309 - <Trans>I have a code</Trans> 310 - </ButtonText> 311 - </Button> 312 - </> 313 - ) : currentStep === 'StepTwo' ? ( 314 - <> 315 - <Button 316 - label={_(msg`Confirm`)} 317 - variant="solid" 318 - color="primary" 319 - size="large" 320 - disabled={isProcessing} 321 - onPress={onVerifyEmail}> 322 - <ButtonText> 323 - <Trans>Confirm</Trans> 324 - </ButtonText> 325 - {isProcessing ? ( 326 - <Loader size="sm" style={[{color: 'white'}]} /> 327 - ) : null} 328 - </Button> 329 - <Button 330 - label={_(msg`Resend email`)} 331 - variant="solid" 332 - color="secondary" 333 - size="large" 334 - disabled={isProcessing} 335 - onPress={() => { 336 - setConfirmationCode('') 337 - setCurrentStep('StepOne') 338 - }}> 339 - <ButtonText> 340 - <Trans>Resend email</Trans> 341 - </ButtonText> 342 - </Button> 343 - </> 344 - ) : currentStep === 'StepThree' ? ( 345 - <Button 346 - label={_(msg`Close`)} 347 - variant="solid" 348 - color="primary" 349 - size="large" 350 - onPress={() => control.close()}> 351 - <ButtonText> 352 - <Trans>Close</Trans> 353 - </ButtonText> 354 - </Button> 355 - ) : null} 356 - </View> 357 - </View> 358 - </Dialog.ScrollableInner> 359 - ) 360 - }
-335
src/view/com/modals/EditProfile.tsx
··· 1 - import {useCallback, useState} from 'react' 2 - import { 3 - ActivityIndicator, 4 - KeyboardAvoidingView, 5 - ScrollView, 6 - StyleSheet, 7 - TextInput, 8 - TouchableOpacity, 9 - View, 10 - } from 'react-native' 11 - import Animated, {FadeOut} from 'react-native-reanimated' 12 - import {LinearGradient} from 'expo-linear-gradient' 13 - import {type AppBskyActorDefs} from '@atproto/api' 14 - import {msg, Trans} from '@lingui/macro' 15 - import {useLingui} from '@lingui/react' 16 - 17 - import {MAX_DESCRIPTION, MAX_DISPLAY_NAME, urls} from '#/lib/constants' 18 - import {usePalette} from '#/lib/hooks/usePalette' 19 - import {compressIfNeeded} from '#/lib/media/manip' 20 - import {type PickerImage} from '#/lib/media/picker.shared' 21 - import {cleanError} from '#/lib/strings/errors' 22 - import {enforceLen} from '#/lib/strings/helpers' 23 - import {colors, gradients, s} from '#/lib/styles' 24 - import {useTheme} from '#/lib/ThemeContext' 25 - import {logger} from '#/logger' 26 - import {isWeb} from '#/platform/detection' 27 - import {useModalControls} from '#/state/modals' 28 - import {useProfileUpdateMutation} from '#/state/queries/profile' 29 - import {Text} from '#/view/com/util/text/Text' 30 - import * as Toast from '#/view/com/util/Toast' 31 - import {EditableUserAvatar} from '#/view/com/util/UserAvatar' 32 - import {UserBanner} from '#/view/com/util/UserBanner' 33 - import {Admonition} from '#/components/Admonition' 34 - import {InlineLinkText} from '#/components/Link' 35 - import {useSimpleVerificationState} from '#/components/verification' 36 - import {ErrorMessage} from '../util/error/ErrorMessage' 37 - 38 - const AnimatedTouchableOpacity = 39 - Animated.createAnimatedComponent(TouchableOpacity) 40 - 41 - export const snapPoints = ['fullscreen'] 42 - 43 - export function Component({ 44 - profile, 45 - onUpdate, 46 - }: { 47 - profile: AppBskyActorDefs.ProfileViewDetailed 48 - onUpdate?: () => void 49 - }) { 50 - const pal = usePalette('default') 51 - const theme = useTheme() 52 - const {_} = useLingui() 53 - const {closeModal} = useModalControls() 54 - const updateMutation = useProfileUpdateMutation() 55 - const [imageError, setImageError] = useState<string>('') 56 - const initialDisplayName = profile.displayName || '' 57 - const [displayName, setDisplayName] = useState<string>( 58 - profile.displayName || '', 59 - ) 60 - const [description, setDescription] = useState<string>( 61 - profile.description || '', 62 - ) 63 - const [userBanner, setUserBanner] = useState<string | undefined | null>( 64 - profile.banner, 65 - ) 66 - const [userAvatar, setUserAvatar] = useState<string | undefined | null>( 67 - profile.avatar, 68 - ) 69 - const [newUserBanner, setNewUserBanner] = useState< 70 - PickerImage | undefined | null 71 - >() 72 - const [newUserAvatar, setNewUserAvatar] = useState< 73 - PickerImage | undefined | null 74 - >() 75 - const onPressCancel = () => { 76 - closeModal() 77 - } 78 - const onSelectNewAvatar = useCallback( 79 - async (img: PickerImage | null) => { 80 - setImageError('') 81 - if (img === null) { 82 - setNewUserAvatar(null) 83 - setUserAvatar(null) 84 - return 85 - } 86 - try { 87 - const finalImg = await compressIfNeeded(img, 1000000) 88 - setNewUserAvatar(finalImg) 89 - setUserAvatar(finalImg.path) 90 - } catch (e: any) { 91 - setImageError(cleanError(e)) 92 - } 93 - }, 94 - [setNewUserAvatar, setUserAvatar, setImageError], 95 - ) 96 - 97 - const onSelectNewBanner = useCallback( 98 - async (img: PickerImage | null) => { 99 - setImageError('') 100 - if (!img) { 101 - setNewUserBanner(null) 102 - setUserBanner(null) 103 - return 104 - } 105 - try { 106 - const finalImg = await compressIfNeeded(img, 1000000) 107 - setNewUserBanner(finalImg) 108 - setUserBanner(finalImg.path) 109 - } catch (e: any) { 110 - setImageError(cleanError(e)) 111 - } 112 - }, 113 - [setNewUserBanner, setUserBanner, setImageError], 114 - ) 115 - 116 - const onPressSave = useCallback(async () => { 117 - setImageError('') 118 - try { 119 - await updateMutation.mutateAsync({ 120 - profile, 121 - updates: { 122 - displayName, 123 - description, 124 - }, 125 - newUserAvatar, 126 - newUserBanner, 127 - }) 128 - Toast.show(_(msg({message: 'Profile updated', context: 'toast'}))) 129 - onUpdate?.() 130 - closeModal() 131 - } catch (e: any) { 132 - logger.error('Failed to update user profile', {message: String(e)}) 133 - } 134 - }, [ 135 - updateMutation, 136 - profile, 137 - onUpdate, 138 - closeModal, 139 - displayName, 140 - description, 141 - newUserAvatar, 142 - newUserBanner, 143 - setImageError, 144 - _, 145 - ]) 146 - const verification = useSimpleVerificationState({ 147 - profile, 148 - }) 149 - 150 - return ( 151 - <KeyboardAvoidingView style={s.flex1} behavior="height"> 152 - <ScrollView style={[pal.view]} testID="editProfileModal"> 153 - <Text style={[styles.title, pal.text]}> 154 - <Trans>Edit my profile</Trans> 155 - </Text> 156 - <View style={styles.photos}> 157 - <UserBanner 158 - banner={userBanner} 159 - onSelectNewBanner={onSelectNewBanner} 160 - /> 161 - <View style={[styles.avi, {borderColor: pal.colors.background}]}> 162 - <EditableUserAvatar 163 - size={80} 164 - avatar={userAvatar} 165 - onSelectNewAvatar={onSelectNewAvatar} 166 - /> 167 - </View> 168 - </View> 169 - {updateMutation.isError && ( 170 - <View style={styles.errorContainer}> 171 - <ErrorMessage message={cleanError(updateMutation.error)} /> 172 - </View> 173 - )} 174 - {imageError !== '' && ( 175 - <View style={styles.errorContainer}> 176 - <ErrorMessage message={imageError} /> 177 - </View> 178 - )} 179 - <View style={styles.form}> 180 - <View> 181 - <Text style={[styles.label, pal.text]}> 182 - <Trans>Display Name</Trans> 183 - </Text> 184 - <TextInput 185 - testID="editProfileDisplayNameInput" 186 - style={[styles.textInput, pal.border, pal.text]} 187 - placeholder={_(msg`e.g. Alice Roberts`)} 188 - placeholderTextColor={colors.gray4} 189 - value={displayName} 190 - onChangeText={v => 191 - setDisplayName(enforceLen(v, MAX_DISPLAY_NAME)) 192 - } 193 - accessible={true} 194 - accessibilityLabel={_(msg`Display name`)} 195 - accessibilityHint={_(msg`Edit your display name`)} 196 - /> 197 - 198 - {verification.isVerified && 199 - verification.role === 'default' && 200 - displayName !== initialDisplayName && ( 201 - <View style={{paddingTop: 8}}> 202 - <Admonition type="error"> 203 - <Trans> 204 - You are verified. You will lose your verification status 205 - if you change your display name.{' '} 206 - <InlineLinkText 207 - label={_(msg`Learn more`)} 208 - to={urls.website.blog.initialVerificationAnnouncement}> 209 - <Trans>Learn more.</Trans> 210 - </InlineLinkText> 211 - </Trans> 212 - </Admonition> 213 - </View> 214 - )} 215 - </View> 216 - <View style={s.pb10}> 217 - <Text style={[styles.label, pal.text]}> 218 - <Trans>Description</Trans> 219 - </Text> 220 - <TextInput 221 - testID="editProfileDescriptionInput" 222 - style={[styles.textArea, pal.border, pal.text]} 223 - placeholder={_(msg`e.g. Artist, dog-lover, and avid reader.`)} 224 - placeholderTextColor={colors.gray4} 225 - keyboardAppearance={theme.colorScheme} 226 - multiline 227 - value={description} 228 - onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))} 229 - accessible={true} 230 - accessibilityLabel={_(msg`Description`)} 231 - accessibilityHint={_(msg`Edit your profile description`)} 232 - /> 233 - </View> 234 - {updateMutation.isPending ? ( 235 - <View style={[styles.btn, s.mt10, {backgroundColor: colors.gray2}]}> 236 - <ActivityIndicator /> 237 - </View> 238 - ) : ( 239 - <TouchableOpacity 240 - testID="editProfileSaveBtn" 241 - style={s.mt10} 242 - onPress={onPressSave} 243 - accessibilityRole="button" 244 - accessibilityLabel={_(msg`Save`)} 245 - accessibilityHint={_(msg`Saves any changes to your profile`)}> 246 - <LinearGradient 247 - colors={[gradients.blueLight.start, gradients.blueLight.end]} 248 - start={{x: 0, y: 0}} 249 - end={{x: 1, y: 1}} 250 - style={[styles.btn]}> 251 - <Text style={[s.white, s.bold]}> 252 - <Trans>Save Changes</Trans> 253 - </Text> 254 - </LinearGradient> 255 - </TouchableOpacity> 256 - )} 257 - {!updateMutation.isPending && ( 258 - <AnimatedTouchableOpacity 259 - exiting={!isWeb ? FadeOut : undefined} 260 - testID="editProfileCancelBtn" 261 - style={s.mt5} 262 - onPress={onPressCancel} 263 - accessibilityRole="button" 264 - accessibilityLabel={_(msg`Cancel profile editing`)} 265 - accessibilityHint="" 266 - onAccessibilityEscape={onPressCancel}> 267 - <View style={[styles.btn]}> 268 - <Text style={[s.black, s.bold, pal.text]}> 269 - <Trans>Cancel</Trans> 270 - </Text> 271 - </View> 272 - </AnimatedTouchableOpacity> 273 - )} 274 - </View> 275 - </ScrollView> 276 - </KeyboardAvoidingView> 277 - ) 278 - } 279 - 280 - const styles = StyleSheet.create({ 281 - title: { 282 - textAlign: 'center', 283 - fontWeight: '600', 284 - fontSize: 24, 285 - marginBottom: 18, 286 - }, 287 - label: { 288 - fontWeight: '600', 289 - paddingHorizontal: 4, 290 - paddingBottom: 4, 291 - marginTop: 20, 292 - }, 293 - form: { 294 - paddingHorizontal: 14, 295 - }, 296 - textInput: { 297 - borderWidth: 1, 298 - borderRadius: 6, 299 - paddingHorizontal: 14, 300 - paddingVertical: 10, 301 - fontSize: 16, 302 - }, 303 - textArea: { 304 - borderWidth: 1, 305 - borderRadius: 6, 306 - paddingHorizontal: 12, 307 - paddingTop: 10, 308 - fontSize: 16, 309 - height: 120, 310 - textAlignVertical: 'top', 311 - }, 312 - btn: { 313 - flexDirection: 'row', 314 - alignItems: 'center', 315 - justifyContent: 'center', 316 - width: '100%', 317 - borderRadius: 32, 318 - padding: 10, 319 - marginBottom: 10, 320 - }, 321 - avi: { 322 - position: 'absolute', 323 - top: 80, 324 - left: 24, 325 - width: 84, 326 - height: 84, 327 - borderWidth: 2, 328 - borderRadius: 42, 329 - }, 330 - photos: { 331 - marginBottom: 36, 332 - marginHorizontal: -14, 333 - }, 334 - errorContainer: {marginTop: 20}, 335 - })