about summary refs log tree commit diff
path: root/src/view/com/modals/EditProfile.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/modals/EditProfile.tsx')
-rw-r--r--src/view/com/modals/EditProfile.tsx115
1 files changed, 63 insertions, 52 deletions
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index dfd5305f5..e044f8c0e 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -11,10 +11,9 @@ import {
 } from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
 import {Image as RNImage} from 'react-native-image-crop-picker'
+import {AppBskyActorDefs} from '@atproto/api'
 import {Text} from '../util/text/Text'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {useStores} from 'state/index'
-import {ProfileModel} from 'state/models/content/profile'
 import {s, colors, gradients} from 'lib/styles'
 import {enforceLen} from 'lib/strings/helpers'
 import {MAX_DISPLAY_NAME, MAX_DESCRIPTION} from 'lib/constants'
@@ -24,9 +23,14 @@ import {EditableUserAvatar} from '../util/UserAvatar'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useTheme} from 'lib/ThemeContext'
 import {useAnalytics} from 'lib/analytics/analytics'
-import {cleanError, isNetworkError} from 'lib/strings/errors'
+import {cleanError} from 'lib/strings/errors'
 import Animated, {FadeOut} from 'react-native-reanimated'
 import {isWeb} from 'platform/detection'
+import {Trans, msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useModalControls} from '#/state/modals'
+import {useProfileUpdateMutation} from '#/state/queries/profile'
+import {logger} from '#/logger'
 
 const AnimatedTouchableOpacity =
   Animated.createAnimatedComponent(TouchableOpacity)
@@ -34,30 +38,30 @@ const AnimatedTouchableOpacity =
 export const snapPoints = ['fullscreen']
 
 export function Component({
-  profileView,
+  profile,
   onUpdate,
 }: {
-  profileView: ProfileModel
+  profile: AppBskyActorDefs.ProfileViewDetailed
   onUpdate?: () => void
 }) {
-  const store = useStores()
-  const [error, setError] = useState<string>('')
   const pal = usePalette('default')
   const theme = useTheme()
   const {track} = useAnalytics()
-
-  const [isProcessing, setProcessing] = useState<boolean>(false)
+  const {_} = useLingui()
+  const {closeModal} = useModalControls()
+  const updateMutation = useProfileUpdateMutation()
+  const [imageError, setImageError] = useState<string>('')
   const [displayName, setDisplayName] = useState<string>(
-    profileView.displayName || '',
+    profile.displayName || '',
   )
   const [description, setDescription] = useState<string>(
-    profileView.description || '',
+    profile.description || '',
   )
   const [userBanner, setUserBanner] = useState<string | undefined | null>(
-    profileView.banner,
+    profile.banner,
   )
   const [userAvatar, setUserAvatar] = useState<string | undefined | null>(
-    profileView.avatar,
+    profile.avatar,
   )
   const [newUserBanner, setNewUserBanner] = useState<
     RNImage | undefined | null
@@ -66,10 +70,11 @@ export function Component({
     RNImage | undefined | null
   >()
   const onPressCancel = () => {
-    store.shell.closeModal()
+    closeModal()
   }
   const onSelectNewAvatar = useCallback(
     async (img: RNImage | null) => {
+      setImageError('')
       if (img === null) {
         setNewUserAvatar(null)
         setUserAvatar(null)
@@ -81,14 +86,15 @@ export function Component({
         setNewUserAvatar(finalImg)
         setUserAvatar(finalImg.path)
       } catch (e: any) {
-        setError(cleanError(e))
+        setImageError(cleanError(e))
       }
     },
-    [track, setNewUserAvatar, setUserAvatar, setError],
+    [track, setNewUserAvatar, setUserAvatar, setImageError],
   )
 
   const onSelectNewBanner = useCallback(
     async (img: RNImage | null) => {
+      setImageError('')
       if (!img) {
         setNewUserBanner(null)
         setUserBanner(null)
@@ -100,58 +106,50 @@ export function Component({
         setNewUserBanner(finalImg)
         setUserBanner(finalImg.path)
       } catch (e: any) {
-        setError(cleanError(e))
+        setImageError(cleanError(e))
       }
     },
-    [track, setNewUserBanner, setUserBanner, setError],
+    [track, setNewUserBanner, setUserBanner, setImageError],
   )
 
   const onPressSave = useCallback(async () => {
     track('EditProfile:Save')
-    setProcessing(true)
-    if (error) {
-      setError('')
-    }
+    setImageError('')
     try {
-      await profileView.updateProfile(
-        {
+      await updateMutation.mutateAsync({
+        profile,
+        updates: {
           displayName,
           description,
         },
         newUserAvatar,
         newUserBanner,
-      )
+      })
       Toast.show('Profile updated')
       onUpdate?.()
-      store.shell.closeModal()
+      closeModal()
     } catch (e: any) {
-      if (isNetworkError(e)) {
-        setError(
-          'Failed to save your profile. Check your internet connection and try again.',
-        )
-      } else {
-        setError(cleanError(e))
-      }
+      logger.error('Failed to update user profile', {error: String(e)})
     }
-    setProcessing(false)
   }, [
     track,
-    setProcessing,
-    setError,
-    error,
-    profileView,
+    updateMutation,
+    profile,
     onUpdate,
-    store,
+    closeModal,
     displayName,
     description,
     newUserAvatar,
     newUserBanner,
+    setImageError,
   ])
 
   return (
     <KeyboardAvoidingView style={s.flex1} behavior="height">
       <ScrollView style={[pal.view]} testID="editProfileModal">
-        <Text style={[styles.title, pal.text]}>Edit my profile</Text>
+        <Text style={[styles.title, pal.text]}>
+          <Trans>Edit my profile</Trans>
+        </Text>
         <View style={styles.photos}>
           <UserBanner
             banner={userBanner}
@@ -165,14 +163,21 @@ export function Component({
             />
           </View>
         </View>
-        {error !== '' && (
+        {updateMutation.isError && (
+          <View style={styles.errorContainer}>
+            <ErrorMessage message={cleanError(updateMutation.error)} />
+          </View>
+        )}
+        {imageError !== '' && (
           <View style={styles.errorContainer}>
-            <ErrorMessage message={error} />
+            <ErrorMessage message={imageError} />
           </View>
         )}
         <View style={styles.form}>
           <View>
-            <Text style={[styles.label, pal.text]}>Display Name</Text>
+            <Text style={[styles.label, pal.text]}>
+              <Trans>Display Name</Trans>
+            </Text>
             <TextInput
               testID="editProfileDisplayNameInput"
               style={[styles.textInput, pal.border, pal.text]}
@@ -183,12 +188,14 @@ export function Component({
                 setDisplayName(enforceLen(v, MAX_DISPLAY_NAME))
               }
               accessible={true}
-              accessibilityLabel="Display name"
+              accessibilityLabel={_(msg`Display name`)}
               accessibilityHint="Edit your display name"
             />
           </View>
           <View style={s.pb10}>
-            <Text style={[styles.label, pal.text]}>Description</Text>
+            <Text style={[styles.label, pal.text]}>
+              <Trans>Description</Trans>
+            </Text>
             <TextInput
               testID="editProfileDescriptionInput"
               style={[styles.textArea, pal.border, pal.text]}
@@ -199,11 +206,11 @@ export function Component({
               value={description}
               onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
               accessible={true}
-              accessibilityLabel="Description"
+              accessibilityLabel={_(msg`Description`)}
               accessibilityHint="Edit your profile description"
             />
           </View>
-          {isProcessing ? (
+          {updateMutation.isPending ? (
             <View style={[styles.btn, s.mt10, {backgroundColor: colors.gray2}]}>
               <ActivityIndicator />
             </View>
@@ -213,29 +220,33 @@ export function Component({
               style={s.mt10}
               onPress={onPressSave}
               accessibilityRole="button"
-              accessibilityLabel="Save"
+              accessibilityLabel={_(msg`Save`)}
               accessibilityHint="Saves any changes to your profile">
               <LinearGradient
                 colors={[gradients.blueLight.start, gradients.blueLight.end]}
                 start={{x: 0, y: 0}}
                 end={{x: 1, y: 1}}
                 style={[styles.btn]}>
-                <Text style={[s.white, s.bold]}>Save Changes</Text>
+                <Text style={[s.white, s.bold]}>
+                  <Trans>Save Changes</Trans>
+                </Text>
               </LinearGradient>
             </TouchableOpacity>
           )}
-          {!isProcessing && (
+          {!updateMutation.isPending && (
             <AnimatedTouchableOpacity
               exiting={!isWeb ? FadeOut : undefined}
               testID="editProfileCancelBtn"
               style={s.mt5}
               onPress={onPressCancel}
               accessibilityRole="button"
-              accessibilityLabel="Cancel profile editing"
+              accessibilityLabel={_(msg`Cancel profile editing`)}
               accessibilityHint=""
               onAccessibilityEscape={onPressCancel}>
               <View style={[styles.btn]}>
-                <Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
+                <Text style={[s.black, s.bold, pal.text]}>
+                  <Trans>Cancel</Trans>
+                </Text>
               </View>
             </AnimatedTouchableOpacity>
           )}