Merge branch 'dev'

# Conflicts:
#	firebase/functions/index.js
This commit is contained in:
Milan Paunovic
2025-02-12 00:43:48 +01:00
11 changed files with 536 additions and 67 deletions

View File

@ -0,0 +1,117 @@
import { SafeAreaView } from "react-native-safe-area-context";
import { Button, Text, View, DateTimePicker } from "react-native-ui-lib";
import React, { useState } from "react";
import { useRouter } from "expo-router";
import { Platform, StyleSheet } from "react-native";
import { useAuthContext } from "@/contexts/AuthContext";
import firestore from "@react-native-firebase/firestore";
import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
export default function BirthdayScreen() {
const router = useRouter();
const { user } = useAuthContext();
const [date, setDate] = useState(new Date());
const { mutateAsync: updateUserData } = useUpdateUserData();
const onDateChange = (event: any, selectedDate?: Date) => {
const currentDate = selectedDate || date;
setDate(currentDate);
};
const handleContinue = async () => {
try {
updateUserData({
newUserData: {
birthday: date,
},
}).then(() => router.push("/(unauth)/cal_sync"));
} catch (error) {
console.error("Error saving birthday:", error);
}
};
const getMaxDate = () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 3); // Minimum age: 3 years
return date;
};
const getMinDate = () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 18); // Maximum age: 18 years
return date;
};
return (
<SafeAreaView style={{ flex: 1 }}>
<View
style={{
flex: 1,
padding: 21,
paddingBottom: 45,
paddingTop: "20%",
alignItems: "center",
}}
>
<View gap-13 width={"100%"} marginB-20>
<Text style={{ fontSize: 40, fontFamily: "Manrope_600SemiBold" }}>
When's your birthday?
</Text>
<Text color={"#919191"} style={{ fontSize: 20 }}>
We'll use this to celebrate your special day!
</Text>
</View>
<View width={"100%"} flexG>
<DateTimePicker
value={date}
mode="date"
minimumDate={getMinDate()}
maximumDate={getMaxDate()}
onChange={(date) => {
if (date) {
const validDate = new Date(date);
if (!isNaN(validDate.getTime())) {
setDate(validDate);
}
}
}}
style={styles.textfield}
textAlign="center"
/>
</View>
<View flexG />
<View width={"100%"}>
<Button
label="Continue"
onPress={handleContinue}
style={{
height: 50,
}}
backgroundColor="#fd1775"
labelStyle={{
fontFamily: "PlusJakartaSans_600SemiBold",
fontSize: 16,
}}
/>
</View>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
textfield: {
backgroundColor: "white",
marginVertical: 100,
padding: 30,
height: 44,
borderRadius: 50,
fontFamily: "PlusJakartaSans_300Light",
fontSize: 15,
color: "#919191",
alignContent: "center",
},
});

View File

