added Feedback page, added braindump backend

This commit is contained in:
ivic00
2024-11-02 22:31:19 +01:00
parent b9e33c3e1e
commit b35871aed8
23 changed files with 2064 additions and 1024 deletions

View File

@ -7,7 +7,7 @@ import {
DrawerItemList, DrawerItemList,
} from "@react-navigation/drawer"; } from "@react-navigation/drawer";
import { Button, View, Text, ButtonSize } from "react-native-ui-lib"; import { Button, View, Text, ButtonSize } from "react-native-ui-lib";
import { ImageBackground, StyleSheet } from "react-native"; import { Dimensions, ImageBackground, StyleSheet } from "react-native";
import Feather from "@expo/vector-icons/Feather"; import Feather from "@expo/vector-icons/Feather";
import DrawerButton from "@/components/shared/DrawerButton"; import DrawerButton from "@/components/shared/DrawerButton";
import { import {
@ -30,6 +30,7 @@ import {
toDosPageIndex, toDosPageIndex,
userSettingsView, userSettingsView,
} from "@/components/pages/calendar/atoms"; } from "@/components/pages/calendar/atoms";
import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon";
export default function TabLayout() { export default function TabLayout() {
const { mutateAsync: signOut } = useSignOut(); const { mutateAsync: signOut } = useSignOut();
@ -52,8 +53,8 @@ export default function TabLayout() {
}} }}
drawerContent={(props) => { drawerContent={(props) => {
return ( return (
<DrawerContentScrollView {...props} style={{ height: "100%" }}> <DrawerContentScrollView {...props} style={{}}>
<View centerV margin-30 row> <View centerV marginH-30 marginT-20 marginB-20 row>
<ImageBackground <ImageBackground
source={require("../../assets/images/splash.png")} source={require("../../assets/images/splash.png")}
style={{ style={{
@ -98,6 +99,19 @@ export default function TabLayout() {
}} }}
icon={<NavGroceryIcon />} icon={<NavGroceryIcon />}
/> />
<DrawerButton
color="#ea156d"
title={"Feedback"}
bgColor={"#fdedf4"}
pressFunc={() => {
props.navigation.navigate("feedback");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
icon={<FeedbackNavIcon />}
/>
</View> </View>
<View style={{ flex: 1 }}> <View style={{ flex: 1 }}>
{/*<DrawerButton {/*<DrawerButton
@ -177,9 +191,9 @@ export default function TabLayout() {
<Button <Button
size={ButtonSize.large} size={ButtonSize.large}
marginH-30 marginH-30
marginT-12
paddingV-15 paddingV-15
style={{ style={{
marginTop: "42%",
backgroundColor: "transparent", backgroundColor: "transparent",
borderWidth: 1.3, borderWidth: 1.3,
borderColor: "#fd1775", borderColor: "#fd1775",
@ -243,6 +257,10 @@ export default function TabLayout() {
title: "To-Dos", title: "To-Dos",
}} }}
/> />
<Drawer.Screen
name="feedback"
options={{ drawerLabel: "Feedback", title: "Feedback" }}
/>
</Drawer> </Drawer>
); );
} }

View File

@ -0,0 +1,5 @@
import {Stack} from "expo-router";
export default function StackLayout () {
return <Stack screenOptions={{headerShown: false}}/>
}

View File

@ -0,0 +1,13 @@
import FeedbackPage from "@/components/pages/feedback/FeedbackPage";
import { FeedbackProvider } from "@/contexts/FeedbackContext";
import { View } from "react-native-ui-lib";
export default function Screen() {
return (
<FeedbackProvider>
<View>
<FeedbackPage />
</View>
</FeedbackProvider>
);
}

View File

@ -0,0 +1,20 @@
import * as React from "react"
import Svg, { SvgProps, Path } from "react-native-svg"
const FeedbackNavIcon = (props: SvgProps) => (
<Svg
width={25}
height={25}
fill="none"
viewBox="0 0 24 24"
{...props}
>
<Path
stroke="#ea156d"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="M10.5 21H4a7.001 7.001 0 0 1 6-6.93m6.498 2.142c-.7-.78-1.867-.989-2.744-.275-.877.713-1 1.906-.311 2.75.388.476 1.312 1.311 2.042 1.948.347.302.52.453.73.515.178.053.387.053.566 0 .21-.061.382-.213.729-.515.73-.637 1.654-1.472 2.043-1.948.688-.844.58-2.044-.312-2.75-.892-.706-2.044-.504-2.743.275ZM15 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</Svg>
)
export default FeedbackNavIcon

View File

@ -0,0 +1,157 @@
import {
Button,
Dialog,
TextField,
TextFieldRef,
TouchableOpacity,
View,
} from "react-native-ui-lib";
import React, { useEffect, useRef, useState } from "react";
import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
import { Dimensions, Platform, StyleSheet } from "react-native";
import DropModalIcon from "@/assets/svgs/DropModalIcon";
import { useBrainDumpContext } from "@/contexts/DumpContext";
import KeyboardManager from "react-native-keyboard-manager";
import { useFeedbackContext } from "@/contexts/FeedbackContext";
interface IAddFeedbackProps {
isVisible: boolean;
setIsVisible: (value: boolean) => void;
}
const AddFeedback = ({
addFeedbackProps,
}: {
addFeedbackProps: IAddFeedbackProps;
}) => {
const { addFeedback } = useFeedbackContext();
const [feedback, setFeedback] = useState<string>("");
const [feedbackTitle, setFeedbackTitle] = useState<string>("");
const { width } = Dimensions.get("screen");
const descriptionRef = useRef<TextFieldRef>(null);
const titleRef = useRef<TextFieldRef>(null);
useEffect(() => {
setFeedback("");
}, [addFeedbackProps.isVisible]);
useEffect(() => {
if (addFeedbackProps.isVisible) {
setTimeout(() => {
titleRef?.current?.focus();
}, 500);
}
}, [addFeedbackProps.isVisible]);
useEffect(() => {
if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(false);
setFeedbackTitle("");
setFeedback("");
}, []);
return (
<Dialog
bottom={true}
height={"90%"}
width={width}
panDirection={PanningDirectionsEnum.DOWN}
onDismiss={() => addFeedbackProps.setIsVisible(false)}
containerStyle={styles.dialogContainer}
visible={addFeedbackProps.isVisible}
>
<View row spread style={styles.topBtns} marginB-20>
<Button
color="#05a8b6"
label="Cancel"
style={styles.topBtn}
onPress={() => {
addFeedbackProps.setIsVisible(false);
}}
/>
<TouchableOpacity onPress={() => addFeedbackProps.setIsVisible(false)}>
<DropModalIcon style={{ marginTop: 15 }} />
</TouchableOpacity>
<Button
color="#05a8b6"
label="Save"
style={styles.topBtn}
onPress={() => {
addFeedback({
id: 99,
title: feedbackTitle.trimEnd().trimStart(),
text: feedback.trimEnd().trimStart(),
});
addFeedbackProps.setIsVisible(false);
}}
/>
</View>
<View marginH-20>
<TextField
value={feedbackTitle}
ref={titleRef}
placeholder="Set Title"
text60R
onChangeText={(text) => {
setFeedbackTitle(text);
}}
onSubmitEditing={() => {
descriptionRef.current?.focus();
}}
style={styles.title}
blurOnSubmit={false}
returnKeyType="next"
/>
<View height={2} backgroundColor="#b3b3b3" width={"100%"} marginB-20 />
<TextField
ref={descriptionRef}
value={feedback}
placeholder="Write Description"
text70
onChangeText={(text) => {
setFeedback(text);
}}
style={styles.description}
multiline
numberOfLines={4}
maxLength={255}
onEndEditing={() => {
descriptionRef.current?.blur();
}}
returnKeyType="done"
/>
</View>
</Dialog>
);
};
const styles = StyleSheet.create({
dialogContainer: {
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
backgroundColor: "white",
padding: 0,
paddingTop: 3,
margin: 0,
},
topBtns: {},
topBtn: {
backgroundColor: "white",
color: "#05a8b6",
},
title: {
fontSize: 22,
fontFamily: "Manrope_500Medium",
},
description: {
fontFamily: "Manrope_400Regular",
fontSize: 14,
textAlignVertical: "top",
},
});
export default AddFeedback;

View File

@ -0,0 +1,196 @@
import React, { useEffect, useRef, useState } from "react";
import {
Button,
Dialog,
View,
Text,
TextField,
TouchableOpacity,
TextFieldRef,
} from "react-native-ui-lib";
import { Dimensions, StyleSheet } from "react-native";
import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
import PenIcon from "@/assets/svgs/PenIcon";
import BinIcon from "@/assets/svgs/BinIcon";
import DropModalIcon from "@/assets/svgs/DropModalIcon";
import CloseXIcon from "@/assets/svgs/CloseXIcon";
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import RemindersIcon from "@/assets/svgs/RemindersIcon";
import MenuIcon from "@/assets/svgs/MenuIcon";
import { IFeedback, useFeedbackContext } from "@/contexts/FeedbackContext";
import FeedbackDialog from "./FeedbackDialog";
const EditFeedback = (props: {
item: IFeedback;
isVisible: boolean;
setIsVisible: (value: boolean) => void;
}) => {
const { updateFeedback, deleteFeedback } = useFeedbackContext();
const [text, setText] = useState<string>(props.item.text);
const [modalVisible, setModalVisible] = useState<boolean>(false);
const textRef = useRef<TextFieldRef>(null);
const { width } = Dimensions.get("screen");
useEffect(() => {
setText(props.item.text);
}, []);
useEffect(() => {
if (props.isVisible) {
setTimeout(() => {
textRef?.current?.focus();
}, 500);
}
}, [props.isVisible]);
const showConfirmationDialog = () => {
setModalVisible(true);
};
const handleDeleteNote = () => {
deleteFeedback(props.item.id);
};
const hideConfirmationDialog = () => {
setModalVisible(false);
};
return (
<Dialog
bottom={true}
height={"90%"}
width={width}
panDirection={PanningDirectionsEnum.DOWN}
onDismiss={() => props.setIsVisible(false)}
containerStyle={{
borderRadius: 15,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
backgroundColor: "white",
width: "100%",
alignSelf: "stretch",
padding: 10,
paddingTop: 3,
margin: 0,
}}
visible={props.isVisible}
>
<View center paddingT-8>
<TouchableOpacity onPress={() => props.setIsVisible(false)}>
<DropModalIcon />
</TouchableOpacity>
</View>
<View row spread paddingH-10 paddingB-15>
<Button
color="#05a8b6"
style={styles.topBtn}
iconSource={() => <CloseXIcon />}
onPress={() => {
props.setIsVisible(false);
}}
/>
<View row>
<Button
style={styles.topBtn}
marginR-10
iconSource={() => <PenIcon />}
onPress={() => {
console.log("selview");
updateFeedback(props.item.id, { text: text });
props.setIsVisible(false);
}}
/>
<Button
style={styles.topBtn}
marginL-5
iconSource={() => <BinIcon />}
onPress={() => {
showConfirmationDialog();
}}
/>
</View>
</View>
<View centerH>
<Text style={styles.title}>{props.item.title} </Text>
</View>
<View style={styles.divider} />
<View row gap-5 paddingR-20>
<View paddingT-8 marginR-5>
<MenuIcon width={20} height={12} />
</View>
<TextField
textAlignVertical="top"
multiline
style={styles.description}
placeholder="Add description"
numberOfLines={3}
value={text}
onChangeText={(value) => {
setText(value);
}}
ref={textRef}
returnKeyType="done"
/>
</View>
<View style={styles.divider} />
<FeedbackDialog
visible={modalVisible}
title={props.item.title}
onDismiss={hideConfirmationDialog}
onConfirm={handleDeleteNote}
/>
</Dialog>
);
};
const styles = StyleSheet.create({
divider: { height: 1, backgroundColor: "#e4e4e4", marginVertical: 15 },
gradient: {
height: "25%",
position: "absolute",
bottom: 0,
width: "100%",
},
buttonContainer: {
position: "absolute",
bottom: 25,
width: "100%",
},
button: {
backgroundColor: "rgb(253, 23, 117)",
paddingVertical: 20,
},
topBtn: {
backgroundColor: "white",
color: "#05a8b6",
marginTop: -3,
},
rotateSwitch: {
marginLeft: 35,
marginBottom: 10,
marginTop: 25,
},
optionsReg: {
fontSize: 16,
fontFamily: "PlusJakartaSans_400Regular",
},
optionsBold: {
fontSize: 16,
fontFamily: "PlusJakartaSans_600SemiBold",
},
optionsIcon: {
marginRight: 10,
},
title: {
fontSize: 22,
fontFamily: "Manrope_500Medium",
},
description: {
fontFamily: "Manrope_400Regular",
fontSize: 14,
},
});
export default EditFeedback;

View File

@ -0,0 +1,50 @@
import { View, Text } from "react-native-ui-lib";
import React, { useState } from "react";
import {
TouchableWithoutFeedback,
} from "react-native-gesture-handler";
import { IFeedback } from "@/contexts/FeedbackContext";
import EditFeedback from "./EditFeedback";
const Feedback = (props: { item: IFeedback }) => {
const [isVisible, setIsVisible] = useState<boolean>(false);
return (
<View>
<TouchableWithoutFeedback onPress={() => setIsVisible(true)}>
<View
backgroundColor="white"
marginV-5
paddingH-13
paddingV-10
style={{ borderRadius: 15, elevation: 2 }}
>
<Text
text70B
style={{ fontSize: 15, fontFamily: "Manrope_600SemiBold" }}
marginB-8
>
{props.item.title}
</Text>
<Text
text70
style={{
fontSize: 13,
fontFamily: "Manrope_400Regular",
color: "#5c5c5c",
}}
>
{props.item.text}
</Text>
</View>
</TouchableWithoutFeedback>
<EditFeedback
item={props.item}
isVisible={isVisible}
setIsVisible={setIsVisible}
/>
</View>
);
};
export default Feedback;

View File

@ -0,0 +1,81 @@
import React from "react";
import { Dialog, Button, Text, View } from "react-native-ui-lib";
import { StyleSheet } from "react-native";
interface FeedbackDialogProps {
visible: boolean;
title: string;
onDismiss: () => void;
onConfirm: () => void;
}
const FeedbackDialog: React.FC<FeedbackDialogProps> = ({
visible,
title,
onDismiss,
onConfirm,
}) => {
return (
<Dialog
visible={visible}
onDismiss={onDismiss}
containerStyle={styles.dialog}
>
<Text center style={styles.title}>
Delete Note
</Text>
<View center>
<Text style={styles.text} center>
Are you sure you want to delete this feedback? {"\n\n"}
<Text style={{ fontSize: 16, fontFamily: "PlusJakartaSans_700Bold" }}>
{title}
</Text>
</Text>
</View>
<View row right gap-8>
<Button
label="Cancel"
onPress={onDismiss}
style={styles.cancelBtn}
color="#999999"
labelStyle={{ fontFamily: "Poppins_500Medium", fontSize: 13.53 }}
/>
<Button
label="Yes"
onPress={onConfirm}
style={styles.confirmBtn}
labelStyle={{ fontFamily: "PlusJakartaSans_500Medium" }}
/>
</View>
</Dialog>
);
};
// Empty stylesheet for future styles
const styles = StyleSheet.create({
confirmBtn: {
backgroundColor: "#ea156d",
},
cancelBtn: {
backgroundColor: "white",
},
dialog: {
backgroundColor: "white",
paddingHorizontal: 25,
paddingTop: 35,
paddingBottom: 17,
borderRadius: 20,
},
title: {
fontFamily: "Manrope_600SemiBold",
fontSize: 22,
marginBottom: 20,
},
text: {
fontFamily: "PlusJakartaSans_400Regular",
fontSize: 16,
marginBottom: 25,
},
});
export default FeedbackDialog;

View File

@ -0,0 +1,35 @@
import { View } from "react-native-ui-lib";
import React from "react";
import { FlatList } from "react-native";
import { useFeedbackContext } from "@/contexts/FeedbackContext";
import Feedback from "./Feedback";
const FeedbackList = (props: { searchText: string }) => {
const { feedbacks } = useFeedbackContext();
const filteredBrainDumps =
props.searchText.trim() === ""
? feedbacks
: feedbacks.filter(
(item) =>
item.title.toLowerCase().includes(props.searchText.toLowerCase()) ||
item.text
.toLowerCase()
.includes(props.searchText.toLowerCase())
);
return (
<View marginB-70>
<FlatList
style={{ zIndex: -1 }}
data={filteredBrainDumps}
keyExtractor={(item) => item.title}
renderItem={({ item }) => (
<Feedback key={item.title} item={item} />
)}
/>
</View>
);
};
export default FeedbackList;

View File

@ -0,0 +1,120 @@
import {Dimensions, ScrollView, StyleSheet} from "react-native";
import React, {useState} from "react";
import {Button, Text, TextField, View} from "react-native-ui-lib";
import HeaderTemplate from "@/components/shared/HeaderTemplate";
import {Feather, MaterialIcons} from "@expo/vector-icons";
import LinearGradient from "react-native-linear-gradient";
import PlusIcon from "@/assets/svgs/PlusIcon";
import AddFeedback from "./AddFeedback";
import FeedbackList from "./FeedbackList";
const FeedbackPage = () => {
const [searchText, setSearchText] = useState<string>("");
const [isAddVisible, setIsAddVisible] = useState<boolean>(false);
return (
<View height={"100%"}>
<View>
<ScrollView
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
>
<View marginH-25>
<HeaderTemplate
message={"Welcome to your Feedback!"}
isWelcome={false}
children={
<Text
style={{fontFamily: "Manrope_400Regular", fontSize: 14}}
>
Drop your feedback here, and{"\n"}organize it later.
</Text>
}
/>
<View>
<View style={styles.searchField} centerV>
<TextField
value={searchText}
onChangeText={(value) => {
setSearchText(value);
}}
leadingAccessory={
<Feather
name="search"
size={24}
color="#9b9b9b"
style={{paddingRight: 10}}
/>
}
style={{
fontFamily: "Manrope_500Medium",
fontSize: 15,
}}
placeholder="Search your feedbacks..."
/>
</View>
<FeedbackList searchText={searchText}/>
</View>
</View>
</ScrollView>
</View>
<LinearGradient
colors={["#f9f8f700", "#f9f8f7"]}
locations={[0,1]}
style={{
position: "absolute",
bottom: 0,
height: 120,
width: Dimensions.get("screen").width,
justifyContent:'center',
alignItems:"center"
}}
>
<Button
style={{
height: 40,
position: "relative",
width: "90%",
bottom: -10,
borderRadius: 30,
backgroundColor: "#fd1775",
}}
color="white"
enableShadow
onPress={() => {
setIsAddVisible(true);
}}
>
<View row centerV centerH>
<PlusIcon />
<Text
white
style={{fontSize: 16, fontFamily: "Manrope_600SemiBold", marginLeft: 5}}
>
New
</Text>
</View>
</Button>
</LinearGradient>
<AddFeedback
addFeedbackProps={{
isVisible: isAddVisible,
setIsVisible: setIsAddVisible,
}}
/>
</View>
);
};
const styles = StyleSheet.create({
searchField: {
borderWidth: 0.7,
borderColor: "#9b9b9b",
borderRadius: 15,
height: 42,
paddingLeft: 10,
marginVertical: 20,
},
});
export default FeedbackPage;

View File

@ -1,183 +1,193 @@
import { import {
Button, Button,
ButtonSize, ButtonSize,
Colors, Colors,
KeyboardAwareScrollView, KeyboardAwareScrollView,
LoaderScreen, LoaderScreen,
Text, Text,
TextField, TextField,
TextFieldRef, TextFieldRef,
View, View,
} from "react-native-ui-lib"; } from "react-native-ui-lib";
import React, {useRef, useState} from "react"; import React, { useRef, useState } from "react";
import {useSignIn} from "@/hooks/firebase/useSignIn"; import { useSignIn } from "@/hooks/firebase/useSignIn";
import {KeyboardAvoidingView, Platform, StyleSheet} from "react-native"; import { KeyboardAvoidingView, Platform, StyleSheet } from "react-native";
import Toast from "react-native-toast-message"; import Toast from "react-native-toast-message";
import KeyboardManager from "react-native-keyboard-manager"; import KeyboardManager from "react-native-keyboard-manager";
import {SafeAreaView} from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
import {useRouter} from "expo-router"; import { useRouter } from "expo-router";
KeyboardManager.setEnableAutoToolbar(true); if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(true);
const SignInPage = () => { const SignInPage = () => {
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const passwordRef = useRef<TextFieldRef>(null); const passwordRef = useRef<TextFieldRef>(null);
const {mutateAsync: signIn, error, isError, isLoading} = useSignIn(); const { mutateAsync: signIn, error, isError, isLoading } = useSignIn();
const router = useRouter() const router = useRouter();
const handleSignIn = async () => { const handleSignIn = async () => {
await signIn({email, password}); await signIn({ email, password });
if (!isError) { if (!isError) {
Toast.show({ Toast.show({
type: "success", type: "success",
text1: "Login successful!", text1: "Login successful!",
}); });
} else { } else {
Toast.show({ Toast.show({
type: "error", type: "error",
text1: "Error logging in", text1: "Error logging in",
text2: `${error}`, text2: `${error}`,
}); });
} }
}; };
return ( return (
<SafeAreaView style={{flex: 1}}> <SafeAreaView style={{ flex: 1 }}>
<KeyboardAwareScrollView contentContainerStyle={{flexGrow: 1}} enableOnAndroid> <KeyboardAwareScrollView
<View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%"}}> contentContainerStyle={{ flexGrow: 1 }}
<View gap-13 width={"100%"} marginB-20> enableOnAndroid
<Text style={{fontSize: 40, fontFamily: 'Manrope_600SemiBold'}}> >
Jump back into Cally <View
</Text> style={{ flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%" }}
<Text color={"#919191"} style={{fontSize: 20}}> >
Please enter your details. <View gap-13 width={"100%"} marginB-20>
</Text> <Text style={{ fontSize: 40, fontFamily: "Manrope_600SemiBold" }}>
</View> Jump back into Cally
</Text>
<Text color={"#919191"} style={{ fontSize: 20 }}>
Please enter your details.
</Text>
</View>
<KeyboardAvoidingView style={{width: "100%"}} <KeyboardAvoidingView
contentContainerStyle={{justifyContent: "center"}} style={{ width: "100%" }}
keyboardVerticalOffset={50} contentContainerStyle={{ justifyContent: "center" }}
behavior={Platform.OS === "ios" ? "padding" : "height"} keyboardVerticalOffset={50}
> behavior={Platform.OS === "ios" ? "padding" : "height"}
<TextField >
placeholder="Email" <TextField
keyboardType={"email-address"} placeholder="Email"
returnKeyType={"next"} keyboardType={"email-address"}
textContentType={"emailAddress"} returnKeyType={"next"}
defaultValue={email} textContentType={"emailAddress"}
onChangeText={setEmail} defaultValue={email}
style={styles.textfield} onChangeText={setEmail}
autoComplete={"email"} style={styles.textfield}
autoCorrect={false} autoComplete={"email"}
onSubmitEditing={() => { autoCorrect={false}
// Move focus to the description field onSubmitEditing={() => {
passwordRef.current?.focus(); // Move focus to the description field
}} passwordRef.current?.focus();
/> }}
<TextField />
ref={passwordRef} <TextField
placeholder="Password" ref={passwordRef}
textContentType={"oneTimeCode"} placeholder="Password"
value={password} textContentType={"oneTimeCode"}
onChangeText={setPassword} value={password}
secureTextEntry onChangeText={setPassword}
style={styles.textfield} secureTextEntry
autoCorrect={false} style={styles.textfield}
/> autoCorrect={false}
</KeyboardAvoidingView> />
</KeyboardAvoidingView>
<View flexG/> <View flexG />
<Button <Button
label="Log in" label="Log in"
marginT-50 marginT-50
labelStyle={{ labelStyle={{
fontFamily: "PlusJakartaSans_600SemiBold", fontFamily: "PlusJakartaSans_600SemiBold",
fontSize: 16, fontSize: 16,
}} }}
onPress={handleSignIn} onPress={handleSignIn}
style={{marginBottom: 20, height: 50}} style={{ marginBottom: 20, height: 50 }}
backgroundColor="#fd1775" backgroundColor="#fd1775"
/> />
{isError && ( {isError && (
<Text center style={{marginBottom: 20}}>{`${ <Text center style={{ marginBottom: 20 }}>{`${
error?.toString()?.split("]")?.[1] error?.toString()?.split("]")?.[1]
}`}</Text> }`}</Text>
)} )}
<View row centerH marginB-5 gap-5> <View row centerH marginB-5 gap-5>
<Text style={styles.jakartaLight}>Don't have an account?</Text> <Text style={styles.jakartaLight}>Don't have an account?</Text>
<Button <Button
onPress={() => router.replace("/(unauth)/sign_up")} onPress={() => router.replace("/(unauth)/sign_up")}
label="Sign Up" label="Sign Up"
labelStyle={[ labelStyle={[
styles.jakartaMedium, styles.jakartaMedium,
{textDecorationLine: "none", color: "#fd1575"}, { textDecorationLine: "none", color: "#fd1575" },
]} ]}
link link
size={ButtonSize.xSmall} size={ButtonSize.xSmall}
padding-0 padding-0
margin-0 margin-0
text70 text70
left left
color="#fd1775" color="#fd1775"
/> />
</View> </View>
{/*<View row centerH marginB-5 gap-5>*/} {/*<View row centerH marginB-5 gap-5>*/}
{/* <Text text70>Forgot your password?</Text>*/} {/* <Text text70>Forgot your password?</Text>*/}
{/* <Button*/} {/* <Button*/}
{/* onPress={() => router.replace("/(unauth)/sign_up")}*/} {/* onPress={() => router.replace("/(unauth)/sign_up")}*/}
{/* label="Reset password"*/} {/* label="Reset password"*/}
{/* labelStyle={[*/} {/* labelStyle={[*/}
{/* styles.jakartaMedium,*/} {/* styles.jakartaMedium,*/}
{/* {textDecorationLine: "none", color: "#fd1575"},*/} {/* {textDecorationLine: "none", color: "#fd1575"},*/}
{/* ]}*/} {/* ]}*/}
{/* link*/} {/* link*/}
{/* size={ButtonSize.xSmall}*/} {/* size={ButtonSize.xSmall}*/}
{/* padding-0*/} {/* padding-0*/}
{/* margin-0*/} {/* margin-0*/}
{/* text70*/} {/* text70*/}
{/* left*/} {/* left*/}
{/* avoidInnerPadding*/} {/* avoidInnerPadding*/}
{/* color="#fd1775"*/} {/* color="#fd1775"*/}
{/* />*/} {/* />*/}
{/*</View>*/} {/*</View>*/}
{isLoading && ( {isLoading && (
<LoaderScreen overlay message={"Signing in..."} backgroundColor={Colors.white} <LoaderScreen
color={Colors.grey40}/> overlay
)} message={"Signing in..."}
</View> backgroundColor={Colors.white}
</KeyboardAwareScrollView> color={Colors.grey40}
</SafeAreaView> />
); )}
</View>
</KeyboardAwareScrollView>
</SafeAreaView>
);
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
textfield: { textfield: {
backgroundColor: "white", backgroundColor: "white",
marginVertical: 10, marginVertical: 10,
padding: 30, padding: 30,
height: 45, height: 45,
borderRadius: 50, borderRadius: 50,
fontFamily: "PlusJakartaSans_300Light", fontFamily: "PlusJakartaSans_300Light",
}, },
jakartaLight: { jakartaLight: {
fontFamily: "PlusJakartaSans_300Light", fontFamily: "PlusJakartaSans_300Light",
fontSize: 16, fontSize: 16,
color: "#484848", color: "#484848",
}, },
jakartaMedium: { jakartaMedium: {
fontFamily: "PlusJakartaSans_500Medium", fontFamily: "PlusJakartaSans_500Medium",
fontSize: 16, fontSize: 16,
color: "#919191", color: "#919191",
textDecorationLine: "underline", textDecorationLine: "underline",
}, },
}); });
export default SignInPage; export default SignInPage;

View File

@ -1,260 +1,276 @@
import React, {useRef, useState} from "react"; import React, { useRef, useState } from "react";
import { import {
Button, Button,
ButtonSize, ButtonSize,
Checkbox, Checkbox,
Colors, Colors,
KeyboardAwareScrollView, KeyboardAwareScrollView,
LoaderScreen, LoaderScreen,
Text, Text,
TextField, TextField,
TextFieldRef, TextFieldRef,
TouchableOpacity, TouchableOpacity,
View, View,
} from "react-native-ui-lib"; } from "react-native-ui-lib";
import {useSignUp} from "@/hooks/firebase/useSignUp"; import { useSignUp } from "@/hooks/firebase/useSignUp";
import {KeyboardAvoidingView, StyleSheet} from "react-native"; import { KeyboardAvoidingView, Platform, StyleSheet } from "react-native";
import {AntDesign} from "@expo/vector-icons"; import { AntDesign } from "@expo/vector-icons";
import KeyboardManager from "react-native-keyboard-manager"; import KeyboardManager from "react-native-keyboard-manager";
import {SafeAreaView} from "react-native-safe-area-context"; import { SafeAreaView } from "react-native-safe-area-context";
import {useRouter} from "expo-router"; import { useRouter } from "expo-router";
KeyboardManager.setEnableAutoToolbar(true); if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(true);
const SignUpPage = () => { const SignUpPage = () => {
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [firstName, setFirstName] = useState<string>(""); const [firstName, setFirstName] = useState<string>("");
const [lastName, setLastName] = useState<string>(""); const [lastName, setLastName] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [isPasswordVisible, setIsPasswordVisible] = useState<boolean>(false); const [isPasswordVisible, setIsPasswordVisible] = useState<boolean>(false);
const [allowFaceID, setAllowFaceID] = useState<boolean>(false); const [allowFaceID, setAllowFaceID] = useState<boolean>(false);
const [acceptTerms, setAcceptTerms] = useState<boolean>(false); const [acceptTerms, setAcceptTerms] = useState<boolean>(false);
const {mutateAsync: signUp, isLoading} = useSignUp(); const { mutateAsync: signUp, isLoading } = useSignUp();
const lnameRef = useRef<TextFieldRef>(null); const lnameRef = useRef<TextFieldRef>(null);
const emailRef = useRef<TextFieldRef>(null); const emailRef = useRef<TextFieldRef>(null);
const passwordRef = useRef<TextFieldRef>(null); const passwordRef = useRef<TextFieldRef>(null);
const router = useRouter() const router = useRouter();
const handleSignUp = async () => { const handleSignUp = async () => {
await signUp({email, password, firstName, lastName}); await signUp({ email, password, firstName, lastName });
router.replace("/(unauth)/cal_sync") router.replace("/(unauth)/cal_sync");
}; };
return ( return (
<SafeAreaView style={{flex: 1}}> <SafeAreaView style={{ flex: 1 }}>
<KeyboardAwareScrollView contentContainerStyle={{flexGrow: 1}} enableOnAndroid> <KeyboardAwareScrollView
<View style={{flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%"}}> contentContainerStyle={{ flexGrow: 1 }}
<View gap-13 width={"100%"} marginB-20> enableOnAndroid
<Text style={{fontSize: 40, fontFamily: 'Manrope_600SemiBold'}}> >
Get started with Cally <View
</Text> style={{ flex: 1, padding: 21, paddingBottom: 45, paddingTop: "20%" }}
<Text color={"#919191"} style={{fontSize: 20}}> >
Please enter your details. <View gap-13 width={"100%"} marginB-20>
</Text> <Text style={{ fontSize: 40, fontFamily: "Manrope_600SemiBold" }}>
</View> Get started with Cally
</Text>
<Text color={"#919191"} style={{ fontSize: 20 }}>
Please enter your details.
</Text>
</View>
<KeyboardAvoidingView style={{width: '100%'}}> <KeyboardAvoidingView style={{ width: "100%" }}>
<TextField <TextField
marginT-30 marginT-30
autoFocus autoFocus
placeholder="First name" placeholder="First name"
value={firstName} value={firstName}
onChangeText={setFirstName} onChangeText={setFirstName}
style={styles.textfield} style={styles.textfield}
onSubmitEditing={() => { onSubmitEditing={() => {
lnameRef.current?.focus(); lnameRef.current?.focus();
}} }}
blurOnSubmit={false} blurOnSubmit={false}
accessibilityLabel="First name input" accessibilityLabel="First name input"
accessibilityHint="Enter your first name" accessibilityHint="Enter your first name"
accessible accessible
returnKeyType="next" returnKeyType="next"
textContentType="givenName" textContentType="givenName"
importantForAccessibility="yes" importantForAccessibility="yes"
/> />
<TextField <TextField
ref={lnameRef} ref={lnameRef}
placeholder="Last name" placeholder="Last name"
value={lastName} value={lastName}
onChangeText={setLastName} onChangeText={setLastName}
style={styles.textfield} style={styles.textfield}
onSubmitEditing={() => { onSubmitEditing={() => {
emailRef.current?.focus(); emailRef.current?.focus();
}} }}
blurOnSubmit={false} blurOnSubmit={false}
accessibilityLabel="Last name input" accessibilityLabel="Last name input"
accessibilityHint="Enter your last name" accessibilityHint="Enter your last name"
accessible accessible
returnKeyType="next" returnKeyType="next"
textContentType="familyName" textContentType="familyName"
importantForAccessibility="yes" importantForAccessibility="yes"
/> />
<TextField <TextField
placeholder="Email" placeholder="Email"
keyboardType={"email-address"} keyboardType={"email-address"}
returnKeyType={"next"} returnKeyType={"next"}
textContentType={"emailAddress"} textContentType={"emailAddress"}
defaultValue={email} defaultValue={email}
onChangeText={setEmail} onChangeText={setEmail}
style={styles.textfield} style={styles.textfield}
autoComplete={"email"} autoComplete={"email"}
autoCorrect={false} autoCorrect={false}
ref={emailRef} ref={emailRef}
onSubmitEditing={() => { onSubmitEditing={() => {
passwordRef.current?.focus(); passwordRef.current?.focus();
}} }}
/> />
<View <View
centerV centerV
style={[styles.textfield, {padding: 0, paddingHorizontal: 30}]} style={[styles.textfield, { padding: 0, paddingHorizontal: 30 }]}
> >
<TextField <TextField
ref={passwordRef} ref={passwordRef}
placeholder="Password" placeholder="Password"
style={styles.jakartaLight} style={styles.jakartaLight}
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
secureTextEntry={!isPasswordVisible} secureTextEntry={!isPasswordVisible}
trailingAccessory={ trailingAccessory={
<TouchableOpacity <TouchableOpacity
onPress={() => setIsPasswordVisible(!isPasswordVisible)} onPress={() => setIsPasswordVisible(!isPasswordVisible)}
> >
<AntDesign <AntDesign
name={isPasswordVisible ? "eye" : "eyeo"} name={isPasswordVisible ? "eye" : "eyeo"}
size={24} size={24}
color="gray" color="gray"
/> />
</TouchableOpacity> </TouchableOpacity>
} }
/> />
</View> </View>
</KeyboardAvoidingView>
</KeyboardAvoidingView> <View gap-5 marginT-15>
<View row centerV>
<Checkbox
style={[styles.check]}
color="#919191"
value={allowFaceID}
onValueChange={(value) => {
setAllowFaceID(value);
}}
/>
<Text style={styles.jakartaLight} marginL-10>
Allow FaceID for login in future
</Text>
</View>
<View row centerV>
<Checkbox
style={styles.check}
color="#919191"
value={acceptTerms}
onValueChange={(value) => setAcceptTerms(value)}
/>
<View row style={{ flexWrap: "wrap", marginLeft: 10 }}>
<Text style={styles.jakartaLight}>I accept the</Text>
<TouchableOpacity>
<Text text90 style={styles.jakartaMedium}>
{" "}
terms and conditions
</Text>
</TouchableOpacity>
<Text style={styles.jakartaLight}> and </Text>
<TouchableOpacity>
<Text text90 style={styles.jakartaMedium}>
{" "}
privacy policy
</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View gap-5 marginT-15> <View flexG style={{ minHeight: 50 }} />
<View row centerV>
<Checkbox
style={[styles.check]}
color="#919191"
value={allowFaceID}
onValueChange={(value) => {
setAllowFaceID(value);
}}
/>
<Text style={styles.jakartaLight} marginL-10>
Allow FaceID for login in future
</Text>
</View>
<View row centerV>
<Checkbox
style={styles.check}
color="#919191"
value={acceptTerms}
onValueChange={(value) => setAcceptTerms(value)}
/>
<View row style={{flexWrap: "wrap", marginLeft: 10}}>
<Text style={styles.jakartaLight}>
I accept the
</Text>
<TouchableOpacity>
<Text text90 style={styles.jakartaMedium}>
{" "}
terms and conditions
</Text>
</TouchableOpacity>
<Text style={styles.jakartaLight}> and </Text>
<TouchableOpacity>
<Text text90 style={styles.jakartaMedium}>
{" "}
privacy policy
</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View flexG style={{minHeight: 50}}/> <View>
<Button
label="Register"
disabled={!acceptTerms}
labelStyle={{
fontFamily: "PlusJakartaSans_600SemiBold",
fontSize: 16,
}}
onPress={handleSignUp}
backgroundColor={"#fd1775"}
style={{ marginBottom: 0, height: 50 }}
/>
<View row centerH marginT-10 marginB-2 gap-5>
<Text
style={[
styles.jakartaLight,
{ fontSize: 16, color: "#484848" },
]}
center
>
Already have an account?
</Text>
<View> <Button
<Button label="Log in"
label="Register" labelStyle={[
disabled={!acceptTerms} styles.jakartaMedium,
labelStyle={{ {
fontFamily: "PlusJakartaSans_600SemiBold", fontSize: 16,
fontSize: 16, textDecorationLine: "none",
}} color: "#fd1775",
onPress={handleSignUp} },
backgroundColor={"#fd1775"} ]}
style={{marginBottom: 0, height: 50}} flexS
/> margin-0
<View row centerH marginT-10 marginB-2 gap-5> link
<Text style={[styles.jakartaLight, {fontSize: 16, color: "#484848"}]} center> color="#fd1775"
Already have an account? size={ButtonSize.small}
</Text> text70
onPress={() => router.replace("/(unauth)/sign_in")}
/>
</View>
</View>
</View>
</KeyboardAwareScrollView>
<Button {isLoading && (
label="Log in" <LoaderScreen
labelStyle={[ overlay
styles.jakartaMedium, message={"Signing up..."}
{fontSize: 16, textDecorationLine: "none", color: "#fd1775"}, backgroundColor={Colors.white}
]} color={Colors.grey40}
flexS />
margin-0 )}
link </SafeAreaView>
color="#fd1775" );
size={ButtonSize.small}
text70
onPress={() => router.replace("/(unauth)/sign_in")}
/>
</View>
</View>
</View>
</KeyboardAwareScrollView>
{isLoading && (
<LoaderScreen overlay message={"Signing up..."} backgroundColor={Colors.white}
color={Colors.grey40}/>
)}
</SafeAreaView>
);
}; };
export default SignUpPage; export default SignUpPage;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
textfield: { textfield: {
backgroundColor: "white", backgroundColor: "white",
marginVertical: 8, marginVertical: 8,
padding: 30, padding: 30,
height: 44, height: 44,
borderRadius: 50, borderRadius: 50,
fontFamily: "PlusJakartaSans_300Light", fontFamily: "PlusJakartaSans_300Light",
fontSize: 13, fontSize: 13,
color: "#919191", color: "#919191",
}, },
jakartaLight: { jakartaLight: {
fontFamily: "PlusJakartaSans_300Light", fontFamily: "PlusJakartaSans_300Light",
fontSize: 13, fontSize: 13,
color: "#919191", color: "#919191",
}, },
jakartaMedium: { jakartaMedium: {
fontFamily: "PlusJakartaSans_500Medium", fontFamily: "PlusJakartaSans_500Medium",
fontSize: 13, fontSize: 13,
color: "#919191", color: "#919191",
textDecorationLine: "underline", textDecorationLine: "underline",
}, },
title: {fontFamily: "Manrope_600SemiBold", fontSize: 34, marginTop: 50}, title: { fontFamily: "Manrope_600SemiBold", fontSize: 34, marginTop: 50 },
subtitle: {fontFamily: "PlusJakartaSans_400Regular", fontSize: 16}, subtitle: { fontFamily: "PlusJakartaSans_400Regular", fontSize: 16 },
check: { check: {
borderRadius: 3, borderRadius: 3,
aspectRatio: 1, aspectRatio: 1,
width: 18, width: 18,
color: "#919191", color: "#919191",
borderColor: "#919191", borderColor: "#919191",
borderWidth: 1, borderWidth: 1,
}, },
}); });

View File

@ -1,5 +1,10 @@
import { useCreateNote } from "@/hooks/firebase/useCreateNote";
import { useDeleteNote } from "@/hooks/firebase/useDeleteNote";
import { useGetNotes } from "@/hooks/firebase/useGetNotes";
import { useUpdateNote } from "@/hooks/firebase/useUpdateNote";
import { MaterialCommunityIcons } from "@expo/vector-icons"; import { MaterialCommunityIcons } from "@expo/vector-icons";
import { createContext, useContext, useState } from "react"; import { createContext, useContext, useState } from "react";
import { create } from "react-test-renderer";
export interface IBrainDump { export interface IBrainDump {
id: number; id: number;
@ -8,7 +13,7 @@ export interface IBrainDump {
} }
interface IBrainDumpContext { interface IBrainDumpContext {
brainDumps: IBrainDump[]; brainDumps: IBrainDump[] | undefined;
updateBrainDumpItem: (id: number, changes: Partial<IBrainDump>) => void; updateBrainDumpItem: (id: number, changes: Partial<IBrainDump>) => void;
isAddingBrainDump: boolean; isAddingBrainDump: boolean;
setIsAddingBrainDump: (value: boolean) => void; setIsAddingBrainDump: (value: boolean) => void;
@ -23,70 +28,43 @@ const BrainDumpContext = createContext<IBrainDumpContext | undefined>(
export const BrainDumpProvider: React.FC<{ children: React.ReactNode }> = ({ export const BrainDumpProvider: React.FC<{ children: React.ReactNode }> = ({
children, children,
}) => { }) => {
const { data: brainDumps } = useGetNotes();
const { mutate: deleteNote } = useDeleteNote();
const { mutateAsync: createBrainDump } = useCreateNote();
const { mutateAsync: updateNoteMutate } = useUpdateNote();
const [isAddingBrainDump, setIsAddingBrainDump] = useState<boolean>(false); const [isAddingBrainDump, setIsAddingBrainDump] = useState<boolean>(false);
const [brainDumps, setBrainDumps] = useState<IBrainDump[]>([
{
id: 0,
title: "Favorite Weekend Activities",
description:
"What's something fun we can do together this weekend? Maybe a new game, a picnic?",
},
{
id: 1,
title: "Whats For Dinner",
description:
"Whats one meal youd love to have for dinner this week?",
},
{
id: 2,
title: "The Best Thing About Today",
description:
"What was the highlight of your day? Lets each take a moment to share something!",
},
{
id: 3,
title: "A Dream Vacation Spot",
description:
"If we could go anywhere in the world right now, where would it be? Everyone pick one dream destination and tell us why.",
},
{
id: 4,
title: "Favorite Childhood Memory",
description:
"Whats a favorite memory from your childhood? Lets take a trip down memory lane and share some of the moments that made us smile.",
},
{
id: 5,
title: "A New Family Tradition",
description:
"Whats one new tradition we could start as a family? Maybe a weekly movie night, a monthly game day, or a yearly family trip. Share your ideas!",
},
]);
const addBrainDump = (BrainDump: IBrainDump) => { const addBrainDump = (BrainDump: IBrainDump) => {
setBrainDumps((prevBrainDumps) => [ createBrainDump(BrainDump);
...prevBrainDumps,
{
...BrainDump,
id: prevBrainDumps.length
? prevBrainDumps[prevBrainDumps.length - 1].id + 1
: 0,
},
]);
}; };
const updateBrainDumpItem = (id: number, changes: Partial<IBrainDump>) => { const updateBrainDumpItem = (id: number, changes: Partial<IBrainDump>) => {
setBrainDumps((prevBrainDumps) => updateNoteMutate(
prevBrainDumps.map((BrainDump) => {
BrainDump.id === id ? { ...BrainDump, ...changes } : BrainDump id: id,
) changes: changes,
},
{
onSuccess: (data) => {
console.log("Note updated successfully", data);
},
onError: (error) => {
console.error("Failed to update note:", error);
},
}
); );
}; };
const deleteBrainDump = (id: number) => { const deleteBrainDump = (id: number) => {
setBrainDumps((prevBrainDumps) => deleteNote(id.toString(), {
prevBrainDumps.filter((BrainDump) => BrainDump.id !== id) onSuccess: () => {
); console.log("Feedback deleted successfully");
},
onError: (error) => {
console.error("Failed to delete feedback:", error);
},
});
}; };
return ( return (
@ -97,7 +75,7 @@ export const BrainDumpProvider: React.FC<{ children: React.ReactNode }> = ({
isAddingBrainDump, isAddingBrainDump,
setIsAddingBrainDump, setIsAddingBrainDump,
addBrainDump, addBrainDump,
deleteBrainDump deleteBrainDump,
}} }}
> >
{children} {children}

View File

@ -0,0 +1,87 @@
import { useCreateFeedback } from "@/hooks/firebase/useCreateFeedback";
import { useDeleteFeedback } from "@/hooks/firebase/useDeleteFeedback";
import { useGetFeedbacks } from "@/hooks/firebase/useGetFeedbacks";
import { useUpdateFeedback } from "@/hooks/firebase/useUpdateFeedback";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { createContext, useContext, useState } from "react";
export interface IFeedback {
id: number;
title: string;
text: string;
}
interface IFeedbackContext {
feedbacks: IFeedback[] | undefined;
isAddingFeedback: boolean;
setIsAddingFeedback: (value: boolean) => void;
addFeedback: (BrainDump: IFeedback) => void;
updateFeedback: (id: number, changes: Partial<IFeedback>) => void;
deleteFeedback: (id: number) => void;
}
const FeedbackContext = createContext<IFeedbackContext | undefined>(undefined);
export const FeedbackProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const {
mutateAsync: createFeedback,
isLoading: isAdding,
isError,
} = useCreateFeedback();
const { data: feedbacks } = useGetFeedbacks();
const { mutate: deleteFeedbackMutate } = useDeleteFeedback();
const { mutate: updateFeedbackMutate } = useUpdateFeedback();
const [isAddingFeedback, setIsAddingFeedback] = useState<boolean>(false);
const addFeedback = (Feedback: IFeedback) => {
createFeedback({ title: Feedback.title, text: Feedback.text });
};
const updateFeedback = (id: number, changes: Partial<IFeedback>) => {
updateFeedbackMutate(
{
id: id,
changes: changes,
},
{
onSuccess: (data) => {
console.log("Feedback updated successfully", data);
},
onError: (error) => {
console.error("Failed to update feedback:", error);
},
}
);
};
const deleteFeedback = (id: number) => {
deleteFeedbackMutate(id.toString(), {
onSuccess: () => {
console.log("Feedback deleted successfully");
},
onError: (error) => {
console.error("Failed to delete feedback:", error);
},
});
};
return (
<FeedbackContext.Provider
value={{
feedbacks,
isAddingFeedback,
setIsAddingFeedback,
addFeedback,
updateFeedback,
deleteFeedback,
}}
>
{children}
</FeedbackContext.Provider>
);
};
export const useFeedbackContext = () => useContext(FeedbackContext)!;

View File

@ -0,0 +1,85 @@
import {useAuthContext} from "@/contexts/AuthContext";
import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
import { IFeedback } from "@/contexts/FeedbackContext";
export const useCreateFeedback = () => {
const {user: currentUser, profileData} = useAuthContext()
const queryClients = useQueryClient()
return useMutation({
mutationKey: ["createFeedback"],
mutationFn: async (feedback: Partial<IFeedback>) => {
try {
if (feedback.id) {
const snapshot = await firestore()
.collection("Feedbacks")
.where("id", "==", feedback.id)
.get();
if (!snapshot.empty) {
const docId = snapshot.docs[0].id;
await firestore()
.collection("Feedbacks")
.doc(docId)
.set({
...feedback,
creatorId: currentUser?.uid,
}, {merge: true});
return;
}
}
const newDoc = firestore().collection('Feedbacks').doc();
await firestore()
.collection("Feedbacks")
.add({...feedback, id: newDoc.id, creatorId: currentUser?.uid});
} catch (e) {
console.error(e);
}
},
onSuccess: () => {
queryClients.invalidateQueries("feedbacks")
}
})
}
export const useCreateFeedbacksFromProvider = () => {
const { user: currentUser } = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["createFeedbacksFromProvider"],
mutationFn: async (feedbackDataArray: Partial<IFeedback>[]) => {
try {
const promises = feedbackDataArray.map(async (feedbackData) => {
console.log("Processing FeedbackData: ", feedbackData);
const snapshot = await firestore()
.collection("Feedbacks")
.where("id", "==", feedbackData.id)
.get();
if (snapshot.empty) {
return firestore()
.collection("Feedbacks")
.add({ ...feedbackData, creatorId: currentUser?.uid });
} else {
const docId = snapshot.docs[0].id;
return firestore()
.collection("Feedbacks")
.doc(docId)
.set({ ...feedbackData, creatorId: currentUser?.uid }, { merge: true });
}
});
await Promise.all(promises);
} catch (e) {
console.error("Error creating/updating feedbacks: ", e);
}
},
onSuccess: () => {
queryClient.invalidateQueries("feedbacks");
}
});
};

View File

@ -0,0 +1,91 @@
import { useAuthContext } from "@/contexts/AuthContext";
import { useMutation, useQueryClient } from "react-query";
import firestore from "@react-native-firebase/firestore";
import { IFeedback } from "@/contexts/FeedbackContext";
import { IBrainDump } from "@/contexts/DumpContext";
export const useCreateNote = () => {
const { user: currentUser, profileData } = useAuthContext();
const queryClients = useQueryClient();
return useMutation({
mutationKey: ["createNote"],
mutationFn: async (note: Partial<IBrainDump>) => {
try {
if (note.id) {
const snapshot = await firestore()
.collection("BrainDumps")
.where("id", "==", note.id)
.get();
if (!snapshot.empty) {
const docId = snapshot.docs[0].id;
await firestore()
.collection("BrainDumps")
.doc(docId)
.set(
{
...note,
creatorId: currentUser?.uid,
},
{ merge: true }
);
return;
}
}
const newDoc = firestore().collection("BrainDumps").doc();
await firestore()
.collection("BrainDumps")
.add({ ...note, id: newDoc.id, creatorId: currentUser?.uid });
} catch (e) {
console.error(e);
}
},
onSuccess: () => {
queryClients.invalidateQueries("braindumps");
},
});
};
export const useCreateNotesFromProvider = () => {
const { user: currentUser } = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["createNotesFromProvider"],
mutationFn: async (noteDataArray: Partial<IFeedback>[]) => {
try {
const promises = noteDataArray.map(async (noteData) => {
console.log("Processing NoteData: ", noteData);
const snapshot = await firestore()
.collection("BrainDumps")
.where("id", "==", noteData.id)
.get();
if (snapshot.empty) {
return firestore()
.collection("BrainDumps")
.add({ ...noteData, creatorId: currentUser?.uid });
} else {
const docId = snapshot.docs[0].id;
return firestore()
.collection("BrainDumps")
.doc(docId)
.set(
{ ...noteData, creatorId: currentUser?.uid },
{ merge: true }
);
}
});
await Promise.all(promises);
} catch (e) {
console.error("Error creating/updating braindumps: ", e);
}
},
onSuccess: () => {
queryClient.invalidateQueries("braindumps");
},
});
};

View File

@ -0,0 +1,45 @@
import {useAuthContext} from "@/contexts/AuthContext";
import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
export const useDeleteFeedback = () => {
const { user: currentUser } = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["deleteFeedback"],
mutationFn: async (feedbackId: string) => {
try {
// Find the document with matching id field
const snapshot = await firestore()
.collection("Feedbacks")
.where("id", "==", feedbackId)
.get();
if (snapshot.empty) {
throw new Error("Feedback not found");
}
// Get the first matching document
const docId = snapshot.docs[0].id;
// Optional: Check if the current user is the creator
const feedbackData = snapshot.docs[0].data();
if (feedbackData.creatorId !== currentUser?.uid) {
throw new Error(
"Unauthorized: You can only delete your own feedback"
);
}
// Delete the document
await firestore().collection("Feedbacks").doc(docId).delete();
} catch (e) {
console.error("Error deleting feedback: ", e);
throw e; // Re-throw the error to be handled by the mutation
}
},
onSuccess: () => {
queryClient.invalidateQueries("feedbacks");
},
});
};

