Skip to content

Commit a735812

Browse files
feat(#61): trainee preference/settings (#81)
1 parent c017178 commit a735812

File tree

7 files changed

+308
-30
lines changed

7 files changed

+308
-30
lines changed

Diff for: app/dashboard/_layout.tsx

+3-24
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import {
55
lightNotifyIcon,
66
menu,
77
} from '@/assets/Icons/dashboard/Icons';
8-
import ProfileAvatar from '@/components/ProfileAvatar';
98
import Sidebar from '@/components/sidebar';
10-
import { GET_PROFILE } from '@/graphql/queries/user';
11-
import { useQuery } from '@apollo/client';
129
import AsyncStorage from '@react-native-async-storage/async-storage';
1310
import { Slot, useRouter } from 'expo-router';
1411
import { useEffect, useState } from 'react';
@@ -18,7 +15,7 @@ import {
1815
ScrollView,
1916
TouchableOpacity,
2017
View,
21-
useColorScheme
18+
useColorScheme,
2219
} from 'react-native';
2320
import { useSafeAreaInsets } from 'react-native-safe-area-context';
2421
import { SvgXml } from 'react-native-svg';
@@ -56,23 +53,16 @@ export type ProfileType = {
5653
};
5754
};
5855

56+
import ProfileDropdown from '@/components/ProfileDropdown';
5957
export default function AuthLayout() {
6058
const router = useRouter();
6159
const insets = useSafeAreaInsets();
6260
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);
6361
const colorScheme = useColorScheme();
6462
const [authToken, setAuthToken] = useState<string | null>(null);
65-
const [profile, setProfile] = useState<ProfileType | null>(null);
6663

6764
const toggleSidebar = () => setIsSidebarOpen(!isSidebarOpen);
6865

