From a7358123dce7257de11b1183bbca2935e70ebb52 Mon Sep 17 00:00:00 2001 From: Ishimwe Christian <88989844+Christian-Ishimwe@users.noreply.github.com> Date: Fri, 1 Nov 2024 13:03:14 +0200 Subject: [PATCH] feat(#61): trainee preference/settings (#81) --- app/dashboard/_layout.tsx | 27 +--- app/dashboard/trainee/preference.tsx | 9 ++ components/LanguagePicker.tsx | 15 +- components/ProfileDropdown.tsx | 102 +++++++++++++ .../settingPreference/SettingPreference.tsx | 142 ++++++++++++++++++ package-lock.json | 41 +++++ package.json | 2 + 7 files changed, 308 insertions(+), 30 deletions(-) create mode 100644 app/dashboard/trainee/preference.tsx create mode 100644 components/ProfileDropdown.tsx create mode 100644 components/settingPreference/SettingPreference.tsx diff --git a/app/dashboard/_layout.tsx b/app/dashboard/_layout.tsx index 1b9322f..467815f 100644 --- a/app/dashboard/_layout.tsx +++ b/app/dashboard/_layout.tsx @@ -5,10 +5,7 @@ import { lightNotifyIcon, menu, } from '@/assets/Icons/dashboard/Icons'; -import ProfileAvatar from '@/components/ProfileAvatar'; import Sidebar from '@/components/sidebar'; -import { GET_PROFILE } from '@/graphql/queries/user'; -import { useQuery } from '@apollo/client'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { Slot, useRouter } from 'expo-router'; import { useEffect, useState } from 'react'; @@ -18,7 +15,7 @@ import { ScrollView, TouchableOpacity, View, - useColorScheme + useColorScheme, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { SvgXml } from 'react-native-svg'; @@ -56,23 +53,16 @@ export type ProfileType = { }; }; +import ProfileDropdown from '@/components/ProfileDropdown'; export default function AuthLayout() { const router = useRouter(); const insets = useSafeAreaInsets(); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const colorScheme = useColorScheme(); const [authToken, setAuthToken] = useState(null); - const [profile, setProfile] = useState(null); const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen); - const { data: profileData } = useQuery(GET_PROFILE, { - context: { - headers: { Authorization: `Bearer ${authToken}` }, - }, - skip: !authToken, - }); - useEffect(() => { (async function () { const cachedToken = await AsyncStorage.getItem('authToken'); @@ -86,15 +76,6 @@ export default function AuthLayout() { })(); }, [authToken]); - useEffect(() => { - (async function () { - if (profileData != undefined) { - setProfile(profileData.getProfile); - await AsyncStorage.setItem('userProfile', JSON.stringify(profileData.getProfile)); - } - })(); - }, [profileData]); - return ( - router.push('/dashboard/trainee/profile')}> - - + diff --git a/app/dashboard/trainee/preference.tsx b/app/dashboard/trainee/preference.tsx new file mode 100644 index 0000000..a77eecb --- /dev/null +++ b/app/dashboard/trainee/preference.tsx @@ -0,0 +1,9 @@ +import { Text, View } from 'react-native'; +import Settings from '@/components/settingPreference/SettingPreference'; +export default function PreferenceDashboard() { + return ( + + + + ); +} diff --git a/components/LanguagePicker.tsx b/components/LanguagePicker.tsx index ea9f570..7a37926 100644 --- a/components/LanguagePicker.tsx +++ b/components/LanguagePicker.tsx @@ -10,7 +10,7 @@ const LANGUAGES = [ { code: 'fr', labelKey: 'languages.french', flagCode: 'FR' }, ]; -export default function LanguagePicker() { +export default function LanguagePicker({ showFlag = true }) { const { i18n, t } = useTranslation(); const [modalVisible, setModalVisible] = useState(false); const colorScheme = useColorScheme(); @@ -35,11 +35,14 @@ export default function LanguagePicker() { activeOpacity={0.6} className="p-2 bg-white rounded dark:bg-primary-dark" > - - lang.code === i18n.language)?.flagCode || 'GB'} - size={24} - /> + + {showFlag ? ( + lang.code === i18n.language)?.flagCode || 'GB'} + size={24} + /> + ) : null} + {getCurrentLanguageLabel()} diff --git a/components/ProfileDropdown.tsx b/components/ProfileDropdown.tsx new file mode 100644 index 0000000..7510ea0 --- /dev/null +++ b/components/ProfileDropdown.tsx @@ -0,0 +1,102 @@ +import React, { useState, useEffect } from 'react'; +import { View, TouchableOpacity, Image, Text, StyleSheet, useColorScheme } from 'react-native'; +import Popover from 'react-native-popover-view'; +import { useRouter } from 'expo-router'; +import { ProfileType } from '@/app/dashboard/_layout'; +import { useQuery } from '@apollo/client'; +import { GET_PROFILE } from '@/graphql/queries/user'; +import ProfileAvatar from './ProfileAvatar'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +const ProfileDropdown = () => { + const theme = useColorScheme(); + const [profile, setProfile] = useState(null); + const [authToken, setAuthToken] = useState(null); + + const [isVisible, setIsVisible] = useState(false); + const router = useRouter(); + + const togglePopover = () => { + setIsVisible(!isVisible); + }; + + const handleNavigate = (route: any) => { + setIsVisible(false); + router.push(route); + }; + + const { data: profileData } = useQuery(GET_PROFILE, { + context: { + headers: { Authorization: `Bearer ${authToken}` }, + }, + skip: !authToken, + }); + + useEffect(() => { + (async function () { + const cachedToken = await AsyncStorage.getItem('authToken'); + if (cachedToken != authToken) { + setAuthToken(cachedToken); + } + + if (cachedToken === null) { + router.push('/auth/login'); + } + })(); + }, [authToken]); + + useEffect(() => { + (async function () { + if (profileData != undefined) { + setProfile(profileData.getProfile); + await AsyncStorage.setItem('userProfile', JSON.stringify(profileData.getProfile)); + } + })(); + }, [profileData]); + return ( + + + + + } + > + + handleNavigate('/dashboard/trainee/profile')} + > + + Profile + + + + handleNavigate('/dashboard/trainee/preference')} + > + + Settings + + + + + + ); +}; + +export default ProfileDropdown; diff --git a/components/settingPreference/SettingPreference.tsx b/components/settingPreference/SettingPreference.tsx new file mode 100644 index 0000000..6ed5e82 --- /dev/null +++ b/components/settingPreference/SettingPreference.tsx @@ -0,0 +1,142 @@ +import React, { useState } from 'react'; +import { View, Switch, Text, useColorScheme, TouchableOpacity, StyleSheet } from 'react-native'; +import { router } from 'expo-router'; +import LanguagePicker from '../LanguagePicker'; +import { Dropdown } from 'react-native-element-dropdown'; + +const Settings = () => { + const [emailNotifications, setEmailNotifications] = useState(false); + const [pushNotifications, setPushNotifications] = useState(false); + const [selectedTheme, setSelectedTheme] = useState('system'); + + const colorScheme = selectedTheme === 'system' ? useColorScheme() : selectedTheme; + const textStyle = colorScheme === 'dark' ? 'text-gray-100' : 'text-gray-800'; + const borderColor = colorScheme === 'dark' ? 'border-gray-700' : 'border-gray-300'; + const containerStyle = colorScheme === 'dark' ? 'bg-primary-dark' : 'bg-primary-light'; + + const themeData = [ + { label: 'System', value: 'system' }, + { label: 'Light', value: 'light' }, + { label: 'Dark', value: 'dark' }, + ]; + + return ( + + Settings + + {/* Profile Section */} + + + Profile + Edit profile, export account, data… + + + router.push('/dashboard/trainee/profile')} + > + Change + + + + + {/* Theme Picker */} + + + Appearance + Theme preferences + + + {}} + placeholder="Select Theme" + selectedTextStyle={{ color: colorScheme === 'dark' ? '#fff' : '#000' }} + //@ts-ignore + style={styles.picker(colorScheme)} + /> + + + + {/* Language Picker */} + + Language + + Language Preference + + + + + + + {/* Email Notifications */} + + + Email Notifications + + Feedback emails, reminder emails, news emails + + + setEmailNotifications((prev) => !prev)} + thumbColor={emailNotifications ? '#6200ee' : '#f4f3f4'} + trackColor={{ false: '#767577', true: '#81b0ff' }} + /> + + + {/* Push Notifications */} + + + Push Notifications + + Grade updates, session reminders, performance comments + + + setPushNotifications((prev) => !prev)} + thumbColor={pushNotifications ? '#6200ee' : '#f4f3f4'} + trackColor={{ false: '#767577', true: '#81b0ff' }} + /> + + + {/* Privacy and Security */} + + + Privacy and Security + Privacy and Security + + Change + + + {/* Login Activity */} + + + Login Activity + History of Your login session + + View + + + ); +}; + +const styles = StyleSheet.create({ + //@ts-ignore + picker: (colorScheme) => ({ + height: 50, + width: 178, + backgroundColor: colorScheme === 'dark' ? '#070E1C' : '#fff', + color: colorScheme === 'dark' ? '#fff' : '#000', + borderColor: colorScheme === 'dark' ? '#374151' : '#ccc', + borderWidth: 1, + borderRadius: 8, + paddingHorizontal: 10, + }), +}); + +export default Settings; diff --git a/package-lock.json b/package-lock.json index c8dcda7..8bfeb55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,9 @@ "react-native-date-picker": "^5.0.7", "react-native-dotenv": "^3.4.11", "react-native-dropdown-picker": "^5.4.6", + "react-native-element-dropdown": "^2.12.2", "react-native-pager-view": "6.3.0", + "react-native-popover-view": "^5.1.9", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1", @@ -7729,6 +7731,11 @@ "@babel/core": "*" } }, + "node_modules/@react-native/normalize-color": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", + "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==" + }, "node_modules/@react-native/normalize-colors": { "version": "0.74.85", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.85.tgz", @@ -11731,6 +11738,16 @@ "node": ">= 0.8" } }, + "node_modules/deprecated-react-native-prop-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-2.3.0.tgz", + "integrity": "sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==", + "dependencies": { + "@react-native/normalize-color": "*", + "invariant": "*", + "prop-types": "*" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -21758,6 +21775,21 @@ "react-native": "*" } }, + "node_modules/react-native-element-dropdown": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.2.tgz", + "integrity": "sha512-Tf8hfRuniYEXo+LGoVgIMoItKWuPLX6jbqlwAFgMbBhmWGTuV+g1OVOAx/ny16kgnwp+NhgJoWpxhVvr7HSmXA==", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-helmet-async": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-native-helmet-async/-/react-native-helmet-async-2.0.4.tgz", @@ -21788,6 +21820,15 @@ "react-native": "*" } }, + "node_modules/react-native-popover-view": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/react-native-popover-view/-/react-native-popover-view-5.1.9.tgz", + "integrity": "sha512-jjcVfH6hUN2bnB8wwtiELyb5184K90L3qZ+h0u6dSfVdyxgDbbkPS336a9JmNoaUGIrQYwZpRUd5Cgai35Am2w==", + "dependencies": { + "deprecated-react-native-prop-types": "^2.3.0", + "prop-types": "^15.8.1" + } + }, "node_modules/react-native-reanimated": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.10.1.tgz", diff --git a/package.json b/package.json index bc12a33..53d27ff 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,9 @@ "react-native-date-picker": "^5.0.7", "react-native-dotenv": "^3.4.11", "react-native-dropdown-picker": "^5.4.6", + "react-native-element-dropdown": "^2.12.2", "react-native-pager-view": "6.3.0", + "react-native-popover-view": "^5.1.9", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.5", "react-native-screens": "3.31.1",