Front: Update User Avatar (#250)

* Front: Update User Avatar

* Front: Fix expo-image-picker version
This commit is contained in:
Arthur Jamet
2023-08-07 10:28:55 +02:00
committed by GitHub
parent 9f542fc9dd
commit a3676fabf8
7 changed files with 110 additions and 8 deletions
+25 -4
View File
@@ -21,6 +21,8 @@ import { PlageHandler } from './models/Plage';
import { ListHandler } from './models/List';
import { AccessTokenResponseHandler } from './models/AccessTokenResponse';
import * as yup from 'yup';
import { base64ToBlob } from 'file64';
import { ImagePickerAsset } from 'expo-image-picker';
type AuthenticationInput = { username: string; password: string };
type RegistrationInput = AuthenticationInput & { email: string };
@@ -30,6 +32,7 @@ export type AccessToken = string;
type FetchParams = {
route: string;
body?: object;
formData?: FormData;
method?: 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT';
};
@@ -81,17 +84,22 @@ export default class API {
public static async fetch(params: FetchParams): Promise<void>;
public static async fetch(params: FetchParams, handle?: HandleParams) {
const jwtToken = store.getState().user.accessToken;
const header = {
'Content-Type': 'application/json',
const headers = {
...(params.formData == undefined && { 'Content-Type': 'application/json' }),
...(jwtToken && { Authorization: `Bearer ${jwtToken}` }),
};
const response = await fetch(`${API.baseUrl}${params.route}`, {
headers: (jwtToken && { ...header, Authorization: `Bearer ${jwtToken}` }) || header,
body: JSON.stringify(params.body),
headers: headers,
body: params.formData ?? JSON.stringify(params.body),
method: params.method ?? 'GET',
}).catch(() => {
throw new Error('Error while fetching API: ' + API.baseUrl);
});
if (!handle || handle.emptyResponse) {
if (!response.ok) {
console.log(await response.json());
throw new APIError(response.statusText, response.status);
}
return;
}
if (handle.raw) {
@@ -164,6 +172,7 @@ export default class API {
{
route: '/auth/guest',
method: 'POST',
body: undefined,
},
{ handler: AccessTokenResponseHandler }
)
@@ -587,4 +596,16 @@ export default class API {
{ handler: UserHandler }
);
}
public static async updateProfileAvatar(image: ImagePickerAsset): Promise<void> {
const data = await base64ToBlob(image.uri);
const formData = new FormData();
formData.append('file', data);
return API.fetch({
route: '/auth/me/picture',
method: 'POST',
formData,
});
}
}
+9 -1
View File
@@ -32,6 +32,14 @@
"eas": {
"projectId": "dade8e5e-3e2c-49f7-98c5-cf8834c7ebb2"
}
}
},
"plugins": [
[
"expo-image-picker",
{
"photosPermission": "The app accesses your photos to let you set your personal avatar."
}
]
]
}
}
+15 -1
View File
@@ -1,6 +1,7 @@
import { Avatar } from 'native-base';
import API from '../API';
import { useQuery } from '../Queries';
import { useMemo } from 'react';
const getInitials = (name: string) => {
return name
@@ -13,9 +14,22 @@ type UserAvatarProps = Pick<Parameters<typeof Avatar>[0], 'size'>;
const UserAvatar = ({ size }: UserAvatarProps) => {
const user = useQuery(API.getUserInfo);
const avatarUrl = useMemo(() => {
if (!user.data) {
return null;
}
const url = new URL(user.data.data.avatar);
url.searchParams.append('updatedAt', user.dataUpdatedAt.toString());
return url;
}, [user.data]);
return (
<Avatar size={size} source={{ uri: user.data?.data.avatar }} style={{ zIndex: 0 }}>
<Avatar
size={size}
source={avatarUrl ? { uri: avatarUrl.toString() } : undefined}
style={{ zIndex: 0 }}
>
{user.data !== undefined && getInitials(user.data.name)}
</Avatar>
);
+7
View File
@@ -180,6 +180,8 @@ export const en = {
recentSearches: 'Recent searches',
noRecentSearches: 'No recent searches',
avatar: 'Avatar',
changeIt: 'Change It',
};
export const fr: typeof en = {
@@ -362,6 +364,8 @@ export const fr: typeof en = {
recentSearches: 'Recherches récentes',
noRecentSearches: 'Aucune recherche récente',
avatar: 'Avatar',
changeIt: 'Modifier',
};
export const sp: typeof en = {
@@ -548,4 +552,7 @@ export const sp: typeof en = {
recentSearches: 'Búsquedas recientes',
noRecentSearches: 'No hay búsquedas recientes',
continuewithgoogle: 'Continuar con Google',
avatar: 'Avatar',
changeIt: 'Cambialo',
};
+2
View File
@@ -34,11 +34,13 @@
"expo": "^47.0.8",
"expo-asset": "~8.7.0",
"expo-dev-client": "~2.0.1",
"expo-image-picker": "~14.0.2",
"expo-linking": "~3.3.1",
"expo-screen-orientation": "~5.0.1",
"expo-secure-store": "~12.0.0",
"expo-splash-screen": "~0.17.5",
"expo-status-bar": "~1.4.2",
"file64": "^1.0.2",
"format-duration": "^2.0.0",
"i18next": "^21.8.16",
"install": "^0.13.0",
+35 -2
View File
@@ -2,13 +2,14 @@ import API from '../../API';
import { useDispatch } from 'react-redux';
import { unsetAccessToken } from '../../state/UserSlice';
import React from 'react';
import { Column, Text, Button, Box, Flex, Center, Heading, Popover } from 'native-base';
import { Column, Text, Button, Box, Flex, Center, Heading, Popover, Toast } from 'native-base';
import TextButton from '../../components/TextButton';
import { LoadingView } from '../../components/Loading';
import ElementList from '../../components/GtkUI/ElementList';
import { translate } from '../../i18n/i18n';
import { useQuery } from '../../Queries';
import UserAvatar from '../../components/UserAvatar';
import * as ImagePicker from 'expo-image-picker';
// Too painful to infer the settings-only, typed navigator. Gave up
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -50,7 +51,39 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
data: {
text: user.email || translate('NoAssociatedEmail'),
onPress: () => {
navigation.navigate('ChangeEmail');
navigation.navigate('changeEmail');
},
},
},
{
type: 'text',
title: translate('avatar'),
data: {
text: translate('changeIt'),
onPress: () => {
ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
aspect: [1, 1],
quality: 1,
base64: true,
}).then((result) => {
console.log(result);
const image = result.assets?.at(0);
if (!result.canceled && image) {
API.updateProfileAvatar(image)
.then(() => {
userQuery.refetch();
Toast.show({
description: 'Update successful',
});
})
.catch((e) => {
console.error(e);
Toast.show({ description: 'Update failed' });
});
}
});
},
},
},
+17
View File
@@ -9116,6 +9116,18 @@ expo-font@~11.0.1:
dependencies:
fontfaceobserver "^2.1.0"
expo-image-loader@~4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/expo-image-loader/-/expo-image-loader-4.0.0.tgz#a17e5f95a4c1671791168dd5dfc221bf2f88480c"
integrity sha512-hVMhXagsO1cSng5s70IEjuJAuHy2hX/inu5MM3T0ecJMf7L/7detKf22molQBRymerbk6Tzu+20h11eU0n/3jQ==
expo-image-picker@~14.0.2:
version "14.0.3"
resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-14.0.3.tgz#ea0bbe796ccc3bd5e58fc00487be22bac317afeb"
integrity sha512-VN5wMWzhYhIRhFq8I1pjMbn/ivjlhWfxzJpz5jUOf3mQ8vxrI5GcR8cJO9kyYwuCrI9W3GUzh/aDt7QRSTQDDA==
dependencies:
expo-image-loader "~4.0.0"
expo-json-utils@~0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/expo-json-utils/-/expo-json-utils-0.4.0.tgz#47ae83a1cc973101d62371f94790e9ad39491751"
@@ -9459,6 +9471,11 @@ file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
file64@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/file64/-/file64-1.0.2.tgz#d3dde9bab142ccf0049e0bd407a2576e94894825"
integrity sha512-cDQefGBdb8OO7Pb2nXiRcZlVjwgzoG0uuJ/H2fxNdz3vbOZctp0iPJoHDQ4VZrirqGYc9n/p9+ZqptLZrcSGRA==
filesize@6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00"