Redesign settings
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Center, Heading } from 'native-base';
|
||||
import { Center, Flex, Heading } from 'native-base';
|
||||
import { translate, Translate } from '../../i18n/i18n';
|
||||
import ElementList from '../../components/GtkUI/ElementList';
|
||||
import useUserSettings from '../../hooks/userSettings';
|
||||
import { LoadingView } from '../../components/Loading';
|
||||
import { Calendar1, MonitorMobbile, Send2, Warning2 } from 'iconsax-react-native';
|
||||
|
||||
const NotificationsView = () => {
|
||||
const { settings, updateSettings } = useUserSettings();
|
||||
@@ -12,10 +13,13 @@ const NotificationsView = () => {
|
||||
return <LoadingView />;
|
||||
}
|
||||
return (
|
||||
<Center style={{ flex: 1, justifyContent: 'center' }}>
|
||||
<Heading style={{ textAlign: 'center' }}>
|
||||
<Translate translationKey="notifBtn" />
|
||||
</Heading>
|
||||
<Flex
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingTop: 32,
|
||||
}}
|
||||
>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
@@ -25,7 +29,9 @@ const NotificationsView = () => {
|
||||
elements={[
|
||||
{
|
||||
type: 'toggle',
|
||||
icon: <MonitorMobbile size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
title: translate('SettingsNotificationsPushNotifications'),
|
||||
description: 'Cette notification apparaitra sur votre apparail en pop-up',
|
||||
data: {
|
||||
value: settings.data.notifications.pushNotif,
|
||||
onToggle: () => {
|
||||
@@ -39,7 +45,9 @@ const NotificationsView = () => {
|
||||
},
|
||||
{
|
||||
type: 'toggle',
|
||||
icon: <Send2 size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
title: translate('SettingsNotificationsEmailNotifications'),
|
||||
description: 'Recevez des mails pour atteindre vos objectifs',
|
||||
data: {
|
||||
value: settings.data.notifications.emailNotif,
|
||||
onToggle: () => {
|
||||
@@ -53,7 +61,9 @@ const NotificationsView = () => {
|
||||
},
|
||||
{
|
||||
type: 'toggle',
|
||||
icon: <Calendar1 size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
title: translate('SettingsNotificationsTrainingReminder'),
|
||||
description: 'Un apprentissage régulier est la clé',
|
||||
data: {
|
||||
value: settings.data.notifications.trainNotif,
|
||||
onToggle: () => {
|
||||
@@ -67,7 +77,9 @@ const NotificationsView = () => {
|
||||
},
|
||||
{
|
||||
type: 'toggle',
|
||||
icon: <Warning2 size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
title: translate('SettingsNotificationsReleaseAlert'),
|
||||
description: 'Restez informé de nos mises à jour',
|
||||
data: {
|
||||
value: settings.data.notifications.newSongNotif,
|
||||
onToggle: () => {
|
||||
@@ -81,7 +93,7 @@ const NotificationsView = () => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Center, Heading } from 'native-base';
|
||||
import { Center, Flex, Heading } from 'native-base';
|
||||
import { useLanguage } from '../../state/LanguageSlice';
|
||||
import { AvailableLanguages, DefaultLanguage, translate, Translate } from '../../i18n/i18n';
|
||||
import { useSelector } from '../../state/Store';
|
||||
import { updateSettings } from '../../state/SettingsSlice';
|
||||
import ElementList from '../../components/GtkUI/ElementList';
|
||||
import LocalSettings from '../../models/LocalSettings';
|
||||
import { Brush, Brush2, Colorfilter, LanguageSquare, Rank, Ranking, Sound, Star1 } from 'iconsax-react-native';
|
||||
|
||||
const PreferencesView = () => {
|
||||
const dispatch = useDispatch();
|
||||
const language = useSelector((state) => state.language.value);
|
||||
const settings = useSelector((state) => state.settings.local);
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Heading style={{ textAlign: 'center' }}>
|
||||
<Translate translationKey="prefBtn" />
|
||||
</Heading>
|
||||
<Flex
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingTop: 32,
|
||||
}}
|
||||
>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
@@ -25,8 +29,10 @@ const PreferencesView = () => {
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
icon: <Brush2 size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'dropdown',
|
||||
title: translate('SettingsPreferencesTheme'),
|
||||
description: 'Définissez le theme (Dark ou Light) de votre application',
|
||||
data: {
|
||||
value: settings.colorScheme,
|
||||
defaultValue: 'system',
|
||||
@@ -45,8 +51,10 @@ const PreferencesView = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <LanguageSquare size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'dropdown',
|
||||
title: translate('SettingsPreferencesLanguage'),
|
||||
description: 'Définissez la langue de votre application',
|
||||
data: {
|
||||
value: language,
|
||||
defaultValue: DefaultLanguage,
|
||||
@@ -61,8 +69,10 @@ const PreferencesView = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <Rank size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'dropdown',
|
||||
title: translate('SettingsPreferencesDifficulty'),
|
||||
description: 'La précision du tempo est de plus en plus élevée',
|
||||
data: {
|
||||
value: settings.difficulty,
|
||||
defaultValue: 'medium',
|
||||
@@ -90,8 +100,10 @@ const PreferencesView = () => {
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
icon: <Colorfilter size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'toggle',
|
||||
title: translate('SettingsPreferencesColorblindMode'),
|
||||
description: 'Augmente le contraste',
|
||||
data: {
|
||||
value: settings.colorBlind,
|
||||
onToggle: () => {
|
||||
@@ -109,8 +121,10 @@ const PreferencesView = () => {
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
icon: <Sound size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'range',
|
||||
title: translate('SettingsPreferencesMicVolume'),
|
||||
description: 'Régler le volume de votre micro selon vos preference',
|
||||
data: {
|
||||
value: settings.micVolume,
|
||||
min: 0,
|
||||
@@ -139,7 +153,7 @@ const PreferencesView = () => {
|
||||
},*/
|
||||
]}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Center, Heading } from 'native-base';
|
||||
import { Center, Flex, Heading } from 'native-base';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import ElementList from '../../components/GtkUI/ElementList';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -7,6 +7,7 @@ import { RootState, useSelector } from '../../state/Store';
|
||||
import { updateSettings } from '../../state/SettingsSlice';
|
||||
import useUserSettings from '../../hooks/userSettings';
|
||||
import { LoadingView } from '../../components/Loading';
|
||||
import { Driver, Driver2, Like1, Shop } from 'iconsax-react-native';
|
||||
|
||||
const PrivacyView = () => {
|
||||
const dispatch = useDispatch();
|
||||
@@ -17,9 +18,13 @@ const PrivacyView = () => {
|
||||
return <LoadingView />;
|
||||
}
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Heading style={{ textAlign: 'center' }}>{translate('privBtn')}</Heading>
|
||||
|
||||
<Flex
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingTop: 32,
|
||||
}}
|
||||
>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
@@ -29,7 +34,9 @@ const PrivacyView = () => {
|
||||
elements={[
|
||||
{
|
||||
type: 'toggle',
|
||||
icon: <Driver size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
title: translate('dataCollection'),
|
||||
description: 'Acceptez-vous la récupération de vos données pour l\'amélioration de Chromacase ?',
|
||||
data: {
|
||||
value: settings.dataCollection,
|
||||
onToggle: () =>
|
||||
@@ -40,7 +47,9 @@ const PrivacyView = () => {
|
||||
},
|
||||
{
|
||||
type: 'toggle',
|
||||
icon: <Shop size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
title: translate('customAds'),
|
||||
description: 'Afficher les suggestions dans la section des recommandations',
|
||||
data: {
|
||||
value: settings.customAds,
|
||||
onToggle: () =>
|
||||
@@ -49,7 +58,9 @@ const PrivacyView = () => {
|
||||
},
|
||||
{
|
||||
type: 'toggle',
|
||||
icon: <Like1 size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
title: translate('recommendations'),
|
||||
description: 'Souhaitez-vous recevoir nos conseils et recommandations ?',
|
||||
data: {
|
||||
value: userSettings.data.recommendations,
|
||||
onToggle: () =>
|
||||
@@ -60,7 +71,7 @@ const PrivacyView = () => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Center>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
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, 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
|
||||
const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
||||
const userQuery = useQuery(API.getUserInfo);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (!userQuery.data || userQuery.isLoading) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
const user = userQuery.data;
|
||||
return (
|
||||
<Flex
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingTop: 40,
|
||||
}}
|
||||
>
|
||||
<Column
|
||||
style={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Center>
|
||||
<UserAvatar size="2xl" />
|
||||
</Center>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('email'),
|
||||
data: {
|
||||
text: user.email || translate('NoAssociatedEmail'),
|
||||
onPress: () => {
|
||||
navigation.navigate('changeEmail');
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('verified'),
|
||||
data: {
|
||||
text: user.emailVerified ? 'verified' : 'not verified',
|
||||
onPress: user.emailVerified
|
||||
? undefined
|
||||
: () => API.fetch({ route: '/auth/reverify', method: 'PUT' }),
|
||||
},
|
||||
},
|
||||
{
|
||||
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' });
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('username'),
|
||||
data: {
|
||||
text: user.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: 'ID',
|
||||
helperText: 'This is your unique ID, be proud of it!',
|
||||
data: {
|
||||
text: user.id.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: 'Google Account',
|
||||
data: {
|
||||
text: user.googleID ? 'Linked' : 'Not linked',
|
||||
},
|
||||
// type: 'custom',
|
||||
// data: user.googleID
|
||||
// ? <Button><Text>Unlink</Text></Button>
|
||||
// : <Button><Text>Link</Text></Button>,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('nbGamesPlayed'),
|
||||
data: {
|
||||
text: user.data.gamesPlayed.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: 'XP',
|
||||
description: translate('XPDescription'),
|
||||
data: {
|
||||
text: user.data.xp.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('userCreatedAt'),
|
||||
helperText:
|
||||
'La date de création est actuellement arbitraire car le serveur ne retourne pas cette information',
|
||||
data: {
|
||||
text: user.data.createdAt.toLocaleDateString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('premiumAccount'),
|
||||
data: {
|
||||
text: translate(user.premium ? 'yes' : 'no'),
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Heading fontSize="20" mt="7">
|
||||
Fonctionnalités premium
|
||||
</Heading>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 10,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
type: 'toggle',
|
||||
title: 'Piano Magique',
|
||||
description:
|
||||
'Fait apparaître de la lumière sur le piano pendant les parties',
|
||||
helperText:
|
||||
'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité',
|
||||
disabled: true,
|
||||
data: {
|
||||
value: false,
|
||||
onToggle: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'dropdown',
|
||||
title: 'Thème de piano',
|
||||
disabled: true,
|
||||
data: {
|
||||
value: 'default',
|
||||
onSelect: () => {},
|
||||
options: [
|
||||
{
|
||||
label: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
label: 'Catpuccino',
|
||||
value: 'catpuccino',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<Box mt={10}>
|
||||
{!user.isGuest && (
|
||||
<TextButton
|
||||
onPress={() => dispatch(unsetAccessToken())}
|
||||
translate={{
|
||||
translationKey: 'signOutBtn',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{user.isGuest && (
|
||||
<Popover
|
||||
trigger={(triggerProps) => (
|
||||
<Button {...triggerProps}>{translate('signOutBtn')}</Button>
|
||||
)}
|
||||
>
|
||||
<Popover.Content>
|
||||
<Popover.Arrow />
|
||||
<Popover.Body>
|
||||
<Heading size="md" mb={2}>
|
||||
{translate('Attention')}
|
||||
</Heading>
|
||||
<Text>
|
||||
{translate('YouAreCurrentlyConnectedWithAGuestAccountWarning')}
|
||||
</Text>
|
||||
<Button.Group variant="ghost" space={2}>
|
||||
<Button
|
||||
onPress={() => dispatch(unsetAccessToken())}
|
||||
colorScheme="red"
|
||||
>
|
||||
{translate('signOutBtn')}
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => {
|
||||
navigation.navigate('GuestToUser');
|
||||
}}
|
||||
colorScheme="green"
|
||||
>
|
||||
{translate('signUpBtn')}
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Popover.Body>
|
||||
</Popover.Content>
|
||||
</Popover>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileSettings;
|
||||
@@ -0,0 +1,91 @@
|
||||
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, 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';
|
||||
import SettingBase from '../../components/UI/SettingsBase';
|
||||
import { Designtools, Google, Magicpen, PasswordCheck, SmsEdit, Star1, UserSquare } from 'iconsax-react-native';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
|
||||
// Too painful to infer the settings-only, typed navigator. Gave up
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const PremiumSettings = () => {
|
||||
const userQuery = useQuery(API.getUserInfo);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (!userQuery.data || userQuery.isLoading) {
|
||||
return <LoadingView />;
|
||||
}
|
||||
const user = userQuery.data;
|
||||
return (
|
||||
<Flex
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingTop: 32,
|
||||
}}
|
||||
>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 10,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
icon: <Star1 size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'text',
|
||||
title: translate('premiumAccount'),
|
||||
description: 'Personalisation premium et outils vous permetant de passer au niveau supperieur',
|
||||
data: {
|
||||
text: translate(user.premium ? 'yes' : 'no'),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <Magicpen size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'toggle',
|
||||
title: 'Piano Magique',
|
||||
description: 'Fait apparaître de la lumière sur le piano pendant les parties',
|
||||
helperText:
|
||||
'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité',
|
||||
disabled: true,
|
||||
data: {
|
||||
value: false,
|
||||
onToggle: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: <Designtools size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'dropdown',
|
||||
title: 'Thème de piano',
|
||||
description: 'Définissez le theme de votre piano',
|
||||
disabled: true,
|
||||
data: {
|
||||
value: 'default',
|
||||
onSelect: () => {},
|
||||
options: [
|
||||
{
|
||||
label: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
label: 'Catpuccino',
|
||||
value: 'catpuccino',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default PremiumSettings;
|
||||
@@ -2,7 +2,7 @@ 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, Toast } from 'native-base';
|
||||
import { Column, Text, Button, Box, Flex, Center, Heading, Popover, Toast, View } from 'native-base';
|
||||
import TextButton from '../../components/TextButton';
|
||||
import { LoadingView } from '../../components/Loading';
|
||||
import ElementList from '../../components/GtkUI/ElementList';
|
||||
@@ -10,10 +10,27 @@ import { translate } from '../../i18n/i18n';
|
||||
import { useQuery } from '../../Queries';
|
||||
import UserAvatar from '../../components/UserAvatar';
|
||||
import * as ImagePicker from 'expo-image-picker';
|
||||
import SettingBase from '../../components/UI/SettingsBase';
|
||||
import { ArrowDown2, EyeSlash, Google, Lock1, PasswordCheck, Sms, SmsEdit, UserSquare } from 'iconsax-react-native';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import TextFormField from '../../components/UI/TextFormField';
|
||||
import ButtonBase from '../../components/UI/ButtonBase';
|
||||
import ChangeEmailForm from '../../components/forms/changeEmailForm';
|
||||
import ChangePasswordForm from '../../components/forms/changePasswordForm';
|
||||
|
||||
const handleChangeEmail = async (newEmail: string): Promise<string> => {
|
||||
await API.updateUserEmail(newEmail);
|
||||
return translate('emailUpdated');
|
||||
};
|
||||
|
||||
const handleChangePassword = async (oldPassword: string, newPassword: string): Promise<string> => {
|
||||
await API.updateUserPassword(oldPassword, newPassword);
|
||||
return translate('passwordUpdated');
|
||||
};
|
||||
|
||||
// Too painful to infer the settings-only, typed navigator. Gave up
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
||||
const ProfileSettings = () => {
|
||||
const userQuery = useQuery(API.getUserInfo);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -26,237 +43,93 @@ const ProfileSettings = ({ navigation }: { navigation: any }) => {
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
paddingTop: 40,
|
||||
paddingTop: 32,
|
||||
}}
|
||||
>
|
||||
<Column
|
||||
<ElementList
|
||||
style={{
|
||||
width: '100%',
|
||||
alignItems: 'center',
|
||||
marginTop: 20,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
>
|
||||
<Center>
|
||||
<UserAvatar size="2xl" />
|
||||
</Center>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('email'),
|
||||
data: {
|
||||
text: user.email || translate('NoAssociatedEmail'),
|
||||
onPress: () => {
|
||||
navigation.navigate('changeEmail');
|
||||
},
|
||||
},
|
||||
elements={[
|
||||
{
|
||||
icon: <Google size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'text',
|
||||
title: "Google account", // TODO translate
|
||||
description: "Liez votre compte Google à ChromaCase", // TODO translate
|
||||
data: {
|
||||
text: user.googleID ? 'Linked' : 'Not linked',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('verified'),
|
||||
data: {
|
||||
text: user.emailVerified ? 'verified' : 'not verified',
|
||||
onPress: user.emailVerified
|
||||
? undefined
|
||||
: () => API.fetch({ route: '/auth/reverify', method: 'PUT' }),
|
||||
},
|
||||
},
|
||||
{
|
||||
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);
|
||||
},
|
||||
{
|
||||
icon: <UserSquare size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'text',
|
||||
title: translate('avatar'),
|
||||
description: "Changer votre photo de profile", // TODO translate
|
||||
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' });
|
||||
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' });
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 20,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('username'),
|
||||
data: {
|
||||
text: user.name,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: 'ID',
|
||||
helperText: 'This is your unique ID, be proud of it!',
|
||||
data: {
|
||||
text: user.id.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: 'Google Account',
|
||||
data: {
|
||||
text: user.googleID ? 'Linked' : 'Not linked',
|
||||
},
|
||||
// type: 'custom',
|
||||
// data: user.googleID
|
||||
// ? <Button><Text>Unlink</Text></Button>
|
||||
// : <Button><Text>Link</Text></Button>,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('nbGamesPlayed'),
|
||||
data: {
|
||||
text: user.data.gamesPlayed.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: 'XP',
|
||||
description: translate('XPDescription'),
|
||||
data: {
|
||||
text: user.data.xp.toString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('userCreatedAt'),
|
||||
helperText:
|
||||
'La date de création est actuellement arbitraire car le serveur ne retourne pas cette information',
|
||||
data: {
|
||||
text: user.data.createdAt.toLocaleDateString(),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
title: translate('premiumAccount'),
|
||||
data: {
|
||||
text: translate(user.premium ? 'yes' : 'no'),
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Heading fontSize="20" mt="7">
|
||||
Fonctionnalités premium
|
||||
</Heading>
|
||||
<ElementList
|
||||
style={{
|
||||
marginTop: 10,
|
||||
width: '90%',
|
||||
maxWidth: 850,
|
||||
}}
|
||||
elements={[
|
||||
{
|
||||
type: 'toggle',
|
||||
title: 'Piano Magique',
|
||||
description:
|
||||
'Fait apparaître de la lumière sur le piano pendant les parties',
|
||||
helperText:
|
||||
'Vous devez posséder le module physique lumineux Chromacase pour pouvoir utiliser cette fonctionnalité',
|
||||
disabled: true,
|
||||
data: {
|
||||
value: false,
|
||||
onToggle: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'dropdown',
|
||||
title: 'Thème de piano',
|
||||
disabled: true,
|
||||
data: {
|
||||
value: 'default',
|
||||
onSelect: () => {},
|
||||
options: [
|
||||
{
|
||||
label: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
label: 'Catpuccino',
|
||||
value: 'catpuccino',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Column>
|
||||
|
||||
<Box mt={10}>
|
||||
{!user.isGuest && (
|
||||
<TextButton
|
||||
onPress={() => dispatch(unsetAccessToken())}
|
||||
translate={{
|
||||
translationKey: 'signOutBtn',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{user.isGuest && (
|
||||
<Popover
|
||||
trigger={(triggerProps) => (
|
||||
<Button {...triggerProps}>{translate('signOutBtn')}</Button>
|
||||
)}
|
||||
>
|
||||
<Popover.Content>
|
||||
<Popover.Arrow />
|
||||
<Popover.Body>
|
||||
<Heading size="md" mb={2}>
|
||||
{translate('Attention')}
|
||||
</Heading>
|
||||
<Text>
|
||||
{translate('YouAreCurrentlyConnectedWithAGuestAccountWarning')}
|
||||
</Text>
|
||||
<Button.Group variant="ghost" space={2}>
|
||||
<Button
|
||||
onPress={() => dispatch(unsetAccessToken())}
|
||||
colorScheme="red"
|
||||
>
|
||||
{translate('signOutBtn')}
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => {
|
||||
navigation.navigate('GuestToUser');
|
||||
}}
|
||||
colorScheme="green"
|
||||
>
|
||||
{translate('signUpBtn')}
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</Popover.Body>
|
||||
</Popover.Content>
|
||||
</Popover>
|
||||
)}
|
||||
</Box>
|
||||
},
|
||||
{
|
||||
icon: <SmsEdit size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'sectionDropdown',
|
||||
title: 'Change email', // TODO translate
|
||||
description: "Saisissez votre adresse électronique actuelle et définissez votre nouvelle adresse électroniquetion", // TODO translate
|
||||
data: {
|
||||
value: true,
|
||||
section: [
|
||||
<ChangePasswordForm
|
||||
onSubmit={(oldPassword, newPassword) =>
|
||||
handleChangePassword(oldPassword, newPassword)
|
||||
}
|
||||
/>
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: <PasswordCheck size="24" color="#FFF" style={{minWidth: 24}}/>,
|
||||
type: 'sectionDropdown',
|
||||
title: 'Change password', // TODO translate
|
||||
description: "Saisissez votre mot de passe actuel et définissez votre nouveau mot de passe", // TODO translate
|
||||
data: {
|
||||
value: true,
|
||||
section: [
|
||||
<ChangeEmailForm
|
||||
onSubmit={(oldEmail, newEmail) =>
|
||||
handleChangeEmail(newEmail)
|
||||
}
|
||||
/>
|
||||
]
|
||||
}
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Center, Text, Heading, Box } from 'native-base';
|
||||
import { Center, Text, Heading, Box, Row } from 'native-base';
|
||||
import { translate } from '../../i18n/i18n';
|
||||
import createTabRowNavigator from '../../components/navigators/TabRowNavigator';
|
||||
import { MaterialCommunityIcons, FontAwesome5 } from '@expo/vector-icons';
|
||||
@@ -13,46 +13,12 @@ import GuestToUserView from './GuestToUserView';
|
||||
import { useQuery } from '../../Queries';
|
||||
import API from '../../API';
|
||||
import { RouteProps } from '../../Navigation';
|
||||
|
||||
const handleChangeEmail = async (newEmail: string): Promise<string> => {
|
||||
await API.updateUserEmail(newEmail);
|
||||
return translate('emailUpdated');
|
||||
};
|
||||
|
||||
const handleChangePassword = async (oldPassword: string, newPassword: string): Promise<string> => {
|
||||
await API.updateUserPassword(oldPassword, newPassword);
|
||||
return translate('passwordUpdated');
|
||||
};
|
||||
|
||||
export const ChangePasswordView = () => {
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Heading paddingBottom={'2%'}>{translate('changePassword')}</Heading>
|
||||
<ChangePasswordForm
|
||||
onSubmit={(oldPassword, newPassword) =>
|
||||
handleChangePassword(oldPassword, newPassword)
|
||||
}
|
||||
/>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChangeEmailView = () => {
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Heading paddingBottom={'2%'}>{translate('changeEmail')}</Heading>
|
||||
<ChangeEmailForm onSubmit={(oldEmail, newEmail) => handleChangeEmail(newEmail)} />
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
|
||||
export const GoogleAccountView = () => {
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Text>GoogleAccount</Text>
|
||||
</Center>
|
||||
);
|
||||
};
|
||||
import { PressableAndroidRippleConfig, StyleProp, TextStyle, View, ViewStyle, useWindowDimensions } from 'react-native';
|
||||
import { TabView, SceneMap, TabBar, NavigationState, Route, SceneRendererProps, TabBarIndicatorProps, TabBarItemProps } from 'react-native-tab-view';
|
||||
import { HeartEdit, Star1, UserEdit, Notification, SecurityUser, Music, Icon } from 'iconsax-react-native';
|
||||
import { Scene, Event } from 'react-native-tab-view/lib/typescript/src/types';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import PremiumSettings from './SettingsPremiumView';
|
||||
|
||||
export const PianoSettingsView = () => {
|
||||
return (
|
||||
@@ -62,125 +28,69 @@ export const PianoSettingsView = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TabRow = createTabRowNavigator();
|
||||
const renderScene = SceneMap({
|
||||
profile: ProfileSettings,
|
||||
premium: PremiumSettings,
|
||||
preferences: PreferencesView,
|
||||
notifications: NotificationsView,
|
||||
privacy: PrivacyView,
|
||||
piano: PianoSettingsView,
|
||||
});
|
||||
|
||||
type SetttingsNavigatorProps = {
|
||||
screen?:
|
||||
| 'profile'
|
||||
| 'preferences'
|
||||
| 'notifications'
|
||||
| 'privacy'
|
||||
| 'changePassword'
|
||||
| 'changeEmail'
|
||||
| 'googleAccount'
|
||||
| 'pianoSettings';
|
||||
};
|
||||
const SetttingsNavigator = () => {
|
||||
const layout = useWindowDimensions();
|
||||
|
||||
const SetttingsNavigator = (props?: RouteProps<SetttingsNavigatorProps>) => {
|
||||
const userQuery = useQuery(API.getUserInfo);
|
||||
const user = useMemo(() => userQuery.data, [userQuery]);
|
||||
const [index, setIndex] = React.useState(0);
|
||||
const [routes] = React.useState([
|
||||
{index: 0, key: 'profile', title: 'Profile', icon: UserEdit},
|
||||
{index: 1, key: 'premium', title: 'Premium', icon: Star1},
|
||||
{index: 2, key: 'preferences', title: 'Preferences', icon: HeartEdit},
|
||||
{index: 3, key: 'notifications', title: 'Notifications', icon: Notification},
|
||||
{index: 4, key: 'privacy', title: 'Privacy', icon: SecurityUser},
|
||||
{index: 5, key: 'piano', title: 'Piano', icon: Music},
|
||||
]);
|
||||
|
||||
if (userQuery.isLoading) {
|
||||
return (
|
||||
<Center style={{ flex: 1 }}>
|
||||
<Text>Loading...</Text>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
const renderTabBar = (props: JSX.IntrinsicAttributes & SceneRendererProps & { navigationState: NavigationState<Route>; scrollEnabled?: boolean | undefined; bounces?: boolean | undefined; activeColor?: string | undefined; inactiveColor?: string | undefined; pressColor?: string | undefined; pressOpacity?: number | undefined; getLabelText?: ((scene: Scene<Route>) => string | undefined) | undefined; getAccessible?: ((scene: Scene<Route>) => boolean | undefined) | undefined; getAccessibilityLabel?: ((scene: Scene<Route>) => string | undefined) | undefined; getTestID?: ((scene: Scene<Route>) => string | undefined) | undefined; renderLabel?: ((scene: Scene<Route> & { focused: boolean; color: string; }) => React.ReactNode) | undefined; renderIcon?: ((scene: Scene<Route> & { focused: boolean; color: string; }) => React.ReactNode) | undefined; renderBadge?: ((scene: Scene<Route>) => React.ReactNode) | undefined; renderIndicator?: ((props: TabBarIndicatorProps<Route>) => React.ReactNode) | undefined; renderTabBarItem?: ((props: TabBarItemProps<Route> & { key: string; }) => React.ReactElement<any, string | React.JSXElementConstructor<any>>) | undefined; onTabPress?: ((scene: Scene<Route> & Event) => void) | undefined; onTabLongPress?: ((scene: Scene<Route>) => void) | undefined; tabStyle?: StyleProp<ViewStyle>; indicatorStyle?: StyleProp<ViewStyle>; indicatorContainerStyle?: StyleProp<ViewStyle>; labelStyle?: StyleProp<TextStyle>; contentContainerStyle?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>; gap?: number | undefined; testID?: string | undefined; android_ripple?: PressableAndroidRippleConfig | undefined; }) => (
|
||||
<TabBar
|
||||
{...props}
|
||||
style={{backgroundColor: 'rgba(0, 0, 0, 0)', borderBottomWidth: 2, borderColor: 'rgba(255,255,255,0.5)'}}
|
||||
indicatorStyle={{ backgroundColor: 'white' }}
|
||||
renderIcon={(scene: Scene<Route> & {
|
||||
focused: boolean;
|
||||
color: string;
|
||||
}) => {
|
||||
const MyIcon: Icon = scene.route?.icon as unknown as Icon;
|
||||
return scene.route?.index == index ?
|
||||
<MyIcon size="18" color="#6075F9" variant='Bold'/>
|
||||
: <MyIcon size="18" color="#6075F9"/>
|
||||
}}
|
||||
renderLabel={({ route, focused, color }) => (
|
||||
layout.width > 750 ?
|
||||
<Text style={{color, paddingLeft: 10, overflow: 'hidden'}}>
|
||||
{route.title}
|
||||
</Text> : null
|
||||
)}
|
||||
tabStyle={{flexDirection: 'row'}}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<TabRow.Navigator
|
||||
initialRouteName={props?.screen ?? 'InternalDefault'}
|
||||
contentStyle={{}}
|
||||
tabBarStyle={{}}
|
||||
>
|
||||
{/* I'm doing this to be able to land on the summary of settings when clicking on settings and directly to the
|
||||
wanted settings page if needed so I need to do special work with the 0 index */}
|
||||
<TabRow.Screen name="InternalDefault" component={Box} />
|
||||
{user && user.isGuest && (
|
||||
<TabRow.Screen
|
||||
name="guestToUser"
|
||||
component={GuestToUserView}
|
||||
options={{
|
||||
title: translate('SettingsCategoryGuest'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'user-clock',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<TabRow.Screen
|
||||
name="profile"
|
||||
component={ProfileSettings}
|
||||
options={{
|
||||
title: translate('SettingsCategoryProfile'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'user',
|
||||
}}
|
||||
<View>
|
||||
<TabView
|
||||
style={{minHeight: layout.height, height: '100%', paddingBottom: 32}}
|
||||
renderTabBar={renderTabBar}
|
||||
navigationState={{ index, routes }}
|
||||
renderScene={renderScene}
|
||||
onIndexChange={setIndex}
|
||||
initialLayout={{ width: layout.width }}
|
||||
/>
|
||||
<TabRow.Screen
|
||||
name="preferences"
|
||||
component={PreferencesView}
|
||||
options={{
|
||||
title: translate('SettingsCategoryPreferences'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'music',
|
||||
}}
|
||||
<LinearGradient
|
||||
start={{x: 0, y: 0}}
|
||||
end={{x: 1, y: 1}}
|
||||
colors={['#101014', '#6075F9']}
|
||||
style={{top: 0, bottom: 0, right: 0, left: 0, width: '100%', height: '100%', margin: 0, padding: 0, position: 'absolute', zIndex: -2}}
|
||||
/>
|
||||
<TabRow.Screen
|
||||
name="notifications"
|
||||
component={NotificationsView}
|
||||
options={{
|
||||
title: translate('SettingsCategoryNotifications'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'bell',
|
||||
}}
|
||||
/>
|
||||
<TabRow.Screen
|
||||
name="privacy"
|
||||
component={PrivacyView}
|
||||
options={{
|
||||
title: translate('SettingsCategoryPrivacy'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'lock',
|
||||
}}
|
||||
/>
|
||||
<TabRow.Screen
|
||||
name="changePassword"
|
||||
component={ChangePasswordView}
|
||||
options={{
|
||||
title: translate('SettingsCategorySecurity'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'key',
|
||||
}}
|
||||
/>
|
||||
<TabRow.Screen
|
||||
name="changeEmail"
|
||||
component={ChangeEmailView}
|
||||
options={{
|
||||
title: translate('SettingsCategoryEmail'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'envelope',
|
||||
}}
|
||||
/>
|
||||
<TabRow.Screen
|
||||
name="googleAccount"
|
||||
component={GoogleAccountView}
|
||||
options={{
|
||||
title: translate('SettingsCategoryGoogle'),
|
||||
iconProvider: FontAwesome5,
|
||||
iconName: 'google',
|
||||
}}
|
||||
/>
|
||||
<TabRow.Screen
|
||||
name="pianoSettings"
|
||||
component={PianoSettingsView}
|
||||
options={{
|
||||
title: translate('SettingsCategoryPiano'),
|
||||
iconProvider: MaterialCommunityIcons,
|
||||
iconName: 'piano',
|
||||
}}
|
||||
/>
|
||||
</TabRow.Navigator>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user