diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx index acb68fd..394628a 100644 --- a/app/(auth)/_layout.tsx +++ b/app/(auth)/_layout.tsx @@ -2,7 +2,13 @@ import React, { useEffect } from "react"; import { Drawer } from "expo-router/drawer"; import { useSignOut } from "@/hooks/firebase/useSignOut"; import { DrawerContentScrollView } from "@react-navigation/drawer"; -import { Button, ButtonSize, Text, View } from "react-native-ui-lib"; +import { + Button, + ButtonSize, + Text, + TouchableOpacity, + View, +} from "react-native-ui-lib"; import { ImageBackground, StyleSheet } from "react-native"; import DrawerButton from "@/components/shared/DrawerButton"; import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon"; @@ -11,7 +17,6 @@ import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon"; import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon"; import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon"; import ViewSwitch from "@/components/pages/(tablet_pages)/ViewSwitch"; -import { MaterialIcons } from "@expo/vector-icons"; import { useSetAtom } from "jotai"; import { isFamilyViewAtom, @@ -23,6 +28,7 @@ import Ionicons from "@expo/vector-icons/Ionicons"; import * as Device from "expo-device"; import { DeviceType } from "expo-device"; import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon"; +import DrawerIcon from "@/assets/svgs/DrawerIcon"; export default function TabLayout() { const { mutateAsync: signOut } = useSignOut(); @@ -37,6 +43,20 @@ export default function TabLayout() { detachInactiveScreens screenOptions={({ navigation, route }) => ({ headerShown: true, + headerTitleAlign: + Device.deviceType === DeviceType.TABLET ? "left" : "center", + headerTitleStyle: { + fontFamily: "Manrope_600SemiBold", + fontSize: Device.deviceType === DeviceType.TABLET ? 22 : 17, + }, + headerLeft: (props) => ( + + + + ), headerRight: () => { // Only show ViewSwitch on calendar and todos pages const showViewSwitch = ["calendar", "todos", "index"].includes( @@ -48,9 +68,8 @@ export default function TabLayout() { ) : null; }, - headerStyle: { height: Device.deviceType === DeviceType.TABLET ? 100 : undefined}, drawerStyle: { - width: Device.deviceType === DeviceType.TABLET ? "50%" : "90%", + width: Device.deviceType === DeviceType.TABLET ? "30%" : "90%", backgroundColor: "#f9f8f7", height: "100%", }, @@ -117,7 +136,7 @@ export default function TabLayout() { icon={} /> - + {/* @@ -276,7 +301,10 @@ export default function TabLayout() { name="todos" options={{ drawerLabel: "To-Do", - title: "To-Dos", + title: + Device.deviceType === DeviceType.TABLET + ? "Family To Do's" + : "To Do's", }} /> { setRefreshing(true); @@ -18,45 +30,76 @@ export default function Screen() { const minimumDelay = new Promise((resolve) => setTimeout(resolve, 1000)); try { - setRefreshTrigger((prev) => !prev); - - await Promise.all([minimumDelay]); + if (isConnectedToGoogle || isConnectedToMicrosoft || isConnectedToApple) { + await Promise.all([resyncAllCalendars(), minimumDelay]); + } else { + await minimumDelay; + } } catch (error) { console.error("Refresh failed:", error); } finally { setRefreshing(false); + setShouldRefresh((prev) => !prev); } - }, [setRefreshTrigger]); - + }, [ + resyncAllCalendars, + isConnectedToGoogle, + isConnectedToMicrosoft, + isConnectedToApple, + ]); + return ( - <> - {Device.deviceType === DeviceType.TABLET ? ( - - ) : ( - - } - bounces={true} - showsVerticalScrollIndicator={false} - > - - - )} - + + + {Device.deviceType === DeviceType.TABLET ? ( + + ) : ( + + )} + + + + } + bounces={true} + showsVerticalScrollIndicator={false} + pointerEvents={refreshing || isSyncing ? "auto" : "none"} + /> + ); } diff --git a/assets/svgs/DrawerIcon.tsx b/assets/svgs/DrawerIcon.tsx new file mode 100644 index 0000000..c3424e9 --- /dev/null +++ b/assets/svgs/DrawerIcon.tsx @@ -0,0 +1,19 @@ +import * as React from "react" +import Svg, { SvgProps, Path } from "react-native-svg" +const DrawerIcon = (props: SvgProps) => ( + + + +) +export default DrawerIcon diff --git a/components/pages/(tablet_pages)/ViewSwitch.tsx b/components/pages/(tablet_pages)/ViewSwitch.tsx index 0f1455e..b450364 100644 --- a/components/pages/(tablet_pages)/ViewSwitch.tsx +++ b/components/pages/(tablet_pages)/ViewSwitch.tsx @@ -1,92 +1,88 @@ -import { Text, TouchableOpacity, View } from "react-native-ui-lib"; -import React, { useEffect, useState } from "react"; +import { SegmentedControl, View } from "react-native-ui-lib"; +import React, { memo, useCallback, useEffect, useRef, useState } from "react"; import { StyleSheet } from "react-native"; -import { NavigationProp } from "@react-navigation/native"; -import view from "react-native-ui-lib/src/components/view"; +import { NavigationProp, useNavigationState } from "@react-navigation/native"; -interface ViewSwitchProps { - navigation: NavigationProp; // Adjust according to your navigation structure -} - -const ViewSwitch: React.FC = ({ navigation }) => { - const [pageIndex, setPageIndex] = useState(navigation.getState().index); +const ViewSwitch = memo(function ViewSwitch({ + navigation, +}: { + navigation: any; +}) { + const isNavigating = useRef(false); + const navigationState = useNavigationState((state) => state); + const [selectedIndex, setSelectedIndex] = useState( + navigationState.index === 6 ? 1 : 0 + ); + // Sync the selected index with navigation state useEffect(() => { - setPageIndex(navigation.getState().index); - }, [navigation.getState().index]) - - return ( - - { - navigation.navigate("calendar"); - }} - > - - - Calendar - - - + const newIndex = navigationState.index === 6 ? 1 : 0; + if (selectedIndex !== newIndex) { + setSelectedIndex(newIndex); + } + isNavigating.current = false; + }, [navigationState.index]); - { - navigation.navigate("todos"); - }} - > - - - Chores - - - + const handleSegmentChange = useCallback( + (index: number) => { + if (isNavigating.current) return; + if (index === selectedIndex) return; + + isNavigating.current = true; + setSelectedIndex(index); + + // Delay navigation slightly to allow the segment control to update + requestAnimationFrame(() => { + navigation.navigate(index === 0 ? "calendar" : "todos"); + }); + console.log(selectedIndex) + }, + [navigation, selectedIndex] + ); + + return ( + + ); -}; - +}); export default ViewSwitch; const styles = StyleSheet.create({ - switchBtnActive: { - backgroundColor: "#ea156c", - borderRadius: 50, - width: 110, - }, - switchBtn: { + container: { + borderRadius: 30, backgroundColor: "#ebebeb", - borderRadius: 50, - width: 110, + shadowColor: "#000", + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0, + shadowRadius: 0, + elevation: 0, }, - switchTxt: { + segmentContainer: { + height: 44, + width: 220, + }, + segment: { + borderRadius: 50, + borderWidth: 0, + height: 44, + }, + labelStyle: { fontSize: 16, fontFamily: "Manrope_600SemiBold", }, diff --git a/components/pages/(tablet_pages)/calendar/TabletCalendarPage.tsx b/components/pages/(tablet_pages)/calendar/TabletCalendarPage.tsx index 8465b9b..d951372 100644 --- a/components/pages/(tablet_pages)/calendar/TabletCalendarPage.tsx +++ b/components/pages/(tablet_pages)/calendar/TabletCalendarPage.tsx @@ -21,7 +21,9 @@ const TabletCalendarPage = () => { return ( - + + + ); }; diff --git a/components/pages/(tablet_pages)/chores/TabletChoresPage.tsx b/components/pages/(tablet_pages)/chores/TabletChoresPage.tsx index f5f2559..56beca6 100644 --- a/components/pages/(tablet_pages)/chores/TabletChoresPage.tsx +++ b/components/pages/(tablet_pages)/chores/TabletChoresPage.tsx @@ -38,7 +38,14 @@ const TabletChoresPage = () => { {user.pfp ? ( ) : ( diff --git a/components/pages/(tablet_pages)/tablet_components/TabletContainer.tsx b/components/pages/(tablet_pages)/tablet_components/TabletContainer.tsx index 4471b2c..52a9d3e 100644 --- a/components/pages/(tablet_pages)/tablet_components/TabletContainer.tsx +++ b/components/pages/(tablet_pages)/tablet_components/TabletContainer.tsx @@ -1,6 +1,6 @@ import { View, Text, ViewProps } from "react-native-ui-lib"; -import React, { ReactNode } from "react"; -import { Dimensions, StyleSheet } from "react-native"; +import React, { ReactNode, useEffect, useState } from "react"; +import { Dimensions, StyleSheet, useWindowDimensions } from "react-native"; import UsersList from "./UsersList"; import { ScrollView } from "react-native-gesture-handler"; @@ -8,12 +8,52 @@ interface TabletContainerProps extends ViewProps { children: ReactNode; } -const { width, height } = Dimensions.get("window"); - const TabletContainer: React.FC = ({ children, ...props }) => { + const window = useWindowDimensions(); + const [containerWidth, setContainerWidth] = useState(Dimensions.get('window').width); + const [containerHeight, setContainerHeight] = useState(Dimensions.get('window').height); + + // Update dimensions on mount and when window size changes + useEffect(() => { + const updateDimensions = () => { + setContainerWidth(window.width); + setContainerHeight(window.height); + }; + + updateDimensions(); + + // Force a second update after a brief delay to handle any initial rendering issues + const timer = setTimeout(updateDimensions, 100); + + return () => clearTimeout(timer); + }, [window.width, window.height]); + + const styles = StyleSheet.create({ + container: { + backgroundColor: "white", + flex: 1, + flexDirection: 'row', + borderTopColor: "#a9a9a9", + width: containerWidth, + borderTopWidth: 1, + }, + calendarContainer: { + backgroundColor: "white", + height: containerHeight, + width: containerWidth * 0.89, + }, + profilesContainer: { + width: containerWidth * 0.11, + height: containerHeight, + borderLeftWidth: 1, + borderLeftColor: "#a9a9a9", + backgroundColor: "white", + }, + }); + return ( @@ -28,27 +68,4 @@ const TabletContainer: React.FC = ({ ); }; -const styles = StyleSheet.create({ - container: { - backgroundColor: "white", - flex: 1, - flexDirection: 'row', - borderTopColor: "#a9a9a9", - width: '80%', - borderTopWidth: 1, - }, - calendarContainer: { - backgroundColor: "white", - height: height, - width: width * 0.89, - }, - profilesContainer: { - width: width * 0.11, - height: height, - borderLeftWidth: 1, - borderLeftColor: "#a9a9a9", - backgroundColor: "white", - }, -}); - -export default TabletContainer; +export default TabletContainer; \ No newline at end of file diff --git a/components/pages/(tablet_pages)/tablet_components/UsersList.tsx b/components/pages/(tablet_pages)/tablet_components/UsersList.tsx index 82b488d..f62f0c7 100644 --- a/components/pages/(tablet_pages)/tablet_components/UsersList.tsx +++ b/components/pages/(tablet_pages)/tablet_components/UsersList.tsx @@ -20,7 +20,14 @@ const UsersList = () => { ) : ( diff --git a/components/pages/calendar/AddEventDialog.tsx b/components/pages/calendar/AddEventDialog.tsx index e6aa012..702b571 100644 --- a/components/pages/calendar/AddEventDialog.tsx +++ b/components/pages/calendar/AddEventDialog.tsx @@ -1,5 +1,4 @@ import React, {useState} from "react"; -import {MaterialIcons,} from "@expo/vector-icons"; import {Button, Card, Dialog, PanningProvider, Text, View,} from "react-native-ui-lib"; import {StyleSheet, TouchableOpacity} from "react-native"; import AddChoreDialog from "../todos/AddChoreDialog"; @@ -11,7 +10,6 @@ import NavToDosIcon from "@/assets/svgs/NavToDosIcon"; import {useSetAtom} from "jotai"; import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms"; import PlusIcon from "@/assets/svgs/PlusIcon"; -import {useNavigation} from "expo-router"; export const AddEventDialog = () => { const [show, setShow] = useState(false); @@ -33,7 +31,6 @@ export const AddEventDialog = () => { }, 100); }; - const navigation = useNavigation() return ( <> @@ -118,7 +115,7 @@ export const AddEventDialog = () => { }} label="Add To Do" labelStyle={styles.btnLabel} - onPress={() => navigation.navigate("todos")} + onPress={() => setChoreDialogVisible(true)} iconSource={() => ( setRefreshEnabled(false); - const enableRefreshControl = () => setRefreshEnabled(true); return ( - { - enableRefreshControl(); - console.log("yeah"); - return true; - }} - onResponderRelease={() => { - disableRefreshControl(); - console.log("sure"); - console.log(refreshEnabledAtom) - }} - > - - + {/**/} ); diff --git a/components/pages/calendar/EventCalendar.tsx b/components/pages/calendar/EventCalendar.tsx index af143af..a2bb06f 100644 --- a/components/pages/calendar/EventCalendar.tsx +++ b/components/pages/calendar/EventCalendar.tsx @@ -324,6 +324,7 @@ export const EventCalendar: React.FC = React.memo( ampm // renderCustomDateForMonth={renderCustomDateForMonth} /> + ); @@ -337,6 +338,7 @@ const styles = StyleSheet.create({ }, calHeader: { borderWidth: 0, + paddingBottom: 60, }, dayModeHeader: { alignSelf: "flex-start", diff --git a/components/pages/calendar/InnerCalendar.tsx b/components/pages/calendar/InnerCalendar.tsx index 5744bdc..269682c 100644 --- a/components/pages/calendar/InnerCalendar.tsx +++ b/components/pages/calendar/InnerCalendar.tsx @@ -25,7 +25,7 @@ export const InnerCalendar = () => { return ( <> diff --git a/components/pages/grocery/EditGroceryItem.tsx b/components/pages/grocery/EditGroceryItem.tsx index 812e98d..4f8e559 100644 --- a/components/pages/grocery/EditGroceryItem.tsx +++ b/components/pages/grocery/EditGroceryItem.tsx @@ -59,7 +59,6 @@ const EditGroceryItem = ({ const handleSubmit = () => { inputRef?.current?.blur(); - console.log("CALLLLLL"); if (editGrocery.setSubmit) { editGrocery.setSubmit(true); } diff --git a/components/pages/grocery/GroceryItem.tsx b/components/pages/grocery/GroceryItem.tsx index 1e8faea..246375a 100644 --- a/components/pages/grocery/GroceryItem.tsx +++ b/components/pages/grocery/GroceryItem.tsx @@ -190,6 +190,8 @@ const GroceryItem = ({ aspectRatio: 1, borderRadius: 22, overflow: "hidden", + borderWidth: 2, + borderColor: profileData.eventColor }} /> ) : ( diff --git a/components/pages/settings/user_settings_views/MyGroup.tsx b/components/pages/settings/user_settings_views/MyGroup.tsx index 613b334..77856e3 100644 --- a/components/pages/settings/user_settings_views/MyGroup.tsx +++ b/components/pages/settings/user_settings_views/MyGroup.tsx @@ -170,7 +170,14 @@ const MyGroup: React.FC = ({ > {member.pfp ? ( diff --git a/components/pages/settings/user_settings_views/MyProfile.tsx b/components/pages/settings/user_settings_views/MyProfile.tsx index e0ecc2e..cabb58c 100644 --- a/components/pages/settings/user_settings_views/MyProfile.tsx +++ b/components/pages/settings/user_settings_views/MyProfile.tsx @@ -173,7 +173,14 @@ const MyProfile = () => { {pfpUri ? ( ) : ( 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/components/pages/todos/AddChoreDialog.tsx b/components/pages/todos/AddChoreDialog.tsx index d095dc9..e5149f9 100644 --- a/components/pages/todos/AddChoreDialog.tsx +++ b/components/pages/todos/AddChoreDialog.tsx @@ -106,6 +106,24 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => { }, 500) }, []); + const repeatPickerRef = useRef(); + const showRepeatPicker = () => { + repeatPickerRef.current?.toggleExpandable(true); + } + + const validateTodo = () => { + if (!todo?.title) { + Alert.alert('Alert', 'Title field cannot be empty'); + return false; + } + if (!selectedAssignees || selectedAssignees?.length === 0) { + Alert.alert('Alert', 'Cannot have a todo without any assignees'); + return false; + } + + return true; + } + return ( { onPress={() => { try { if (addChoreDialogProps.selectedTodo) { - if (!todo?.title) { - Alert.alert('Alert', 'Title field cannot be empty'); + + if (validateTodo()) { + updateToDo({ + ...todo, + points: points, + assignees: selectedAssignees + }); + } else { return; } - updateToDo({ - ...todo, - points: points, - assignees: selectedAssignees - }); } else { - if (!todo?.title) { - Alert.alert('Alert', 'Title field cannot be empty'); + if (validateTodo()) { + addToDo({ + ...todo, + done: false, + points: points, + assignees: selectedAssignees, + repeatDays: todo.repeatDays ?? [] + }); + } else { return; } - addToDo({ - ...todo, - done: false, - points: points, - assignees: selectedAssignees, - repeatDays: todo.repeatDays ?? [] - }); } handleClose(); } catch (error) { @@ -210,26 +229,19 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => { )} - + { if (value) { - if (value.toString() == "None") { - setTodo((oldValue) => ({ - ...oldValue, - date: null, - repeatType: "None", - })); - } else { - setTodo((oldValue) => ({ - ...oldValue, - date: new Date(), - repeatType: value.toString(), - })); - } + setTodo((oldValue) => ({ + ...oldValue, + date: new Date(), + repeatType: value.toString(), + })); } }} topBarProps={{title: "Repeat"}} diff --git a/components/pages/todos/ToDoItem.tsx b/components/pages/todos/ToDoItem.tsx index 81fd8bf..4eff2c7 100644 --- a/components/pages/todos/ToDoItem.tsx +++ b/components/pages/todos/ToDoItem.tsx @@ -179,6 +179,8 @@ const ToDoItem = (props: { aspectRatio: 1, borderRadius: 22, overflow: "hidden", + borderWidth: 2, + borderColor: member.eventColor || 'transparent' }} /> ) : ( @@ -187,6 +189,9 @@ const ToDoItem = (props: { position: "relative", width: 24.64, aspectRatio: 1, + borderWidth: 2, + borderRadius: 100, + borderColor: member.eventColor || 'transparent' }} > { let sortedTodos = toDos.sort((a, b) => a.date - b.date); return sortedTodos.reduce( @@ -75,13 +80,16 @@ const groupToDosByDate = (toDos: IToDo[]) => { const resolveFilterOptions = (members, user) => { - return members?.map((member) => { + let options = members?.map((member) => { let label = member?.firstName; if (member.uid === user?.uid) { - label = "Me"; + label = FILTER_OPTIONS.ME; } - return (member.uid !== user?.uid || member.profileType !== ProfileType.PARENT) && {value: member?.uid, label: label}; + return {value: member?.uid, label: label}; }); + options.push({value: FILTER_OPTIONS.EVERYONE, label: FILTER_OPTIONS.EVERYONE}) + + return options; } const ToDosList = ({ isSettings }: { isSettings?: boolean }) => { @@ -110,10 +118,15 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => { useEffect(() => { if (toDos && selectedFilter) { - let filtered = toDos?.filter((todo) => todo.assignees?.includes(selectedFilter.value) || todo.creatorId === selectedFilter.value) || []; + let resolvedGroupedTodos; + if (selectedFilter?.value === FILTER_OPTIONS.EVERYONE) { + resolvedGroupedTodos = groupToDosByDate(toDos ?? []); + } else { + let filtered = toDos?.filter((todo) => todo.assignees?.includes(selectedFilter.value)) || []; - let filteredGroupedTodos = groupToDosByDate(filtered || []); - setGroupedToDos(filteredGroupedTodos || []); + resolvedGroupedTodos = groupToDosByDate(filtered || []); + } + setGroupedToDos(resolvedGroupedTodos || []); } }, [selectedFilter, JSON.stringify(toDos)]); diff --git a/components/shared/AssigneesDisplay.tsx b/components/shared/AssigneesDisplay.tsx index fdbcb44..b883ee2 100644 --- a/components/shared/AssigneesDisplay.tsx +++ b/components/shared/AssigneesDisplay.tsx @@ -28,11 +28,11 @@ const AssigneesDisplay = ({selectedAttendees, setSelectedAttendees}: { {member?.pfp ? ( } /> ) : ( - + {getInitials(member.firstName, member.lastName)} diff --git a/components/shared/DrawerButton.tsx b/components/shared/DrawerButton.tsx index 53b6c7f..6a15e9e 100644 --- a/components/shared/DrawerButton.tsx +++ b/components/shared/DrawerButton.tsx @@ -17,9 +17,9 @@ const DrawerButton = (props: IDrawerButtonProps) => { const styles = StyleSheet.create({ iconContainer: { - width: "70%", + width: isTablet ? '50%' : "70%", aspectRatio: 1, - borderRadius: 50, + borderRadius: 100, }, labelStyle: { fontSize: 15, fontFamily: "Poppins_400Regular" }, }); @@ -42,7 +42,7 @@ const DrawerButton = (props: IDrawerButtonProps) => { )} style={{ - aspectRatio: 1, + aspectRatio: isTablet ? 1.2 : 1, borderRadius: 18.55, marginBottom: 12, flexDirection: "column", diff --git a/components/shared/HeaderTemplate.tsx b/components/shared/HeaderTemplate.tsx index adbf51b..6005fd4 100644 --- a/components/shared/HeaderTemplate.tsx +++ b/components/shared/HeaderTemplate.tsx @@ -5,10 +5,8 @@ import { StyleSheet } from "react-native"; import { colorMap } from "@/constants/colorMap"; import { useAtom, useSetAtom } from "jotai"; import { isFamilyViewAtom, settingsPageIndex } from "../pages/calendar/atoms"; -import { useGetChildrenByParentId } from "@/hooks/firebase/useGetChildrenByParentId"; import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers"; import { UserProfile } from "@/hooks/firebase/types/profileTypes"; -import { child } from "@react-native-firebase/storage"; import CachedImage from "expo-cached-image"; import { router } from "expo-router"; @@ -24,7 +22,7 @@ const HeaderTemplate = (props: { }) => { const { user, profileData } = useAuthContext(); - const { data: members } = useGetFamilyMembers(); + const { data: members, refetch } = useGetFamilyMembers(); const [children, setChildren] = useState([]); const [isFamilyView] = useAtom(isFamilyViewAtom); const setSettingsPageIndexAtom = useSetAtom(settingsPageIndex); @@ -48,6 +46,7 @@ const HeaderTemplate = (props: { overflow: "hidden", marginRight: 20, backgroundColor: profileData?.eventColor ?? colorMap.pink, + zIndex: 100, }, pfpTxt: { fontFamily: "Manrope_500Medium", @@ -94,13 +93,23 @@ const HeaderTemplate = (props: { - {isFamilyView && props.isCalendar && ( + {isFamilyView && props.isCalendar && children.length > 0 && ( - {children?.slice(0, 3).map((child, index) => { + {children.slice(0, 3).map((child, index) => { + { + console.log("yeaaaah"); + } const bgColor: string = child.eventColor || colorMap.pink; return child.pfp ? ( ) : ( - + ) : ( - - - {profileData?.firstName?.at(0)} - {profileData?.lastName?.at(0)} - - + <> + + + + {profileData?.firstName?.at(0)} + {profileData?.lastName?.at(0)} + + + + {isFamilyView && props.isCalendar && children.length > 0 && ( + + {children.slice(0, 3).map((child, index) => { + { + console.log("yeaaaah"); + } + const bgColor: string = child.eventColor || colorMap.pink; + return child.pfp ? ( + + ) : ( + + + + {child?.firstName?.at(0)} + {child?.firstName?.at(1)} + + + + ); + })} + {children?.length > 3 && ( + + + +{children.length - 3} + + + )} + + )} + )} {props.isWelcome && ( 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