mirror of
https://github.com/urosran/cally.git
synced 2025-07-16 01:56:16 +00:00
250 lines
8.7 KiB
TypeScript
250 lines
8.7 KiB
TypeScript
import React, {useEffect, useRef, useState} from "react";
|
|
import {StyleSheet, TouchableOpacity} from "react-native";
|
|
import {ScrollView} from "react-native-gesture-handler";
|
|
import * as ImagePicker from "expo-image-picker";
|
|
import {Colors, Image, Picker, Text, TextField, View} from "react-native-ui-lib";
|
|
import Ionicons from "@expo/vector-icons/Ionicons";
|
|
import * as tz from "tzdata";
|
|
import * as Localization from "expo-localization";
|
|
import debounce from "debounce";
|
|
import {useAuthContext} from "@/contexts/AuthContext";
|
|
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
|
|
import {useChangeProfilePicture} from "@/hooks/firebase/useChangeProfilePicture";
|
|
|
|
const MyProfile = () => {
|
|
const {user, profileData} = useAuthContext();
|
|
const [timeZone, setTimeZone] = useState<string>(
|
|
profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone
|
|
);
|
|
const [lastName, setLastName] = useState<string>(profileData?.lastName || "");
|
|
const [firstName, setFirstName] = useState<string>(
|
|
profileData?.firstName || ""
|
|
);
|
|
const [profileImage, setProfileImage] = useState<string | ImagePicker.ImagePickerAsset | null>(profileData?.pfp || null);
|
|
|
|
const {mutateAsync: updateUserData} = useUpdateUserData();
|
|
const {mutateAsync: changeProfilePicture} = useChangeProfilePicture();
|
|
const isFirstRender = useRef(true);
|
|
|
|
const handleUpdateUserData = async () => {
|
|
await updateUserData({newUserData: {firstName, lastName, timeZone}});
|
|
};
|
|
|
|
const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500);
|
|
|
|
useEffect(() => {
|
|
if (isFirstRender.current) {
|
|
isFirstRender.current = false;
|
|
return;
|
|
}
|
|
debouncedUserDataUpdate();
|
|
}, [timeZone, lastName, firstName, profileImage]);
|
|
|
|
useEffect(() => {
|
|
if (profileData) {
|
|
setFirstName(profileData.firstName || "");
|
|
setLastName(profileData.lastName || "");
|
|
// setProfileImage(profileData.pfp || null);
|
|
setTimeZone(profileData.timeZone || Localization.getCalendars()[0].timeZone!);
|
|
}
|
|
}, [profileData]);
|
|
|
|
const pickImage = async () => {
|
|
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
|
if (!permissionResult.granted) {
|
|
alert("Permission to access camera roll is required!");
|
|
return;
|
|
}
|
|
|
|
const result = await ImagePicker.launchImageLibraryAsync({
|
|
mediaTypes: ImagePicker.MediaTypeOptions.Images,
|
|
allowsEditing: true,
|
|
aspect: [1, 1],
|
|
quality: 1,
|
|
});
|
|
|
|
if (!result.canceled) {
|
|
setProfileImage(result.assets[0].uri);
|
|
changeProfilePicture(result.assets[0])
|
|
}
|
|
};
|
|
|
|
const handleClearImage = async () => {
|
|
await updateUserData({newUserData: {pfp: null}});
|
|
setProfileImage(null)
|
|
}
|
|
|
|
const pfpUri = profileImage && typeof profileImage === 'object' && 'uri' in profileImage ? profileImage.uri : profileImage;
|
|
|
|
return (
|
|
<ScrollView style={{paddingBottom: 100, flex: 1}}>
|
|
<View style={styles.card}>
|
|
<Text style={styles.subTit}>Your Profile</Text>
|
|
<View row spread paddingH-15 centerV marginV-15>
|
|
<TouchableOpacity onPress={pickImage}>
|
|
<Image
|
|
key={pfpUri}
|
|
style={styles.pfp}
|
|
source={pfpUri ? {uri: pfpUri} : null}
|
|
/>
|
|
</TouchableOpacity>
|
|
|
|
<TouchableOpacity onPress={pickImage}>
|
|
<Text style={styles.photoSet} color="#50be0c" onPress={pickImage}>
|
|
{profileData?.pfp ? "Change" : "Add"} Photo
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
{profileData?.pfp && (
|
|
<TouchableOpacity onPress={handleClearImage}>
|
|
<Text style={styles.photoSet}>Remove Photo</Text>
|
|
</TouchableOpacity>
|
|
)}
|
|
</View>
|
|
<View paddingH-15>
|
|
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
|
First name
|
|
</Text>
|
|
<TextField
|
|
text70
|
|
placeholder="First name"
|
|
style={styles.txtBox}
|
|
value={firstName}
|
|
onChangeText={async (value) => {
|
|
setFirstName(value);
|
|
}}
|
|
/>
|
|
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
|
Last name
|
|
</Text>
|
|
<TextField
|
|
text70
|
|
placeholder="Last name"
|
|
style={styles.txtBox}
|
|
value={lastName}
|
|
onChangeText={async (value) => {
|
|
setLastName(value);
|
|
}}
|
|
/>
|
|
<Text text80 marginT-10 marginB-7 style={styles.label}>
|
|
Email address
|
|
</Text>
|
|
<TextField
|
|
editable={false}
|
|
text70
|
|
placeholder="Email address"
|
|
value={user?.email?.toString()}
|
|
style={styles.txtBox}
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
<View style={styles.card}>
|
|
<Text style={styles.subTit}>Settings</Text>
|
|
<Text style={styles.jakarta12}>Time Zone</Text>
|
|
<View style={styles.viewPicker}>
|
|
<Picker
|
|
value={timeZone}
|
|
onChange={(item) => setTimeZone(item as string)}
|
|
showSearch
|
|
floatingPlaceholder
|
|
style={styles.inViewPicker}
|
|
trailingAccessory={
|
|
<View
|
|
style={{
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
height: "100%",
|
|
marginTop: -38,
|
|
paddingRight: 15,
|
|
}}
|
|
>
|
|
<Ionicons
|
|
name={"chevron-down"}
|
|
style={{alignSelf: "center"}}
|
|
size={20}
|
|
color={"#000000"}
|
|
/>
|
|
</View>
|
|
}
|
|
>
|
|
{timeZoneItems}
|
|
</Picker>
|
|
</View>
|
|
</View>
|
|
</ScrollView>
|
|
);
|
|
};
|
|
|
|
const timeZoneItems = Object.keys(tz.zones)
|
|
.sort()
|
|
.map((zone) => (
|
|
<Picker.Item key={zone} label={zone.replace("/", " / ").replace("_", " ")} value={zone}/>
|
|
));
|
|
|
|
const styles = StyleSheet.create({
|
|
card: {
|
|
marginVertical: 15,
|
|
backgroundColor: "white",
|
|
width: "100%",
|
|
borderRadius: 12,
|
|
paddingHorizontal: 20,
|
|
paddingVertical: 21,
|
|
},
|
|
pfp: {
|
|
aspectRatio: 1,
|
|
width: 65.54,
|
|
backgroundColor: "gray",
|
|
borderRadius: 20,
|
|
},
|
|
txtBox: {
|
|
backgroundColor: "#fafafa",
|
|
borderRadius: 50,
|
|
borderWidth: 2,
|
|
borderColor: "#cecece",
|
|
padding: 15,
|
|
height: 45,
|
|
fontFamily: "PlusJakartaSans_500Medium",
|
|
fontSize: 13,
|
|
},
|
|
subTit: {
|
|
fontFamily: "Manrope_500Medium",
|
|
fontSize: 15,
|
|
},
|
|
label: {
|
|
fontFamily: "PlusJakartaSans_500Medium",
|
|
fontSize: 12,
|
|
color: "#a1a1a1",
|
|
},
|
|
photoSet: {
|
|
fontFamily: "PlusJakartaSans_500Medium",
|
|
fontSize: 13.07,
|
|
},
|
|
jakarta12: {
|
|
paddingVertical: 10,
|
|
fontFamily: "PlusJakartaSans_500Medium",
|
|
fontSize: 12,
|
|
color: "#a1a1a1",
|
|
},
|
|
viewPicker: {
|
|
borderRadius: 50,
|
|
backgroundColor: Colors.grey80,
|
|
marginBottom: 16,
|
|
borderColor: Colors.grey50,
|
|
borderWidth: 1,
|
|
marginTop: 0,
|
|
height: 40,
|
|
zIndex: 10,
|
|
},
|
|
inViewPicker: {
|
|
borderRadius: 50,
|
|
paddingVertical: 12,
|
|
paddingHorizontal: 16,
|
|
marginBottom: 16,
|
|
marginTop: -20,
|
|
height: 40,
|
|
zIndex: 10,
|
|
},
|
|
});
|
|
|
|
export default MyProfile; |