Merge branch 'dev'

This commit is contained in:
Milan Paunovic
2024-11-21 19:22:28 +01:00
25 changed files with 886 additions and 244 deletions

View File

@ -2,7 +2,13 @@ import React, { useEffect } from "react";
import { Drawer } from "expo-router/drawer"; import { Drawer } from "expo-router/drawer";
import { useSignOut } from "@/hooks/firebase/useSignOut"; import { useSignOut } from "@/hooks/firebase/useSignOut";
import { DrawerContentScrollView } from "@react-navigation/drawer"; 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 { ImageBackground, StyleSheet } from "react-native";
import DrawerButton from "@/components/shared/DrawerButton"; import DrawerButton from "@/components/shared/DrawerButton";
import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon"; import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon";
@ -11,7 +17,6 @@ import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon";
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon"; import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon"; import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon";
import ViewSwitch from "@/components/pages/(tablet_pages)/ViewSwitch"; import ViewSwitch from "@/components/pages/(tablet_pages)/ViewSwitch";
import { MaterialIcons } from "@expo/vector-icons";
import { useSetAtom } from "jotai"; import { useSetAtom } from "jotai";
import { import {
isFamilyViewAtom, isFamilyViewAtom,
@ -23,6 +28,7 @@ import Ionicons from "@expo/vector-icons/Ionicons";
import * as Device from "expo-device"; import * as Device from "expo-device";
import { DeviceType } from "expo-device"; import { DeviceType } from "expo-device";
import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon"; import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon";
import DrawerIcon from "@/assets/svgs/DrawerIcon";
export default function TabLayout() { export default function TabLayout() {
const { mutateAsync: signOut } = useSignOut(); const { mutateAsync: signOut } = useSignOut();
@ -37,6 +43,20 @@ export default function TabLayout() {
detachInactiveScreens detachInactiveScreens
screenOptions={({ navigation, route }) => ({ screenOptions={({ navigation, route }) => ({
headerShown: true, headerShown: true,
headerTitleAlign:
Device.deviceType === DeviceType.TABLET ? "left" : "center",
headerTitleStyle: {
fontFamily: "Manrope_600SemiBold",
fontSize: Device.deviceType === DeviceType.TABLET ? 22 : 17,
},
headerLeft: (props) => (
<TouchableOpacity
onPress={navigation.toggleDrawer}
style={{ marginLeft: 16 }}
>
<DrawerIcon />
</TouchableOpacity>
),
headerRight: () => { headerRight: () => {
// Only show ViewSwitch on calendar and todos pages // Only show ViewSwitch on calendar and todos pages
const showViewSwitch = ["calendar", "todos", "index"].includes( const showViewSwitch = ["calendar", "todos", "index"].includes(
@ -48,9 +68,8 @@ export default function TabLayout() {
</View> </View>
) : null; ) : null;
}, },
headerStyle: { height: Device.deviceType === DeviceType.TABLET ? 100 : undefined},
drawerStyle: { drawerStyle: {
width: Device.deviceType === DeviceType.TABLET ? "50%" : "90%", width: Device.deviceType === DeviceType.TABLET ? "30%" : "90%",
backgroundColor: "#f9f8f7", backgroundColor: "#f9f8f7",
height: "100%", height: "100%",
}, },
@ -117,7 +136,7 @@ export default function TabLayout() {
icon={<FeedbackNavIcon />} icon={<FeedbackNavIcon />}
/> />
</View> </View>
<View style={{ flex: 1 }}> <View style={{ flex: 1, paddingRight: 0 }}>
{/*<DrawerButton {/*<DrawerButton
color="#fd1775" color="#fd1775"
title={"My Reminders"} title={"My Reminders"}
@ -233,14 +252,20 @@ export default function TabLayout() {
name="index" name="index"
options={{ options={{
drawerLabel: "Calendar", drawerLabel: "Calendar",
title: "Calendar", title:
Device.deviceType === DeviceType.TABLET
? "Family Calendar"
: "Calendar",
}} }}
/> />
<Drawer.Screen <Drawer.Screen
name="calendar" name="calendar"
options={{ options={{
drawerLabel: "Calendar", drawerLabel: "Calendar",
title: "Calendar", title:
Device.deviceType === DeviceType.TABLET
? "Family Calendar"
: "Calendar",
drawerItemStyle: { display: "none" }, drawerItemStyle: { display: "none" },
}} }}
/> />
@ -276,7 +301,10 @@ export default function TabLayout() {
name="todos" name="todos"
options={{ options={{
drawerLabel: "To-Do", drawerLabel: "To-Do",
title: "To-Dos", title:
Device.deviceType === DeviceType.TABLET
? "Family To Do's"
: "To Do's",
}} }}
/> />
<Drawer.Screen <Drawer.Screen

View File

@ -1,16 +1,28 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { ScrollView, RefreshControl } from "react-native"; import { ScrollView, RefreshControl, View } from "react-native";
import { useSetAtom } from "jotai"; import { useAtom } from "jotai";
import CalendarPage from "@/components/pages/calendar/CalendarPage"; import CalendarPage from "@/components/pages/calendar/CalendarPage";
import { refreshTriggerAtom } from "@/components/pages/calendar/atoms"; import { refreshTriggerAtom } from "@/components/pages/calendar/atoms";
import { colorMap } from "@/constants/colorMap"; import { colorMap } from "@/constants/colorMap";
import TabletCalendarPage from "@/components/pages/(tablet_pages)/calendar/TabletCalendarPage"; import TabletCalendarPage from "@/components/pages/(tablet_pages)/calendar/TabletCalendarPage";
import { DeviceType } from "expo-device"; import { DeviceType } from "expo-device";
import * as Device from "expo-device"; import * as Device from "expo-device";
import { useCalSync } from "@/hooks/useCalSync";
import Toast from "react-native-toast-message";
export default function Screen() { export default function Screen() {
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const setRefreshTrigger = useSetAtom(refreshTriggerAtom); const [shouldRefresh, setShouldRefresh] = useAtom(refreshTriggerAtom);
const isTablet: boolean = Device.deviceType === DeviceType.TABLET;
const {
isConnectedToGoogle,
isConnectedToMicrosoft,
isConnectedToApple,
resyncAllCalendars,
isSyncing,
} = useCalSync();
const onRefresh = React.useCallback(async () => { const onRefresh = React.useCallback(async () => {
setRefreshing(true); setRefreshing(true);
@ -18,24 +30,49 @@ export default function Screen() {
const minimumDelay = new Promise((resolve) => setTimeout(resolve, 1000)); const minimumDelay = new Promise((resolve) => setTimeout(resolve, 1000));
try { try {
setRefreshTrigger((prev) => !prev); if (isConnectedToGoogle || isConnectedToMicrosoft || isConnectedToApple) {
await Promise.all([resyncAllCalendars(), minimumDelay]);
await Promise.all([minimumDelay]); } else {
await minimumDelay;
}
} catch (error) { } catch (error) {
console.error("Refresh failed:", error); console.error("Refresh failed:", error);
} finally { } finally {
setRefreshing(false); setRefreshing(false);
setShouldRefresh((prev) => !prev);
} }
}, [setRefreshTrigger]); }, [
resyncAllCalendars,
isConnectedToGoogle,
isConnectedToMicrosoft,
isConnectedToApple,
]);
return ( return (
<> <View style={{ flex: 1 }}>
<View style={{ flex: 1, zIndex: 0 }}>
{Device.deviceType === DeviceType.TABLET ? ( {Device.deviceType === DeviceType.TABLET ? (
<TabletCalendarPage /> <TabletCalendarPage />
) : ( ) : (
<CalendarPage />
)}
</View>
<ScrollView <ScrollView
style={{ flex: 1 }} style={{
contentContainerStyle={{ flex: 1 }} position: "absolute",
top: 0,
left: isTablet ? "15%" : "0",
height: isTablet ? "9%" : "4%",
width: isTablet ? "62%" : "100%",
zIndex: 50,
backgroundColor: "transparent",
}}
contentContainerStyle={{
flex: 1,
justifyContent: "center",
paddingRight: 200,
}}
refreshControl={ refreshControl={
<RefreshControl <RefreshControl
colors={[ colors={[
@ -47,16 +84,22 @@ export default function Screen() {
]} ]}
tintColor={colorMap.pink} tintColor={colorMap.pink}
progressBackgroundColor={"white"} progressBackgroundColor={"white"}
refreshing={refreshing} refreshing={refreshing || isSyncing}
onRefresh={onRefresh} onRefresh={onRefresh}
style={{
position: "absolute",
left: "50%", // Position at screen center
transform: [
// Offset by half its own width
{ translateX: -20 }, // Assuming the refresh control is ~40px wide
],
}}
/> />
} }
bounces={true} bounces={true}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
> pointerEvents={refreshing || isSyncing ? "auto" : "none"}
<CalendarPage /> />
</ScrollView> </View>
)}
</>
); );
} }

View File

@ -0,0 +1,19 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
const DrawerIcon = (props: SvgProps) => (
<Svg
width={27}
height={18}
fill="none"
{...props}
>
<Path
stroke="#83807F"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2.7}
d="M2 1.995h22.667M2 9.14h14.167M2 16.285h7.083"
/>
</Svg>
)
export default DrawerIcon

View File

@ -1,92 +1,88 @@
import { Text, TouchableOpacity, View } from "react-native-ui-lib"; import { SegmentedControl, View } from "react-native-ui-lib";
import React, { useEffect, useState } from "react"; import React, { memo, useCallback, useEffect, useRef, useState } from "react";
import { StyleSheet } from "react-native"; import { StyleSheet } from "react-native";
import { NavigationProp } from "@react-navigation/native"; import { NavigationProp, useNavigationState } from "@react-navigation/native";
import view from "react-native-ui-lib/src/components/view";
interface ViewSwitchProps { const ViewSwitch = memo(function ViewSwitch({
navigation: NavigationProp<any>; // Adjust according to your navigation structure navigation,
} }: {
navigation: any;
const ViewSwitch: React.FC<ViewSwitchProps> = ({ navigation }) => { }) {
const [pageIndex, setPageIndex] = useState<number>(navigation.getState().index); 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(() => { useEffect(() => {
setPageIndex(navigation.getState().index); const newIndex = navigationState.index === 6 ? 1 : 0;
}, [navigation.getState().index]) if (selectedIndex !== newIndex) {
setSelectedIndex(newIndex);
}
isNavigating.current = false;
}, [navigationState.index]);
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 ( return (
<View <View style={styles.container}>
row <SegmentedControl
spread segments={[
style={{ { label: "Calendar", segmentLabelStyle: styles.labelStyle },
{ label: "To Do's", segmentLabelStyle: styles.labelStyle },
]}
containerStyle={styles.segmentContainer}
style={styles.segment}
backgroundColor="#ebebeb"
inactiveColor="black"
activeColor="white"
activeBackgroundColor="#ea156c"
outlineColor="white"
outlineWidth={3}
onChangeIndex={handleSegmentChange}
initialIndex={selectedIndex}
/>
</View>
);
});
export default ViewSwitch;
const styles = StyleSheet.create({
container: {
borderRadius: 30, borderRadius: 30,
backgroundColor: "#ebebeb", backgroundColor: "#ebebeb",
alignItems: "center",
justifyContent: "center",
// iOS shadow
shadowColor: "#000", shadowColor: "#000",
shadowOffset: { width: 0, height: 0 }, shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0, shadowOpacity: 0,
shadowRadius: 0, shadowRadius: 0,
// Android shadow (elevation)
elevation: 0, elevation: 0,
}}
centerV
>
<TouchableOpacity
onPress={() => {
navigation.navigate("calendar");
}}
>
<View
centerV
centerH
height={54}
paddingH-15
style={ pageIndex == 1 || pageIndex == 0 ? styles.switchBtnActive : styles.switchBtn}
>
<Text color={pageIndex == 1 || pageIndex == 0 ? "white" : "black"} style={styles.switchTxt}>
Calendar
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
navigation.navigate("todos");
}}
>
<View
centerV
centerH
height={54}
paddingH-15
style={pageIndex == 6 ? styles.switchBtnActive : styles.switchBtn}
>
<Text color={pageIndex == 6 ? "white" : "black"} style={styles.switchTxt}>
Chores
</Text>
</View>
</TouchableOpacity>
</View>
);
};
export default ViewSwitch;
const styles = StyleSheet.create({
switchBtnActive: {
backgroundColor: "#ea156c",
borderRadius: 50,
width: 110,
}, },
switchBtn: { segmentContainer: {
backgroundColor: "#ebebeb", height: 44,
borderRadius: 50, width: 220,
width: 110,
}, },
switchTxt: { segment: {
borderRadius: 50,
borderWidth: 0,
height: 44,
},
labelStyle: {
fontSize: 16, fontSize: 16,
fontFamily: "Manrope_600SemiBold", fontFamily: "Manrope_600SemiBold",
}, },

View File

@ -21,7 +21,9 @@ const TabletCalendarPage = () => {
return ( return (
<TabletContainer> <TabletContainer>
<View flexG paddingB-25>
<InnerCalendar /> <InnerCalendar />
</View>
</TabletContainer> </TabletContainer>
); );
}; };

View File

@ -38,7 +38,14 @@ const TabletChoresPage = () => {
{user.pfp ? ( {user.pfp ? (
<ImageBackground <ImageBackground
source={{ uri: user.pfp }} source={{ uri: user.pfp }}
style={styles.pfp} style={[
styles.pfp,
(user.eventColor && {
borderWidth: 2,
borderColor: user.eventColor,
}) ||
undefined,
]}
borderRadius={13.33} borderRadius={13.33}
/> />
) : ( ) : (

View File

@ -1,6 +1,6 @@
import { View, Text, ViewProps } from "react-native-ui-lib"; import { View, Text, ViewProps } from "react-native-ui-lib";
import React, { ReactNode } from "react"; import React, { ReactNode, useEffect, useState } from "react";
import { Dimensions, StyleSheet } from "react-native"; import { Dimensions, StyleSheet, useWindowDimensions } from "react-native";
import UsersList from "./UsersList"; import UsersList from "./UsersList";
import { ScrollView } from "react-native-gesture-handler"; import { ScrollView } from "react-native-gesture-handler";
@ -8,12 +8,52 @@ interface TabletContainerProps extends ViewProps {
children: ReactNode; children: ReactNode;
} }
const { width, height } = Dimensions.get("window");
const TabletContainer: React.FC<TabletContainerProps> = ({ const TabletContainer: React.FC<TabletContainerProps> = ({
children, children,
...props ...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 ( return (
<View style={styles.container}> <View style={styles.container}>
<View row> <View row>
@ -28,27 +68,4 @@ const TabletContainer: React.FC<TabletContainerProps> = ({
); );
}; };
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;

View File

@ -20,7 +20,14 @@ const UsersList = () => {
<ImageBackground <ImageBackground
key={index} key={index}
source={{ uri: member.pfp }} source={{ uri: member.pfp }}
style={styles.pfp} style={[
styles.pfp,
(member.eventColor && {
borderWidth: 2,
borderColor: member.eventColor,
}) ||
undefined,
]}
borderRadius={100} borderRadius={100}
/> />
) : ( ) : (

View File

@ -1,5 +1,4 @@
import React, {useState} from "react"; import React, {useState} from "react";
import {MaterialIcons,} from "@expo/vector-icons";
import {Button, Card, Dialog, PanningProvider, Text, View,} from "react-native-ui-lib"; import {Button, Card, Dialog, PanningProvider, Text, View,} from "react-native-ui-lib";
import {StyleSheet, TouchableOpacity} from "react-native"; import {StyleSheet, TouchableOpacity} from "react-native";
import AddChoreDialog from "../todos/AddChoreDialog"; import AddChoreDialog from "../todos/AddChoreDialog";
@ -11,7 +10,6 @@ import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import {useSetAtom} from "jotai"; import {useSetAtom} from "jotai";
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms"; import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
import PlusIcon from "@/assets/svgs/PlusIcon"; import PlusIcon from "@/assets/svgs/PlusIcon";
import {useNavigation} from "expo-router";
export const AddEventDialog = () => { export const AddEventDialog = () => {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
@ -33,7 +31,6 @@ export const AddEventDialog = () => {
}, 100); }, 100);
}; };
const navigation = useNavigation()
return ( return (
<ToDosContextProvider> <ToDosContextProvider>
<> <>
@ -118,7 +115,7 @@ export const AddEventDialog = () => {
}} }}
label="Add To Do" label="Add To Do"
labelStyle={styles.btnLabel} labelStyle={styles.btnLabel}
onPress={() => navigation.navigate("todos")} onPress={() => setChoreDialogVisible(true)}
iconSource={() => ( iconSource={() => (
<NavToDosIcon <NavToDosIcon
color="white" color="white"

View File

@ -2,38 +2,19 @@ import React from "react";
import { View } from "react-native-ui-lib"; import { View } from "react-native-ui-lib";
import HeaderTemplate from "@/components/shared/HeaderTemplate"; import HeaderTemplate from "@/components/shared/HeaderTemplate";
import { InnerCalendar } from "@/components/pages/calendar/InnerCalendar"; import { InnerCalendar } from "@/components/pages/calendar/InnerCalendar";
import { useSetAtom } from "jotai";
import { refreshEnabledAtom } from "./atoms";
export default function CalendarPage() { export default function CalendarPage() {
const setRefreshEnabled = useSetAtom(refreshEnabledAtom);
const disableRefreshControl = () => setRefreshEnabled(false);
const enableRefreshControl = () => setRefreshEnabled(true);
return ( return (
<View <View
style={{ flex: 1, height: "100%", padding: 10 }} style={{ flex: 1, height: "100%", padding: 10 }}
paddingH-22 paddingH-22
paddingT-0 paddingT-22
> >
<View {/*<HeaderTemplate
onStartShouldSetResponder={() => {
enableRefreshControl();
console.log("yeah");
return true;
}}
onResponderRelease={() => {
disableRefreshControl();
console.log("sure");
console.log(refreshEnabledAtom)
}}
>
<HeaderTemplate
message={"Let's get your week started !"} message={"Let's get your week started !"}
isWelcome isWelcome
isCalendar={true} isCalendar={true}
/> />*/}
</View>
<InnerCalendar /> <InnerCalendar />
</View> </View>
); );

View File

@ -324,6 +324,7 @@ export const EventCalendar: React.FC<EventCalendarProps> = React.memo(
ampm ampm
// renderCustomDateForMonth={renderCustomDateForMonth} // renderCustomDateForMonth={renderCustomDateForMonth}
/> />
<View style={{backgroundColor: 'white', height: 50, width: '100%'}} />
</> </>
); );
@ -337,6 +338,7 @@ const styles = StyleSheet.create({
}, },
calHeader: { calHeader: {
borderWidth: 0, borderWidth: 0,
paddingBottom: 60,
}, },
dayModeHeader: { dayModeHeader: {
alignSelf: "flex-start", alignSelf: "flex-start",

View File

@ -25,7 +25,7 @@ export const InnerCalendar = () => {
return ( return (
<> <>
<View <View
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30, marginBottom: 60, overflow: "hidden"}} style={{flex: 1, backgroundColor: "#fff", borderRadius: 30, marginBottom: 10, overflow: "hidden"}}
ref={calendarContainerRef} ref={calendarContainerRef}
onLayout={onLayout} onLayout={onLayout}
> >

View File

@ -59,7 +59,6 @@ const EditGroceryItem = ({
const handleSubmit = () => { const handleSubmit = () => {
inputRef?.current?.blur(); inputRef?.current?.blur();
console.log("CALLLLLL");
if (editGrocery.setSubmit) { if (editGrocery.setSubmit) {
editGrocery.setSubmit(true); editGrocery.setSubmit(true);
} }

View File

@ -190,6 +190,8 @@ const GroceryItem = ({
aspectRatio: 1, aspectRatio: 1,
borderRadius: 22, borderRadius: 22,
overflow: "hidden", overflow: "hidden",
borderWidth: 2,
borderColor: profileData.eventColor
}} }}
/> />
) : ( ) : (

View File

@ -170,7 +170,14 @@ const MyGroup: React.FC<MyGroupProps> = ({
> >
{member.pfp ? ( {member.pfp ? (
<ImageBackground <ImageBackground
style={styles.pfp} style={[
styles.pfp,
(member.eventColor && {
borderWidth: 2,
borderColor: member.eventColor,
}) ||
undefined,
]}
borderRadius={10.56} borderRadius={10.56}
source={{ uri: member.pfp || undefined }} source={{ uri: member.pfp || undefined }}
/> />

View File

@ -173,7 +173,14 @@ const MyProfile = () => {
{pfpUri ? ( {pfpUri ? (
<Image <Image
key={pfpUri} key={pfpUri}
style={styles.pfp} style={[
styles.pfp,
(profileData?.eventColor && {
borderWidth: 2,
borderColor: profileData.eventColor,
}) ||
undefined,
]}
source={pfpUri ? { uri: pfpUri } : null} source={pfpUri ? { uri: pfpUri } : null}
/> />
) : ( ) : (

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

@ -106,6 +106,24 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
}, 500) }, 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 ( return (
<Dialog <Dialog
bottom={true} bottom={true}
@ -146,20 +164,18 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
onPress={() => { onPress={() => {
try { try {
if (addChoreDialogProps.selectedTodo) { if (addChoreDialogProps.selectedTodo) {
if (!todo?.title) {
Alert.alert('Alert', 'Title field cannot be empty'); if (validateTodo()) {
return;
}
updateToDo({ updateToDo({
...todo, ...todo,
points: points, points: points,
assignees: selectedAssignees assignees: selectedAssignees
}); });
} else { } else {
if (!todo?.title) {
Alert.alert('Alert', 'Title field cannot be empty');
return; return;
} }
} else {
if (validateTodo()) {
addToDo({ addToDo({
...todo, ...todo,
done: false, done: false,
@ -167,6 +183,9 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
assignees: selectedAssignees, assignees: selectedAssignees,
repeatDays: todo.repeatDays ?? [] repeatDays: todo.repeatDays ?? []
}); });
} else {
return;
}
} }
handleClose(); handleClose();
} catch (error) { } catch (error) {
@ -210,27 +229,20 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
)} )}
</View> </View>
<View row centerV> <View row centerV>
<TodoRepeatIcon /> <TodoRepeatIcon onPress={showRepeatPicker}/>
<Picker <Picker
ref={repeatPickerRef}
marginL-12 marginL-12
placeholder="Select Repeat Type" placeholder="Select Repeat Type"
value={todo?.repeatType} value={todo?.repeatType}
onChange={(value) => { onChange={(value) => {
if (value) { if (value) {
if (value.toString() == "None") {
setTodo((oldValue) => ({
...oldValue,
date: null,
repeatType: "None",
}));
} else {
setTodo((oldValue) => ({ setTodo((oldValue) => ({
...oldValue, ...oldValue,
date: new Date(), date: new Date(),
repeatType: value.toString(), repeatType: value.toString(),
})); }));
} }
}
}} }}
topBarProps={{title: "Repeat"}} topBarProps={{title: "Repeat"}}
style={{ style={{

View File

@ -179,6 +179,8 @@ const ToDoItem = (props: {
aspectRatio: 1, aspectRatio: 1,
borderRadius: 22, borderRadius: 22,
overflow: "hidden", overflow: "hidden",
borderWidth: 2,
borderColor: member.eventColor || 'transparent'
}} }}
/> />
) : ( ) : (
@ -187,6 +189,9 @@ const ToDoItem = (props: {
position: "relative", position: "relative",
width: 24.64, width: 24.64,
aspectRatio: 1, aspectRatio: 1,
borderWidth: 2,
borderRadius: 100,
borderColor: member.eventColor || 'transparent'
}} }}
> >
<View <View

View File

@ -8,9 +8,14 @@ import {IToDo} from "@/hooks/firebase/types/todoData";
import DropdownIcon from "@/assets/svgs/DropdownIcon"; import DropdownIcon from "@/assets/svgs/DropdownIcon";
import {Dropdown} from "react-native-element-dropdown"; import {Dropdown} from "react-native-element-dropdown";
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers"; import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import {ProfileType, useAuthContext} from "@/contexts/AuthContext"; import {useAuthContext} from "@/contexts/AuthContext";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
const FILTER_OPTIONS = {
ME: "Me",
EVERYONE: "Everyone"
};
const groupToDosByDate = (toDos: IToDo[]) => { const groupToDosByDate = (toDos: IToDo[]) => {
let sortedTodos = toDos.sort((a, b) => a.date - b.date); let sortedTodos = toDos.sort((a, b) => a.date - b.date);
return sortedTodos.reduce( return sortedTodos.reduce(
@ -75,13 +80,16 @@ const groupToDosByDate = (toDos: IToDo[]) => {
const resolveFilterOptions = (members, user) => { const resolveFilterOptions = (members, user) => {
return members?.map((member) => { let options = members?.map((member) => {
let label = member?.firstName; let label = member?.firstName;
if (member.uid === user?.uid) { 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 }) => { const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
@ -110,10 +118,15 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
useEffect(() => { useEffect(() => {
if (toDos && selectedFilter) { 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 || []); resolvedGroupedTodos = groupToDosByDate(filtered || []);
setGroupedToDos(filteredGroupedTodos || []); }
setGroupedToDos(resolvedGroupedTodos || []);
} }
}, [selectedFilter, JSON.stringify(toDos)]); }, [selectedFilter, JSON.stringify(toDos)]);

View File

@ -28,11 +28,11 @@ const AssigneesDisplay = ({selectedAttendees, setSelectedAttendees}: {
{member?.pfp ? ( {member?.pfp ? (
<Image <Image
source={{uri: member?.pfp}} source={{uri: member?.pfp}}
style={styles.image} style={[styles.image, {borderWidth: 2, borderColor: member.eventColor || 'transparent'}]}
children={<RemoveAssigneeBtn/>} children={<RemoveAssigneeBtn/>}
/> />
) : ( ) : (
<View style={styles.initialsCircle}> <View style={[styles.initialsCircle, {borderWidth: 2, borderColor: member.eventColor || 'transparent'}]}>
<Text style={styles.initialsText}> <Text style={styles.initialsText}>
{getInitials(member.firstName, member.lastName)} {getInitials(member.firstName, member.lastName)}
</Text> </Text>

View File

@ -17,9 +17,9 @@ const DrawerButton = (props: IDrawerButtonProps) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
iconContainer: { iconContainer: {
width: "70%", width: isTablet ? '50%' : "70%",
aspectRatio: 1, aspectRatio: 1,
borderRadius: 50, borderRadius: 100,
}, },
labelStyle: { fontSize: 15, fontFamily: "Poppins_400Regular" }, labelStyle: { fontSize: 15, fontFamily: "Poppins_400Regular" },
}); });
@ -42,7 +42,7 @@ const DrawerButton = (props: IDrawerButtonProps) => {
</View> </View>
)} )}
style={{ style={{
aspectRatio: 1, aspectRatio: isTablet ? 1.2 : 1,
borderRadius: 18.55, borderRadius: 18.55,
marginBottom: 12, marginBottom: 12,
flexDirection: "column", flexDirection: "column",

View File

@ -5,10 +5,8 @@ import { StyleSheet } from "react-native";
import { colorMap } from "@/constants/colorMap"; import { colorMap } from "@/constants/colorMap";
import { useAtom, useSetAtom } from "jotai"; import { useAtom, useSetAtom } from "jotai";
import { isFamilyViewAtom, settingsPageIndex } from "../pages/calendar/atoms"; import { isFamilyViewAtom, settingsPageIndex } from "../pages/calendar/atoms";
import { useGetChildrenByParentId } from "@/hooks/firebase/useGetChildrenByParentId";
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers"; import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
import { UserProfile } from "@/hooks/firebase/types/profileTypes"; import { UserProfile } from "@/hooks/firebase/types/profileTypes";
import { child } from "@react-native-firebase/storage";
import CachedImage from "expo-cached-image"; import CachedImage from "expo-cached-image";
import { router } from "expo-router"; import { router } from "expo-router";
@ -24,7 +22,7 @@ const HeaderTemplate = (props: {
}) => { }) => {
const { user, profileData } = useAuthContext(); const { user, profileData } = useAuthContext();
const { data: members } = useGetFamilyMembers(); const { data: members, refetch } = useGetFamilyMembers();
const [children, setChildren] = useState<UserProfile[]>([]); const [children, setChildren] = useState<UserProfile[]>([]);
const [isFamilyView] = useAtom(isFamilyViewAtom); const [isFamilyView] = useAtom(isFamilyViewAtom);
const setSettingsPageIndexAtom = useSetAtom(settingsPageIndex); const setSettingsPageIndexAtom = useSetAtom(settingsPageIndex);
@ -48,6 +46,7 @@ const HeaderTemplate = (props: {
overflow: "hidden", overflow: "hidden",
marginRight: 20, marginRight: 20,
backgroundColor: profileData?.eventColor ?? colorMap.pink, backgroundColor: profileData?.eventColor ?? colorMap.pink,
zIndex: 100,
}, },
pfpTxt: { pfpTxt: {
fontFamily: "Manrope_500Medium", fontFamily: "Manrope_500Medium",
@ -94,13 +93,23 @@ const HeaderTemplate = (props: {
<TouchableOpacity onPress={handlePfpPress}> <TouchableOpacity onPress={handlePfpPress}>
<CachedImage <CachedImage
source={{ uri: profileData.pfp }} source={{ uri: profileData.pfp }}
style={styles.pfp} style={[
styles.pfp,
(profileData.eventColor && {
borderWidth: 2,
borderColor: profileData.eventColor,
}) ||
undefined,
]}
cacheKey={profileData.pfp} cacheKey={profileData.pfp}
/> />
</TouchableOpacity> </TouchableOpacity>
{isFamilyView && props.isCalendar && ( {isFamilyView && props.isCalendar && children.length > 0 && (
<View style={styles.childrenPfpArr} row> <View style={styles.childrenPfpArr} row>
{children?.slice(0, 3).map((child, index) => { {children.slice(0, 3).map((child, index) => {
{
console.log("yeaaaah");
}
const bgColor: string = child.eventColor || colorMap.pink; const bgColor: string = child.eventColor || colorMap.pink;
return child.pfp ? ( return child.pfp ? (
<Image <Image
@ -108,9 +117,7 @@ const HeaderTemplate = (props: {
style={[styles.childrenPfp, { left: index * 19 }]} style={[styles.childrenPfp, { left: index * 19 }]}
/> />
) : ( ) : (
<TouchableOpacity <TouchableOpacity onPress={handlePfpPress}>
onPress={handlePfpPress}
>
<View <View
style={[ style={[
styles.childrenPfp, styles.childrenPfp,
@ -143,12 +150,60 @@ const HeaderTemplate = (props: {
)} )}
</View> </View>
) : ( ) : (
<View style={styles.pfp} center> <>
<TouchableOpacity onPress={handlePfpPress}>
<View style={[styles.pfp, { zIndex: 200 }]} center>
<Text style={styles.pfpTxt}> <Text style={styles.pfpTxt}>
{profileData?.firstName?.at(0)} {profileData?.firstName?.at(0)}
{profileData?.lastName?.at(0)} {profileData?.lastName?.at(0)}
</Text> </Text>
</View> </View>
</TouchableOpacity>
{isFamilyView && props.isCalendar && children.length > 0 && (
<View style={styles.childrenPfpArr} row>
{children.slice(0, 3).map((child, index) => {
{
console.log("yeaaaah");
}
const bgColor: string = child.eventColor || colorMap.pink;
return child.pfp ? (
<Image
source={{ uri: child.pfp }}
style={[styles.childrenPfp, { left: index * 19 }]}
/>
) : (
<TouchableOpacity onPress={handlePfpPress}>
<View
style={[
styles.childrenPfp,
{ left: index * 19, backgroundColor: bgColor },
]}
center
>
<Text style={{ color: "white" }}>
{child?.firstName?.at(0)}
{child?.firstName?.at(1)}
</Text>
</View>
</TouchableOpacity>
);
})}
{children?.length > 3 && (
<View style={[styles.childrenPfp, { left: 3 * 19 }]} center>
<Text
style={{
color: "white",
fontFamily: "Manrope_600SemiBold",
fontSize: 12,
}}
>
+{children.length - 3}
</Text>
</View>
)}
</View>
)}
</>
)} )}
<View gap-3> <View gap-3>
{props.isWelcome && ( {props.isWelcome && (

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"]})
}
});
}