Merge branch 'dev'

# Conflicts:
#	app/(auth)/calendar/index.tsx
#	components/pages/calendar/ManuallyAddEventModal.tsx
#	components/pages/main/SignUpPage.tsx
#	package.json
This commit is contained in:
Milan Paunovic
2024-09-27 10:35:21 +02:00
25 changed files with 2586 additions and 1019 deletions

View File

@ -5,6 +5,7 @@ import {Button, Picker, PickerModes, SegmentedControl, Text, View} from "react-n
import {MaterialIcons} from "@expo/vector-icons";
import {AddEventDialog} from "@/components/pages/calendar/AddEventDialog";
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
import { useAuthContext } from "@/contexts/AuthContext";
import {ManuallyAddEventModal} from "@/components/pages/calendar/ManuallyAddEventModal";
const modeMap = new Map([
@ -19,6 +20,7 @@ const months = [
];
export default function Screen() {
const { profileData } = useAuthContext();
const [calendarHeight, setCalendarHeight] = useState(0);
const [mode, setMode] = useState<"week" | "month" | "day">("week");
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
@ -55,7 +57,7 @@ export default function Screen() {
return (
<View style={{flex: 1, height: "100%", padding: 10}}>
<View style={{height: 60, justifyContent: "space-evenly", alignItems: "flex-start"}}>
<Text>Welcome Dalia</Text>
<Text>Welcome {profileData?.firstName}</Text>
<Text>Let's get your week started!</Text>
</View>

View File

@ -1,6 +1,3 @@
import { Text, View } from "react-native-ui-lib";
import GroceryList from "@/components/pages/grocery/GroceryList";
import AddGroceryItem from "@/components/pages/grocery/AddGroceryItem";
import { GroceryProvider } from "@/contexts/GroceryContext";
import React from "react";
import GroceryWrapper from "@/components/pages/grocery/GroceryWrapper";

View File

@ -2,24 +2,18 @@ import AddChore from "@/components/pages/todos/AddChore";
import ProgressCard from "@/components/pages/todos/ProgressCard";
import ToDoItem from "@/components/pages/todos/ToDoItem";
import ToDosList from "@/components/pages/todos/ToDosList";
import ToDosPage from "@/components/pages/todos/ToDosPage";
import HeaderTemplate from "@/components/shared/HeaderTemplate";
import { useAuthContext } from "@/contexts/AuthContext";
import { ToDosContextProvider, useToDosContext } from "@/contexts/ToDosContext";
import { AntDesign } from "@expo/vector-icons";
import { ScrollView } from "react-native-gesture-handler";
import { Button, ButtonSize, View } from "react-native-ui-lib";
import { Button, ButtonSize, View, Text } from "react-native-ui-lib";
export default function Screen() {
return (
<ToDosContextProvider>
<ScrollView>
<View backgroundColor="#f9f8f7">
<HeaderTemplate message="Here are your To Do's" isWelcome={true} />
<ProgressCard />
<ToDosList />
</View>
</ScrollView>
<AddChore />
<ToDosPage />
</ToDosContextProvider>
);
}

View File

@ -51,7 +51,7 @@ export const AddEventDialog = () => {
}}
onPress={handleOpenManualInputModal}
>
<Text style={{color: "white"}}>Manually Input</Text>
<Text style={{color: "white"}}>Create New</Text>
</Button>
<Button
@ -62,7 +62,7 @@ export const AddEventDialog = () => {
}}
disabled
>
<Text style={{color: "white"}}>Scan an Image</Text>
<Text style={{color: "white"}}>Event</Text>
</Button>
<Button
@ -73,7 +73,18 @@ export const AddEventDialog = () => {
}}
disabled
>
<Text style={{color: "white"}}>Paste in text</Text>
<Text style={{color: "white"}}>To Do</Text>
</Button>
<Button
style={{
marginBottom: 10,
backgroundColor: "#007bff",
opacity: 0.5
}}
disabled
>
<Text style={{color: "white"}}>Upload Image</Text>
</Button>
</View>

View File

@ -8,7 +8,7 @@ import {
Switch,
Text,
TextField,
View
View,
} from "react-native-ui-lib";
import { ScrollView, TouchableOpacity } from "react-native-gesture-handler";
import { useSafeAreaInsets } from "react-native-safe-area-context";
@ -29,28 +29,34 @@ const daysOfWeek = [
{ label: "Sunday", value: "sunday" },
];
export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolean, close: () => void, initialDate?: Date }) => {
const {user} = useAuthContext()
export const ManuallyAddEventModal = ({
show,
close,
}: {
show: boolean;
close: () => void;
}) => {
const { user } = useAuthContext();
const insets = useSafeAreaInsets();
const [title, setTitle] = useState<string>("");
const [isAllDay, setIsAllDay] = useState(false);
const [startTime, setStartTime] = useState(initialDate ?? new Date());
const [endTime, setEndTime] = useState(new Date())
const [endTime, setEndTime] = useState(new Date());
const [startDate, setStartDate] = useState(new Date());
const [endDate, setEndDate] = useState(new Date())
const [endDate, setEndDate] = useState(new Date());
const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent()
const { mutateAsync: createEvent, isLoading, isError } = useCreateEvent();
const formatDateTime = (date: Date) => {
return date.toLocaleDateString('en-US', {
weekday: 'long',
month: 'short',
day: 'numeric'
return date.toLocaleDateString("en-US", {
weekday: "long",
month: "short",
day: "numeric",
});
};
@ -82,31 +88,43 @@ export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolea
title,
startDate: finalStartDate,
endDate: finalEndDate,
repeatDays: repeatInterval.map(x => x.toString()),
allDay: isAllDay
repeatDays: repeatInterval.map((x) => x.toString()),
allDay: isAllDay,
};
await createEvent(eventData)
await createEvent(eventData);
close();
};
const getRepeatLabel = () => {
const selectedDays = repeatInterval
const allDays = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
const selectedDays = repeatInterval;
const allDays = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
];
const workDays = ["monday", "tuesday", "wednesday", "thursday", "friday"];
const isEveryWorkDay = workDays.every(day => selectedDays.includes(day));
const isEveryWorkDay = workDays.every((day) => selectedDays.includes(day));
const isEveryDay = allDays.every(day => selectedDays.includes(day));
const isEveryDay = allDays.every((day) => selectedDays.includes(day));
if (isEveryDay) {
return "Every day";
} else if (isEveryWorkDay && !selectedDays.includes("saturday") && !selectedDays.includes("sunday")) {
} else if (
isEveryWorkDay &&
!selectedDays.includes("saturday") &&
!selectedDays.includes("sunday")
) {
return "Every work day";
} else {
return selectedDays
.map(item => daysOfWeek.find(day => day.value === item)?.label)
.map((item) => daysOfWeek.find((day) => day.value === item)?.label)
.join(", ");
}
};
@ -119,9 +137,9 @@ export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolea
onRequestClose={close}
transparent={false}
>
<LoaderScreen message={'Saving event...'} color={Colors.grey40}/>
<LoaderScreen message={"Saving event..."} color={Colors.grey40} />
</Modal>
)
);
}
return (
@ -131,15 +149,23 @@ export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolea
onRequestClose={close}
transparent={false}
>
<View style={{
<View
style={{
flex: 1,
backgroundColor: "#fff",
paddingTop: insets.top, // Safe area inset for top
paddingBottom: insets.bottom, // Safe area inset for bottom
paddingLeft: insets.left, // Safe area inset for left
paddingRight: insets.right, // Safe area inset for right
}}>
<View style={{flexDirection: "row", justifyContent: "space-between", padding: 16}}>
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
padding: 16,
}}
>
<TouchableOpacity onPress={close}>
<Text style={{ color: "#007bff" }}>Cancel</Text>
</TouchableOpacity>
@ -149,10 +175,12 @@ export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolea
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={{paddingHorizontal: 16, paddingTop: 10}}>
<ScrollView
contentContainerStyle={{ paddingHorizontal: 16, paddingTop: 10 }}
>
<View style={{ marginVertical: 10 }}>
<TextField
placeholder={'Title'}
placeholder={"Title"}
floatingPlaceholder
value={title}
onChangeText={setTitle}
@ -160,19 +188,20 @@ export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolea
maxLength={200}
fieldStyle={{
borderBottomWidth: 1,
borderBottomColor: 'black',
borderStyle: 'solid',
borderBottomColor: "black",
borderStyle: "solid",
}}
/>
</View>
<View style={{
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20
}}>
marginBottom: 20,
}}
>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<MaterialIcons name="schedule" size={24} color="gray" />
<Text style={{ marginLeft: 10 }}>All-day</Text>
@ -183,49 +212,64 @@ export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolea
/>
</View>
<View style={{
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20
}}>
marginBottom: 20,
}}
>
<DateTimePicker
mode="date"
dateFormatter={formatDateTime}
value={startDate}
onChange={setStartDate}
display="default"
/>
{!isAllDay && (
<DateTimePicker
mode="time"
value={startTime}
onChange={setStartTime}
display="spinner"
/>
)}
</View>
{!isAllDay && (
<View style={{
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20
}}>
marginBottom: 20,
}}
>
<DateTimePicker
mode="date"
dateFormatter={formatDateTime}
value={endDate}
onChange={setEndDate}
display="default"
/>
<DateTimePicker
mode="time"
value={endTime}
onChange={setEndTime}
minuteInterval={1}
display="spinner"
/>
</View>
)}
<View style={{flexDirection: "row", alignItems: "center", marginBottom: 20}}>
<View
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 20,
}}
>
<MaterialIcons name="repeat" size={24} color="gray" />
<Picker
value={repeatInterval}
@ -235,17 +279,33 @@ export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolea
style={{ marginLeft: 10, flex: 1 }}
mode={Picker.modes.MULTI}
getLabel={getRepeatLabel}
>
{daysOfWeek.map((option) => (
<Picker.Item key={option.value} label={option.label} value={option.value}/>
<Picker.Item
key={option.value}
label={option.label}
value={option.value}
/>
))}
</Picker>
</View>
<View style={{flexDirection: "row", alignItems: "center", marginBottom: 20}}>
<View
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 20,
}}
>
<MaterialIcons name="person-add" size={24} color="gray" />
<TouchableOpacity style={{marginLeft: 10, flexDirection: "row", alignItems: "center", flex: 1}}>
<TouchableOpacity
style={{
marginLeft: 10,
flexDirection: "row",
alignItems: "center",
flex: 1,
}}
>
<Avatar size={40} backgroundColor={Colors.yellow10} />
<View style={{ marginLeft: 10 }}>
<Text>Other</Text>