@ -49,7 +49,7 @@ export default function Screen() {
const debouncedRouterReplace = useCallback( const debouncedRouterReplace = useCallback(
debounce(() => { debounce(() => {
router.push("/(unauth)/cal_sync"); router.push("/(unauth)/birthday_page");
}, 300), }, 300),
[] []
); );

View File

@ -80,11 +80,11 @@ export const DetailedCalendar: React.FC<EventCalendarProps> = React.memo(({
const renderEvent = useCallback((event: any) => { const renderEvent = useCallback((event: any) => {
const attendees = getAttendees(event); const attendees = getAttendees(event);
return ( return (
<MemoizedEventCell <MemoizedEventCell
event={event} event={event}
onPress={handlePressEvent} onPress={handlePressEvent}
attendees={attendees} attendees={attendees}
/> />
); );
}, [getAttendees, handlePressEvent]); }, [getAttendees, handlePressEvent]);

View File

@ -4,6 +4,7 @@ import {
ButtonSize, ButtonSize,
Checkbox, Checkbox,
Colors, Colors,
DateTimePicker,
KeyboardAwareScrollView, KeyboardAwareScrollView,
LoaderScreen, LoaderScreen,
Text, Text,
@ -66,10 +67,25 @@ const SignUpPage = () => {
return "20%"; // non-tablet case, regardless of orientation return "20%"; // non-tablet case, regardless of orientation
}; };
const getMaxDate = () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 3); // Minimum age: 3 years
return date;
};
const getMinDate = () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 18); // Maximum age: 18 years
return date;
};
const [birthday, setBirthday] = useState<Date>(getMinDate());
const router = useRouter(); const router = useRouter();
const handleSignUp = async () => { const handleSignUp = async () => {
await signUp({ email, password, firstName, lastName }); await signUp({ email, password, firstName, lastName, birthday });
if (profileData?.userType === ProfileType.FAMILY_DEVICE) { if (profileData?.userType === ProfileType.FAMILY_DEVICE) {
router.replace("/(auth)/calendar"); router.replace("/(auth)/calendar");
@ -110,7 +126,7 @@ const SignUpPage = () => {
<KeyboardAvoidingView style={{ width: "100%" }}> <KeyboardAvoidingView style={{ width: "100%" }}>
<TextField <TextField
marginT-30 marginT-17
autoFocus autoFocus
placeholder="First name" placeholder="First name"
value={firstName} value={firstName}
@ -160,6 +176,16 @@ const SignUpPage = () => {
}} }}
/> />
<DateTimePicker
placeholder="Birthday"
value={birthday}
mode="date"
minimumDate={getMinDate()}
maximumDate={getMaxDate()}
onChange={(value) => setBirthday(value)}
style={styles.textfield}
/>
<View <View
centerV centerV
style={[styles.textfield, { padding: 0, paddingHorizontal: 30 }]} style={[styles.textfield, { padding: 0, paddingHorizontal: 30 }]}
@ -241,7 +267,7 @@ const SignUpPage = () => {
{isTablet ? ( {isTablet ? (
<View height={50} /> <View height={50} />
) : ( ) : (
<View flexG style={{ minHeight: 50 }} /> <View flexG style={{ minHeight: 65 }} />
)} )}
<View> <View>

View File

@ -1,5 +1,5 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { Dialog, Button, Text, View } from "react-native-ui-lib"; import { Dialog, Button, Text, View, TextField } from "react-native-ui-lib";
import { StyleSheet } from "react-native"; import { StyleSheet } from "react-native";
import { Feather } from "@expo/vector-icons"; import { Feather } from "@expo/vector-icons";
@ -8,6 +8,8 @@ interface ConfirmationDialogProps {
onDismiss: () => void; onDismiss: () => void;
onFirstYes: () => void; onFirstYes: () => void;
onConfirm: () => void; onConfirm: () => void;
isDeleteFamily?: boolean;
householdName: string;
} }
const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
@ -15,8 +17,21 @@ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
onDismiss, onDismiss,
onFirstYes, onFirstYes,
onConfirm, onConfirm,
isDeleteFamily,
householdName,
}) => { }) => {
const [confirmationDialog, setConfirmationDialog] = useState<boolean>(false); const [confirmationDialog, setConfirmationDialog] = useState<boolean>(false);
const [input, setInput] = useState<string>("");
const [isCorrect, setIsCorrect] = useState<boolean>(true);
useEffect(() => {
setInput("");
}, [onDismiss, onConfirm])
useEffect(() => {
setIsCorrect(input === householdName);
}, [input])
return ( return (
<> <>
@ -31,18 +46,33 @@ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
<Text center style={styles.title}> <Text center style={styles.title}>
Are you sure? Are you sure?
</Text> </Text>
<Text {isDeleteFamily ? (
style={{ <Text
fontSize: 18, style={{
fontFamily: "PlusJakartaSans_700Bold", fontSize: 18,
color: "#979797", fontFamily: "PlusJakartaSans_700Bold",
marginBottom: 20, color: "#979797",
}} marginBottom: 20,
center }}
> center
This action will permanently delete all your data, you won't be able >
to recover it! This action will permanently delete all your family profiles and
</Text> data, you won't be able to recover it!
</Text>
) : (
<Text
style={{
fontSize: 18,
fontFamily: "PlusJakartaSans_700Bold",
color: "#979797",
marginBottom: 20,
}}
center
>
This action will permanently delete all your data, you won't be able
to recover it!
</Text>
)}
<View centerV></View> <View centerV></View>
<View row right gap-8> <View row right gap-8>
<Button <Button
@ -69,10 +99,30 @@ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
containerStyle={styles.dialog} containerStyle={styles.dialog}
> >
<View center paddingH-10 paddingT-15 paddingB-5> <View center paddingH-10 paddingT-15 paddingB-5>
<Text style={styles.title}> {isDeleteFamily ? (
We're sorry to see you go, are you really sure you want to delete <View>
everything? <Text style={styles.title}>Deleting family</Text>
</Text> <Text style={styles.text}>
Deleting the family will remove all profiles and data. This
cannot be undone.
</Text>
<Text style={styles.subText}>
Type the name of your household to confirm.
</Text>
<TextField
value={input}
onChangeText={(value) => {
setInput(value);
}}
style={[styles.txtBox, { marginTop: 5 }]}
/>
</View>
) : (
<Text style={styles.title}>
We're sorry to see you go, are you really sure you want to delete
everything?
</Text>
)}
<View row right gap-8 marginT-15> <View row right gap-8 marginT-15>
<Button <Button
label="Cancel" label="Cancel"
@ -85,11 +135,14 @@ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
/> />
<Button <Button
label="Yes" label="Yes"
disabled={!isDeleteFamily ? false : !isCorrect}
onPress={() => { onPress={() => {
onConfirm(); if(input === householdName)
onConfirm();
setConfirmationDialog(false); setConfirmationDialog(false);
}} }}
style={styles.confirmBtn} style={!isDeleteFamily ? styles.confirmBtn : (isCorrect ? styles.confirmBtn : styles.confirmDisabled)}
labelStyle={{ fontFamily: "PlusJakartaSans_500Medium" }} labelStyle={{ fontFamily: "PlusJakartaSans_500Medium" }}
/> />
</View> </View>
@ -106,6 +159,9 @@ const styles = StyleSheet.create({
cancelBtn: { cancelBtn: {
backgroundColor: "white", backgroundColor: "white",
}, },
confirmDisabled: {
backgroundColor: "#999999"
},
dialog: { dialog: {
backgroundColor: "white", backgroundColor: "white",
paddingHorizontal: 25, paddingHorizontal: 25,
@ -123,6 +179,23 @@ const styles = StyleSheet.create({
fontSize: 16, fontSize: 16,
marginBottom: 0, marginBottom: 0,
}, },
subText: {
fontFamily: "PlusJakartaSans_400Regular",
fontSize: 12,
marginBottom: 0,
color: "#999999",
marginTop: 15,
},
txtBox: {
backgroundColor: "#fafafa",
borderRadius: 10,
borderWidth: 2,
borderColor: "#cecece",
padding: 15,
height: 45,
fontFamily: "PlusJakartaSans_500Medium",
fontSize: 13,
},
}); });
export default DeleteProfileDialogs; export default DeleteProfileDialogs;

View File

@ -3,6 +3,7 @@ import {
Button, Button,
Card, Card,
Colors, Colors,
DateTimePicker,
Dialog, Dialog,
Image, Image,
KeyboardAwareScrollView, KeyboardAwareScrollView,
@ -53,6 +54,11 @@ const MyGroup: React.FC<MyGroupProps> = ({
const [firstName, setFirstName] = useState(""); const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState(""); const [lastName, setLastName] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [birthday, setBirthday] = useState<Date>(() => {
const date = new Date();
date.setFullYear(date.getFullYear() - 3);
return date;
});
const { profileData } = useAuthContext(); const { profileData } = useAuthContext();
@ -66,7 +72,8 @@ const MyGroup: React.FC<MyGroupProps> = ({
); );
const { mutateAsync: createSubUser, isLoading, isError } = useCreateSubUser(); const { mutateAsync: createSubUser, isLoading, isError } = useCreateSubUser();
const { data: familyMembers, refetch: refetchFamilyMembers } = useGetFamilyMembers(true); const { data: familyMembers, refetch: refetchFamilyMembers } =
useGetFamilyMembers(true);
const { user } = useAuthContext(); const { user } = useAuthContext();
const { const {
pickImage, pickImage,
@ -77,9 +84,9 @@ const MyGroup: React.FC<MyGroupProps> = ({
} = useUploadProfilePicture(newUserId); } = useUploadProfilePicture(newUserId);
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
refetchFamilyMembers(); refetchFamilyMembers();
}, [refetchFamilyMembers]) }, [refetchFamilyMembers])
); );
const parents = const parents =
@ -115,6 +122,7 @@ const MyGroup: React.FC<MyGroupProps> = ({
firstName, firstName,
lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName, lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName,
email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`, email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`,
birthday: birthday,
password: uuidv4(), password: uuidv4(),
userType: selectedStatus as ProfileType, userType: selectedStatus as ProfileType,
}); });
@ -146,8 +154,24 @@ const MyGroup: React.FC<MyGroupProps> = ({
setFirstName(""); setFirstName("");
setLastName(""); setLastName("");
setEmail(""); setEmail("");
const date = new Date();
date.setFullYear(date.getFullYear() - 3);
setBirthday(date);
}, []); }, []);
const getMaxDate = () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 3); // Minimum age: 3 years
return date;
};
const getMinDate = () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 18); // Maximum age: 18 years
return date;
};
// @ts-ignore // @ts-ignore
return ( return (
<View marginB-70> <View marginB-70>
@ -524,6 +548,24 @@ const MyGroup: React.FC<MyGroupProps> = ({
blurOnSubmit={false} blurOnSubmit={false}
returnKeyType="next" returnKeyType="next"
/> />
<Text style={styles.jakarta12}>Birthday</Text>
{/*<DateTimePicker
editable={!isLoading}
mode="date"
minimumDate={getMinDate()}
maximumDate={getMaxDate()}
style={[styles.inputField, { paddingVertical: 0 }]}
value={birthday}
onChange={(date) => {
if (date) {
const validDate = new Date(date);
if (!isNaN(validDate.getTime())) {
setBirthday(validDate);
console.log("Selected birthday:", validDate);
}
}
}}
/>*/}
</> </>
)} )}