View File

@ -0,0 +1,39 @@
import { useAuthContext } from "@/contexts/AuthContext";
import { useMutation, useQueryClient } from "react-query";
import firestore from "@react-native-firebase/firestore";
export const useDeleteNote = () => {
const { user: currentUser } = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["deleteNote"],
mutationFn: async (noteId: string) => {
try {
const snapshot = await firestore()
.collection("BrainDumps")
.where("id", "==", noteId)
.get();
if (snapshot.empty) {
throw new Error("Note not found");
}
const docId = snapshot.docs[0].id;
const noteData = snapshot.docs[0].data();
if (noteData.creatorId !== currentUser?.uid) {
throw new Error("Unauthorized: You can only delete your own Note");
}
await firestore().collection("BrainDumps").doc(docId).delete();
} catch (e) {
console.error("Error deleting note: ", e);
throw e;
}
},
onSuccess: () => {
queryClient.invalidateQueries("braindumps");
},
});
};

View File

@ -0,0 +1,28 @@
import { useAuthContext } from "@/contexts/AuthContext";
import { useQuery } from "react-query";
import firestore from "@react-native-firebase/firestore";
import { IFeedback } from "@/contexts/FeedbackContext";
export const useGetFeedbacks = () => {
const { user: currentUser } = useAuthContext();
return useQuery<IFeedback[]>({
queryKey: ["feedbacks", currentUser?.uid],
queryFn: async () => {
try {
const snapshot = await firestore()
.collection("Feedbacks")
.where("creatorId", "==", currentUser?.uid)
.get();
return snapshot.docs.map((doc) => ({
...doc.data(),
})) as IFeedback[];
} catch (error) {
console.error("Error fetching feedbacks:", error);
throw error;
}
},
enabled: !!currentUser?.uid, // Only run query if we have a user ID
});
};