View File

@ -1,10 +1,32 @@
import { View, Text } from 'react-native'
import React from 'react'
import { TextField } from 'react-native-ui-lib'
import CategoryDropdown from './CategoryDropdown'
import { GroceryCategory } from '@/contexts/GroceryContext'
import { View, Text } from "react-native";
import React, { useEffect, useState } from "react";
import { TextField } from "react-native-ui-lib";
import {
GroceryCategory,
IGrocery,
useGroceryContext,
} from "@/contexts/GroceryContext";
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
interface IEditGrocery {
id?: number;
title: string;
setTitle: (value: string) => void;
setCategory?: (category: GroceryCategory) => void;
category: GroceryCategory;
setSubmit?: (value: boolean) => void;
updateCategory?: (id: number, changes: Partial<IGrocery>) => void;
closeEdit?: (value: boolean) => void;
}
const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
const { fuzzyMatchGroceryCategory } = useGroceryContext();
useEffect(() => {
if (editGrocery.setCategory)
editGrocery.setCategory(fuzzyMatchGroceryCategory(editGrocery.title));
}, [editGrocery.title]);
const EditGroceryItem = (props: {title: string, setTitle: (value: string) => void, setCategory: (category: GroceryCategory) => void}) => {
return (
<View
style={{
@ -16,13 +38,23 @@ const EditGroceryItem = (props: {title: string, setTitle: (value: string) => voi
>
<TextField
placeholder="Grocery"
value={props.title}
onChangeText={(value) => props.setTitle(value)}
value={editGrocery.title}
onChangeText={(value) => {
editGrocery.setTitle(value);
}}
onSubmitEditing={() => {
if (editGrocery.setSubmit) editGrocery.setSubmit(true);
if (editGrocery.closeEdit) editGrocery.closeEdit(false);
if (editGrocery.updateCategory && editGrocery.id)
editGrocery.updateCategory(editGrocery.id, {
category: fuzzyMatchGroceryCategory(editGrocery.title), title: editGrocery.title
});
}}
maxLength={25}
/>
<CategoryDropdown setSelectedCategory={props.setCategory} />
<Text>{editGrocery.category}</Text>
</View>
)
}
);
};
export default EditGroceryItem
export default EditGroceryItem;

View File

@ -17,6 +17,7 @@ import {
import EditGroceryFrequency from "./EditGroceryFrequency";
import { TextInput } from "react-native";
import EditGroceryItem from "./EditGroceryItem";
import { TouchableWithoutFeedback } from "react-native-gesture-handler";
const GroceryItem = ({
item,
@ -31,6 +32,7 @@ const GroceryItem = ({
const [openFreqEdit, setOpenFreqEdit] = useState<boolean>(false);
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(false);
const [newTitle, setNewTitle] = useState<string>("");
const [category, setCategory] = useState<GroceryCategory>(GroceryCategory.None);
const handleTitleChange = (newTitle: string) => {
updateGroceryItem(item.id, { title: newTitle });
@ -46,6 +48,7 @@ const GroceryItem = ({
return (
<ListItem
key={item.id}
style={{ borderRadius: 50, marginVertical: 5, height: 55 }}
backgroundColor="white"
centerV
@ -63,48 +66,23 @@ const GroceryItem = ({
}}
/>
<ListItem.Part left containerStyle={{ flex: 1, paddingStart: 20 }}>
{/* <View
height={50}
width={50}
style={{ borderRadius: 15 }}
backgroundColor="#e6f1ed"
marginR-10
children={
<MaterialCommunityIcons
name={iconMapping[item.category]}
size={50}
color="orange"
/>
}
/>*/}
{!isEditingTitle ? (
<View>
<TouchableOpacity onPress={() => setIsEditingTitle(true)}>
<Text text70BL>{item.title}</Text>
</TouchableOpacity>
{ /*
<TextInput
value={item.title}
onChangeText={handleTitleChange}
onBlur={() => {
setIsEditingTitle(false);
console.log(groceries);
}}
autoFocus
style={{
fontSize: 16,
padding: 0,
margin: 0,
fontWeight: "bold",
}}
/>
*/}
</View>
) : (
<EditGroceryItem
title={item.title}
setTitle={handleTitleChange}
setCategory={handleCategoryChange}
editGrocery={{
id: item.id,
title: newTitle,
setTitle: setNewTitle,
category:category,
updateCategory: updateGroceryItem,
closeEdit: setIsEditingTitle,
setCategory: setCategory,
}}
/>
)}
</ListItem.Part>

View File

@ -12,6 +12,7 @@ import HeaderTemplate from "@/components/shared/HeaderTemplate";
import CategoryDropdown from "./CategoryDropdown";
import { MaterialIcons } from "@expo/vector-icons";
import EditGroceryItem from "./EditGroceryItem";
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
const GroceryList = () => {
const {
@ -21,6 +22,7 @@ const GroceryList = () => {
setIsAddingGrocery,
addGrocery,
} = useGroceryContext();
const { profileData } = useAuthContext();
const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>(
groceries.filter((item) => item.approved === true)
);
@ -31,6 +33,7 @@ const GroceryList = () => {
GroceryCategory.Bakery
);
const [title, setTitle] = useState<string>("");
const [submit, setSubmitted] = useState<boolean>(false);
// Group approved groceries by category
const approvedGroceriesByCategory = approvedGroceries.reduce(
@ -46,19 +49,27 @@ const GroceryList = () => {
);
useEffect(() => {
if (submit) {
if (title?.length > 2 && title?.length <= 25) {
addGrocery({
id: 0,
title: title,
category: category,
approved: false,
approved: profileData?.userType === ProfileType.PARENT ? true : false,
recurring: false,
frequency: GroceryFrequency.Never,
bought: false,
});
setIsAddingGrocery(false);
setSubmitted(false);
setTitle("");
}
}
}, [submit]);
useEffect(() => {
/**/
}, [category]);
useEffect(() => {
@ -67,7 +78,7 @@ const GroceryList = () => {
}, [groceries]);
return (
<View marginH-20>
<View marginH-20 marginB-20>
<HeaderTemplate
message={"Welcome to your grocery list"}
isWelcome={false}
@ -101,7 +112,7 @@ const GroceryList = () => {
</Text>
</View>
<Button
backgroundColor='transparent'
backgroundColor="transparent"
paddingH-10
iconSource={() => (
<MaterialIcons name="person-add-alt" size={24} color="gray" />
@ -157,7 +168,15 @@ const GroceryList = () => {
</View>
</View>
{isAddingGrocery && (
<EditGroceryItem title={title} setTitle={setTitle} setCategory={setCategory} />
<EditGroceryItem
editGrocery={{
title: title,
setCategory: setCategory,
category: category,
setTitle: setTitle,
setSubmit: setSubmitted,
}}
/>
)}
{/* Render Approved Groceries Grouped by Category */}

View File

@ -1,5 +1,13 @@
import React, { useState } from "react";
import {Button, ButtonSize, Text, TextField, View,} from "react-native-ui-lib";
import {
Button,
ButtonSize,
Checkbox,
Text,
TextField,
TouchableOpacity,
View,
} from "react-native-ui-lib";
import { useSignUp } from "@/hooks/firebase/useSignUp";
import { ProfileType } from "@/contexts/AuthContext";
import { StyleSheet } from "react-native";
@ -10,12 +18,10 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
const [firstName, setFirstName] = useState<string>("");
const [lastName, setLastName] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [isParent, setIsParent] = useState<boolean>(true);
const [isChild, setIsChild] = useState<boolean>(false);
const [isCaregiver, setIsCaregiver] = useState<boolean>(false);
const [profileType, setProfileType] = useState<ProfileType>(
ProfileType.PARENT
);
const [isPasswordVisible, setIsPasswordVisible] = useState<boolean>(false);
const [allowFaceID, setAllowFaceID] = useState<boolean>(false);
const [acceptTerms, setAcceptTerms] = useState<boolean>(false);
const { mutateAsync: signUp } = useSignUp();
const handleSignUp = async () => {
@ -23,7 +29,7 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
};
return (
<View padding-10>
<View padding-10 height={"100%"} flexG>
<Text text30 center>
Get started with Kali
</Text>
@ -51,72 +57,68 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
secureTextEntry={!isPasswordVisible}
style={styles.textfield}
trailingAccessory={
<TouchableOpacity
onPress={() => setIsPasswordVisible(!isPasswordVisible)}
>
<AntDesign
name={isPasswordVisible ? "eye" : "eyeo"}
size={24}
color="gray"
/>
</TouchableOpacity>
}
/>
<View gap-10 marginH-10>
<View row centerV>
<Checkbox
value={allowFaceID}
onValueChange={(value) => {
setAllowFaceID(value);
}}
/>
<Text text90R marginL-10>
Allow FaceID for login in future
</Text>
</View>
<View row centerV>
<Checkbox
value={acceptTerms}
onValueChange={(value) => setAcceptTerms(value)}
/>
<View row>
<Text text90R marginL-10>
I accept the
</Text>
<TouchableOpacity>
<Text text90 style={{ textDecorationLine: "underline" }}>
{" "}
terms and conditions
</Text>
</TouchableOpacity>
<Text text90R> and </Text>
<TouchableOpacity>
<Text text90 style={{ textDecorationLine: "underline" }}>
{" "}
privacy policy
</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.bottomView}>
<Button
label="Register"
onPress={handleSignUp}
style={{ marginBottom: 10, backgroundColor: "#fd1775" }}
/>
<Button
label="Sign up with Google"
backgroundColor="white"
color="black"
iconSource={() => (
<AntDesign
name="google"
size={24}
color="black"
style={{marginRight: 15}}
/>
)}
/>
{/*<Text style={{ marginBottom: 10 }}>Choose Profile Type:</Text>
<Checkbox
label="Parent"
value={isParent}
onValueChange={(value) => {
setIsParent(value);
setProfileType(ProfileType.PARENT);
if (value) {
setIsChild(false);
setIsCaregiver(false);
}
}}
style={{ marginBottom: 10 }}
/>
<Checkbox
label="Child"
value={isChild}
onValueChange={(value) => {
setIsChild(value);
setProfileType(ProfileType.CHILD);
if (value) {
setIsParent(false);
setIsCaregiver(false);
}
}}
style={{ marginBottom: 10 }}
/>
<Checkbox
label="Caregiver"
value={isCaregiver}
onValueChange={(value) => {
setIsCaregiver(value);
setProfileType(ProfileType.CAREGIVER);
if (value) {
setIsParent(false);
setIsChild(false);
}
}}
/>*/}
<View row centerH marginT-10 marginB-5 gap-5>
<Text text70 center>
Already have an account?
</Text>
<Button
label="Sign In"
flexS
@ -129,6 +131,7 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
/>
</View>
</View>
</View>
);
};
@ -142,4 +145,6 @@ const styles = StyleSheet.create({
height: 45,
borderRadius: 50,
},
//mora da se izmeni kako treba
bottomView: { marginTop: 150 },
});

View File

@ -1,7 +1,12 @@
import { View, Text, TouchableOpacity } from "react-native-ui-lib";
import React, { useState } from "react";
import { Ionicons } from "@expo/vector-icons";
import { ScrollView, StyleSheet } from "react-native";
import {
KeyboardAvoidingView,
Platform,
ScrollView,
StyleSheet,
} from "react-native";
import MyProfile from "./user_settings_views/MyProfile";
import MyGroup from "./user_settings_views/MyGroup";
import { useAuthContext } from "@/contexts/AuthContext";
@ -9,8 +14,10 @@ import { useAuthContext } from "@/contexts/AuthContext";
const UserSettings = (props: { setSelectedPage: (page: number) => void }) => {
const [selectedView, setSelectedView] = useState<boolean>(true);
return (
<View>
<ScrollView>
<ScrollView
contentContainerStyle={{ flexGrow: 1 }}
>
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
<View row marginT-20 marginL-20 marginB-35 centerV>
<Ionicons name="chevron-back" size={22} color="#979797" />
@ -28,7 +35,9 @@ const UserSettings = (props: { setSelectedPage: (page: number) => void }) => {
onPress={() => setSelectedView(true)}
centerV
centerH
style={selectedView == true ? styles.btnSelected : styles.btnNot}
style={
selectedView == true ? styles.btnSelected : styles.btnNot
}
>
<View>
<Text text70 color={selectedView ? "white" : "black"}>
@ -40,7 +49,9 @@ const UserSettings = (props: { setSelectedPage: (page: number) => void }) => {
onPress={() => setSelectedView(false)}
centerV
centerH
style={selectedView == false ? styles.btnSelected : styles.btnNot}
style={
selectedView == false ? styles.btnSelected : styles.btnNot
}
>
<View>
<Text text70 color={!selectedView ? "white" : "black"}>
@ -52,8 +63,8 @@ const UserSettings = (props: { setSelectedPage: (page: number) => void }) => {
{selectedView && <MyProfile />}
{!selectedView && <MyGroup />}
</View>
</ScrollView>
</View>
</ScrollView>
);
};

View File

@ -2,13 +2,14 @@ import { View, Text, Button } from "react-native-ui-lib";
import React from "react";
import { Fontisto } from "@expo/vector-icons";
import { ProgressBar } from "react-native-ui-lib/src/components/progressBar";
import { useToDosContext } from "@/contexts/ToDosContext";
const ProgressCard = () => {
const ProgressCard = ({children}: {children?: React.ReactNode}) => {
const { maxPoints } = useToDosContext();
return (
<View
backgroundColor="white"
marginH-25
marginB-30
marginB-5
padding-15
style={{ borderRadius: 22 }}
>
@ -30,16 +31,10 @@ const ProgressCard = () => {
/>
<View row spread>
<Text color={"#868686"}>0</Text>
<Text color={"#868686"}>1000</Text>
<Text color={"#868686"}>{maxPoints}</Text>
</View>
<View centerV centerH>
<Button
backgroundColor="transparent"
>
<Text style={{ textDecorationLine: "underline", color: "#05a8b6" }}>
View your full progress report here
</Text>
</Button>
{children}
</View>
</View>
);

View File

@ -10,7 +10,6 @@ const ToDoItem = (props: { item: IToDo }) => {
centerV
paddingV-10
paddingH-10
marginH-25
marginV-10
style={{
borderRadius: 22,

View File

@ -0,0 +1,76 @@
import { View, Text, Button, ButtonSize } from "react-native-ui-lib";
import React, { useState } from "react";
import HeaderTemplate from "@/components/shared/HeaderTemplate";
import AddChore from "./AddChore";
import ProgressCard from "./ProgressCard";
import ToDosList from "./ToDosList";
import { ScrollView } from "react-native";
import { StyleSheet } from "react-native";
import { TouchableOpacity } from "react-native-gesture-handler";
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
import FamilyChoresProgress from "./family-chores/FamilyChoresProgress";
import UserChoresProgress from "./user-chores/UserChoresProgress";
const ToDosPage = () => {
const [pageIndex, setPageIndex] = useState<number>(0);
const { profileData } = useAuthContext();
const pageLink = (
<TouchableOpacity onPress={() => setPageIndex(1)}>
<Text color="#ea156d">View family progress</Text>
</TouchableOpacity>
);
return (
<View paddingH-25 backgroundColor="#f9f8f7" height={"100%"}>
{pageIndex == 0 && (
<View>
<ScrollView
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
>
<View>
<HeaderTemplate
message="Here are your To Do's"
isWelcome={true}
link={profileData?.userType == ProfileType.PARENT && pageLink}
/>
{profileData?.userType == ProfileType.CHILD && (
<View marginB-25>
<ProgressCard
children={
<Button
backgroundColor="transparent"
onPress={() => setPageIndex(2)}
>
<Text
style={{
textDecorationLine: "underline",
color: "#05a8b6",
}}
>
View your full progress report here
</Text>
</Button>
}
/>
</View>
)}
<ToDosList />
</View>
</ScrollView>
{profileData?.userType == ProfileType.PARENT && <AddChore />}
</View>
)}
{pageIndex == 1 && <FamilyChoresProgress setPageIndex={setPageIndex} />}
{pageIndex == 2 && <UserChoresProgress setPageIndex={setPageIndex} />}
</View>
);
};
const styles = StyleSheet.create({
linkBtn: {
backgroundColor: "transparent",
padding: 0,
},
});
export default ToDosPage;

View File

@ -0,0 +1,87 @@
import React from "react";
import { View } from "react-native";
import { BarChart } from "react-native-gifted-charts";
const FamilyChart = () => {
// Define the data for the bars
const data = [
{
value: 600, // Total value of the bar
stacks: [
{ value: 290, color: "#e7d526" }, // First part of the bar
{ value: 310, color: "#00a8b6" }, // Second part of the bar
],
label: "M",
},
{
value: 400,
stacks: [
{ value: 190, color: "#e7d526" },
{ value: 210, color: "#00a8b6" },
],
label: "Tu",
},
{
value: 400,
stacks: [
{ value: 210, color: "#e7d526" },
{ value: 190, color: "#00a8b6" },
],
label: "W",
},
{
value: 800,
stacks: [
{ value: 410, color: "#e7d526" },
{ value: 390, color: "#00a8b6" },
],
label: "Th",
},
{
value: 600,
stacks: [
{ value: 220, color: "#e7d526" },
{ value: 380, color: "#00a8b6" },
],
label: "F",
},
{
value: 200,
stacks: [
{ value: 160, color: "#e7d526" },
{ value: 40, color: "#00a8b6" },
],
label: "Sa",
},
{
value: 200,
stacks: [
{ value: 160, color: "#e7d526" },
{ value: 40, color: "#00a8b6" },
],
label: "Su",
},
];
return (
<BarChart
stackData={data}
width={250}
height={150} // Height of the chart
barWidth={20} // Width of each bar
noOfSections={5} // Number of horizontal sections (for 0 to 1000 in steps of 200)
maxValue={1000} // Max value on the chart
stepValue={200} // Step size for horizontal lines
yAxisThickness={0} // Hide the Y-axis line
yAxisLabelTexts={["0", "200", "400", "600", "800", "1000"]} // Custom Y-axis labels
hideRules={false} // Show the horizontal lines
rulesColor="#dadada" // Color for the horizontal lines
stackBorderTopLeftRadius={5} // Round the bars
stackBorderTopRightRadius={5} // Round the bars
spacing={16}
disableScroll
/>
);
};
export default FamilyChart;

View File

@ -0,0 +1,67 @@
import { View, Text } from "react-native-ui-lib";
import React from "react";
import { StyleSheet } from "react-native";
import FamilyChart from "./FamilyChart";
import { TouchableOpacity } from "react-native-ui-lib/src/incubator";
const FamilyChoresProgress = ({
setPageIndex,
}: {
setPageIndex: (value: number) => void;
}) => {
return (
<View marginT-20 marginH-5>
<TouchableOpacity onPress={() => setPageIndex(0)}>
<Text>Back to ToDos</Text>
</TouchableOpacity>
<View centerH>
<Text text50R>Family Chores Progress Report</Text>
</View>
<View row spread marginT-25 marginB-20>
<Text text70>Points earned this week</Text>
<View row>
<View style={styles.pfpSmall} backgroundColor="#05a8b6" />
<View style={styles.pfpSmall} backgroundColor="#ebd825" />
</View>
</View>
<View style={styles.card} paddingL-10>
<FamilyChart />
</View>
<Text text70 marginV-20>
Chore Tracker
</Text>
<View style={styles.card} marginB-20 row spread>
<View style={styles.pfpBig} backgroundColor="#05a8b6" />
<View width={"100%"} centerV centerH>
<Text> x/y chores completed</Text>
</View>
</View>
<View style={styles.card} row spread>
<View style={styles.pfpBig} backgroundColor="#ebd825" />
<View width={"100%"} centerV centerH>
<Text> x/y chores completed</Text>
</View>
</View>
</View>
);
};
const styles = StyleSheet.create({
pfpSmall: {
width: 30,
aspectRatio: 1,
borderRadius: 50,
marginHorizontal: 2,
},
pfpBig: {
width: 50,
aspectRatio: 1,
borderRadius: 50,
},
card: {
backgroundColor: "white",
borderRadius: 20,
padding: 20,
},
});
export default FamilyChoresProgress;

View File

@ -0,0 +1,66 @@
import React from "react";
import { View } from "react-native";
import { BarChart } from "react-native-gifted-charts";
const UserChart = () => {
const barColor = "#05a8b6"
const data = [
{
value: 290, // Direct value of the bar
frontColor: barColor, // Color of the bar
label: "M",
},
{
value: 190,
frontColor: barColor,
label: "Tu",
},
{
value: 210,
frontColor: barColor,
label: "W",
},
{
value: 410,
frontColor: barColor,
label: "Th",
},
{
value: 220,
frontColor: barColor,
label: "F",
},
{
value: 160,
frontColor: barColor,
label: "Sa",
},
{
value: 160,
frontColor: barColor,
label: "Su",
},
];
return (
<BarChart
data={data}
width={255}
height={150} // Height of the chart
barWidth={20} // Width of each bar
noOfSections={5} // Number of horizontal sections (for 0 to 1000 in steps of 200)
maxValue={1000} // Max value on the chart
stepValue={200} // Step size for horizontal lines
yAxisThickness={0} // Hide the Y-axis line
yAxisLabelTexts={["0", "200", "400", "600", "800", "1000"]} // Custom Y-axis labels
hideRules={false} // Show the horizontal lines
rulesColor="#dadada" // Color for the horizontal lines
barBorderTopLeftRadius={5} // Round the bars
barBorderTopRightRadius={5} // Round the bars
spacing={16}
disableScroll
/>
);
};
export default UserChart;

View File

@ -0,0 +1,152 @@
import {
View,
Text,
ProgressBar,
Button,
ButtonSize,
Modal,
Dialog,
} from "react-native-ui-lib";
import React, { useState } from "react";
import { StyleSheet } from "react-native";
import { TouchableOpacity } from "react-native-ui-lib/src/incubator";
import UserChart from "./UserChart";
import ProgressCard from "../ProgressCard";
import { AntDesign, Feather, Ionicons } from "@expo/vector-icons";
import { ScrollView } from "react-native-gesture-handler";
import { PanViewDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
const UserChoresProgress = ({
setPageIndex,
}: {
setPageIndex: (value: number) => void;
}) => {
const [modalVisible, setModalVisible] = useState<boolean>(false);
return (
<View marginT-20 paddingB-20>
<ScrollView
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}
>
<TouchableOpacity onPress={() => setPageIndex(0)}>
<Text>Back to ToDos</Text>
</TouchableOpacity>
<View centerH>
<Text text50R>Your To Dos Progress Report</Text>
</View>
<View row spread marginT-25 marginB-5>
<Text text70>Daily Goal</Text>
</View>
<ProgressCard />
<View row spread marginT-10 marginB-5>
<Text text70>Points Earned This Week</Text>
</View>
<View style={styles.card} paddingL-10>
<UserChart />
</View>
<View row spread marginT-20 marginB-8 centerV>
<Text text70>Total Reward Points</Text>
<Button
size={ButtonSize.small}
label="Spend my points"
color="#50be0c"
backgroundColor="#ebf2e4"
onPress={() => setModalVisible(true)}
iconSource={() => (
<AntDesign
name="gift"
size={20}
style={{ marginRight: 5 }}
color="#50be0c"
/>
)}
/>
</View>
<View style={styles.card}>
<View row centerV>
<Ionicons name="flower-outline" size={30} color="#8005eb" />
<Text text70 marginL-5>
You have 1200 points saved!
</Text>
</View>
<ProgressBar
progress={80}
progressColor="#ff9900"
style={{
height: 21,
backgroundColor: "#faeedb",
marginTop: 15,
marginBottom: 5,
}}
/>
<View row spread>
<Text>0</Text>
<Text>5000</Text>
</View>
</View>
</ScrollView>
<Dialog
visible={modalVisible}
onDismiss={() => setModalVisible(false)}
children={
<View style={styles.card} paddingH-35 paddingT-35>
<Text text60 center marginB-35>
How would you like to spend your points?
</Text>
<Button
label="Skip a Chore Cor a Day - 150 pts"
text70
marginB-15
backgroundColor="#05a8b6"
size={ButtonSize.large}
/>
<Button
label="Extra Screen Time - 100 pts"
text70
marginB-15
backgroundColor="#ea156c"
size={ButtonSize.large}
/>
<Button
label="Movie Night - 50 pts"
text70
marginB-15
backgroundColor="#7305d4"
size={ButtonSize.large}
/>
<Button
label="Ice Cream Treat - 25 pts"
text70
marginB-15
backgroundColor="#e28800"
size={ButtonSize.large}
/>
<TouchableOpacity onPress={() => setModalVisible(false)}>
<Text text70 center color="#999999">Go back to my to dos</Text>
</TouchableOpacity>
</View>
}
/>
</View>
);
};
const styles = StyleSheet.create({
pfpSmall: {
width: 30,
aspectRatio: 1,
borderRadius: 50,
marginHorizontal: 2,
},
pfpBig: {
width: 50,
aspectRatio: 1,
borderRadius: 50,
},
card: {
backgroundColor: "white",
borderRadius: 20,
padding: 20,
},
});
export default UserChoresProgress;

View File

@ -2,7 +2,12 @@ import { View, Text } from "react-native-ui-lib";
import React from "react";
import { useAuthContext } from "@/contexts/AuthContext";
const HeaderTemplate = (props: { message: string; isWelcome: boolean; children?: React.ReactNode }) => {
const HeaderTemplate = (props: {
message: string;
isWelcome: boolean;
children?: React.ReactNode;
link?: React.ReactNode;
}) => {
const { user, profileData } = useAuthContext();
return (
<View row centerV padding-25>
@ -14,9 +19,12 @@ const HeaderTemplate = (props: { message: string; isWelcome: boolean; children?:
marginR-20
/>
<View>
{props.isWelcome && <Text text70L>Welcome, {user?.email}!</Text>}
{props.isWelcome && (
<Text text70L>Welcome, {profileData?.firstName}!</Text>
)}
<Text text70BL>{props.message}</Text>
{props.children && <View>{props.children}</View>}
{props.link && <View>{props.link}</View>}
</View>
</View>
);

View File

@ -1,5 +1,6 @@
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { createContext, useContext, useState } from "react";
import fuzzysort from "fuzzysort";
export enum GroceryFrequency {
Never = "Never",
@ -32,9 +33,10 @@ export enum GroceryCategory {
Household = "Household",
PersonalCare = "Personal Care",
Frozen = "Frozen",
None = "No Category",
}
type MaterialIconNames = keyof typeof MaterialCommunityIcons.glyphMap;
/*type MaterialIconNames = keyof typeof MaterialCommunityIcons.glyphMap;
const iconMapping: { [key in GroceryCategory]: MaterialIconNames } = {
//за сад се иконице за категорију бирају одавде
[GroceryCategory.Fruit]: "food-apple",
@ -48,15 +50,16 @@ const iconMapping: { [key in GroceryCategory]: MaterialIconNames } = {
[GroceryCategory.Household]: "home",
[GroceryCategory.PersonalCare]: "face-man-profile",
[GroceryCategory.Frozen]: "snowflake",
};
};*/
interface IGroceryContext {
groceries: IGrocery[];
iconMapping: { [key in GroceryCategory]: MaterialIconNames };
//iconMapping: { [key in GroceryCategory]: MaterialIconNames };
updateGroceryItem: (id: number, changes: Partial<IGrocery>) => void;
isAddingGrocery: boolean;
setIsAddingGrocery: (value: boolean) => void;
addGrocery: (grocery: IGrocery) => void;
fuzzyMatchGroceryCategory: (groceryTitle: string) => GroceryCategory;
}
const GroceryContext = createContext<IGroceryContext | undefined>(undefined);
@ -109,11 +112,114 @@ export const GroceryProvider: React.FC<{ children: React.ReactNode }> = ({
...prevGroceries,
{
...grocery,
id: prevGroceries.length ? prevGroceries[prevGroceries.length - 1].id + 1 : 0,
id: prevGroceries.length
? prevGroceries[prevGroceries.length - 1].id + 1
: 0,
},
]);
};
const groceryExamples = {
[GroceryCategory.Fruit]: [
'apple', 'apples', 'banana', 'bananas', 'orange', 'oranges', 'grape', 'grapes',
'pear', 'pears', 'pineapple', 'pineapples', 'kiwi', 'kiwis', 'strawberry',
'strawberries', 'blueberry', 'blueberries', 'mango', 'mangoes', 'watermelon',
'watermelons', 'peach', 'peaches', 'plum', 'plums', 'cherry', 'cherries',
'raspberry', 'raspberries', 'blackberry', 'blackberries', 'pomegranate',
'pomegranates', 'lemon', 'lemons', 'lime', 'limes', 'coconut', 'coconuts'
],
[GroceryCategory.Dairy]: [
'milk', 'whole milk', 'skim milk', 'almond milk', 'soy milk', 'cheese',
'cheeses', 'yoghurt', 'yogurt', 'greek yoghurt', 'greek yogurt', 'butter',
'margarine', 'cream', 'whipping cream', 'heavy cream', 'ice cream', 'ice creams',
'sour cream', 'whipped cream', 'cream cheese', 'cream cheeses', 'buttermilk',
'cottage cheese', 'ghee', 'kefir'
],
[GroceryCategory.Vegetables]: [
'carrot', 'carrots', 'broccoli', 'lettuce', 'lettuces', 'spinach', 'kale',
'cabbage', 'cabbages', 'cauliflower', 'zucchini', 'zucchinis', 'onion', 'onions',
'garlic', 'pepper', 'peppers', 'bell pepper', 'bell peppers', 'tomato', 'tomatoes',
'cucumber', 'cucumbers', 'potato', 'potatoes', 'sweet potato', 'sweet potatoes',
'beet', 'beets', 'eggplant', 'eggplants', 'celery', 'radish', 'radishes',
'asparagus', 'mushroom', 'mushrooms'
],
[GroceryCategory.Meat]: [
'steak', 'steaks', 'beef', 'pork', 'lamb', 'veal', 'ribeye', 'tenderloin',
'filet mignon', 'bacon', 'ham', 'sausage', 'sausages', 'salami', 'ground beef',
'beef mince', 'hamburger meat', 'prosciutto', 'brisket', 'pork chop', 'pork chops'
],
[GroceryCategory.Poultry]: [
'chicken', 'whole chicken', 'chickens', 'chicken breast', 'chicken breasts',
'breast of chicken', 'chicken thigh', 'chicken thighs', 'chicken leg', 'chicken legs',
'chicken wing', 'chicken wings', 'wing', 'wings', 'drumsticks', 'chicken drumstick',
'turkey', 'whole turkey', 'ground turkey', 'turkey breast', 'turkey breasts',
'turkey leg', 'duck', 'ducks', 'goose', 'quail', 'pheasant'
],
[GroceryCategory.Bakery]: [
'bread', 'breads', 'whole wheat bread', 'sourdough bread', 'bagel', 'bagels',
'croissant', 'croissants', 'muffin', 'muffins', 'buns', 'hamburger bun',
'hamburger buns', 'hotdog bun', 'hotdog buns', 'donut', 'donuts', 'roll', 'rolls',
'dinner roll', 'dinner rolls', 'scone', 'scones', 'toast', 'ciabatta',
'focaccia', 'brioche', 'pita', 'pitas', 'naan', 'baguette', 'baguettes',
'pastry', 'pastries', 'pretzel', 'pretzels'
],
[GroceryCategory.Beverages]: [
'coffee', 'decaf coffee', 'iced coffee', 'cold brew', 'tea', 'iced tea',
'black tea', 'green tea', 'herbal tea', 'juice', 'apple juice', 'orange juice',
'grape juice', 'water', 'sparkling water', 'flavored water', 'soda', 'cola',
'diet soda', 'beer', 'lager', 'ale', 'wine', 'red wine', 'white wine',
'whiskey', 'whisky', 'vodka', 'rum', 'gin', 'smoothie', 'smoothies', 'milkshake',
'milkshakes', 'energy drink', 'energy drinks', 'sports drink', 'sports drinks',
'lemonade', 'sparkling lemonade', 'iced lemonade', 'sparkling water', 'cider',
'hard cider', 'kombucha'
],
[GroceryCategory.Snacks]: [
'chips', 'potato chips', 'tortilla chips', 'corn chips', 'candy', 'candies',
'chocolate', 'chocolates', 'dark chocolate', 'milk chocolate', 'white chocolate',
'cookies', 'popcorn', 'pretzel', 'pretzels', 'nuts', 'mixed nuts', 'almonds',
'cashews', 'trail mix', 'granola bar', 'granola bars', 'protein bar', 'protein bars',
'crackers', 'gummies', 'gummy bears', 'fruit snacks', 'dried fruit',
'peanut butter', 'rice cakes', 'snack cakes'
],
[GroceryCategory.Household]: [
'detergent', 'laundry detergent', 'dish soap', 'dishwasher detergent',
'toilet paper', 'paper towels', 'trash bags', 'bin liners', 'broom', 'mop',
'cleaner', 'surface cleaner', 'multi-purpose cleaner', 'disinfectant', 'bleach',
'fabric softener', 'vacuum bags', 'aluminum foil', 'plastic wrap', 'cling wrap',
'light bulbs', 'batteries', 'laundry soap', 'sponges', 'scrubbers',
'dishwashing liquid'
],
[GroceryCategory.PersonalCare]: [
'shampoo', 'conditioner', 'hair shampoo', 'hair conditioner', 'soap', 'bar soap',
'liquid soap', 'toothpaste', 'toothbrush', 'toothbrushes', 'electric toothbrush',
'deodorant', 'antiperspirant', 'lotion', 'moisturizer', 'razor', 'shaving razor',
'shaving cream', 'body wash', 'face wash', 'sunscreen', 'hair gel', 'hair spray',
'nail polish', 'cotton swabs', 'lip balm', 'chapstick', 'hand cream', 'sanitizer'
],
[GroceryCategory.Frozen]: [
'ice cream', 'ice creams', 'frozen pizza', 'frozen pizzas', 'frozen vegetables',
'frozen veg', 'frozen peas', 'frozen fruit', 'frozen fish', 'frozen chicken',
'frozen wings', 'frozen fries', 'frozen french fries', 'frozen shrimp',
'frozen prawns', 'frozen dumplings', 'frozen waffles', 'frozen pancakes',
'frozen pie', 'frozen pies', 'frozen lasagna', 'frozen burrito', 'frozen burritos',
'frozen nuggets', 'frozen pastry', 'frozen pastries', 'frozen meals'
],
};
const fuzzyMatchGroceryCategory = (groceryTitle: string): GroceryCategory => {
let bestMatchCategory = GroceryCategory.None;
let bestMatchScore = -1000;
Object.entries(groceryExamples).forEach(([category, examples]) => {
const matches = fuzzysort.go(groceryTitle, examples);
if (matches.length > 0 && matches[0].score > bestMatchScore) {
bestMatchScore = matches[0].score;
bestMatchCategory = category as GroceryCategory;
}
});
return bestMatchCategory;
};
const updateGroceryItem = (id: number, changes: Partial<IGrocery>) => {
setGroceries((prevGroceries) =>
@ -125,7 +231,15 @@ export const GroceryProvider: React.FC<{ children: React.ReactNode }> = ({
return (
<GroceryContext.Provider
value={{ groceries, iconMapping, updateGroceryItem, isAddingGrocery, setIsAddingGrocery, addGrocery }}
value={{
groceries,
fuzzyMatchGroceryCategory,
//iconMapping,
updateGroceryItem,
isAddingGrocery,
setIsAddingGrocery,
addGrocery,
}}
>
{children}
</GroceryContext.Provider>

View File

@ -21,6 +21,7 @@ interface IToDosContext {
toDos: IToDo[];
updateToDo: (id: number, changes: Partial<IToDo>) => void;
addToDo: (newToDo: IToDo) => void;
maxPoints: number;
}
const ToDosContext = createContext<IToDosContext>(undefined!);
@ -35,7 +36,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: new Date(),
rotate: true,
repeatType: "Every week"
repeatType: "Every week",
},
{
id: 1,
@ -43,7 +44,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: new Date(),
rotate: false,
repeatType: "Every week"
repeatType: "Every week",
},
{
id: 2,
@ -51,7 +52,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: new Date(),
rotate: true,
repeatType: "Every week"
repeatType: "Every week",
},
{
id: 3,
@ -60,7 +61,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
date: new Date(Date.now() + 86400000),
points: 40,
rotate: false,
repeatType: "Every week"
repeatType: "Every week",
},
{
id: 4,
@ -68,7 +69,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: new Date(Date.now() + 86400000),
rotate: false,
repeatType: "Once a Month"
repeatType: "Once a Month",
},
{
id: 5,
@ -76,7 +77,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: new Date(Date.now() + 2 * 86400000),
rotate: true,
repeatType: "Once a Month"
repeatType: "Once a Month",
},
{
id: 6,
@ -84,7 +85,8 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: new Date(Date.now() + 3 * 86400000),
rotate: false,
repeatType: "Once a year"
repeatType: "Once a year",
points: 50,
},
{
id: 7,
@ -92,7 +94,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: null,
rotate: false,
repeatType: "None"
repeatType: "None",
},
{
id: 8,
@ -100,14 +102,34 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false,
date: null,
rotate: false,
repeatType: "None"
repeatType: "None",
points: 10,
},
]);
const initCalc = (): number => {
return toDos.reduce(
(sum, todo) => sum + (todo.points ? todo.points : 0),
50
);
};
const calculateMaxPoints = () => {
const totalPoints = toDos.reduce(
(sum, todo) => sum + (todo.points ? todo.points : 0),
0
);
setMaxPoints(totalPoints);
};
const [maxPoints, setMaxPoints] = useState<number>(initCalc);
const updateToDo = (id: number, changes: Partial<IToDo>) => {
setToDos((prevToDos) =>
prevToDos.map((toDo) => (toDo.id === id ? { ...toDo, ...changes } : toDo))
);
calculateMaxPoints();
};
const addToDo = (newToDo: IToDo) => {
@ -118,10 +140,11 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
id: prevToDos.length ? prevToDos[prevToDos.length - 1].id + 1 : 0,
},
]);
calculateMaxPoints();
};
return (
<ToDosContext.Provider value={{ toDos, updateToDo, addToDo }}>
<ToDosContext.Provider value={{ toDos, updateToDo, addToDo, maxPoints }}>
{children}
</ToDosContext.Provider>
);

View File

@ -0,0 +1,30 @@
import {useAuthContext} from "@/contexts/AuthContext";
import {useMutation} from "react-query";
import firestore from "@react-native-firebase/firestore";
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
import {FirebaseAuthTypes} from "@react-native-firebase/auth";
export const useSetUserData = () => {
const {user: currentUser, setProfileData} = useAuthContext()
return useMutation({
mutationKey: ["updateUserData"],
mutationFn: async ({newUserData, customUser}: {newUserData: Partial<UserProfile>, customUser?: FirebaseAuthTypes.User }) => {
const user = currentUser ?? customUser
if (user) {
try {
await firestore()
.collection("Profiles")
.doc(user.uid)
.set(newUserData);
const profileData = await firestore().collection("Profiles").doc(user?.uid!).get()
setProfileData(profileData.data() as UserProfile)
} catch (e) {
console.error(e)
}
}
}
})
}

View File

@ -1,16 +1,40 @@
import { useMutation } from "react-query";
import auth from "@react-native-firebase/auth";
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
import { ProfileType } from "@/contexts/AuthContext";
import { useSetUserData } from "./useSetUserData";
export const useSignUp = () => {
const {mutateAsync: updateUserData} = useUpdateUserData()
const { mutateAsync: setUserData } = useSetUserData();
return useMutation({
mutationKey: ["signUp"],
mutationFn: async ({email, password, firstName, lastName}: { email: string, password: string, firstName: string, lastName: string }) => {
const res = await auth().createUserWithEmailAndPassword(email, password);
await updateUserData({newUserData: {userType: ProfileType.PARENT, firstName: firstName, lastName: lastName}, customUser: res.user});
mutationFn: async ({
email,
password,
firstName,
lastName,
}: {
email: string;
password: string;
firstName: string;
lastName: string;
}) => {
await auth()
.createUserWithEmailAndPassword(email, password)
.then(async (res) => {
try {
await setUserData({
newUserData: {
userType: ProfileType.PARENT,
firstName: firstName,
lastName: lastName,
},
customUser: res.user,
});
} catch (error) {
console.error(error);
}
});
}
},
});
};

317
package-lock.json generated
View File

@ -32,6 +32,7 @@
"expo-web-browser": "~13.0.3",
"firebase-admin": "^12.3.1",
"firebase-functions": "^5.1.0",
"fuzzysort": "^3.0.2",
"jotai": "^2.9.1",
"react": "18.2.0",
"react-dom": "18.2.0",
@ -39,12 +40,13 @@
"react-native-big-calendar": "^4.14.0",
"react-native-calendars": "^1.1306.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-gifted-charts": "^1.4.41",
"react-native-linear-gradient": "^2.8.3",
"react-native-onboarding-swiper": "^1.3.0",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1",
"react-native-svg": "^15.6.0",
"react-native-svg": "^15.7.1",
"react-native-ui-lib": "^7.27.0",
"react-native-web": "~0.19.10",
"react-query": "^3.39.3"
@ -2522,9 +2524,9 @@
}
},
"node_modules/@expo/config-plugins": {
"version": "8.0.8",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.8.tgz",
"integrity": "sha512-Fvu6IO13EUw0R9WeqxUO37FkM62YJBNcZb9DyJAOgMz7Ez/vaKQGEjKt9cwT+Q6uirtCATMgaq6VWAW7YW8xXw==",
"version": "8.0.9",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.9.tgz",
"integrity": "sha512-dNCG45C7BbDPV9MdWvCbsFtJtVn4w/TJbb5b7Yr6FA8HYIlaaVM0wqUMzTPmGj54iYXw8X/Vge8uCPxg7RWgeA==",
"license": "MIT",
"dependencies": {
"@expo/config-types": "^51.0.0-unreleased",
@ -3449,9 +3451,9 @@
}
},
"node_modules/@expo/vector-icons": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.2.tgz",
"integrity": "sha512-70LpmXQu4xa8cMxjp1fydgRPsalefnHaXLzIwaHMEzcZhnyjw2acZz8azRrZOslPVAWlxItOa2Dd7WtD/kI+CA==",
"version": "14.0.3",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.3.tgz",
"integrity": "sha512-UJAKOXPPi6ez/1QZfoFVopCH3+c12Sw+T+IIVkvONCEN7zjN1fLxxWHkZ7Spz4WO5EH2ObtaJfCe/k4rw+ftxA==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.8.1"
@ -9379,9 +9381,9 @@
}
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
@ -9392,7 +9394,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@ -11522,24 +11524,24 @@
}
},
"node_modules/expo": {
"version": "51.0.30",
"resolved": "https://registry.npmjs.org/expo/-/expo-51.0.30.tgz",
"integrity": "sha512-eo4T2TGnKyDkLryY6lYuGVV1FZyWa1FG7Ns0aqfZ7N/PFQYDfbowVwkcEWbRppSzqSfjGXYqvVV4WZNFbpgZZw==",
"version": "51.0.34",
"resolved": "https://registry.npmjs.org/expo/-/expo-51.0.34.tgz",
"integrity": "sha512-l2oi+hIj/ph3qGcvM54Nyd2uF3Zq5caVmSg7AXfBUgtvcdv5Pj1EI/2xCXP9tfMNQo351CWyOwBkTGjv+GdrLg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "0.18.29",
"@expo/config": "9.0.3",
"@expo/config-plugins": "8.0.8",
"@expo/config-plugins": "8.0.9",
"@expo/metro-config": "0.18.11",
"@expo/vector-icons": "^14.0.0",
"@expo/vector-icons": "^14.0.3",
"babel-preset-expo": "~11.0.14",
"expo-asset": "~10.0.10",
"expo-file-system": "~17.0.1",
"expo-font": "~12.0.9",
"expo-font": "~12.0.10",
"expo-keep-awake": "~13.0.2",
"expo-modules-autolinking": "1.11.2",
"expo-modules-core": "1.12.22",
"expo-modules-core": "1.12.24",
"fbemitter": "^3.0.0",
"whatwg-url-without-unicode": "8.0.0-3"
},
@ -11703,9 +11705,9 @@
}
},
"node_modules/expo-font": {
"version": "12.0.9",
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.9.tgz",
"integrity": "sha512-seTCyf0tbgkAnp3ZI9ZfK9QVtURQUgFnuj+GuJ5TSnN0XsOtVe1s2RxTvmMgkfuvfkzcjJ69gyRpsZS1cC8hjw==",
"version": "12.0.10",
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.10.tgz",
"integrity": "sha512-Q1i2NuYri3jy32zdnBaHHCya1wH1yMAsI+3CCmj9zlQzlhsS9Bdwcj2W3c5eU5FvH2hsNQy4O+O1NnM6o/pDaQ==",
"license": "MIT",
"dependencies": {
"fontfaceobserver": "^2.1.0"
@ -11877,9 +11879,9 @@
}
},
"node_modules/expo-modules-core": {
"version": "1.12.22",
"resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.22.tgz",
"integrity": "sha512-MX9qJRuVszyuGksOZ1QkMcUGcAZ1o2AmDigkQAl9yxqFtwEpBOxELs3rXeQol7WiEabvK+bERTF9LtSTDCVCYw==",
"version": "1.12.24",
"resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.24.tgz",
"integrity": "sha512-3geIe2ecizlp7l26iY8Nmc59z2d1RUC5nQZtI9iJoi5uHEUV/zut8e4zRLFVnZb8KOcMcEDsrvaBL5DPnqdfpg==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
@ -12055,37 +12057,37 @@
"license": "Apache-2.0"
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@ -12111,14 +12113,23 @@
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@ -12129,6 +12140,18 @@
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -12147,6 +12170,45 @@
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/express/node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/express/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -12832,6 +12894,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fuzzysort": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.0.2.tgz",
"integrity": "sha512-ZyahVgxvckB1Qosn7YGWLDJJp2XlyaQ2WmZeI+d0AzW0AMqVYnz5N89G6KAKa6m/LOtv+kzJn4lhDF/yVg11Cg==",
"license": "MIT"
},
"node_modules/gaxios": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
@ -12998,6 +13066,16 @@
"node": ">=6"
}
},
"node_modules/gifted-charts-core": {
"version": "0.1.41",
"resolved": "https://registry.npmjs.org/gifted-charts-core/-/gifted-charts-core-0.1.41.tgz",
"integrity": "sha512-17xzJ0kJw+BVbxX7J1JmDHHJt/Yp1n+h0sTrC6eK4rQg9Hzw0EC/MJcv5fLJzg6dkVo+3wQ3anWXL5itapP+nQ==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -17495,10 +17573,13 @@
"license": "BSD-2-Clause"
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==",
"license": "MIT"
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
@ -18906,9 +18987,9 @@
"license": "ISC"
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==",
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"license": "MIT"
},
"node_modules/path-type": {
@ -19300,12 +19381,12 @@
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@ -19614,6 +19695,30 @@
"react-native": "*"
}
},
"node_modules/react-native-gifted-charts": {
"version": "1.4.41",
"resolved": "https://registry.npmjs.org/react-native-gifted-charts/-/react-native-gifted-charts-1.4.41.tgz",
"integrity": "sha512-UQ3E6ork24VFVTOhgAAbGkGplYB89mRjNEEPJKRAzwSYoJ1Gjtt1pleuY7OKpMb5TQychreFBA2oVBrwTLdeKw==",
"license": "MIT",
"dependencies": {
"gifted-charts-core": "0.1.41"
},
"peerDependencies": {
"expo-linear-gradient": "*",
"react": "*",
"react-native": "*",
"react-native-linear-gradient": "*",
"react-native-svg": "*"
},
"peerDependenciesMeta": {
"expo-linear-gradient": {
"optional": true
},
"react-native-linear-gradient": {
"optional": true
}
}
},
"node_modules/react-native-helmet-async": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-native-helmet-async/-/react-native-helmet-async-2.0.4.tgz",
@ -19716,9 +19821,9 @@
}
},
"node_modules/react-native-svg": {
"version": "15.6.0",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.6.0.tgz",
"integrity": "sha512-TUtR+h+yi1ODsd8FHdom1TpjfWOmnaK5pri5rnSBXnMqpzq8o2zZfonHTjPX+nS3wb/Pu2XsoARgYaHNjVWXhQ==",
"version": "15.7.1",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.7.1.tgz",
"integrity": "sha512-Xc11L4t6/DtmUwrQqHR7S45Qy3cIWpcfGlmEatVeZ9c1N8eAK79heJmGRgCOVrXESrrLEHfP/AYGf0BGyrvV6A==",
"license": "MIT",
"dependencies": {
"css-select": "^5.1.0",
@ -20631,20 +20736,116 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"license": "MIT",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/serve-static/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/serve-static/node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/serve-static/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serve-static/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/serve-static/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/serve-static/node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serve-static/node_modules/send": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/serve-static/node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serve-static/node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",

View File

@ -51,6 +51,7 @@
"expo-web-browser": "~13.0.3",
"firebase-admin": "^12.3.1",
"firebase-functions": "^5.1.0",
"fuzzysort": "^3.0.2",
"jotai": "^2.9.1",
"react": "18.2.0",
"react-dom": "18.2.0",
@ -58,12 +59,13 @@
"react-native-big-calendar": "^4.14.0",
"react-native-calendars": "^1.1306.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-gifted-charts": "^1.4.41",
"react-native-linear-gradient": "^2.8.3",
"react-native-onboarding-swiper": "^1.3.0",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1",
"react-native-svg": "^15.6.0",
"react-native-svg": "^15.7.1",
"react-native-toast-message": "^2.2.1",
"react-native-ui-lib": "^7.27.0",
"react-native-web": "~0.19.10",

1430
yarn.lock

File diff suppressed because it is too large Load Diff