mirror of
https://github.com/urosran/cally.git
synced 2025-11-26 00:24:53 +00:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@ -3,6 +3,7 @@ import { ScrollView, RefreshControl } from "react-native";
|
||||
import { useSetAtom } from "jotai";
|
||||
import CalendarPage from "@/components/pages/calendar/CalendarPage";
|
||||
import { refreshTriggerAtom } from "@/components/pages/calendar/atoms";
|
||||
import { colorMap } from "@/constants/colorMap";
|
||||
|
||||
export default function Screen() {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
@ -29,7 +30,19 @@ export default function Screen() {
|
||||
style={{ flex: 1 }}
|
||||
contentContainerStyle={{ flex: 1 }}
|
||||
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}
|
||||
showsVerticalScrollIndicator={false}
|
||||
|
||||
@ -1,16 +1,23 @@
|
||||
import {SafeAreaView} from "react-native-safe-area-context";
|
||||
import {Button, Text, View} from "react-native-ui-lib";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import {useCalSync} from "@/hooks/useCalSync";
|
||||
import GoogleIcon from "@/assets/svgs/GoogleIcon";
|
||||
import AppleIcon from "@/assets/svgs/AppleIcon";
|
||||
import OutlookIcon from "@/assets/svgs/OutlookIcon";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {StyleSheet} from "react-native";
|
||||
import { useGetHouseholdName } from "@/hooks/firebase/useGetHouseholdName";
|
||||
|
||||
export default function Screen() {
|
||||
const {profileData, setRedirectOverride} = useAuthContext()
|
||||
const {handleStartGoogleSignIn, handleAppleSignIn, handleMicrosoftSignIn} = useCalSync()
|
||||
const {data: householdName, refetch} = useGetHouseholdName(profileData?.familyId);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
refetch();
|
||||
}, [profileData?.familyId])
|
||||
|
||||
const hasSomeCalendarsSynced =
|
||||
!!profileData?.appleAccounts || !!profileData?.microsoftAccounts || !!profileData?.googleAccounts
|
||||
@ -19,6 +26,9 @@ export default function Screen() {
|
||||
<SafeAreaView style={{flex: 1}}>
|
||||
<View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%", alignItems: "center"}}>
|
||||
<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'}}>
|
||||
Let's get started!
|
||||
</Text>
|
||||
|
||||
@ -2,19 +2,38 @@ import React from "react";
|
||||
import { View } from "react-native-ui-lib";
|
||||
import HeaderTemplate from "@/components/shared/HeaderTemplate";
|
||||
import { InnerCalendar } from "@/components/pages/calendar/InnerCalendar";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { refreshEnabledAtom } from "./atoms";
|
||||
|
||||
export default function CalendarPage() {
|
||||
const setRefreshEnabled = useSetAtom(refreshEnabledAtom);
|
||||
|
||||
const disableRefreshControl = () => setRefreshEnabled(false);
|
||||
const enableRefreshControl = () => setRefreshEnabled(true);
|
||||
return (
|
||||
<View
|
||||
style={{ flex: 1, height: "100%", padding: 10 }}
|
||||
paddingH-22
|
||||
paddingT-0
|
||||
>
|
||||
<HeaderTemplate
|
||||
message={"Let's get your week started !"}
|
||||
isWelcome
|
||||
isCalendar={true}
|
||||
/>
|
||||
<View
|
||||
onStartShouldSetResponder={() => {
|
||||
enableRefreshControl();
|
||||
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 />
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -12,3 +12,4 @@ export const settingsPageIndex = atom<number>(0);
|
||||
export const userSettingsView = atom<boolean>(true);
|
||||
export const toDosPageIndex = atom<number>(0);
|
||||
export const refreshTriggerAtom = atom<boolean>(false);
|
||||
export const refreshEnabledAtom = atom<boolean>(true);
|
||||
|
||||
@ -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 { ScrollView } from "react-native-gesture-handler";
|
||||
import * as ImagePicker from "expo-image-picker";
|
||||
@ -20,11 +20,17 @@ import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
|
||||
import { useChangeProfilePicture } from "@/hooks/firebase/useChangeProfilePicture";
|
||||
import { colorMap } from "@/constants/colorMap";
|
||||
import DeleteProfileDialogs from "../user_components/DeleteProfileDialogs";
|
||||
import {AntDesign} from "@expo/vector-icons";
|
||||
import {useDeleteUser} from "@/hooks/firebase/useDeleteUser";
|
||||
import { AntDesign } from "@expo/vector-icons";
|
||||
import { useDeleteUser } from "@/hooks/firebase/useDeleteUser";
|
||||
import { useUpdateHouseholdName } from "@/hooks/firebase/useUpdateHouseholdName";
|
||||
import { useGetHouseholdName } from "@/hooks/firebase/useGetHouseholdName";
|
||||
|
||||
const MyProfile = () => {
|
||||
const { user, profileData } = useAuthContext();
|
||||
const { data: hhName, refetch: refetchHHName } = useGetHouseholdName(
|
||||
profileData.familyId
|
||||
);
|
||||
const [householdName, setHouseholdName] = useState<string>("");
|
||||
const [timeZone, setTimeZone] = useState<string>(
|
||||
profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone
|
||||
);
|
||||
@ -37,10 +43,10 @@ const MyProfile = () => {
|
||||
>(profileData?.pfp || null);
|
||||
|
||||
const [selectedColor, setSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState<boolean>(false);
|
||||
@ -52,15 +58,25 @@ const MyProfile = () => {
|
||||
setShowDeleteDialog(true);
|
||||
};
|
||||
|
||||
const { mutateAsync: updateHouseholdName } = useUpdateHouseholdName();
|
||||
const { mutateAsync: updateUserData } = useUpdateUserData();
|
||||
const { mutateAsync: changeProfilePicture } = useChangeProfilePicture();
|
||||
const { mutateAsync: deleteAsync } = useDeleteUser()
|
||||
const { mutateAsync: deleteAsync } = useDeleteUser();
|
||||
const isFirstRender = useRef(true);
|
||||
|
||||
const handleUpdateUserData = async () => {
|
||||
await updateUserData({ newUserData: { firstName, lastName, timeZone } });
|
||||
};
|
||||
|
||||
const handleUpdateHouseholdName = async () => {
|
||||
if (profileData?.familyId) {
|
||||
await updateHouseholdName({
|
||||
familyId: profileData.familyId,
|
||||
name: householdName,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500);
|
||||
|
||||
useEffect(() => {
|
||||
@ -71,6 +87,10 @@ const MyProfile = () => {
|
||||
debouncedUserDataUpdate();
|
||||
}, [timeZone, lastName, firstName]);
|
||||
|
||||
useEffect(() => {
|
||||
handleUpdateHouseholdName();
|
||||
}, [householdName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (profileData) {
|
||||
setFirstName(profileData.firstName || "");
|
||||
@ -81,6 +101,16 @@ const MyProfile = () => {
|
||||
}
|
||||
}, [profileData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (profileData?.familyId) {
|
||||
refetchHHName();
|
||||
}
|
||||
}, [profileData?.familyId]);
|
||||
|
||||
useEffect(() => {
|
||||
setHouseholdName(hhName);
|
||||
}, [hhName])
|
||||
|
||||
const pickImage = async () => {
|
||||
const permissionResult =
|
||||
await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||
@ -119,19 +149,19 @@ const MyProfile = () => {
|
||||
};
|
||||
|
||||
const debouncedUpdateUserData = useCallback(
|
||||
debounce(async (color: string) => {
|
||||
try {
|
||||
await updateUserData({
|
||||
newUserData: {
|
||||
eventColor: color,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update color:", error);
|
||||
setSelectedColor(previousSelectedColor);
|
||||
}
|
||||
}, 500),
|
||||
[]
|
||||
debounce(async (color: string) => {
|
||||
try {
|
||||
await updateUserData({
|
||||
newUserData: {
|
||||
eventColor: color,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to update color:", error);
|
||||
setSelectedColor(previousSelectedColor);
|
||||
}
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -177,6 +207,18 @@ const MyProfile = () => {
|
||||
)}
|
||||
</View>
|
||||
<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}>
|
||||
First name
|
||||
</Text>
|
||||
@ -255,39 +297,35 @@ const MyProfile = () => {
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.pink}>
|
||||
{selectedColor == colorMap.pink && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => handleChangeColor(colorMap.orange)}
|
||||
>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.orange)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.orange}>
|
||||
{selectedColor == colorMap.orange && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => handleChangeColor(colorMap.purple)}
|
||||
>
|
||||
<TouchableOpacity onPress={() => handleChangeColor(colorMap.purple)}>
|
||||
<View style={styles.colorBox} backgroundColor={colorMap.purple}>
|
||||
{selectedColor == colorMap.purple && (
|
||||
<AntDesign name="check" size={30} color="white"/>
|
||||
<AntDesign name="check" size={30} color="white" />
|
||||
)}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
36
hooks/firebase/useGetHouseholdName.ts
Normal file
36
hooks/firebase/useGetHouseholdName.ts
Normal 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
|
||||
}
|
||||
);
|
||||
};
|
||||
50
hooks/firebase/useUpdateHouseholdName.ts
Normal file
50
hooks/firebase/useUpdateHouseholdName.ts
Normal 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
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user