diff --git a/components/pages/settings/user_settings_views/UpdateUserDialog.tsx b/components/pages/settings/user_settings_views/UpdateUserDialog.tsx new file mode 100644 index 0000000..eaa8807 --- /dev/null +++ b/components/pages/settings/user_settings_views/UpdateUserDialog.tsx @@ -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( + profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone + ); + const [lastName, setLastName] = useState(profileData?.lastName || ""); + const [firstName, setFirstName] = useState( + profileData?.firstName || "" + ); + const [profileImage, setProfileImage] = useState< + string | ImagePicker.ImagePickerAsset | null + >(profileData?.pfp || null); + + const [selectedColor, setSelectedColor] = useState( + 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 ( + handleClose} + containerStyle={{ + borderRadius: 10, + backgroundColor: "white", + alignSelf: "stretch", + padding: 0, + paddingTop: 4, + margin: 0 + }} + > + + + + Update Profile + + + + {pfpUri ? ( + + ) : ( + + + {profileData?.firstName?.at(0)} + {profileData?.lastName?.at(0)} + + + )} + + + + + {profileData?.pfp ? "Change" : "Add"} Photo + + + + {profileData?.pfp && ( + + Remove Photo + + )} + + + + First name + + { + setFirstName(value); + }} + /> + + Last name + + { + setLastName(value); + }} + /> + + Email address + + + + + Settings + Time Zone + + setTimeZone(item as string)} + showSearch + floatingPlaceholder + style={styles.inViewPicker} + trailingAccessory={ + + + + } + > + {timeZoneItems} + + + + + + Color Preference + + + handleChangeColor(colorMap.pink)}> + + {selectedColor == colorMap.pink && ( + + )} + + + handleChangeColor(colorMap.orange)}> + + {selectedColor == colorMap.orange && ( + + )} + + + handleChangeColor(colorMap.green)}> + + {selectedColor == colorMap.green && ( + + )} + + + handleChangeColor(colorMap.teal)}> + + {selectedColor == colorMap.teal && ( + + )} + + + handleChangeColor(colorMap.purple)}> + + {selectedColor == colorMap.purple && ( + + )} + + + + + + + + ) +}; + +const timeZoneItems = Object.keys(tz.zones) + .sort() + .map((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; \ No newline at end of file diff --git a/components/pages/settings/user_settings_views/UserOptions.tsx b/components/pages/settings/user_settings_views/UserOptions.tsx index 936eedb..f703e0e 100644 --- a/components/pages/settings/user_settings_views/UserOptions.tsx +++ b/components/pages/settings/user_settings_views/UserOptions.tsx @@ -5,10 +5,12 @@ import { Dialog, Text, View, Button } from "react-native-ui-lib"; import MenuDotsIcon from "@/assets/svgs/MenuDotsIcon"; import { UserProfile } from "@/hooks/firebase/types/profileTypes"; import { useRemoveSubUser } from "@/hooks/firebase/useRemoveSubUser"; +import UpdateUserDialog from "@/components/pages/settings/user_settings_views/UpdateUserDialog"; const UserOptions = ({ user }: { user: UserProfile }) => { const [visible, setVisible] = useState(false); const { mutateAsync: removeSubUser } = useRemoveSubUser(); + const [updateUserDialogOpen, setUpdateUserDialogOpen] = useState(false); const handleDeleteUser = async () => { try { @@ -20,6 +22,14 @@ const UserOptions = ({ user }: { user: UserProfile }) => { } }; + const handleOpenUpdateDialog = () => { + setUpdateUserDialogOpen(true); + } + + const handleCloseUpdateDialog = () => { + setUpdateUserDialogOpen(false); + } + const menuOptions = [ { id: "edit", @@ -53,7 +63,7 @@ const UserOptions = ({ user }: { user: UserProfile }) => { onPressAction={({ nativeEvent }) => { switch (nativeEvent.event) { case "edit": - console.log("Edit User here"); + handleOpenUpdateDialog(); break; case "delete": setTimeout(() => setVisible(true), 300); @@ -104,6 +114,7 @@ const UserOptions = ({ user }: { user: UserProfile }) => { /> + {updateUserDialogOpen && } ); }; diff --git a/hooks/firebase/useUpdateSubUser.ts b/hooks/firebase/useUpdateSubUser.ts new file mode 100644 index 0000000..8a86aa9 --- /dev/null +++ b/hooks/firebase/useUpdateSubUser.ts @@ -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; }) => { + 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"]}) + } + }); +} \ No newline at end of file