mirror of
https://github.com/urosran/cally.git
synced 2025-11-26 00:24:53 +00:00
Merge branch 'dev'
This commit is contained in:
@ -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
|
||||||
|
|||||||
@ -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,45 +30,76 @@ 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 }}>
|
||||||
{Device.deviceType === DeviceType.TABLET ? (
|
<View style={{ flex: 1, zIndex: 0 }}>
|
||||||
<TabletCalendarPage />
|
{Device.deviceType === DeviceType.TABLET ? (
|
||||||
) : (
|
<TabletCalendarPage />
|
||||||
<ScrollView
|
) : (
|
||||||
style={{ flex: 1 }}
|
<CalendarPage />
|
||||||
contentContainerStyle={{ flex: 1 }}
|
)}
|
||||||
refreshControl={
|
</View>
|
||||||
<RefreshControl
|
|
||||||
colors={[
|
<ScrollView
|
||||||
colorMap.pink,
|
style={{
|
||||||
colorMap.green,
|
position: "absolute",
|
||||||
colorMap.orange,
|
top: 0,
|
||||||
colorMap.purple,
|
left: isTablet ? "15%" : "0",
|
||||||
colorMap.teal,
|
height: isTablet ? "9%" : "4%",
|
||||||
]}
|
width: isTablet ? "62%" : "100%",
|
||||||
tintColor={colorMap.pink}
|
zIndex: 50,
|
||||||
progressBackgroundColor={"white"}
|
backgroundColor: "transparent",
|
||||||
refreshing={refreshing}
|
}}
|
||||||
onRefresh={onRefresh}
|
contentContainerStyle={{
|
||||||
/>
|
flex: 1,
|
||||||
}
|
justifyContent: "center",
|
||||||
bounces={true}
|
paddingRight: 200,
|
||||||
showsVerticalScrollIndicator={false}
|
}}
|
||||||
>
|
refreshControl={
|
||||||
<CalendarPage />
|
<RefreshControl
|
||||||
</ScrollView>
|
colors={[
|
||||||
)}
|
colorMap.pink,
|
||||||
</>
|
colorMap.green,
|
||||||
|
colorMap.orange,
|
||||||
|
colorMap.purple,
|
||||||
|
colorMap.teal,
|
||||||
|
]}
|
||||||
|
tintColor={colorMap.pink}
|
||||||
|
progressBackgroundColor={"white"}
|
||||||
|
refreshing={refreshing || isSyncing}
|
||||||
|
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}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
pointerEvents={refreshing || isSyncing ? "auto" : "none"}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
19
assets/svgs/DrawerIcon.tsx
Normal file
19
assets/svgs/DrawerIcon.tsx
Normal 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
|
||||||
@ -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);
|
||||||
return (
|
}
|
||||||
<View
|
isNavigating.current = false;
|
||||||
row
|
}, [navigationState.index]);
|
||||||
spread
|
|
||||||
style={{
|
|
||||||
borderRadius: 30,
|
|
||||||
backgroundColor: "#ebebeb",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
// iOS shadow
|
|
||||||
shadowColor: "#000",
|
|
||||||
shadowOffset: { width: 0, height: 0 },
|
|
||||||
shadowOpacity: 0,
|
|
||||||
shadowRadius: 0,
|
|
||||||
// Android shadow (elevation)
|
|
||||||
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
|
const handleSegmentChange = useCallback(
|
||||||
onPress={() => {
|
(index: number) => {
|
||||||
navigation.navigate("todos");
|
if (isNavigating.current) return;
|
||||||
}}
|
if (index === selectedIndex) return;
|
||||||
>
|
|
||||||
<View
|
isNavigating.current = true;
|
||||||
centerV
|
setSelectedIndex(index);
|
||||||
centerH
|
|
||||||
height={54}
|
// Delay navigation slightly to allow the segment control to update
|
||||||
paddingH-15
|
requestAnimationFrame(() => {
|
||||||
style={pageIndex == 6 ? styles.switchBtnActive : styles.switchBtn}
|
navigation.navigate(index === 0 ? "calendar" : "todos");
|
||||||
>
|
});
|
||||||
<Text color={pageIndex == 6 ? "white" : "black"} style={styles.switchTxt}>
|
console.log(selectedIndex)
|
||||||
Chores
|
},
|
||||||
</Text>
|
[navigation, selectedIndex]
|
||||||
</View>
|
);
|
||||||
</TouchableOpacity>
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<SegmentedControl
|
||||||
|
segments={[
|
||||||
|
{ 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>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default ViewSwitch;
|
export default ViewSwitch;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
switchBtnActive: {
|
container: {
|
||||||
backgroundColor: "#ea156c",
|
borderRadius: 30,
|
||||||
borderRadius: 50,
|
|
||||||
width: 110,
|
|
||||||
},
|
|
||||||
switchBtn: {
|
|
||||||
backgroundColor: "#ebebeb",
|
backgroundColor: "#ebebeb",
|
||||||
borderRadius: 50,
|
shadowColor: "#000",
|
||||||
width: 110,
|
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,
|
fontSize: 16,
|
||||||
fontFamily: "Manrope_600SemiBold",
|
fontFamily: "Manrope_600SemiBold",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -21,7 +21,9 @@ const TabletCalendarPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabletContainer>
|
<TabletContainer>
|
||||||
<InnerCalendar />
|
<View flexG paddingB-25>
|
||||||
|
<InnerCalendar />
|
||||||
|
</View>
|
||||||
</TabletContainer>
|
</TabletContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -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({
|
export default TabletContainer;
|
||||||
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;
|
|
||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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={() => {
|
message={"Let's get your week started !"}
|
||||||
enableRefreshControl();
|
isWelcome
|
||||||
console.log("yeah");
|
isCalendar={true}
|
||||||
return true;
|
/>*/}
|
||||||
}}
|
|
||||||
onResponderRelease={() => {
|
|
||||||
disableRefreshControl();
|
|
||||||
console.log("sure");
|
|
||||||
console.log(refreshEnabledAtom)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HeaderTemplate
|
|
||||||
message={"Let's get your week started !"}
|
|
||||||
isWelcome
|
|
||||||
isCalendar={true}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<InnerCalendar />
|
<InnerCalendar />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -190,6 +190,8 @@ const GroceryItem = ({
|
|||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
borderRadius: 22,
|
borderRadius: 22,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: profileData.eventColor
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -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 }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,27 +164,28 @@ 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()) {
|
||||||
|
updateToDo({
|
||||||
|
...todo,
|
||||||
|
points: points,
|
||||||
|
assignees: selectedAssignees
|
||||||
|
});
|
||||||
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateToDo({
|
|
||||||
...todo,
|
|
||||||
points: points,
|
|
||||||
assignees: selectedAssignees
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (!todo?.title) {
|
if (validateTodo()) {
|
||||||
Alert.alert('Alert', 'Title field cannot be empty');
|
addToDo({
|
||||||
|
...todo,
|
||||||
|
done: false,
|
||||||
|
points: points,
|
||||||
|
assignees: selectedAssignees,
|
||||||
|
repeatDays: todo.repeatDays ?? []
|
||||||
|
});
|
||||||
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addToDo({
|
|
||||||
...todo,
|
|
||||||
done: false,
|
|
||||||
points: points,
|
|
||||||
assignees: selectedAssignees,
|
|
||||||
repeatDays: todo.repeatDays ?? []
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
handleClose();
|
handleClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -210,26 +229,19 @@ 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) => ({
|
||||||
setTodo((oldValue) => ({
|
...oldValue,
|
||||||
...oldValue,
|
date: new Date(),
|
||||||
date: null,
|
repeatType: value.toString(),
|
||||||
repeatType: "None",
|
}));
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
setTodo((oldValue) => ({
|
|
||||||
...oldValue,
|
|
||||||
date: new Date(),
|
|
||||||
repeatType: value.toString(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
topBarProps={{title: "Repeat"}}
|
topBarProps={{title: "Repeat"}}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)]);
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>
|
<>
|
||||||
<Text style={styles.pfpTxt}>
|
<TouchableOpacity onPress={handlePfpPress}>
|
||||||
{profileData?.firstName?.at(0)}
|
<View style={[styles.pfp, { zIndex: 200 }]} center>
|
||||||
{profileData?.lastName?.at(0)}
|
<Text style={styles.pfpTxt}>
|
||||||
</Text>
|
{profileData?.firstName?.at(0)}
|
||||||
</View>
|
{profileData?.lastName?.at(0)}
|
||||||
|
</Text>
|
||||||
|
</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 && (
|
||||||
|
|||||||
49
hooks/firebase/useUpdateSubUser.ts
Normal file
49
hooks/firebase/useUpdateSubUser.ts
Normal 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"]})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user