fixes, deleteFamily function, household signup

This commit is contained in:
ivic00
2025-02-15 00:34:42 +01:00
parent a8957c7ac7
commit f649828d80
14 changed files with 2559 additions and 698 deletions

View File

@ -0,0 +1,87 @@
import { SafeAreaView } from "react-native-safe-area-context";
import { Button, Text, View, TextField } from "react-native-ui-lib";
import React, { useState } from "react";
import { useRouter } from "expo-router";
import { StyleSheet } from "react-native";
import { useAuthContext } from "@/contexts/AuthContext";
import { useUpdateHouseholdName } from "@/hooks/firebase/useUpdateHouseholdName";
export default function NewHouseholdScreen() {
const router = useRouter();
const { user, profileData } = useAuthContext();
const [householdName, setHouseholdName] = useState("");
const { mutateAsync: newHousehold } = useUpdateHouseholdName();
const handleContinue = async () => {
try {
if(profileData?.familyId)
newHousehold({familyId: profileData?.familyId, name: householdName}).then(() => router.push("/(unauth)/cal_sync"));
} catch (error) {
console.error("Error saving household name:", error);
}
};
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" }}>
Name your household
</Text>
<Text color={"#919191"} style={{ fontSize: 20 }}>
Give your family group a unique name!
</Text>
</View>
<View width={"100%"} flexG>
<TextField
value={householdName}
onChangeText={setHouseholdName}
placeholder="Enter household name"
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

@ -57,6 +57,8 @@ import KeyboardManager from 'react-native-keyboard-manager';
import {enableScreens} from 'react-native-screens'; import {enableScreens} from 'react-native-screens';
import {PersistQueryClientProvider} from "@/contexts/PersistQueryClientProvider"; import {PersistQueryClientProvider} from "@/contexts/PersistQueryClientProvider";
import auth from "@react-native-firebase/auth"; import auth from "@react-native-firebase/auth";
import firestore from '@react-native-firebase/firestore';
import functions from '@react-native-firebase/functions';
enableScreens(true) enableScreens(true)

View File

@ -13,8 +13,8 @@ interface IAddBrainDumpProps {
} }
const AddBrainDump = ({ const AddBrainDump = ({
addBrainDumpProps, addBrainDumpProps,
}: { }: {
addBrainDumpProps: IAddBrainDumpProps; addBrainDumpProps: IAddBrainDumpProps;
}) => { }) => {
const {addBrainDump} = useBrainDumpContext(); const {addBrainDump} = useBrainDumpContext();
@ -22,11 +22,11 @@ const AddBrainDump = ({
const [dumpDesc, setDumpDesc] = useState<string>(""); const [dumpDesc, setDumpDesc] = useState<string>("");
const {width} = Dimensions.get("screen"); const {width} = Dimensions.get("screen");
// Refs for the two TextFields
const descriptionRef = useRef<TextFieldRef>(null); const descriptionRef = useRef<TextFieldRef>(null);
const titleRef = useRef<TextFieldRef>(null); const titleRef = useRef<TextFieldRef>(null);
const isTitleValid = dumpTitle.trim().length >= 3;
useEffect(() => { useEffect(() => {
setDumpDesc(""); setDumpDesc("");
setDumpTitle(""); setDumpTitle("");
@ -40,9 +40,9 @@ const AddBrainDump = ({
} }
}, [addBrainDumpProps.isVisible]); }, [addBrainDumpProps.isVisible]);
useEffect(() => { useEffect(() => {
if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(false); if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(false);
}, []); }, []);
return ( return (
<Dialog <Dialog
@ -69,18 +69,17 @@ const AddBrainDump = ({
<Button <Button
color="#05a8b6" color="#05a8b6"
label="Save" label="Save"
style={styles.topBtn} style={[styles.topBtn, !isTitleValid && styles.disabledBtn]}
disabled={!isTitleValid}
onPress={() => { onPress={() => {
addBrainDump({ if (isTitleValid) {
addBrainDump({
id: '99', id: '99',
title: dumpTitle.trim(),
title: dumpTitle.trimEnd().trimStart(), description: dumpDesc.trim(),
});
description: dumpDesc.trimEnd().trimStart(), addBrainDumpProps.setIsVisible(false);
}
});
addBrainDumpProps.setIsVisible(false);
}} }}
/> />
</View> </View>
@ -94,11 +93,10 @@ const AddBrainDump = ({
setDumpTitle(text); setDumpTitle(text);
}} }}
onSubmitEditing={() => { onSubmitEditing={() => {
// Move focus to the description field
descriptionRef.current?.focus(); descriptionRef.current?.focus();
}} }}
style={styles.title} style={styles.title}
blurOnSubmit={false} // Keep the keyboard open when moving focus blurOnSubmit={false}
returnKeyType="next" returnKeyType="next"
/> />
<View height={2} backgroundColor="#b3b3b3" width={"100%"} marginB-20/> <View height={2} backgroundColor="#b3b3b3" width={"100%"} marginB-20/>
@ -125,28 +123,31 @@ const AddBrainDump = ({
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
dialogContainer: { dialogContainer: {
borderTopRightRadius: 15, borderTopRightRadius: 15,
borderTopLeftRadius: 15, borderTopLeftRadius: 15,
backgroundColor: "white", backgroundColor: "white",
padding: 0, padding: 0,
paddingTop: 3, paddingTop: 3,
margin: 0, margin: 0,
}, },
topBtns: {}, topBtns: {},
topBtn: { topBtn: {
backgroundColor: "white", backgroundColor: "white",
color: "#05a8b6", color: "#05a8b6",
}, },
title: { disabledBtn: {
fontSize: 22, opacity: 0.2,
fontFamily: "Manrope_500Medium", },
}, title: {
description: { fontSize: 22,
fontFamily: "Manrope_400Regular", fontFamily: "Manrope_500Medium",
fontSize: 14, },
textAlignVertical: "top", description: {
}, fontFamily: "Manrope_400Regular",
fontSize: 14,
textAlignVertical: "top",
},
}); });
export default AddBrainDump; export default AddBrainDump;

View File

@ -20,14 +20,10 @@ const DumpList = (props: { searchText: string }) => {
return ( return (
<View marginB-70> <View marginB-70>
{brainDumps?.length ? <FlatList {sortedDumps?.length ? (
style={{ zIndex: -1 }} sortedDumps.map((item) => (
data={sortedDumps} <BrainDumpItem key={item.id} item={item} />
keyExtractor={(item) => item.title} ))) : <Text marginT-20 center style={styles.alert}>You have no notes</Text>}
renderItem={({ item }) => (
<BrainDumpItem key={item.title} item={item} />
)}
/> : <Text marginT-20 center style={styles.alert}>You have no notes</Text>}
</View> </View>
); );
}; };

View File

@ -32,8 +32,11 @@ const AddFeedback = ({
const descriptionRef = useRef<TextFieldRef>(null); const descriptionRef = useRef<TextFieldRef>(null);
const titleRef = useRef<TextFieldRef>(null); const titleRef = useRef<TextFieldRef>(null);
const isTitleValid = feedbackTitle.trim().length >= 3;
useEffect(() => { useEffect(() => {
setFeedback(""); setFeedback("");
setFeedbackTitle("");
}, [addFeedbackProps.isVisible]); }, [addFeedbackProps.isVisible]);
useEffect(() => { useEffect(() => {
@ -46,9 +49,8 @@ const AddFeedback = ({
useEffect(() => { useEffect(() => {
if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(false); if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(false);
setFeedbackTitle('');
setFeedbackTitle(""); setFeedback('');
setFeedback("");
}, []); }, []);
return ( return (
@ -76,16 +78,17 @@ const AddFeedback = ({
<Button <Button
color="#05a8b6" color="#05a8b6"
label="Save" label="Save"
style={styles.topBtn} style={[styles.topBtn, !isTitleValid && styles.disabledBtn]}
disabled={!isTitleValid}
onPress={() => { onPress={() => {
addFeedback({ if (isTitleValid) {
id: 99, addFeedback({
id: 99,
title: feedbackTitle.trimEnd().trimStart(), title: feedbackTitle.trim(),
text: feedback.trim(),
text: feedback.trimEnd().trimStart(), });
}); addFeedbackProps.setIsVisible(false);
addFeedbackProps.setIsVisible(false); }
}} }}
/> />
</View> </View>
@ -142,6 +145,9 @@ const styles = StyleSheet.create({
backgroundColor: "white", backgroundColor: "white",
color: "#05a8b6", color: "#05a8b6",
}, },
disabledBtn: {
opacity: 0.2,
},
title: { title: {
fontSize: 22, fontSize: 22,
fontFamily: "Manrope_500Medium", fontFamily: "Manrope_500Medium",
@ -153,4 +159,4 @@ const styles = StyleSheet.create({
}, },
}); });
export default AddFeedback; export default AddFeedback;

View File

@ -10,7 +10,7 @@ const Feedback = (props: { item: IFeedback }) => {
const [isVisible, setIsVisible] = useState<boolean>(false); const [isVisible, setIsVisible] = useState<boolean>(false);
return ( return (
<View> <View key={props.item.id}>
<TouchableWithoutFeedback onPress={() => setIsVisible(true)}> <TouchableWithoutFeedback onPress={() => setIsVisible(true)}>
<View <View
backgroundColor="white" backgroundColor="white"

View File

@ -1,13 +1,13 @@
import { View } from "react-native-ui-lib"; import { View, Text } from "react-native-ui-lib";
import React from "react"; import React from "react";
import { FlatList } from "react-native"; import { StyleSheet } from "react-native";
import { useFeedbackContext } from "@/contexts/FeedbackContext"; import { useFeedbackContext } from "@/contexts/FeedbackContext";
import Feedback from "./Feedback"; import Feedback from "./Feedback";
const FeedbackList = (props: { searchText: string }) => { const FeedbackList = (props: { searchText: string }) => {
const { feedbacks } = useFeedbackContext(); const { feedbacks } = useFeedbackContext();
const filteredBrainDumps = const filteredFeedbacks =
props.searchText.trim() === "" props.searchText.trim() === ""
? feedbacks ? feedbacks
: feedbacks.filter( : feedbacks.filter(
@ -20,16 +20,24 @@ const FeedbackList = (props: { searchText: string }) => {
return ( return (
<View marginB-70> <View marginB-70>
<FlatList {filteredFeedbacks?.length ? (
style={{ zIndex: -1 }} filteredFeedbacks.map((item) => (
data={filteredBrainDumps} <Feedback key={item.id} item={item} />
keyExtractor={(item) => item.title} ))
renderItem={({ item }) => ( ) : (
<Feedback key={item.title} item={item} /> <Text marginT-20 center style={styles.alert}>
)} You have no Feedbacks
/> </Text>
)}
</View> </View>
); );
}; };
export default FeedbackList; const styles = StyleSheet.create({
alert: {
fontFamily: "PlusJakartaSans_300Light",
fontSize: 20
}
});
export default FeedbackList;

View File

@ -165,21 +165,18 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
</View> </View>
</View> </View>
{pendingGroceries?.length > 0 {pendingGroceries?.length > 0
? pendingVisible && ( ? pendingVisible && (
<FlatList pendingGroceries.map((item) => (
data={pendingGroceries} <GroceryItem
renderItem={({item}) => ( key={item.id.toString()}
<GroceryItem item={item}
item={item} handleItemApproved={(id, changes) =>
handleItemApproved={(id, changes) => updateGroceryItem({...changes, id: id})
updateGroceryItem({...changes, id: id}) }
} onInputFocus={onInputFocus}
onInputFocus={onInputFocus} />
/> ))
)} )
keyExtractor={(item) => item.id.toString()}
/>
)
: pendingVisible && ( : pendingVisible && (
<Text style={styles.noItemTxt}>No items pending approval.</Text> <Text style={styles.noItemTxt}>No items pending approval.</Text>
)} )}
@ -230,38 +227,34 @@ const GroceryList = ({onInputFocus}: {onInputFocus: (y: number) => void}) => {
{/* Render Approved Groceries Grouped by Category */} {/* Render Approved Groceries Grouped by Category */}
{approvedGroceries?.length > 0 {approvedGroceries?.length > 0
? approvedVisible && ( ? approvedVisible && (
<FlatList Object.keys(approvedGroceriesByCategory)
data={Object.keys(approvedGroceriesByCategory).sort((a, b) => { .sort((a, b) => {
if (a !== "Done") return -1; if (a !== "Done") return -1;
if (b === "Done") return 1; if (b === "Done") return 1;
return 0; return 0;
})} })
renderItem={({item: category}) => ( .map((category) => (
<View key={category}> <View key={category}>
{/* Render Category Header */} {/* Render Category Header */}
<Text text80M style={{marginTop: 10}} color="#666"> <Text text80M style={{marginTop: 10}} color="#666">
{category} {category}
</Text> </Text>
{/* Render Grocery Items for this Category */} {/* Render Grocery Items for this Category */}
{approvedGroceriesByCategory[category].map( {approvedGroceriesByCategory[category].map((grocery: IGrocery) => (
(grocery: IGrocery) => ( <GroceryItem
<GroceryItem key={grocery.id}
key={grocery.id} item={grocery}
item={grocery} handleItemApproved={(id, changes) =>
handleItemApproved={(id, changes) => updateGroceryItem({...changes, id: id})
updateGroceryItem({...changes, id: id}) }
} onInputFocus={onInputFocus}
onInputFocus={onInputFocus} approvedGroceries={approvedGroceries}
approvedGroceries={approvedGroceries} setApprovedGroceries={setApprovedGroceries}
setApprovedGroceries={setApprovedGroceries} />
/> ))}
)
)}
</View> </View>
)} ))
keyExtractor={(category) => category} )
/>
)
: approvedVisible && ( : approvedVisible && (
<Text style={styles.noItemTxt}>No approved items.</Text> <Text style={styles.noItemTxt}>No approved items.</Text>
)} )}

View File

@ -41,14 +41,9 @@ const GroceryWrapper = () => {
const handleInputFocus = (y: number) => { const handleInputFocus = (y: number) => {
if (scrollViewRef.current) { if (scrollViewRef.current) {
// Get the window height
const windowHeight = Dimensions.get('window').height; const windowHeight = Dimensions.get('window').height;
// Calculate the space we want to leave at the top
const topSpacing = 20; const topSpacing = 20;
// Calculate the target scroll position:
// y (position of input) - topSpacing (space we want at top)
// if keyboard is shown, we need to account for its height
const scrollPosition = Math.max(0, y - topSpacing); const scrollPosition = Math.max(0, y - topSpacing);
scrollViewRef.current.scrollTo({ scrollViewRef.current.scrollTo({

View File

@ -22,14 +22,14 @@ const DeleteProfileDialogs: React.FC<ConfirmationDialogProps> = ({
}) => { }) => {
const [confirmationDialog, setConfirmationDialog] = useState<boolean>(false); const [confirmationDialog, setConfirmationDialog] = useState<boolean>(false);
const [input, setInput] = useState<string>(""); const [input, setInput] = useState<string>("");
const [isCorrect, setIsCorrect] = useState<boolean>(true); const [isCorrect, setIsCorrect] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
setInput(""); setInput("");
}, [onDismiss, onConfirm]) }, [onDismiss, onConfirm])
useEffect(() => { useEffect(() => {
setIsCorrect(input === householdName); setIsCorrect(input !== "" && input === householdName);
}, [input]) }, [input])

View File

@ -246,6 +246,7 @@ const ToDoItem = (props: {
/> />
) : ( ) : (
<View <View
key={member.uid}
style={{ style={{
position: "relative", position: "relative",
width: 24.64, width: 24.64,
@ -260,7 +261,7 @@ const ToDoItem = (props: {
backgroundColor: member.eventColor || "#ccc", backgroundColor: member.eventColor || "#ccc",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
borderRadius: 100, // Circular shape borderRadius: 100,
width: "100%", width: "100%",
height: "100%", height: "100%",
}} }}

View File

@ -1813,7 +1813,8 @@ exports.updateHouseholdTimestampOnEventUpdate = functions.firestore
'BrainDumps', 'BrainDumps',
'Groceries', 'Groceries',
'Todos', 'Todos',
'Events' 'Events',
//'Feedbacks'
]; ];
for (const collectionName of collections) { for (const collectionName of collections) {
@ -1829,13 +1830,16 @@ exports.updateHouseholdTimestampOnEventUpdate = functions.firestore
batch.delete(profile.ref); batch.delete(profile.ref);
} }
const householdDoc = await db.collection('Households') const householdSnapshot = await db.collection('Households')
.doc(familyId) .where('familyId', '==', familyId)
.get(); .get();
if (householdDoc.exists) { if (!householdSnapshot.empty) {
batch.delete(householdDoc.ref); const householdDoc = householdSnapshot.docs[0];
} batch.delete(householdDoc.ref);
} else {
console.log('Household not found for familyId:', familyId);
}
await batch.commit(); await batch.commit();

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,11 @@ import { useAuthContext } from "@/contexts/AuthContext";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import functions from '@react-native-firebase/functions'; import functions from '@react-native-firebase/functions';
import { Alert } from 'react-native'; import { Alert } from 'react-native';
import { useSignOut } from './useSignOut';
export const useDeleteFamily = () => { export const useDeleteFamily = () => {
const { user } = useAuthContext(); const { user } = useAuthContext();
const signOut = useSignOut();
return useMutation({ return useMutation({
mutationKey: ["deleteFamily"], mutationKey: ["deleteFamily"],
@ -16,6 +18,11 @@ export const useDeleteFamily = () => {
try { try {
const deleteFamilyFunction = functions().httpsCallable('deleteFamily'); const deleteFamilyFunction = functions().httpsCallable('deleteFamily');
const result = await deleteFamilyFunction({ familyId }); const result = await deleteFamilyFunction({ familyId });
if (result.data.success) {
await signOut.mutateAsync();
}
return result.data; return result.data;
} catch (error: any) { } catch (error: any) {
if (error.code === 'permission-denied') { if (error.code === 'permission-denied') {