View File

@ -5,6 +5,7 @@ import * as ImagePicker from "expo-image-picker";
import { import {
Button, Button,
Colors, Colors,
DateTimePicker,
Image, Image,
Picker, Picker,
Text, Text,
@ -25,11 +26,13 @@ import { useDeleteUser } from "@/hooks/firebase/useDeleteUser";
import { useUpdateHouseholdName } from "@/hooks/firebase/useUpdateHouseholdName"; import { useUpdateHouseholdName } from "@/hooks/firebase/useUpdateHouseholdName";
import { useGetHouseholdName } from "@/hooks/firebase/useGetHouseholdName"; import { useGetHouseholdName } from "@/hooks/firebase/useGetHouseholdName";
import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers"; import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
import { useDeleteFamily } from "@/hooks/firebase/useDeleteFamily";
const MyProfile = () => { const MyProfile = () => {
const { user, profileData } = useAuthContext(); const { user, profileData } = useAuthContext();
const { data: familyMembers } = useGetFamilyMembers(); const { data: familyMembers } = useGetFamilyMembers();
const [takenColors, setTakenColors] = useState<string[]>([]); const [takenColors, setTakenColors] = useState<string[]>([]);
const { mutate: deleteFamily, isLoading } = useDeleteFamily();
const { data: hhName, refetch: refetchHHName } = useGetHouseholdName( const { data: hhName, refetch: refetchHHName } = useGetHouseholdName(
profileData.familyId profileData.familyId
@ -52,13 +55,26 @@ const MyProfile = () => {
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>( const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(
profileData?.eventColor ?? colorMap.pink profileData?.eventColor ?? colorMap.pink
); );
const [birthday, setBirthday] = useState<Date>(() => {
if (profileData?.birthday) {
if (profileData.birthday.toDate) {
return profileData.birthday.toDate();
}
const date = new Date(profileData.birthday);
return isNaN(date.getTime()) ? new Date() : date;
}
return new Date();
});
const [showDeleteDialog, setShowDeleteDialog] = useState<boolean>(false); const [showDeleteDialog, setShowDeleteDialog] = useState<boolean>(false);
const [isDeleteFamily, setIsDeleteFamily] = useState<boolean>(false);
const handleHideDeleteDialog = () => { const handleHideDeleteDialog = () => {
setShowDeleteDialog(false); setShowDeleteDialog(false);
}; };
const handleShowDeleteDialog = () => { const handleShowDeleteDialog = (isFamily: boolean) => {
setIsDeleteFamily(isFamily);
setShowDeleteDialog(true); setShowDeleteDialog(true);
}; };
@ -91,18 +107,14 @@ const MyProfile = () => {
debouncedUserDataUpdate(); debouncedUserDataUpdate();
}, [timeZone, lastName, firstName]); }, [timeZone, lastName, firstName]);
useEffect(() => {
handleUpdateHouseholdName();
}, [householdName]);
useEffect(() => { useEffect(() => {
if (familyMembers) { if (familyMembers) {
const colors = familyMembers const colors = familyMembers
.filter(member => member?.eventColor && member.uid !== user?.uid) .filter((member) => member?.eventColor && member.uid !== user?.uid)
.map(member => member.eventColor!); .map((member) => member.eventColor!);
setTakenColors(colors); setTakenColors(colors);
} }
}, [familyMembers]); }, [familyMembers]);
useEffect(() => { useEffect(() => {
if (profileData) { if (profileData) {
@ -111,6 +123,16 @@ const MyProfile = () => {
setTimeZone( setTimeZone(
profileData.timeZone || Localization.getCalendars()[0].timeZone! profileData.timeZone || Localization.getCalendars()[0].timeZone!
); );
if (profileData?.birthday) {
if (profileData.birthday.toDate) {
setBirthday(profileData.birthday.toDate());
} else {
const date = new Date(profileData.birthday);
if (!isNaN(date.getTime())) {
setBirthday(date);
}
}
}
} }
}, [profileData]); }, [profileData]);
@ -161,6 +183,19 @@ const MyProfile = () => {
debouncedUpdateUserData(color); debouncedUpdateUserData(color);
}; };
const handleDeleteFamily = () => {
if(profileData?.familyId){
deleteFamily({familyId: profileData?.familyId}, {
onSuccess: () => {
},
onError: (error) => {
console.log("from delete fam:\n" + error);
}
})
}
}
const debouncedUpdateUserData = useCallback( const debouncedUpdateUserData = useCallback(
debounce(async (color: string) => { debounce(async (color: string) => {
try { try {
@ -240,6 +275,7 @@ const MyProfile = () => {
onChangeText={async (value) => { onChangeText={async (value) => {
setHouseholdName(value); setHouseholdName(value);
}} }}
onEndEditing={() => handleUpdateHouseholdName()}
/> />
</> </>
)} )}
@ -267,6 +303,27 @@ const MyProfile = () => {
setLastName(value); setLastName(value);
}} }}
/> />
<Text text80 marginT-10 marginB-7 style={styles.label}>
Birthday
</Text>
<DateTimePicker
style={styles.txtBox}
mode="date"
value={birthday}
onChange={(date) => {
if (date) {
const validDate = new Date(date);
if (!isNaN(validDate.getTime())) {
setBirthday(validDate);
updateUserData({
newUserData: {
birthday: validDate,
},
});
}
}
}}
/>
<Text text80 marginT-10 marginB-7 style={styles.label}> <Text text80 marginT-10 marginB-7 style={styles.label}>
Email address Email address
</Text> </Text>
@ -284,36 +341,83 @@ const MyProfile = () => {
Color Preference Color Preference
</Text> </Text>
<View row spread> <View row spread>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)} disabled={takenColors.includes(colorMap.pink)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.pink) ? 0.1 : 1}]} backgroundColor={colorMap.pink}> onPress={() => handleChangeColor(colorMap.pink)}
disabled={takenColors.includes(colorMap.pink)}
>
<View
style={[
styles.colorBox,
{ opacity: takenColors.includes(colorMap.pink) ? 0.1 : 1 },
]}
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 onPress={() => handleChangeColor(colorMap.lightPink)} disabled={takenColors.includes(colorMap.lightPink)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.lightPink) ? 0.1 : 1}]} backgroundColor={colorMap.lightPink}> onPress={() => handleChangeColor(colorMap.lightPink)}
disabled={takenColors.includes(colorMap.lightPink)}
>
<View
style={[
styles.colorBox,
{ opacity: takenColors.includes(colorMap.lightPink) ? 0.1 : 1 },
]}
backgroundColor={colorMap.lightPink}
>
{selectedColor == colorMap.lightPink && ( {selectedColor == colorMap.lightPink && (
<AntDesign name="check" size={30} color="black" /> <AntDesign name="check" size={30} color="black" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.orange)} disabled={takenColors.includes(colorMap.orange)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.orange) ? 0.1 : 1}]} backgroundColor={colorMap.orange}> onPress={() => handleChangeColor(colorMap.orange)}
disabled={takenColors.includes(colorMap.orange)}
>
<View
style={[
styles.colorBox,
{ opacity: takenColors.includes(colorMap.orange) ? 0.1 : 1 },
]}
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.lightOrange)} disabled={takenColors.includes(colorMap.lightOrange)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.lightOrange) ? 0.1 : 1}]} backgroundColor={colorMap.lightOrange}> onPress={() => handleChangeColor(colorMap.lightOrange)}
disabled={takenColors.includes(colorMap.lightOrange)}
>
<View
style={[
styles.colorBox,
{
opacity: takenColors.includes(colorMap.lightOrange) ? 0.1 : 1,
},
]}
backgroundColor={colorMap.lightOrange}
>
{selectedColor == colorMap.lightOrange && ( {selectedColor == colorMap.lightOrange && (
<AntDesign name="check" size={30} color="black" /> <AntDesign name="check" size={30} color="black" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.green)}disabled={takenColors.includes(colorMap.green)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.green) ? 0.1 : 1}]} backgroundColor={colorMap.green}> onPress={() => handleChangeColor(colorMap.green)}
disabled={takenColors.includes(colorMap.green)}
>
<View
style={[
styles.colorBox,
{ opacity: takenColors.includes(colorMap.green) ? 0.1 : 1 },
]}
backgroundColor={colorMap.green}
>
{selectedColor == colorMap.green && ( {selectedColor == colorMap.green && (
<AntDesign name="check" size={30} color="white" /> <AntDesign name="check" size={30} color="white" />
)} )}
@ -321,36 +425,85 @@ const MyProfile = () => {
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View row spread marginT-10> <View row spread marginT-10>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.lightGreen)} disabled={takenColors.includes(colorMap.lightGreen)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.lightGreen) ? 0.1 : 1}]} backgroundColor={colorMap.lightGreen}> onPress={() => handleChangeColor(colorMap.lightGreen)}
disabled={takenColors.includes(colorMap.lightGreen)}
>
<View
style={[
styles.colorBox,
{
opacity: takenColors.includes(colorMap.lightGreen) ? 0.1 : 1,
},
]}
backgroundColor={colorMap.lightGreen}
>
{selectedColor == colorMap.lightGreen && ( {selectedColor == colorMap.lightGreen && (
<AntDesign name="check" size={30} color="black" /> <AntDesign name="check" size={30} color="black" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.teal)} disabled={takenColors.includes(colorMap.teal)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.teal) ? 0.1 : 1}]} backgroundColor={colorMap.teal}> onPress={() => handleChangeColor(colorMap.teal)}
disabled={takenColors.includes(colorMap.teal)}
>
<View
style={[
styles.colorBox,
{ opacity: takenColors.includes(colorMap.teal) ? 0.1 : 1 },
]}
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 onPress={() => handleChangeColor(colorMap.lightTeal)} disabled={takenColors.includes(colorMap.lightTeal)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.lightTeal) ? 0.1 : 1}]} backgroundColor={colorMap.lightTeal}> onPress={() => handleChangeColor(colorMap.lightTeal)}
disabled={takenColors.includes(colorMap.lightTeal)}
>
<View
style={[
styles.colorBox,
{ opacity: takenColors.includes(colorMap.lightTeal) ? 0.1 : 1 },
]}
backgroundColor={colorMap.lightTeal}
>
{selectedColor == colorMap.lightTeal && ( {selectedColor == colorMap.lightTeal && (
<AntDesign name="check" size={30} color="black" /> <AntDesign name="check" size={30} color="black" />
)} )}
</View> </View>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.purple)} disabled={takenColors.includes(colorMap.purple)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.purple) ? 0.1 : 1}]} backgroundColor={colorMap.purple}> onPress={() => handleChangeColor(colorMap.purple)}
disabled={takenColors.includes(colorMap.purple)}
>
<View
style={[
styles.colorBox,
{ opacity: takenColors.includes(colorMap.purple) ? 0.1 : 1 },
]}
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>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.lightPurple)} disabled={takenColors.includes(colorMap.lightPurple)}> <TouchableOpacity
<View style={[styles.colorBox, {opacity: takenColors.includes(colorMap.lightPurple) ? 0.1 : 1}]} backgroundColor={colorMap.lightPurple}> onPress={() => handleChangeColor(colorMap.lightPurple)}
disabled={takenColors.includes(colorMap.lightPurple)}
>
<View
style={[
styles.colorBox,
{
opacity: takenColors.includes(colorMap.lightPurple) ? 0.1 : 1,
},
]}
backgroundColor={colorMap.lightPurple}
>
{selectedColor == colorMap.lightPurple && ( {selectedColor == colorMap.lightPurple && (
<AntDesign name="check" size={30} color="black" /> <AntDesign name="check" size={30} color="black" />
)} )}
@ -393,7 +546,7 @@ const MyProfile = () => {
</View> </View>
</View> </View>
<TouchableOpacity <TouchableOpacity
onPress={handleShowDeleteDialog} onPress={() => handleShowDeleteDialog(false)}
style={{ marginTop: 10, alignSelf: "center" }} style={{ marginTop: 10, alignSelf: "center" }}
> >
<Text <Text
@ -406,13 +559,40 @@ const MyProfile = () => {
Delete Profile Delete Profile
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
{profileData?.userType === ProfileType.PARENT && (
<TouchableOpacity
onPress={() => handleShowDeleteDialog(true)}
style={{ marginTop: 20, alignSelf: "center" }}
>
<Text
style={{
color: "#ff1637",
fontFamily: "PlusJakartaSans_500Medium",
fontSize: 15,
}}
>
Delete Family
</Text>
</TouchableOpacity>
)}
<DeleteProfileDialogs <DeleteProfileDialogs
onFirstYes={() => { onFirstYes={() => {
setShowDeleteDialog(false); setShowDeleteDialog(false);
}} }}
visible={showDeleteDialog} visible={showDeleteDialog}
onDismiss={handleHideDeleteDialog} onDismiss={handleHideDeleteDialog}
onConfirm={() => deleteAsync({})} onConfirm={() => {
if(isDeleteFamily)
//deletes family
handleDeleteFamily();
else
{
//deletes profile
deleteAsync({});
}
}}
isDeleteFamily={isDeleteFamily}
householdName={householdName}
/> />
</ScrollView> </ScrollView>
); );

