diff --git a/assets/Preference_icons/down_arrow.png b/assets/Preference_icons/down_arrow.png new file mode 100644 index 0000000..0b353b7 Binary files /dev/null and b/assets/Preference_icons/down_arrow.png differ diff --git a/assets/Preference_icons/forward-pref-icon.png b/assets/Preference_icons/forward-pref-icon.png new file mode 100644 index 0000000..852d630 Binary files /dev/null and b/assets/Preference_icons/forward-pref-icon.png differ diff --git a/assets/Preference_icons/index.d.ts b/assets/Preference_icons/index.d.ts new file mode 100644 index 0000000..c5e20fb --- /dev/null +++ b/assets/Preference_icons/index.d.ts @@ -0,0 +1,6 @@ +declare module '*.png' { + const content: any; + export default content; + } + + \ No newline at end of file diff --git a/assets/Preference_icons/preference_icons.ts b/assets/Preference_icons/preference_icons.ts new file mode 100644 index 0000000..a6c5ab5 --- /dev/null +++ b/assets/Preference_icons/preference_icons.ts @@ -0,0 +1,7 @@ +import forward_pref_icon from '@/assets/Preference_icons/forward-pref-icon.png' +import down_arrow from '@/assets/Preference_icons/down_arrow.png' + +export { + forward_pref_icon, + down_arrow +} \ No newline at end of file diff --git a/components/settingPreference/SettingPreference.tsx b/components/settingPreference/SettingPreference.tsx index 6d1cf8b..96d4e16 100644 --- a/components/settingPreference/SettingPreference.tsx +++ b/components/settingPreference/SettingPreference.tsx @@ -1,16 +1,35 @@ -import React, { useState } from 'react'; -import { View, Switch, Text, useColorScheme, TouchableOpacity, StyleSheet } from 'react-native'; +import { useEffect, useState } from 'react'; +import { View, Switch, Text, useColorScheme, TouchableOpacity, StyleSheet, Image } from 'react-native'; import { router } from 'expo-router'; import LanguagePicker from '../LanguagePicker'; import { Dropdown } from 'react-native-element-dropdown'; import { useTranslation } from 'react-i18next'; +import { useMutation, useQuery } from '@apollo/client'; +import { EnableTwoFactorAuth,DisableTwoFactorAuth } from '@/graphql/mutations/two-factor.mutation'; +import {updatePushNotifications,updateEmailNotifications} from '@/graphql/mutations/notificationMutation'; +import { useToast } from 'react-native-toast-notifications'; +import { + forward_pref_icon, + down_arrow +} from '@/assets/Preference_icons/preference_icons' +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { GET_PROFILE } from '@/graphql/queries/user'; const settings = () => { - const [emailNotifications, setEmailNotifications] = useState(false); - const [pushNotifications, setPushNotifications] = useState(false); + const toast = useToast(); + const [isTwoFactorEnabled, setIsTwoFactorEnabled] = useState(false); + const [pushEnabled, setPushEnabled] = useState(false); + const [emailEnabled, setEmailEnabled] = useState(false); + const [enableTwoFactorAuth] = useMutation(EnableTwoFactorAuth); + const [disableTwoFactorAuth] = useMutation(DisableTwoFactorAuth); + const [updateEmailNotificationsMutation] = useMutation(updateEmailNotifications); + const [updatePushNotificationsMutation] = useMutation(updatePushNotifications); + const [userToken, setUserToken] = useState(null); const [selectedTheme, setSelectedTheme] = useState('system'); const { t } = useTranslation(); + const [isProfileExpanded, setIsProfileExpanded] = useState(false); + 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'; @@ -22,23 +41,108 @@ const settings = () => { { label: 'Dark', value: 'dark' }, ]; + useEffect(() => { + (async () => { + const token = await AsyncStorage.getItem('authToken'); + if (token) { + setUserToken(token); + } + })(); + }, []); + + const { data, error } = useQuery(GET_PROFILE, { + context: { + headers: { + Authorization: `Bearer ${userToken}`, + }, + }, + skip: !userToken, + }); + + const handleEnableTwoFactor = async () => { + try { + setIsTwoFactorEnabled(true); + await enableTwoFactorAuth({ variables: { email: data.getProfile.user?.email } }); + toast.show(t('toasts.preferences.enableTwo-way'), { type: 'success', placement: 'top', duration: 3000 }); + + } catch (error) { + setIsTwoFactorEnabled(false); + toast.show(t('toasts.preferences.failEnableTwoway'), { type: 'danger', placement: 'top', duration: 3000 }); + } + }; + + const handleDisableTwoFactor = async () => { + try { + setIsTwoFactorEnabled(false); + await disableTwoFactorAuth({ variables: { email: data.getProfile.user?.email } }); + toast.show(t('toasts.preferences.desableTwo-way'), { type: 'success', placement: 'top', duration: 3000 }); + + } catch (error) { + setIsTwoFactorEnabled(true); + toast.show(t('toasts.preferences.failDesableTwoway'), { type: 'danger', placement: 'top', duration: 3000 }); + } + }; + + const handleEmailNotificationChange = async () => { + try { + await updateEmailNotificationsMutation({ + variables: { updateEmailNotificationsId: data.getProfile.user.id }, + }); + setEmailEnabled((prevEmailEnabled) => !prevEmailEnabled); + toast.show(t('toasts.preferences.updateEmailNotification'), { type: 'success', placement: 'top', duration: 3000 }); + + } catch (error) { + toast.show(t('toasts.preferences.failUpdatingeEmail'), { type: 'danger', placement: 'top', duration: 3000 }); + } + }; + + const handlePushNotificationChange = async () => { + try { + await updatePushNotificationsMutation({ + variables: { updatePushNotificationsId: data.getProfile.user?.id }, + }); + setPushEnabled((prevPushEnabled) => !prevPushEnabled); + toast.show(t('toasts.preferences.updatePushNotification'), { type: 'success', placement: 'top', duration: 3000 }); + + } catch (error) { + toast.show(t('toasts.preferences.failUpdatingPush'), { type: 'danger', placement: 'top', duration: 3000 }); + } + }; + return ( {t('settings.title')} {/* Profile Section */} - - - {t('settings.profile')} - {t('settings.editProfile')} - - - router.push('/dashboard/trainee/profile')} - > - {t('settings.change')} - + + setIsProfileExpanded(!isProfileExpanded)} + > + + {t('settings.profile')} + + + {isProfileExpanded && ( + + {t('settings.editProfile')} + + router.push('/dashboard/trainee/profile')} + > + {t('settings.change')} + + + + )} {/* Theme Picker */} @@ -82,11 +186,11 @@ const settings = () => { setEmailNotifications((prev) => !prev)} - thumbColor={emailNotifications ? '#6200ee' : '#f4f3f4'} - trackColor={{ false: '#767577', true: '#81b0ff' }} - /> + value={emailEnabled} + onValueChange={handleEmailNotificationChange} + thumbColor={emailEnabled ? '#6200ee' : '#f4f3f4'} + trackColor={{ false: '#767577', true: '#81b0ff' }} + /> {/* Push Notifications */} @@ -98,30 +202,31 @@ const settings = () => { setPushNotifications((prev) => !prev)} - thumbColor={pushNotifications ? '#6200ee' : '#f4f3f4'} - trackColor={{ false: '#767577', true: '#81b0ff' }} - /> + value={pushEnabled} + onValueChange={handlePushNotificationChange} + thumbColor={pushEnabled ? '#6200ee' : '#f4f3f4'} + trackColor={{ false: '#767577', true: '#81b0ff' }} + /> {/* Privacy and Security */} - + - {t('settings.privacy')} - {t('settings.privacy')} + {t('settings.Two-factor')} + + {t('settings.Two-factor-Preference')} + - {t('settings.change')} + {/* Login Activity */} - - - {t('settings.login')} - {t('settings.loginHistory')} - - {t('settings.view')} - + ); }; @@ -140,4 +245,8 @@ const styles = StyleSheet.create({ }), }); + export default settings; + + + diff --git a/graphql/mutations/two-factor.mutation.ts b/graphql/mutations/two-factor.mutation.ts new file mode 100644 index 0000000..e6fe7c6 --- /dev/null +++ b/graphql/mutations/two-factor.mutation.ts @@ -0,0 +1,13 @@ +import { gql } from '@apollo/client'; + +export const EnableTwoFactorAuth = gql` + mutation EnableTwoFactorAuth($email: String!) { + enableTwoFactorAuth(email: $email) + } +`; + +export const DisableTwoFactorAuth = gql` + mutation DisableTwoFactorAuth($email: String!) { + disableTwoFactorAuth(email: $email) + } +`; \ No newline at end of file diff --git a/graphql/queries/user.ts b/graphql/queries/user.ts index d145819..43befc5 100644 --- a/graphql/queries/user.ts +++ b/graphql/queries/user.ts @@ -16,6 +16,7 @@ export const GET_PROFILE = gql` biography githubUsername user { + id organizations email role diff --git a/internationalization/locales/en.json b/internationalization/locales/en.json index c788473..7b2b25a 100644 --- a/internationalization/locales/en.json +++ b/internationalization/locales/en.json @@ -220,7 +220,8 @@ "emailFeeds": "Feedback emails, reminder emails, news emails", "pushNotify": "Push Notifications", "pushUpdates": "Grade updates, session reminders, performance comments", - "privacy": "Privacy and Security", + "Two-factor": "Two-factor authentication", + "Two-factor-Preference":"Get extra security by receiving a code on your email", "login": "Login Activity" , "loginHistory": "History of Your login session", "view": "View" @@ -260,7 +261,18 @@ "avatarUpdated": "Profile picture updated successfully", "avatarError": "Failed to update profile picture", "imagePickerError": "Error selecting image" +}, +"preferences":{ + "enableTwo-way":"Two-factor authentication Enabled", + "failEnableTwoway":"Error enabling two-factor authentication", + "desableTwo-way":"Two-factor authentication Disabled", + "failDesableTwoway":"Error Desabling two-factor authentication", + "updateEmailNotification":"Email notifications updated successfull", + "failUpdatingeEmail":"Error updating Email Notifications", + "updatePushNotification":"Push Notifications updated successfull", + "failUpdatingPush":"Error enabling Push Notifications" } + }, "notifications":{ "deleteSuccess": "Notification deleted successfully", diff --git a/internationalization/locales/fr.json b/internationalization/locales/fr.json index e3f3cc6..054755e 100644 --- a/internationalization/locales/fr.json +++ b/internationalization/locales/fr.json @@ -220,7 +220,8 @@ "emailFeeds": "E-mails de retour, rappels, actualités", "pushNotify": "Notifications poussées", "pushUpdates": "Mises à jour des notes, rappels de session, commentaires de performance", - "privacy": "Confidentialité et Sécurité", + "Two-factor": "Two-factor authentication", + "Two-factor-Preference":"Bénéficiez d'une sécurité supplémentaire en recevant un code sur votre email", "login": "Activité de connexion", "loginHistory": "Historique de vos sessions de connexion", "view": "Voir" @@ -261,6 +262,16 @@ "avatarUpdated": "Photo de profil mise à jour avec succès", "avatarError": "Erreur lors de la mise à jour de la photo de profil", "imagePickerError": "Erreur lors de la sélection de l'image" +}, +"preferences":{ + "enableTwo-way":"authentification à deux facteurs activée", + "failEnableTwoway":"Erreur lors de l'activation de l'authentification à deux facteurs", + "desableTwo-way":"Authentification à deux facteurs désactivée", + "failDesableTwoway":"Erreur lors de la désactivation de l'authentification à deux facteurs", + "updateEmailNotification":"Notifications par e-mail mises à jour avec succès", + "failUpdatingeEmail":"Erreur lors de la mise à jour des notifications par e-mail", + "updatePushNotification":"Notifications push mises à jour avec succès", + "failUpdatingPush":"Erreur lors de la mise à jour des notifications push" } }, "notifications": { diff --git a/internationalization/locales/kin.json b/internationalization/locales/kin.json index e1fdb57..57a96d8 100644 --- a/internationalization/locales/kin.json +++ b/internationalization/locales/kin.json @@ -220,7 +220,8 @@ "emailFeeds": "Imeyili z'ibitekerezo, kwibutsa, amakuru", "pushNotify": "Gutanga amatangazo", "pushUpdates": "Amakuru ku manota, kwibutsa amasomo, ibisobanuro ku myitwarire", - "privacy": "Ubuzima bwite n'Umutekano", + "Two-factor": "Two-factor authentication", + "Two-factor-Preference":"Shaka umutekano wongeyeho wakiriye kode kuri imeri yawe", "login": "Ibikorwa byo kwinjira", "loginHistory": "Amateka y'ibihe wakoresheje winjira", "view": "Reba" @@ -261,6 +262,16 @@ "avatarUpdated": "Foto ya umwirondoro yashyizweho neza", "avatarError": "Ikosa mu kuvugurura foto ya umwirondoro", "imagePickerError": "Ikosa mu kuvugurura foto ya umwirondoro" +}, +"preferences":{ + "enableTwo-way":"Kwemeza ibintu bibiri", + "failEnableTwoway":"Ikosa rishobora kwemeza ibintu bibiri", + "desableTwo-way":"Kwemeza ibintu bibiri byahagaritswe", + "failDesableTwoway":"Ikosa Gusiba ibintu bibiri byemewe", + "updateEmailNotification":"Imenyesha rya imeri ryagezweho neza", + "failUpdatingeEmail":"Ikosa ryo kuvugurura imenyesha rya imeri", + "updatePushNotification":"Gusunika Kumenyesha bigezweho", + "failUpdatingPush":"Ikosa ryo kuvugurura Amatangazo" } }, "notifications": { diff --git a/package-lock.json b/package-lock.json index 36a6a5d..2687fb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,7 +53,6 @@ "react-native-dropdown-picker": "^5.4.6", "react-native-element-dropdown": "^2.12.2", "react-native-gesture-handler": "~2.16.1", - "react-native-get-random-values": "~1.11.0", "react-native-pager-view": "6.3.0", "react-native-popover-view": "^5.1.9", "react-native-reanimated": "~3.10.1", @@ -63,7 +62,6 @@ "react-native-toast-notifications": "^3.4.0", "react-native-vector-icons": "^10.2.0", "react-test-renderer": "^18.2.0", - "undefined": "react-native-async-storage/async-storage", "vector-icons": "^0.1.0", "yup": "^1.4.0" }, @@ -8779,9 +8777,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001680", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", - "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", + "version": "1.0.30001682", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001682.tgz", + "integrity": "sha512-rJFwz3yRO6NU6Y8aEJKPzS4fngOE8j05pd33FW5Uk9v9b5StWNhGFeVpogwS2FFl78wNDGW5NsVvlwySPEDU5w==", "funding": [ { "type": "opencollective", @@ -11055,14 +11053,14 @@ } }, "node_modules/expo-constants/node_modules/@expo/config": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-10.0.4.tgz", - "integrity": "sha512-pkvdPqKTaP6+Qvc8aTmDLQ9Dfwp98P1GO37MFKwsF5XormfN/9/eN8HfIRoM6d3uSIVKCcWW3X2yAEbNmOyfXw==", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-10.0.5.tgz", + "integrity": "sha512-wq48h3HlAPq5v/gMprarAiVY1aEXNBVJ+Em0vrHcYFO8UyxzR6oIao2E4Ed3VWHqhTzPXkMPH4hKCKlzFVBFwQ==", "license": "MIT", "peer": true, "dependencies": { "@babel/code-frame": "~7.10.4", - "@expo/config-plugins": "~9.0.0", + "@expo/config-plugins": "~9.0.10", "@expo/config-types": "^52.0.0", "@expo/json-file": "^9.0.0", "deepmerge": "^4.3.1", @@ -11077,9 +11075,9 @@ } }, "node_modules/expo-constants/node_modules/@expo/config-plugins": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-9.0.9.tgz", - "integrity": "sha512-pbgbY3SwCMwkijhfe163J05BrTx4MqzeaV+nVgUMs7vRcjHY1tfM57Pdv6SPtgeDvZ8fvdXFXXzkJva+a7C9Bw==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-9.0.10.tgz", + "integrity": "sha512-4piPSylJ8z3to+YZpl/6M2mLxASOdIFANA8FYihsTf9kWlyimV9L/+MGgPXJcieaHXYZZqOryf8hQFVeg/68+A==", "license": "MIT", "peer": true, "dependencies": { @@ -11614,12 +11612,6 @@ ], "license": "MIT" }, - "node_modules/fast-base64-decode": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-base64-decode/-/fast-base64-decode-1.0.0.tgz", - "integrity": "sha512-qwaScUgUGBYeDNRnbc/KyllVU88Jk1pRHPStuF/lO7B0/RTRLj7U0lkdTAutlBblY08rwZDff6tNU9cjv6j//Q==", - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -12024,9 +12016,9 @@ "license": "MIT" }, "node_modules/flow-parser": { - "version": "0.253.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.253.0.tgz", - "integrity": "sha512-EbxtzRIzp8dDSzTloPhsc6uOvrEFIyu08cqQzXBWLAgxK+i2d/5qOos9ryQHRmk+RyDDXfnz/7qteh3jnAlc4w==", + "version": "0.254.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.254.0.tgz", + "integrity": "sha512-FhO64nGWlkrCxMWRDL1Wbok+ep4iSw2t6EtuyYOFZRzBh902iynZ/GMDU/3RSbiKTmALkcmCmKQLe0eOWdMA8Q==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -12854,9 +12846,9 @@ } }, "node_modules/i18next": { - "version": "23.16.6", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.6.tgz", - "integrity": "sha512-wGdE5rUfkZtrL5k6MCptxbpjmgwku4rBRVU/YOJ7Xfd841fgaZjlxHpVJ5NIz8sfSvAJhEhJrvJ8qE7AWXE4Xg==", + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", "funding": [ { "type": "individual", @@ -14824,9 +14816,9 @@ } }, "node_modules/jest-watch-typeahead/node_modules/char-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", - "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", "dev": true, "license": "MIT", "engines": { @@ -17237,27 +17229,15 @@ } }, "node_modules/optimism": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.0.tgz", - "integrity": "sha512-tGn8+REwLRNFnb9WmcY5IfpOqeX2kpaYJ1s6Ae3mn12AeydLkR3j+jSCmVQFoXqU8D41PAJ1RG1rCRNWmNZVmQ==", + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.1.tgz", + "integrity": "sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==", "license": "MIT", "dependencies": { "@wry/caches": "^1.0.0", "@wry/context": "^0.7.0", - "@wry/trie": "^0.4.3", - "tslib": "^2.3.0" - } - }, - "node_modules/optimism/node_modules/@wry/trie": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.4.3.tgz", - "integrity": "sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w==", - "license": "MIT", - "dependencies": { + "@wry/trie": "^0.5.0", "tslib": "^2.3.0" - }, - "engines": { - "node": ">=8" } }, "node_modules/optionator": { @@ -18796,18 +18776,6 @@ "react-native": "*" } }, - "node_modules/react-native-get-random-values": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.11.0.tgz", - "integrity": "sha512-4BTbDbRmS7iPdhYLRcz3PGFIpFJBwNZg9g42iwa2P6FOv9vZj/xJc678RZXnLNZzd0qd7Q3CCF6Yd+CU2eoXKQ==", - "license": "MIT", - "dependencies": { - "fast-base64-decode": "^1.0.0" - }, - "peerDependencies": { - "react-native": ">=0.56" - } - }, "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", @@ -21918,21 +21886,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undefined": { - "name": "@react-native-async-storage/root", - "version": "0.0.1-dev", - "resolved": "git+ssh://git@github.com/react-native-async-storage/async-storage.git#91b0e16a43c37fb81b503eda27ef00c543f34150", - "workspaces": [ - "packages/api", - "packages/default-storage", - "packages/eslint-config", - "packages/sqlite-storage", - "packages/website" - ], - "engines": { - "node": "v20.11.1" - } - }, "node_modules/undici": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz",