mirror of
https://github.com/tooot-app/app
synced 2025-02-21 14:20:50 +01:00
Done profile editing
This commit is contained in:
parent
5bb77d0114
commit
fd1a6b3415
@ -69,7 +69,7 @@ const apiGeneral = async <T = unknown>({
|
||||
error.response.status,
|
||||
error.response.data.error
|
||||
)
|
||||
return Promise.reject(error.response)
|
||||
return Promise.reject(error.response.data.error)
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// `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.data.error
|
||||
)
|
||||
return Promise.reject(error.response)
|
||||
return Promise.reject(error.response.data.error)
|
||||
} else if (error.request) {
|
||||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
|
@ -76,84 +76,86 @@ const MenuRow: React.FC<Props> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<View style={styles.core}>
|
||||
<View style={styles.front}>
|
||||
{iconFront && (
|
||||
<Icon
|
||||
name={iconFront}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme[iconFrontColor]}
|
||||
style={styles.iconFront}
|
||||
/>
|
||||
)}
|
||||
{badge ? (
|
||||
<View
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
backgroundColor: theme.red,
|
||||
borderRadius: 8,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<View style={styles.main}>
|
||||
<Text
|
||||
style={[styles.title, { color: theme.primaryDefault }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{content || switchValue !== undefined || iconBack ? (
|
||||
<View style={styles.back}>
|
||||
{content ? (
|
||||
typeof content === 'string' ? (
|
||||
<Text
|
||||
style={[
|
||||
styles.content,
|
||||
{
|
||||
color: theme.secondary,
|
||||
opacity: !iconBack && loading ? 0 : 1
|
||||
}
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{content}
|
||||
</Text>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
) : null}
|
||||
{switchValue !== undefined ? (
|
||||
<Switch
|
||||
value={switchValue}
|
||||
onValueChange={switchOnValueChange}
|
||||
disabled={switchDisabled}
|
||||
trackColor={{ true: theme.blue, false: theme.disabled }}
|
||||
style={{ opacity: loading ? 0 : 1 }}
|
||||
/>
|
||||
) : null}
|
||||
{iconBack ? (
|
||||
<View>
|
||||
<View style={styles.core}>
|
||||
<View style={styles.front}>
|
||||
{iconFront && (
|
||||
<Icon
|
||||
name={iconBack}
|
||||
name={iconFront}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme[iconBackColor]}
|
||||
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
|
||||
color={theme[iconFrontColor]}
|
||||
style={styles.iconFront}
|
||||
/>
|
||||
)}
|
||||
{badge ? (
|
||||
<View
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
backgroundColor: theme.red,
|
||||
borderRadius: 8,
|
||||
marginRight: StyleConstants.Spacing.S
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{loading && loadingSpinkit}
|
||||
<View style={styles.main}>
|
||||
<Text
|
||||
style={[styles.title, { color: theme.primaryDefault }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{content || switchValue !== undefined || iconBack ? (
|
||||
<View style={styles.back}>
|
||||
{content ? (
|
||||
typeof content === 'string' ? (
|
||||
<Text
|
||||
style={[
|
||||
styles.content,
|
||||
{
|
||||
color: theme.secondary,
|
||||
opacity: !iconBack && loading ? 0 : 1
|
||||
}
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{content}
|
||||
</Text>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
) : null}
|
||||
{switchValue !== undefined ? (
|
||||
<Switch
|
||||
value={switchValue}
|
||||
onValueChange={switchOnValueChange}
|
||||
disabled={switchDisabled}
|
||||
trackColor={{ true: theme.blue, false: theme.disabled }}
|
||||
style={{ opacity: loading ? 0 : 1 }}
|
||||
/>
|
||||
) : null}
|
||||
{iconBack ? (
|
||||
<Icon
|
||||
name={iconBack}
|
||||
size={StyleConstants.Font.Size.L}
|
||||
color={theme[iconBackColor]}
|
||||
style={[styles.iconBack, { opacity: loading ? 0 : 1 }]}
|
||||
/>
|
||||
) : null}
|
||||
{loading && loadingSpinkit}
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
{description ? (
|
||||
<Text style={[styles.description, { color: theme.secondary }]}>
|
||||
{description}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</TapGestureHandler>
|
||||
{description ? (
|
||||
<Text style={[styles.description, { color: theme.secondary }]}>
|
||||
{description}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -102,11 +102,11 @@
|
||||
},
|
||||
"avatar": {
|
||||
"title": "Avatar",
|
||||
"description": "Available in next version"
|
||||
"description": "Will be downscaled to 400x400px"
|
||||
},
|
||||
"banner": {
|
||||
"header": {
|
||||
"title": "Banner",
|
||||
"description": "Available in next version"
|
||||
"description": "Will be downscaled to 1500x500px"
|
||||
},
|
||||
"note": {
|
||||
"title": "Description"
|
||||
|
@ -30,7 +30,6 @@ const TabMeProfile: React.FC<StackScreenProps<
|
||||
>
|
||||
<Stack.Screen
|
||||
name='Tab-Me-Profile-Root'
|
||||
component={TabMeProfileRoot}
|
||||
options={{
|
||||
headerTitle: t('me.stacks.profile.name'),
|
||||
...(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
|
||||
name='Tab-Me-Profile-Name'
|
||||
options={{
|
||||
|
@ -95,12 +95,13 @@ const TabMeProfileFields: React.FC<StackScreenProps<
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(err => {
|
||||
displayMessage({
|
||||
ref: messageRef,
|
||||
message: t('me.profile.feedback.failed', {
|
||||
type: t('me.profile.root.note.title')
|
||||
}),
|
||||
...(err && { description: err }),
|
||||
mode,
|
||||
type: 'error'
|
||||
})
|
||||
|
@ -77,12 +77,13 @@ const TabMeProfileName: React.FC<StackScreenProps<
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(err => {
|
||||
displayMessage({
|
||||
ref: messageRef,
|
||||
message: t('me.profile.feedback.failed', {
|
||||
type: t('me.profile.root.name.title')
|
||||
}),
|
||||
...(err && { description: err }),
|
||||
mode,
|
||||
type: 'error'
|
||||
})
|
||||
|
@ -77,12 +77,13 @@ const TabMeProfileNote: React.FC<StackScreenProps<
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
.catch(err => {
|
||||
displayMessage({
|
||||
ref: messageRef,
|
||||
message: t('me.profile.feedback.failed', {
|
||||
type: t('me.profile.root.note.title')
|
||||
}),
|
||||
...(err && { description: err }),
|
||||
mode,
|
||||
type: 'error'
|
||||
})
|
||||
|
@ -1,24 +1,26 @@
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import mediaSelector from '@components/mediaSelector'
|
||||
import { MenuContainer, MenuRow } from '@components/Menu'
|
||||
import { displayMessage } from '@components/Message'
|
||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||
import { StackScreenProps } from '@react-navigation/stack'
|
||||
import { useProfileMutation, useProfileQuery } from '@utils/queryHooks/profile'
|
||||
import * as ImagePicker from 'expo-image-picker'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { RefObject, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import FlashMessage from 'react-native-flash-message'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import ProfileAvatarHeader from './Root/AvatarHeader'
|
||||
|
||||
const TabMeProfileRoot: React.FC<StackScreenProps<
|
||||
Nav.TabMeProfileStackParamList,
|
||||
'Tab-Me-Profile-Root'
|
||||
>> = ({ navigation }) => {
|
||||
> & { messageRef: RefObject<FlashMessage> }> = ({ messageRef, navigation }) => {
|
||||
const { mode } = useTheme()
|
||||
const { t } = useTranslation('screenTabs')
|
||||
|
||||
const { showActionSheetWithOptions } = useActionSheet()
|
||||
|
||||
const { data, isLoading } = useProfileQuery({})
|
||||
const { mutate } = useProfileMutation()
|
||||
const { mutateAsync } = useProfileMutation()
|
||||
|
||||
const onPressVisibility = useCallback(() => {
|
||||
showActionSheetWithOptions(
|
||||
@ -35,13 +37,46 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
|
||||
async buttonIndex => {
|
||||
switch (buttonIndex) {
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -50,25 +85,88 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
|
||||
|
||||
const onPressSensitive = useCallback(() => {
|
||||
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 {
|
||||
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])
|
||||
|
||||
const onPressLock = useCallback(() => {
|
||||
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 {
|
||||
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])
|
||||
|
||||
const onPressBot = useCallback(() => {
|
||||
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 {
|
||||
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])
|
||||
|
||||
@ -87,52 +185,8 @@ const TabMeProfileRoot: React.FC<StackScreenProps<
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<MenuRow
|
||||
title={t('me.profile.root.avatar.title')}
|
||||
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 })
|
||||
}}
|
||||
/>
|
||||
<ProfileAvatarHeader type='avatar' messageRef={messageRef} />
|
||||
<ProfileAvatarHeader type='header' messageRef={messageRef} />
|
||||
<MenuRow
|
||||
title={t('me.profile.root.note.title')}
|
||||
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