View File

@ -60,7 +60,6 @@ const styles = StyleSheet.create({
overflow: 'hidden', overflow: 'hidden',
}, },
initialsCircle: { initialsCircle: {
backgroundColor: '#ccc',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
borderRadius: 100, // Circular shape borderRadius: 100, // Circular shape

View File

@ -0,0 +1,32 @@
import { useAuthContext } from "@/contexts/AuthContext";
import { useMutation } from "@tanstack/react-query";
import functions from '@react-native-firebase/functions';
import { Alert } from 'react-native';
export const useDeleteFamily = () => {
const { user } = useAuthContext();
return useMutation({
mutationKey: ["deleteFamily"],
mutationFn: async ({ familyId }: { familyId: string }) => {
if (!user) {
throw new Error('User must be logged in');
}
try {
const deleteFamilyFunction = functions().httpsCallable('deleteFamily');
const result = await deleteFamilyFunction({ familyId });
return result.data;
} catch (error: any) {
if (error.code === 'permission-denied') {
Alert.alert('Error', 'Only parents can delete families');
} else if (error.code === 'unauthenticated') {
Alert.alert('Error', 'Please log in to perform this action');
} else {
Alert.alert('Error', 'Failed to delete family. Please try again.');
}
throw error;
}
}
});
};

View File

@ -16,11 +16,13 @@ export const useSignUp = () => {
password, password,
firstName, firstName,
lastName, lastName,
birthday
}: { }: {
email: string; email: string;
password: string; password: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
birthday: Date;
}) => { }) => {
setRedirectOverride(true) setRedirectOverride(true)
@ -35,6 +37,7 @@ export const useSignUp = () => {
lastName: lastName, lastName: lastName,
familyId: uuidv4(), familyId: uuidv4(),
timeZone: Localization.getCalendars()[0].timeZone, timeZone: Localization.getCalendars()[0].timeZone,
birthday: birthday
}, },
customUser: res.user, customUser: res.user,
}); });

View File

@ -13,7 +13,6 @@ export const useUpdateHouseholdName = () => {
familyId: string; familyId: string;
name: string; name: string;
}) => { }) => {
console.log("Mutation function called with data:", { familyId, name });
try { try {
// Reference to the Households collection // Reference to the Households collection
@ -25,11 +24,9 @@ export const useUpdateHouseholdName = () => {
if (!snapshot.empty) { if (!snapshot.empty) {
// If a household with the familyId exists, update the name // If a household with the familyId exists, update the name
const docId = snapshot.docs[0].id; const docId = snapshot.docs[0].id;
console.log(`Household found with ID ${docId}, updating name...`);
await householdRef.doc(docId).update({ name }); await householdRef.doc(docId).update({ name });
console.log("Household name updated successfully.");
} else { } else {
// If no household exists, create a new one with familyId and name // If no household exists, create a new one with familyId and name
console.log("No household found, creating a new one..."); console.log("No household found, creating a new one...");