- Added User update dialog, useUpdateSubUser.ts hook and implemented update of a selected user from the family group

This commit is contained in:
Dejan
2024-11-20 23:58:13 +01:00
parent b1a5d4c171
commit 4f80525f13
3 changed files with 444 additions and 1 deletions

View File

@ -0,0 +1,383 @@
import {Button, Colors, Dialog, Image, Picker, Text, TextField, View} from "react-native-ui-lib";
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
import {Dimensions, ScrollView, StyleSheet, TouchableOpacity} from "react-native";
import {colorMap} from "@/constants/colorMap";
import Ionicons from "@expo/vector-icons/Ionicons";
import {AntDesign} from "@expo/vector-icons";
import React, {useState} from "react";
import * as Localization from "expo-localization";
import * as ImagePicker from "expo-image-picker";
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
import {useChangeProfilePicture} from "@/hooks/firebase/useChangeProfilePicture";
import * as tz from "tzdata";
import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
import {useUpdateSubUser} from "@/hooks/firebase/useUpdateSubUser";
type Props = {
open: boolean,
handleClose: Function,
profileData: UserProfile
}
const UpdateUserDialog = ({open, handleClose, profileData} : Props) => {
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 [selectedColor, setSelectedColor] = useState<string>(
profileData?.eventColor ?? colorMap.pink
);
const { mutateAsync: updateUserData } = useUpdateUserData();
const { mutateAsync: updateSubUser} = useUpdateSubUser();
const { mutateAsync: changeProfilePicture } = useChangeProfilePicture();
const handleUpdateUserData = async () => {
await updateSubUser({ userProfile: {...profileData, firstName, lastName, timeZone, eventColor: selectedColor } });
handleClose();
};
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);
await 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;
const handleChangeColor = (color: string) => {
setSelectedColor(color);
};
const {width} = Dimensions.get("screen");
return (
<Dialog
visible={open}
height={"90%"}
width={width}
panDirection={PanningDirectionsEnum.DOWN}
center
onDismiss={() => handleClose}
containerStyle={{
borderRadius: 10,
backgroundColor: "white",
alignSelf: "stretch",
padding: 0,
paddingTop: 4,
margin: 0
}}
>
<ScrollView style={{ flex: 1, display: 'flex' }}>
<View style={styles.dialogContent}>
<View>
<Text style={styles.title}>Update Profile</Text>
</View>
<View row spread paddingH-15 centerV marginV-15>
<TouchableOpacity onPress={pickImage}>
{pfpUri ? (
<Image
key={pfpUri}
style={styles.pfp}
source={pfpUri ? { uri: pfpUri } : null}
/>
) : (
<View
center
style={{
aspectRatio: 1,
width: 65.54,
backgroundColor: profileData?.eventColor ?? colorMap.pink,
borderRadius: 20,
}}
>
<Text style={styles.pfpTxt}>
{profileData?.firstName?.at(0)}
{profileData?.lastName?.at(0)}
</Text>
</View>
)}
</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={profileData?.email?.toString()}
style={styles.txtBox}
/>
</View>
<View paddingH-15 marginT-15>
<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>
<View paddingH-15 marginT-15 style={{ display: 'flex', flexGrow: 1}}>
<Text style={styles.cardTitle} marginB-14>
Color Preference
</Text>
<View row spread>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)}>
<View style={styles.colorBox} backgroundColor={colorMap.pink}>
{selectedColor == colorMap.pink && (
<AntDesign name="check" size={30} color="white" />
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.orange)}>
<View style={styles.colorBox} backgroundColor={colorMap.orange}>
{selectedColor == colorMap.orange && (
<AntDesign name="check" size={30} color="white" />
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.green)}>
<View style={styles.colorBox} backgroundColor={colorMap.green}>
{selectedColor == colorMap.green && (
<AntDesign name="check" size={30} color="white" />
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.teal)}>
<View style={styles.colorBox} backgroundColor={colorMap.teal}>
{selectedColor == colorMap.teal && (
<AntDesign name="check" size={30} color="white" />
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.purple)}>
<View style={styles.colorBox} backgroundColor={colorMap.purple}>
{selectedColor == colorMap.purple && (
<AntDesign name="check" size={30} color="white" />
)}
</View>
</TouchableOpacity>
</View>
</View>
</View>
<View row center style={{
display: 'flex',
gap: 10,
alignItems: "flex-end",
marginTop: 50
}}>
<Button
style={{
backgroundColor: profileData.eventColor ?? colorMap.pink,
justifyContent: "center",
}}
label="Save"
onPress={handleUpdateUserData}
/>
<Button
style={{
backgroundColor: "#9c978f",
justifyContent: "center",
}}
label="Cancel"
onPress={handleClose}
/>
</View>
</ScrollView>
</Dialog>
)
};
const timeZoneItems = Object.keys(tz.zones)
.sort()
.map((zone) => (
<Picker.Item
key={zone}
label={zone.replace("/", " / ").replace("_", " ")}
value={zone}
/>
));
const styles = StyleSheet.create({
dialogContent: {
paddingHorizontal: 10,
paddingTop: 10,
paddingBottom: 10,
flexGrow: 1
},
cardTitle: {
fontFamily: "Manrope_500Medium",
fontSize: 15,
},
colorBox: {
aspectRatio: 1,
justifyContent: "center",
alignItems: "center",
width: 51,
borderRadius: 12,
},
card: {
marginVertical: 15,
backgroundColor: "white",
width: "100%",
borderRadius: 12,
paddingHorizontal: 20,
paddingVertical: 21,
},
pfpTxt: {
fontFamily: "Manrope_500Medium",
fontSize: 30,
color: "white",
},
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,
},
title: {
fontFamily: "Manrope_500Medium",
fontSize: 20
},
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 UpdateUserDialog;

View File

@ -5,10 +5,12 @@ import { Dialog, Text, View, Button } from "react-native-ui-lib";
import MenuDotsIcon from "@/assets/svgs/MenuDotsIcon"; import MenuDotsIcon from "@/assets/svgs/MenuDotsIcon";
import { UserProfile } from "@/hooks/firebase/types/profileTypes"; import { UserProfile } from "@/hooks/firebase/types/profileTypes";
import { useRemoveSubUser } from "@/hooks/firebase/useRemoveSubUser"; import { useRemoveSubUser } from "@/hooks/firebase/useRemoveSubUser";
import UpdateUserDialog from "@/components/pages/settings/user_settings_views/UpdateUserDialog";
const UserOptions = ({ user }: { user: UserProfile }) => { const UserOptions = ({ user }: { user: UserProfile }) => {
const [visible, setVisible] = useState<boolean>(false); const [visible, setVisible] = useState<boolean>(false);
const { mutateAsync: removeSubUser } = useRemoveSubUser(); const { mutateAsync: removeSubUser } = useRemoveSubUser();
const [updateUserDialogOpen, setUpdateUserDialogOpen] = useState(false);
const handleDeleteUser = async () => { const handleDeleteUser = async () => {
try { try {
@ -20,6 +22,14 @@ const UserOptions = ({ user }: { user: UserProfile }) => {
} }
}; };
const handleOpenUpdateDialog = () => {
setUpdateUserDialogOpen(true);
}
const handleCloseUpdateDialog = () => {
setUpdateUserDialogOpen(false);
}
const menuOptions = [ const menuOptions = [
{ {
id: "edit", id: "edit",
@ -53,7 +63,7 @@ const UserOptions = ({ user }: { user: UserProfile }) => {
onPressAction={({ nativeEvent }) => { onPressAction={({ nativeEvent }) => {
switch (nativeEvent.event) { switch (nativeEvent.event) {
case "edit": case "edit":
console.log("Edit User here"); handleOpenUpdateDialog();
break; break;
case "delete": case "delete":
setTimeout(() => setVisible(true), 300); setTimeout(() => setVisible(true), 300);
@ -104,6 +114,7 @@ const UserOptions = ({ user }: { user: UserProfile }) => {
/> />
</View> </View>
</Dialog> </Dialog>
{updateUserDialogOpen && <UpdateUserDialog open={updateUserDialogOpen} handleClose={handleCloseUpdateDialog} profileData={user} />}
</> </>
); );
}; };

View File

@ -0,0 +1,49 @@
import {useMutation, useQueryClient} from "react-query";
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
import firestore from "@react-native-firebase/firestore";
export const useUpdateSubUser = () => {
const queryClient = useQueryClient()
const {profileType} = useAuthContext()
return useMutation({
mutationKey: ["updateSubUser"],
mutationFn: async ({ userProfile }: { userProfile: Partial<UserProfile>; }) => {
if (profileType === ProfileType.PARENT) {
if (userProfile) {
console.log("Updating user data for UID:", userProfile.uid);
try {
const updatedUserData = Object.fromEntries(
Object.entries(userProfile).map(([key, value]) =>
[key, value === null ? firestore.FieldValue.delete() : value]
)
);
console.log("Updated user data with deletions:", updatedUserData);
await firestore()
.collection("Profiles")
.doc(userProfile.uid)
.update(updatedUserData);
console.log("User data updated successfully, fetching updated profile...");
console.log("Profile data updated in context.");
} catch (e) {
console.error("Error updating user data:", e);
}
} else {
console.warn("No user found: user profile is undefined.");
}
} else {
throw Error("Can't update sub-users as a non-parent.")
}
},
onSuccess: () => {
queryClient.invalidateQueries({queryKey: ["getChildrenByParentId"]})
queryClient.invalidateQueries({queryKey: ["familyMembers"]})
}
});
}