69-
const { data: profileData } = useQuery(GET_PROFILE, {
70-
context: {
71-
headers: { Authorization: `Bearer ${authToken}` },
72-
},
73-
skip: !authToken,
74-
});
75-
7666
useEffect(() => {
7767
(async function () {
7868
const cachedToken = await AsyncStorage.getItem('authToken');
@@ -86,15 +76,6 @@ export default function AuthLayout() {
8676
})();
8777
}, [authToken]);
8878

89-
useEffect(() => {
90-
(async function () {
91-
if (profileData != undefined) {
92-
setProfile(profileData.getProfile);
93-
await AsyncStorage.setItem('userProfile', JSON.stringify(profileData.getProfile));
94-
}
95-
})();
96-
}, [profileData]);
97-
9879
return (
9980
<KeyboardAvoidingView
10081
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
@@ -129,9 +110,7 @@ export default function AuthLayout() {
129110
height={40}
130111
/>
131112
</TouchableOpacity>
132-
<TouchableOpacity onPress={() => router.push('/dashboard/trainee/profile')}>
133-
<ProfileAvatar name={profile?.name} src={profile?.avatar} size='xs' />
134-
</TouchableOpacity>
113+
<ProfileDropdown />
135114
</View>
136115
</View>
137116
</View>

Diff for: app/dashboard/trainee/preference.tsx

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Text, View } from 'react-native';
2+
import Settings from '@/components/settingPreference/SettingPreference';
3+
export default function PreferenceDashboard() {
4+
return (
5+
<View>
6+
<Settings />
7+
</View>
8+
);
9+
}

Diff for: components/LanguagePicker.tsx

+9-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const LANGUAGES = [
1010
{ code: 'fr', labelKey: 'languages.french', flagCode: 'FR' },
1111
];
1212

13-
export default function LanguagePicker() {
13+
export default function LanguagePicker({ showFlag = true }) {
1414
const { i18n, t } = useTranslation();
1515
const [modalVisible, setModalVisible] = useState(false);
1616
const colorScheme = useColorScheme();
@@ -35,11 +35,14 @@ export default function LanguagePicker() {
3535
activeOpacity={0.6}
3636
className="p-2 bg-white rounded dark:bg-primary-dark"
3737
>
38-
<View className="flex-row items-center p-4">
39-
<CountryFlag
40-
isoCode={LANGUAGES.find((lang) => lang.code === i18n.language)?.flagCode || 'GB'}
41-
size={24}
42-
/>
38+
<View className="flex-row items-center ">
39+
{showFlag ? (
40+
<CountryFlag
41+
isoCode={LANGUAGES.find((lang) => lang.code === i18n.language)?.flagCode || 'GB'}
42+
size={24}
43+
/>
44+
) : null}
45+
4346
<Text className="flex-1 ml-3 text-lg font-medium text-gray-900 dark:text-white">
4447
{getCurrentLanguageLabel()}
4548
</Text>

Diff for: components/ProfileDropdown.tsx

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, { useState, useEffect } from 'react';
2+
import { View, TouchableOpacity, Image, Text, StyleSheet, useColorScheme } from 'react-native';
3+
import Popover from 'react-native-popover-view';
4+
import { useRouter } from 'expo-router';
5+
import { ProfileType } from '@/app/dashboard/_layout';
6+
import { useQuery } from '@apollo/client';
7+
import { GET_PROFILE } from '@/graphql/queries/user';
8+
import ProfileAvatar from './ProfileAvatar';
9+
import AsyncStorage from '@react-native-async-storage/async-storage';
10+
const ProfileDropdown = () => {
11+
const theme = useColorScheme();
12+
const [profile, setProfile] = useState<ProfileType | null>(null);
13+
const [authToken, setAuthToken] = useState<string | null>(null);
14+
15+
const [isVisible, setIsVisible] = useState(false);
16+
const router = useRouter();
17+
18+
const togglePopover = () => {
19+
setIsVisible(!isVisible);
20+
};
21+
22+
const handleNavigate = (route: any) => {
23+
setIsVisible(false);
24+
router.push(route);
25+
};
26+
27+
const { data: profileData } = useQuery(GET_PROFILE, {
28+
context: {
29+
headers: { Authorization: `Bearer ${authToken}` },
30+
},
31+
skip: !authToken,
32+
});
33+
34+
useEffect(() => {
35+
(async function () {
36+
const cachedToken = await AsyncStorage.getItem('authToken');
37+
if (cachedToken != authToken) {
38+
setAuthToken(cachedToken);
39+
}
40+
41+
if (cachedToken === null) {
42+
router.push('/auth/login');
43+
}
44+
})();
45+
}, [authToken]);
46+
47+
useEffect(() => {
48+
(async function () {
49+
if (profileData != undefined) {
50+
setProfile(profileData.getProfile);
51+
await AsyncStorage.setItem('userProfile', JSON.stringify(profileData.getProfile));
52+
}
53+
})();
54+
}, [profileData]);
55+
return (
56+
<View className="flex flex-row items-center">
57+
<Popover
58+
isVisible={isVisible}
59+
onRequestClose={togglePopover}
60+
from={
61+
<TouchableOpacity onPress={togglePopover}>
62+
<ProfileAvatar name={profile?.name} src={profile?.avatar} size="xs" />
63+
</TouchableOpacity>
64+
}
65+
>
66+
<View
67+
className={`py-2 px-8 border border-gray-300 shadow-lg shadow-black/30 w-48
68+
${theme === 'light' ? 'bg-[#f9f9f9] text-[#333] border-[#ccc]' : 'bg-[#2b2b2b] text-[#f5f5f5] border-[#444]'}
69+
`}
70+
>
71+
<TouchableOpacity
72+
className="border-b border-gray-300 py-2"
73+
onPress={() => handleNavigate('/dashboard/trainee/profile')}
74+
>
75+
<Text
76+
className={`
77+
${theme === 'light' ? 'text-[#333]' : 'text-[#f5f5f5]'}
78+
`}
79+
>
80+
Profile
81+
</Text>
82+
</TouchableOpacity>
83+
84+
<TouchableOpacity
85+
className="py-2"
86+
onPress={() => handleNavigate('/dashboard/trainee/preference')}
87+
>
88+
<Text
89+
className={`
90+
${theme === 'light' ? 'text-[#333]' : 'text-[#f5f5f5]'}
91+
`}
92+
>
93+
Settings
94+
</Text>
95+
</TouchableOpacity>
96+
</View>
97+
</Popover>
98+
</View>
99+
);
100+
};
101+
102+
export default ProfileDropdown;

Diff for: components/settingPreference/SettingPreference.tsx

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React, { useState } from 'react';
2+
import { View, Switch, Text, useColorScheme, TouchableOpacity, StyleSheet } from 'react-native';
3+
import { router } from 'expo-router';
4+
import LanguagePicker from '../LanguagePicker';
5+
import { Dropdown } from 'react-native-element-dropdown';
6+
7+
const Settings = () => {
8+
const [emailNotifications, setEmailNotifications] = useState(false);
9+
const [pushNotifications, setPushNotifications] = useState(false);
10+
const [selectedTheme, setSelectedTheme] = useState('system');
11+
12+
const colorScheme = selectedTheme === 'system' ? useColorScheme() : selectedTheme;
13+
const textStyle = colorScheme === 'dark' ? 'text-gray-100' : 'text-gray-800';
14+
const borderColor = colorScheme === 'dark' ? 'border-gray-700' : 'border-gray-300';
15+
const containerStyle = colorScheme === 'dark' ? 'bg-primary-dark' : 'bg-primary-light';
16+
17+
const themeData = [
18+
{ label: 'System', value: 'system' },
19+
{ label: 'Light', value: 'light' },
20+
{ label: 'Dark', value: 'dark' },
21+
];
22+
23+
return (
24+
<View className={`p-4 mb-8 ${containerStyle}`}>
25+
<Text className={`text-2xl font-extrabold ml-4 mb-4 ${textStyle}`}>Settings</Text>
26+
27+
{/* Profile Section */}
28+
<View className="mb-6 p-4 rounded-lg flex flex-row justify-center">
29+
<View className="flex-1 mr-4">
30+
<Text className={`text-xl font-bold ${textStyle}`}>Profile</Text>
31+
<Text className={`text-lg mt-2 ${textStyle}`}>Edit profile, export account, data…</Text>
32+
</View>
33+
<TouchableOpacity className="flex items-end">
34+
<Text
35+
className={`${textStyle} mt-2`}
36+
onPress={() => router.push('/dashboard/trainee/profile')}
37+
>
38+
Change
39+
</Text>
40+
</TouchableOpacity>
41+
</View>
42+
43+
{/* Theme Picker */}
44+
<View className={`mb-6 p-4 border-t ${borderColor} rounded-lg flex flex-row`}>
45+
<View className="flex-1 mr-4">
46+
<Text className={`text-xl font-bold ${textStyle}`}>Appearance</Text>
47+
<Text className={`text-lg mt-2 ${textStyle}`}>Theme preferences</Text>
48+
</View>
49+
<View className="flex-row items-center gap-5">
50+
<Dropdown
51+
labelField="label"
52+
valueField="value"
53+
data={themeData}
54+
value={selectedTheme}
55+
onChange={(item) => {}}
56+
placeholder="Select Theme"
57+
selectedTextStyle={{ color: colorScheme === 'dark' ? '#fff' : '#000' }}
58+
//@ts-ignore
59+
style={styles.picker(colorScheme)}
60+
/>
61+
</View>
62+
</View>
63+
64+
{/* Language Picker */}
65+
<View className={`mb-6 p-4 border-t ${borderColor} rounded-lg justify-between`}>
66+
<Text className={`text-xl font-bold ${textStyle}`}>Language</Text>
67+
<View className="flex-row justify-between w-full mr-4">
68+
<Text className={`${textStyle} mt-2 text-lg flex-1`}>Language Preference</Text>
69+
<View className={`border ${borderColor} flex-1 rounded-md `}>
70+
<LanguagePicker showFlag={false} />
71+
</View>
72+
</View>
73+
</View>
74+
75+
{/* Email Notifications */}
76+
<View className={`mb-6 p-4 border-t ${borderColor} rounded-lg flex-row justify-between`}>
77+
<View className="flex-1 mr-4">
78+
<Text className={`text-xl font-bold ${textStyle}`}>Email Notifications</Text>
79+
<Text className={`text-lg mt-2 ${textStyle}`}>
80+
Feedback emails, reminder emails, news emails
81+
</Text>
82+
</View>
83+
<Switch
84+
value={emailNotifications}
85+
onValueChange={() => setEmailNotifications((prev) => !prev)}
86+
thumbColor={emailNotifications ? '#6200ee' : '#f4f3f4'}
87+
trackColor={{ false: '#767577', true: '#81b0ff' }}
88+
/>
89+
</View>
90+
91+
{/* Push Notifications */}
92+
<View className={`mb-6 p-4 border-t ${borderColor} rounded-lg flex-row justify-between`}>
93+
<View className="flex-1 mr-4">
94+
<Text className={`text-xl font-bold ${textStyle}`}>Push Notifications</Text>
95+
<Text className={`text-lg mt-2 ${textStyle}`}>
96+
Grade updates, session reminders, performance comments
97+
</Text>
98+
</View>
99+
<Switch
100+
value={pushNotifications}
101+
onValueChange={() => setPushNotifications((prev) => !prev)}
102+
thumbColor={pushNotifications ? '#6200ee' : '#f4f3f4'}
103+
trackColor={{ false: '#767577', true: '#81b0ff' }}
104+
/>
105+
</View>
106+
107+
{/* Privacy and Security */}
108+
<View className={`mb-6 p-4 border-t ${borderColor} rounded-lg flex-row justify-center`}>
109+
<View className="flex-1 mr-4">
110+
<Text className={`text-xl font-bold ${textStyle}`}>Privacy and Security</Text>
111+
<Text className={`text-lg mt-2 ${textStyle}`}>Privacy and Security</Text>
112+
</View>
113+
<Text className={`mt-2 ${textStyle}`}>Change</Text>
114+
</View>
115+
116+
{/* Login Activity */}
117+
<View className={`p-4 mb-7 border-t ${borderColor} rounded-lg flex flex-row justify-center`}>
118+
<View className="flex-1 mr-4">
119+
<Text className={`text-xl font-bold ${textStyle}`}>Login Activity</Text>
120+
<Text className={`text-lg ${textStyle}`}>History of Your login session</Text>
121+
</View>
122+
<Text className={`mt-2 ${textStyle}`}>View</Text>
123+
</View>
124+
</View>
125+
);
126+
};
127+
128+
const styles = StyleSheet.create({
129+
//@ts-ignore
130+
picker: (colorScheme) => ({
131+
height: 50,
132+
width: 178,
133+
backgroundColor: colorScheme === 'dark' ? '#070E1C' : '#fff',
134+
color: colorScheme === 'dark' ? '#fff' : '#000',
135+
borderColor: colorScheme === 'dark' ? '#374151' : '#ccc',
136+
borderWidth: 1,
137+
borderRadius: 8,
138+
paddingHorizontal: 10,
139+
}),
140+
});
141+
142+
export default Settings;

0 commit comments

Comments
 (0)