mirror of
https://github.com/tooot-app/app
synced 2025-04-16 03:07:32 +02:00
Done profile editing
This commit is contained in:
parent
5bb77d0114
commit
fd1a6b3415
src
api
components/Menu
i18n/en/screens
screens/Tabs/Me
@ -69,7 +69,7 @@ const apiGeneral = async <T = unknown>({
|
|||||||
error.response.status,
|
error.response.status,
|
||||||
error.response.data.error
|
error.response.data.error
|
||||||
)
|
)
|
||||||
return Promise.reject(error.response)
|
return Promise.reject(error.response.data.error)
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
|
@ -98,7 +98,7 @@ const apiInstance = async <T = unknown>({
|
|||||||
error.response.status,
|
error.response.status,
|
||||||
error.response.data.error
|
error.response.data.error
|
||||||
)
|
)
|
||||||
return Promise.reject(error.response)
|
return Promise.reject(error.response.data.error)
|
||||||
} else if (error.request) {
|
} else if (error.request) {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
|
@ -76,6 +76,7 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<View>
|
||||||
<View style={styles.core}>
|
<View style={styles.core}>
|
||||||
<View style={styles.front}>
|
<View style={styles.front}>
|
||||||
{iconFront && (
|
{iconFront && (
|
||||||
@ -148,13 +149,14 @@ const MenuRow: React.FC<Props> = ({
|
|||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</TapGestureHandler>
|
|
||||||
{description ? (
|
{description ? (
|
||||||
<Text style={[styles.description, { color: theme.secondary }]}>
|
<Text style={[styles.description, { color: theme.secondary }]}>
|
||||||
{description}
|
{description}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
|
</TapGestureHandler>
|
||||||
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,11 +102,11 @@
|
|||||||
},
|
},
|
||||||
"avatar": {
|
"avatar": {
|
||||||
"title": "Avatar",
|
"title": "Avatar",
|
||||||
"description": "Available in next version"
|
"description": "Will be downscaled to 400x400px"
|
||||||
},
|
},
|
||||||
"banner": {
|
"header": {
|
||||||
"title": "Banner",
|
"title": "Banner",
|
||||||
"description": "Available in next version"
|
"description": "Will be downscaled to 1500x500px"
|
||||||
},
|
},
|
||||||
"note": {
|
"note": {
|
||||||
"title": "Description"
|
"title": "Description"
|
||||||
|
@ -30,7 +30,6 @@ const TabMeProfile: React.FC<StackScreenProps<
|
|||||||
>
|
>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Profile-Root'
|
name='Tab-Me-Profile-Root'
|
||||||
component={TabMeProfileRoot}
|
|
||||||
options={{
|
options={{
|
||||||
headerTitle: t('me.stacks.profile.name'),
|
headerTitle: t('me.stacks.profile.name'),
|
||||||
...(Platform.OS === 'android' && {
|
...(Platform.OS === 'android' && {
|
||||||
@ -45,7 +44,15 @@ const TabMeProfile: React.FC<StackScreenProps<
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
{({ route, navigation }) => (
|
||||||
|
<TabMeProfileRoot
|
||||||
|
messageRef={messageRef}
|
||||||
|
route={route}
|
||||||
|
navigation={navigation}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</Stack.Screen>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name='Tab-Me-Profile-Name'
|
name='Tab-Me-Profile-Name'
|
||||||
options={{
|
options={{
|
||||||
|
@ -95,12 +95,13 @@ const TabMeProfileFields: React.FC<StackScreenProps<
|
|||||||
type: 'success'
|
type: 'success'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(err => {
|
||||||
displayMessage({
|
displayMessage({
|
||||||
ref: messageRef,
|
ref: messageRef,
|
||||||
message: t('me.profile.feedback.failed', {
|
message: t('me.profile.feedback.failed', {
|
||||||
type: t('me.profile.root.note.title')
|
type: t('me.profile.root.note.title')
|
||||||
}),
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
mode,
|
mode,
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
|
@ -77,12 +77,13 @@ const TabMeProfileName: React.FC<StackScreenProps<
|
|||||||
type: 'success'
|
type: 'success'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(err => {
|
||||||
displayMessage({
|
displayMessage({
|
||||||
ref: messageRef,
|
ref: messageRef,
|
||||||
message: t('me.profile.feedback.failed', {
|
message: t('me.profile.feedback.failed', {
|
||||||
type: t('me.profile.root.name.title')
|
type: t('me.profile.root.name.title')
|
||||||
}),
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
mode,
|
mode,
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
|
@ -77,12 +77,13 @@ const TabMeProfileNote: React.FC<StackScreenProps<
|
|||||||
type: 'success'
|
type: 'success'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(err => {
|
||||||
displayMessage({
|
displayMessage({
|
||||||
ref: messageRef,
|
ref: messageRef,
|
||||||
message: t('me.profile.feedback.failed', {
|
message: t('me.profile.feedback.failed', {
|
||||||
type: t('me.profile.root.note.title')
|
type: t('me.profile.root.note.title')
|
||||||
}),
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
mode,
|
mode,
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
import GracefullyImage from '@components/GracefullyImage'
|
|
||||||
import mediaSelector from '@components/mediaSelector'
|
|
||||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||||
|
import { displayMessage } from '@components/Message'
|
||||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||||
import { StackScreenProps } from '@react-navigation/stack'
|
import { StackScreenProps } from '@react-navigation/stack'
|
||||||
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
||||||
import * as ImagePicker from 'expo-image-picker'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback } from 'react'
|
import React, { RefObject, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import FlashMessage from 'react-native-flash-message'
|
||||||
import { ScrollView } from 'react-native-gesture-handler'
|
import { ScrollView } from 'react-native-gesture-handler'
|
||||||
|
import ProfileAvatarHeader from './Root/AvatarHeader'
|
||||||
|
|
||||||
const TabMeProfileRoot: React.FC<StackScreenProps<
|
const TabMeProfileRoot: React.FC<StackScreenProps<
|
||||||
Nav.TabMeProfileStackParamList,
|
Nav.TabMeProfileStackParamList,
|
||||||
'Tab-Me-Profile-Root'
|
'Tab-Me-Profile-Root'
|
||||||
>> = ({ navigation }) => {
|
> & { messageRef: RefObject<FlashMessage> }> = ({ messageRef, navigation }) => {
|
||||||
|
const { mode } = useTheme()
|
||||||
const { t } = useTranslation('screenTabs')
|
const { t } = useTranslation('screenTabs')
|
||||||
|
|
||||||
const { showActionSheetWithOptions } = useActionSheet()
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
|
|
||||||
const { data, isLoading } = useProfileQuery({})
|
const { data, isLoading } = useProfileQuery({})
|
||||||
const { mutate } = useProfileMutation()
|
const { mutateAsync } = useProfileMutation()
|
||||||
|
|
||||||
const onPressVisibility = useCallback(() => {
|
const onPressVisibility = useCallback(() => {
|
||||||
showActionSheetWithOptions(
|
showActionSheetWithOptions(
|
||||||
@ -35,13 +37,46 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
|
|||||||
async buttonIndex => {
|
async buttonIndex => {
|
||||||
switch (buttonIndex) {
|
switch (buttonIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
mutate({ type: 'source[privacy]', data: 'public' })
|
mutateAsync({ type: 'source[privacy]', data: 'public' }).catch(
|
||||||
|
err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.visibility.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
mutate({ type: 'source[privacy]', data: 'unlisted' })
|
mutateAsync({ type: 'source[privacy]', data: 'unlisted' }).catch(
|
||||||
|
err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.visibility.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
mutate({ type: 'source[privacy]', data: 'private' })
|
mutateAsync({ type: 'source[privacy]', data: 'private' }).catch(
|
||||||
|
err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.visibility.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,25 +85,88 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
|
|||||||
|
|
||||||
const onPressSensitive = useCallback(() => {
|
const onPressSensitive = useCallback(() => {
|
||||||
if (data?.source.sensitive === undefined) {
|
if (data?.source.sensitive === undefined) {
|
||||||
mutate({ type: 'source[sensitive]', data: true })
|
mutateAsync({ type: 'source[sensitive]', data: true }).catch(err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.sensitive.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
mutate({ type: 'source[sensitive]', data: !data.source.sensitive })
|
mutateAsync({
|
||||||
|
type: 'source[sensitive]',
|
||||||
|
data: !data.source.sensitive
|
||||||
|
}).catch(err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.sensitive.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [data?.source.sensitive])
|
}, [data?.source.sensitive])
|
||||||
|
|
||||||
const onPressLock = useCallback(() => {
|
const onPressLock = useCallback(() => {
|
||||||
if (data?.locked === undefined) {
|
if (data?.locked === undefined) {
|
||||||
mutate({ type: 'locked', data: true })
|
mutateAsync({ type: 'locked', data: true }).catch(err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.lock.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
mutate({ type: 'locked', data: !data.locked })
|
mutateAsync({ type: 'locked', data: !data.locked }).catch(err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.lock.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [data?.locked])
|
}, [data?.locked])
|
||||||
|
|
||||||
const onPressBot = useCallback(() => {
|
const onPressBot = useCallback(() => {
|
||||||
if (data?.bot === undefined) {
|
if (data?.bot === undefined) {
|
||||||
mutate({ type: 'bot', data: true })
|
mutateAsync({ type: 'bot', data: true }).catch(err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.bot.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
mutate({ type: 'bot', data: !data?.bot })
|
mutateAsync({ type: 'bot', data: !data?.bot }).catch(err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t('me.profile.root.bot.title')
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, [data?.bot])
|
}, [data?.bot])
|
||||||
|
|
||||||
@ -87,52 +185,8 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<ProfileAvatarHeader type='avatar' messageRef={messageRef} />
|
||||||
title={t('me.profile.root.avatar.title')}
|
<ProfileAvatarHeader type='header' messageRef={messageRef} />
|
||||||
description={t('me.profile.root.avatar.description')}
|
|
||||||
content={
|
|
||||||
<GracefullyImage
|
|
||||||
key={data?.avatar_static}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
uri={{
|
|
||||||
original: data?.avatar_static
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
loading={isLoading}
|
|
||||||
iconBack='ChevronRight'
|
|
||||||
onPress={async () => {
|
|
||||||
const image = await mediaSelector({
|
|
||||||
showActionSheetWithOptions,
|
|
||||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
||||||
resize: { width: 400, height: 400 }
|
|
||||||
})
|
|
||||||
mutate({ type: 'avatar', data: image.uri })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<MenuRow
|
|
||||||
title={t('me.profile.root.banner.title')}
|
|
||||||
description={t('me.profile.root.banner.description')}
|
|
||||||
content={
|
|
||||||
<GracefullyImage
|
|
||||||
key={data?.header_static}
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
uri={{
|
|
||||||
original: data?.header_static
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
loading={isLoading}
|
|
||||||
iconBack='ChevronRight'
|
|
||||||
onPress={async () => {
|
|
||||||
const image = await mediaSelector({
|
|
||||||
showActionSheetWithOptions,
|
|
||||||
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
||||||
resize: { width: 1500, height: 500 }
|
|
||||||
})
|
|
||||||
mutate({ type: 'header', data: image.uri })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={t('me.profile.root.note.title')}
|
title={t('me.profile.root.note.title')}
|
||||||
content={data?.source.note}
|
content={data?.source.note}
|
||||||
|
66
src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx
Normal file
66
src/screens/Tabs/Me/Profile/Root/AvatarHeader.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import mediaSelector from '@components/mediaSelector'
|
||||||
|
import { MenuRow } from '@components/Menu'
|
||||||
|
import { displayMessage } from '@components/Message'
|
||||||
|
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||||
|
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
||||||
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
import * as ImagePicker from 'expo-image-picker'
|
||||||
|
import React, { RefObject } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import FlashMessage from 'react-native-flash-message'
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
type: 'avatar' | 'header'
|
||||||
|
messageRef: RefObject<FlashMessage>
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileAvatarHeader: React.FC<Props> = ({ type, messageRef }) => {
|
||||||
|
const { mode } = useTheme()
|
||||||
|
const { t } = useTranslation('screenTabs')
|
||||||
|
|
||||||
|
const { showActionSheetWithOptions } = useActionSheet()
|
||||||
|
|
||||||
|
const query = useProfileQuery({})
|
||||||
|
const mutation = useProfileMutation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuRow
|
||||||
|
title={t(`me.profile.root.${type}.title`)}
|
||||||
|
description={t(`me.profile.root.${type}.description`)}
|
||||||
|
loading={query.isLoading || mutation.isLoading}
|
||||||
|
iconBack='ChevronRight'
|
||||||
|
onPress={async () => {
|
||||||
|
const image = await mediaSelector({
|
||||||
|
showActionSheetWithOptions,
|
||||||
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
||||||
|
resize: { width: 400, height: 400 }
|
||||||
|
})
|
||||||
|
mutation
|
||||||
|
.mutateAsync({ type, data: image.uri })
|
||||||
|
.then(() =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.succeed', {
|
||||||
|
type: t(`me.profile.root.${type}.title`)
|
||||||
|
}),
|
||||||
|
mode,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.catch(err =>
|
||||||
|
displayMessage({
|
||||||
|
ref: messageRef,
|
||||||
|
message: t('me.profile.feedback.failed', {
|
||||||
|
type: t(`me.profile.root.${type}.title`)
|
||||||
|
}),
|
||||||
|
...(err && { description: err }),
|
||||||
|
mode,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProfileAvatarHeader
|
Loading…
x
Reference in New Issue
Block a user