View File

@ -0,0 +1,28 @@
import { useAuthContext } from "@/contexts/AuthContext";
import { useQuery } from "react-query";
import firestore from "@react-native-firebase/firestore";
import { IBrainDump } from "@/contexts/DumpContext";
export const useGetNotes = () => {
const { user: currentUser } = useAuthContext();
return useQuery<IBrainDump[]>({
queryKey: ["braindumps", currentUser?.uid],
queryFn: async () => {
try {
const snapshot = await firestore()
.collection("BrainDumps")
.where("creatorId", "==", currentUser?.uid)
.get();
return snapshot.docs.map((doc) => ({
...doc.data(),
})) as IBrainDump[];
} catch (error) {
console.error("Error fetching braindumps:", error);
throw error;
}
},
enabled: !!currentUser?.uid,
});
};

View File

@ -0,0 +1,65 @@
import { useAuthContext } from "@/contexts/AuthContext";
import { useMutation, useQueryClient } from "react-query";
import firestore from "@react-native-firebase/firestore";
import { IFeedback } from "@/contexts/FeedbackContext";
interface UpdateFeedbackParams {
id: number;
changes: Partial<IFeedback>;
}
export const useUpdateFeedback = () => {
const { user: currentUser } = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["updateFeedback"],
mutationFn: async ({ id, changes }: UpdateFeedbackParams) => {
try {
const snapshot = await firestore()
.collection("Feedbacks")
.where("id", "==", id)
.get();
if (snapshot.empty) {
throw new Error("Feedback not found");
}
const docId = snapshot.docs[0].id;
const feedbackData = snapshot.docs[0].data();
if (feedbackData.creatorId !== currentUser?.uid) {
throw new Error(
"Unauthorized: You can only update your own feedback"
);
}
await firestore()
.collection("Feedbacks")
.doc(docId)
.update({
...changes,
updatedAt: firestore.FieldValue.serverTimestamp(),
lastModifiedBy: currentUser?.uid,
});
return {
id,
...feedbackData,
...changes,
};
} catch (e) {
console.error("Error updating feedback: ", e);
throw e;
}
},
onSuccess: (updatedFeedback) => {
queryClient.invalidateQueries("feedbacks");
queryClient.setQueryData(
["feedback", updatedFeedback.id],
updatedFeedback
);
},
});
};

