Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Dejan
2024-11-18 00:17:58 +01:00
7 changed files with 204 additions and 37 deletions

View File

@ -3,6 +3,7 @@ import { ScrollView, RefreshControl } from "react-native";
import { useSetAtom } from "jotai"; import { useSetAtom } 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";
export default function Screen() { export default function Screen() {
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
@ -29,7 +30,19 @@ export default function Screen() {
style={{ flex: 1 }} style={{ flex: 1 }}
contentContainerStyle={{ flex: 1 }} contentContainerStyle={{ flex: 1 }}
refreshControl={ refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} /> <RefreshControl
colors={[
colorMap.pink,
colorMap.green,
colorMap.orange,
colorMap.purple,
colorMap.teal,
]}
tintColor={colorMap.pink}
progressBackgroundColor={"white"}
refreshing={refreshing}
onRefresh={onRefresh}
/>
} }
bounces={true} bounces={true}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}

View File

@ -1,16 +1,23 @@
import {SafeAreaView} from "react-native-safe-area-context"; import {SafeAreaView} from "react-native-safe-area-context";
import {Button, Text, View} from "react-native-ui-lib"; import {Button, Text, View} from "react-native-ui-lib";
import React from "react"; import React, { useEffect } from "react";
import {useCalSync} from "@/hooks/useCalSync"; import {useCalSync} from "@/hooks/useCalSync";
import GoogleIcon from "@/assets/svgs/GoogleIcon"; import GoogleIcon from "@/assets/svgs/GoogleIcon";
import AppleIcon from "@/assets/svgs/AppleIcon"; import AppleIcon from "@/assets/svgs/AppleIcon";
import OutlookIcon from "@/assets/svgs/OutlookIcon"; import OutlookIcon from "@/assets/svgs/OutlookIcon";
import {useAuthContext} from "@/contexts/AuthContext"; import {useAuthContext} from "@/contexts/AuthContext";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import { useGetHouseholdName } from "@/hooks/firebase/useGetHouseholdName";
export default function Screen() { export default function Screen() {
const {profileData, setRedirectOverride} = useAuthContext() const {profileData, setRedirectOverride} = useAuthContext()
const {handleStartGoogleSignIn, handleAppleSignIn, handleMicrosoftSignIn} = useCalSync() const {handleStartGoogleSignIn, handleAppleSignIn, handleMicrosoftSignIn} = useCalSync()
const {data: householdName, refetch} = useGetHouseholdName(profileData?.familyId);
useEffect(() => {
refetch();
}, [profileData?.familyId])
const hasSomeCalendarsSynced = const hasSomeCalendarsSynced =
!!profileData?.appleAccounts || !!profileData?.microsoftAccounts || !!profileData?.googleAccounts !!profileData?.appleAccounts || !!profileData?.microsoftAccounts || !!profileData?.googleAccounts
@ -19,6 +26,9 @@ export default function Screen() {
<SafeAreaView style={{flex: 1}}> <SafeAreaView style={{flex: 1}}>
<View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%", alignItems: "center"}}> <View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%", alignItems: "center"}}>
<View gap-13 width={"100%"} marginB-20> <View gap-13 width={"100%"} marginB-20>
{householdName && <Text style={{fontSize: 25, fontFamily: 'Manrope_600SemiBold'}}>
You Joined {householdName}
</Text>}
<Text style={{fontSize: 40, fontFamily: 'Manrope_600SemiBold'}}> <Text style={{fontSize: 40, fontFamily: 'Manrope_600SemiBold'}}>
Let's get started! Let's get started!
</Text> </Text>

View File

@ -2,19 +2,38 @@ 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-0
> >
<HeaderTemplate <View
message={"Let's get your week started !"} onStartShouldSetResponder={() => {
isWelcome enableRefreshControl();
isCalendar={true} console.log("yeah");
/> 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>
); );

View File

@ -12,3 +12,4 @@ export const settingsPageIndex = atom<number>(0);
export const userSettingsView = atom<boolean>(true); export const userSettingsView = atom<boolean>(true);
export const toDosPageIndex = atom<number>(0); export const toDosPageIndex = atom<number>(0);
export const refreshTriggerAtom = atom<boolean>(false); export const refreshTriggerAtom = atom<boolean>(false);
export const refreshEnabledAtom = atom<boolean>(true);

View File

@ -1,4 +1,4 @@
import React, {useCallback, useEffect, useRef, useState} from "react"; import React, { useCallback, useEffect, useRef, useState } from "react";
import { StyleSheet, TouchableOpacity } from "react-native"; import { StyleSheet, TouchableOpacity } from "react-native";
import { ScrollView } from "react-native-gesture-handler"; import { ScrollView } from "react-native-gesture-handler";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
@ -20,11 +20,17 @@ import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
import { useChangeProfilePicture } from "@/hooks/firebase/useChangeProfilePicture"; import { useChangeProfilePicture } from "@/hooks/firebase/useChangeProfilePicture";
import { colorMap } from "@/constants/colorMap"; import { colorMap } from "@/constants/colorMap";
import DeleteProfileDialogs from "../user_components/DeleteProfileDialogs"; import DeleteProfileDialogs from "../user_components/DeleteProfileDialogs";
import {AntDesign} from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import {useDeleteUser} from "@/hooks/firebase/useDeleteUser"; import { useDeleteUser } from "@/hooks/firebase/useDeleteUser";
import { useUpdateHouseholdName } from "@/hooks/firebase/useUpdateHouseholdName";
import { useGetHouseholdName } from "@/hooks/firebase/useGetHouseholdName";
const MyProfile = () => { const MyProfile = () => {
const { user, profileData } = useAuthContext(); const { user, profileData } = useAuthContext();
const { data: hhName, refetch: refetchHHName } = useGetHouseholdName(
profileData.familyId
);
const [householdName, setHouseholdName] = useState<string>("");
const [timeZone, setTimeZone] = useState<string>( const [timeZone, setTimeZone] = useState<string>(
profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone
); );
@ -37,10 +43,10 @@ const MyProfile = () => {
>(profileData?.pfp || null); >(profileData?.pfp || null);
const [selectedColor, setSelectedColor] = useState<string>( const [selectedColor, setSelectedColor] = useState<string>(
profileData?.eventColor ?? colorMap.pink profileData?.eventColor ?? colorMap.pink
); );
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>( const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(
profileData?.eventColor ?? colorMap.pink profileData?.eventColor ?? colorMap.pink
); );
const [showDeleteDialog, setShowDeleteDialog] = useState<boolean>(false); const [showDeleteDialog, setShowDeleteDialog] = useState<boolean>(false);
@ -52,15 +58,25 @@ const MyProfile = () => {
setShowDeleteDialog(true); setShowDeleteDialog(true);
}; };
const { mutateAsync: updateHouseholdName } = useUpdateHouseholdName();
const { mutateAsync: updateUserData } = useUpdateUserData(); const { mutateAsync: updateUserData } = useUpdateUserData();
const { mutateAsync: changeProfilePicture } = useChangeProfilePicture(); const { mutateAsync: changeProfilePicture } = useChangeProfilePicture();
const { mutateAsync: deleteAsync } = useDeleteUser() const { mutateAsync: deleteAsync } = useDeleteUser();
const isFirstRender = useRef(true); const isFirstRender = useRef(true);
const handleUpdateUserData = async () => { const handleUpdateUserData = async () => {
await updateUserData({ newUserData: { firstName, lastName, timeZone } }); await updateUserData({ newUserData: { firstName, lastName, timeZone } });
}; };
const handleUpdateHouseholdName = async () => {
if (profileData?.familyId) {
await updateHouseholdName({
familyId: profileData.familyId,
name: householdName,
});
}
};
const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500); const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500);
useEffect(() => { useEffect(() => {
@ -71,6 +87,10 @@ const MyProfile = () => {
debouncedUserDataUpdate(); debouncedUserDataUpdate();
}, [timeZone, lastName, firstName]); }, [timeZone, lastName, firstName]);
useEffect(() => {
handleUpdateHouseholdName();
}, [householdName]);
useEffect(() => { useEffect(() => {
if (profileData) { if (profileData) {
setFirstName(profileData.firstName || ""); setFirstName(profileData.firstName || "");
@ -81,6 +101,16 @@ const MyProfile = () => {
} }
}, [profileData]); }, [profileData]);
useEffect(() => {
if (profileData?.familyId) {
refetchHHName();
}
}, [profileData?.familyId]);
useEffect(() => {
setHouseholdName(hhName);
}, [hhName])
const pickImage = async () => { const pickImage = async () => {
const permissionResult = const permissionResult =
await ImagePicker.requestMediaLibraryPermissionsAsync(); await ImagePicker.requestMediaLibraryPermissionsAsync();
@ -119,19 +149,19 @@ const MyProfile = () => {
}; };
const debouncedUpdateUserData = useCallback( const debouncedUpdateUserData = useCallback(
debounce(async (color: string) => { debounce(async (color: string) => {
try { try {
await updateUserData({ await updateUserData({
newUserData: { newUserData: {
eventColor: color, eventColor: color,
}, },
}); });
} catch (error) { } catch (error) {
console.error("Failed to update color:", error); console.error("Failed to update color:", error);
setSelectedColor(previousSelectedColor); setSelectedColor(previousSelectedColor);
} }
}, 500), }, 500),
[] []
); );
return ( return (
@ -177,6 +207,18 @@ const MyProfile = () => {
)} )}
</View> </View>
<View paddingH-15> <View paddingH-15>
<Text text80 marginT-10 marginB-7 style={styles.label}>
Household
</Text>
<TextField
text70
placeholder="Household name"
style={styles.txtBox}
value={householdName}
onChangeText={async (value) => {
setHouseholdName(value);
}}
/>
<Text text80 marginT-10 marginB-7 style={styles.label}> <Text text80 marginT-10 marginB-7 style={styles.label}>
First name First name
</Text> </Text>
@ -255,39 +297,35 @@ const MyProfile = () => {
<TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)}> <TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)}>
<View style={styles.colorBox} backgroundColor={colorMap.pink}> <View style={styles.colorBox} backgroundColor={colorMap.pink}>
{selectedColor == colorMap.pink && ( {selectedColor == colorMap.pink && (
<AntDesign name="check" size={30} color="white"/> <AntDesign name="check" size={30} color="white" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity onPress={() => handleChangeColor(colorMap.orange)}>
onPress={() => handleChangeColor(colorMap.orange)}
>
<View style={styles.colorBox} backgroundColor={colorMap.orange}> <View style={styles.colorBox} backgroundColor={colorMap.orange}>
{selectedColor == colorMap.orange && ( {selectedColor == colorMap.orange && (
<AntDesign name="check" size={30} color="white"/> <AntDesign name="check" size={30} color="white" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.green)}> <TouchableOpacity onPress={() => handleChangeColor(colorMap.green)}>
<View style={styles.colorBox} backgroundColor={colorMap.green}> <View style={styles.colorBox} backgroundColor={colorMap.green}>
{selectedColor == colorMap.green && ( {selectedColor == colorMap.green && (
<AntDesign name="check" size={30} color="white"/> <AntDesign name="check" size={30} color="white" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.teal)}> <TouchableOpacity onPress={() => handleChangeColor(colorMap.teal)}>
<View style={styles.colorBox} backgroundColor={colorMap.teal}> <View style={styles.colorBox} backgroundColor={colorMap.teal}>
{selectedColor == colorMap.teal && ( {selectedColor == colorMap.teal && (
<AntDesign name="check" size={30} color="white"/> <AntDesign name="check" size={30} color="white" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity onPress={() => handleChangeColor(colorMap.purple)}>
onPress={() => handleChangeColor(colorMap.purple)}
>
<View style={styles.colorBox} backgroundColor={colorMap.purple}> <View style={styles.colorBox} backgroundColor={colorMap.purple}>
{selectedColor == colorMap.purple && ( {selectedColor == colorMap.purple && (
<AntDesign name="check" size={30} color="white"/> <AntDesign name="check" size={30} color="white" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>

View File

@ -0,0 +1,36 @@
import { useQuery } from "react-query";
import firestore from "@react-native-firebase/firestore";
export const useGetHouseholdName = (familyId: string) => {
return useQuery(
["getHouseholdName", familyId], // Unique query key
async () => {
console.log(`Fetching household name for familyId: ${familyId}`);
try {
// Query the Households collection for the given familyId
const snapshot = await firestore()
.collection("Households")
.where("familyId", "==", familyId)
.get();
if (!snapshot.empty) {
// Extract the name from the first matching document
const householdData = snapshot.docs[0].data();
console.log("Household found:", householdData);
return householdData.name || null; // Return the name or null if missing
} else {
console.log("No household found for the given familyId.");
return null; // Return null if no household found
}
} catch (e) {
console.error("Error fetching household name:", e);
throw e; // Ensure error propagates to the query error handling
}
},
{
enabled: !!familyId, // Only fetch if familyId is provided
staleTime: 5 * 60 * 1000, // Cache the data for 5 minutes
}
);
};

View File

@ -0,0 +1,50 @@
import firestore from "@react-native-firebase/firestore";
import { useMutation, useQueryClient } from "react-query";
export const useUpdateHouseholdName = () => {
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["updateHouseholdName"],
mutationFn: async ({
familyId,
name,
}: {
familyId: string;
name: string;
}) => {
console.log("Mutation function called with data:", { familyId, name });
try {
// Reference to the Households collection
const householdRef = firestore().collection("Households");
// Query to check if the household exists
const snapshot = await householdRef.where("familyId", "==", familyId).get();
if (!snapshot.empty) {
// If a household with the familyId exists, update the name
const docId = snapshot.docs[0].id;
console.log(`Household found with ID ${docId}, updating name...`);
await householdRef.doc(docId).update({ name });
console.log("Household name updated successfully.");
} else {
// If no household exists, create a new one with familyId and name
console.log("No household found, creating a new one...");
await householdRef.add({ familyId, name });
console.log("New household created successfully.");
}
} catch (e) {
console.error("Error updating or creating household:", e);
throw e; // Ensure error propagates to the mutation error handling
}
},
onSuccess: () => {
queryClient.invalidateQueries("households"); // Invalidate the "households" query to refresh data
},
});
};