View File

@ -0,0 +1,65 @@
import { useAuthContext } from "@/contexts/AuthContext";
import { useMutation, useQueryClient } from "react-query";
import firestore from "@react-native-firebase/firestore";
import { IBrainDump } from "@/contexts/DumpContext";
interface UpdateNoteParams {
id: number;
changes: Partial<IBrainDump>;
}
export const useUpdateNote = () => {
const { user: currentUser } = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["updateNote"],
mutationFn: async ({ id, changes }: UpdateNoteParams) => {
try {
const snapshot = await firestore()
.collection("BrainDumps")
.where("id", "==", id)
.get();
if (snapshot.empty) {
throw new Error("Note not found");
}
const docId = snapshot.docs[0].id;
const noteData = snapshot.docs[0].data();
if (noteData.creatorId !== currentUser?.uid) {
throw new Error(
"Unauthorized: You can only update your own note"
);
}
await firestore()
.collection("BrainDumps")
.doc(docId)
.update({
...changes,
updatedAt: firestore.FieldValue.serverTimestamp(),
lastModifiedBy: currentUser?.uid,
});
return {
id,
...noteData,
...changes,
};
} catch (e) {
console.error("Error updating note: ", e);
throw e;
}
},
onSuccess: (updatedNote) => {
queryClient.invalidateQueries("braindumps");
queryClient.setQueryData(
["feedback", updatedNote.id],
updatedNote
);
},
});
};

946
yarn.lock

File diff suppressed because it is too large Load Diff