fix groceries, todo, calendar

This commit is contained in:
ivic00
2024-09-25 20:45:14 +02:00
parent 1533ec525b
commit 857e60a3d3
15 changed files with 1923 additions and 857 deletions

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 { GroceryProvider } from "@/contexts/GroceryContext";
import React from "react"; import React from "react";
import GroceryWrapper from "@/components/pages/grocery/GroceryWrapper"; import GroceryWrapper from "@/components/pages/grocery/GroceryWrapper";

View File

@ -51,7 +51,7 @@ export const AddEventDialog = () => {
}} }}
onPress={handleOpenManualInputModal} onPress={handleOpenManualInputModal}
> >
<Text style={{color: "white"}}>Manually Input</Text> <Text style={{color: "white"}}>Create New</Text>
</Button> </Button>
<Button <Button
@ -62,7 +62,7 @@ export const AddEventDialog = () => {
}} }}
disabled disabled
> >
<Text style={{color: "white"}}>Scan an Image</Text> <Text style={{color: "white"}}>Event</Text>
</Button> </Button>
<Button <Button
@ -73,7 +73,18 @@ export const AddEventDialog = () => {
}} }}
disabled 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> </Button>
</View> </View>

View File

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

View File

@ -1,10 +1,32 @@
import { View, Text } from 'react-native' import { View, Text } from "react-native";
import React from 'react' import React, { useEffect, useState } from "react";
import { TextField } from 'react-native-ui-lib' import { TextField } from "react-native-ui-lib";
import CategoryDropdown from './CategoryDropdown' import {
import { GroceryCategory } from '@/contexts/GroceryContext' 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 ( return (
<View <View
style={{ style={{
@ -16,13 +38,23 @@ const EditGroceryItem = (props: {title: string, setTitle: (value: string) => voi
> >
<TextField <TextField
placeholder="Grocery" placeholder="Grocery"
value={props.title} value={editGrocery.title}
onChangeText={(value) => props.setTitle(value)} 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} maxLength={25}
/> />
<CategoryDropdown setSelectedCategory={props.setCategory} /> <Text>{editGrocery.category}</Text>
</View> </View>
) );
} };
export default EditGroceryItem export default EditGroceryItem;

View File

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

View File

@ -31,6 +31,7 @@ const GroceryList = () => {
GroceryCategory.Bakery GroceryCategory.Bakery
); );
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [submit, setSubmitted] = useState<boolean>(false);
// Group approved groceries by category // Group approved groceries by category
const approvedGroceriesByCategory = approvedGroceries.reduce( const approvedGroceriesByCategory = approvedGroceries.reduce(
@ -46,6 +47,7 @@ const GroceryList = () => {
); );
useEffect(() => { useEffect(() => {
if(submit){
if (title?.length > 2 && title?.length <= 25) { if (title?.length > 2 && title?.length <= 25) {
addGrocery({ addGrocery({
id: 0, id: 0,
@ -58,7 +60,14 @@ const GroceryList = () => {
}); });
setIsAddingGrocery(false); setIsAddingGrocery(false);
setSubmitted(false);
setTitle("");
} }
}
}, [submit]);
useEffect(() => {
/**/
}, [category]); }, [category]);
useEffect(() => { useEffect(() => {
@ -67,7 +76,7 @@ const GroceryList = () => {
}, [groceries]); }, [groceries]);
return ( return (
<View marginH-20> <View marginH-20 marginB-20>
<HeaderTemplate <HeaderTemplate
message={"Welcome to your grocery list"} message={"Welcome to your grocery list"}
isWelcome={false} isWelcome={false}
@ -101,7 +110,7 @@ const GroceryList = () => {
</Text> </Text>
</View> </View>
<Button <Button
backgroundColor='transparent' backgroundColor="transparent"
paddingH-10 paddingH-10
iconSource={() => ( iconSource={() => (
<MaterialIcons name="person-add-alt" size={24} color="gray" /> <MaterialIcons name="person-add-alt" size={24} color="gray" />
@ -157,7 +166,15 @@ const GroceryList = () => {
</View> </View>
</View> </View>
{isAddingGrocery && ( {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 */} {/* Render Approved Groceries Grouped by Category */}

View File

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

View File

@ -2,8 +2,10 @@ import { View, Text, Button } from "react-native-ui-lib";
import React from "react"; import React from "react";
import { Fontisto } from "@expo/vector-icons"; import { Fontisto } from "@expo/vector-icons";
import { ProgressBar } from "react-native-ui-lib/src/components/progressBar"; import { ProgressBar } from "react-native-ui-lib/src/components/progressBar";
import { useToDosContext } from "@/contexts/ToDosContext";
const ProgressCard = () => { const ProgressCard = () => {
const { maxPoints } = useToDosContext();
return ( return (
<View <View
backgroundColor="white" backgroundColor="white"
@ -30,12 +32,10 @@ const ProgressCard = () => {
/> />
<View row spread> <View row spread>
<Text color={"#868686"}>0</Text> <Text color={"#868686"}>0</Text>
<Text color={"#868686"}>1000</Text> <Text color={"#868686"}>{maxPoints}</Text>
</View> </View>
<View centerV centerH> <View centerV centerH>
<Button <Button backgroundColor="transparent">
backgroundColor="transparent"
>
<Text style={{ textDecorationLine: "underline", color: "#05a8b6" }}> <Text style={{ textDecorationLine: "underline", color: "#05a8b6" }}>
View your full progress report here View your full progress report here
</Text> </Text>

View File

@ -1,5 +1,6 @@
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 fuzzysort from "fuzzysort";
export enum GroceryFrequency { export enum GroceryFrequency {
Never = "Never", Never = "Never",
@ -32,9 +33,10 @@ export enum GroceryCategory {
Household = "Household", Household = "Household",
PersonalCare = "Personal Care", PersonalCare = "Personal Care",
Frozen = "Frozen", Frozen = "Frozen",
None = "No Category",
} }
type MaterialIconNames = keyof typeof MaterialCommunityIcons.glyphMap; /*type MaterialIconNames = keyof typeof MaterialCommunityIcons.glyphMap;
const iconMapping: { [key in GroceryCategory]: MaterialIconNames } = { const iconMapping: { [key in GroceryCategory]: MaterialIconNames } = {
//за сад се иконице за категорију бирају одавде //за сад се иконице за категорију бирају одавде
[GroceryCategory.Fruit]: "food-apple", [GroceryCategory.Fruit]: "food-apple",
@ -48,15 +50,16 @@ const iconMapping: { [key in GroceryCategory]: MaterialIconNames } = {
[GroceryCategory.Household]: "home", [GroceryCategory.Household]: "home",
[GroceryCategory.PersonalCare]: "face-man-profile", [GroceryCategory.PersonalCare]: "face-man-profile",
[GroceryCategory.Frozen]: "snowflake", [GroceryCategory.Frozen]: "snowflake",
}; };*/
interface IGroceryContext { interface IGroceryContext {
groceries: IGrocery[]; groceries: IGrocery[];
iconMapping: { [key in GroceryCategory]: MaterialIconNames }; //iconMapping: { [key in GroceryCategory]: MaterialIconNames };
updateGroceryItem: (id: number, changes: Partial<IGrocery>) => void; updateGroceryItem: (id: number, changes: Partial<IGrocery>) => void;
isAddingGrocery: boolean; isAddingGrocery: boolean;
setIsAddingGrocery: (value: boolean) => void; setIsAddingGrocery: (value: boolean) => void;
addGrocery: (grocery: IGrocery) => void; addGrocery: (grocery: IGrocery) => void;
fuzzyMatchGroceryCategory: (groceryTitle: string) => GroceryCategory;
} }
const GroceryContext = createContext<IGroceryContext | undefined>(undefined); const GroceryContext = createContext<IGroceryContext | undefined>(undefined);
@ -109,11 +112,114 @@ export const GroceryProvider: React.FC<{ children: React.ReactNode }> = ({
...prevGroceries, ...prevGroceries,
{ {
...grocery, ...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>) => { const updateGroceryItem = (id: number, changes: Partial<IGrocery>) => {
setGroceries((prevGroceries) => setGroceries((prevGroceries) =>
@ -125,7 +231,15 @@ export const GroceryProvider: React.FC<{ children: React.ReactNode }> = ({
return ( return (
<GroceryContext.Provider <GroceryContext.Provider
value={{ groceries, iconMapping, updateGroceryItem, isAddingGrocery, setIsAddingGrocery, addGrocery }} value={{
groceries,
fuzzyMatchGroceryCategory,
//iconMapping,
updateGroceryItem,
isAddingGrocery,
setIsAddingGrocery,
addGrocery,
}}
> >
{children} {children}
</GroceryContext.Provider> </GroceryContext.Provider>

View File

@ -21,6 +21,7 @@ interface IToDosContext {
toDos: IToDo[]; toDos: IToDo[];
updateToDo: (id: number, changes: Partial<IToDo>) => void; updateToDo: (id: number, changes: Partial<IToDo>) => void;
addToDo: (newToDo: IToDo) => void; addToDo: (newToDo: IToDo) => void;
maxPoints: number;
} }
const ToDosContext = createContext<IToDosContext>(undefined!); const ToDosContext = createContext<IToDosContext>(undefined!);
@ -35,7 +36,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: new Date(), date: new Date(),
rotate: true, rotate: true,
repeatType: "Every week" repeatType: "Every week",
}, },
{ {
id: 1, id: 1,
@ -43,7 +44,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: new Date(), date: new Date(),
rotate: false, rotate: false,
repeatType: "Every week" repeatType: "Every week",
}, },
{ {
id: 2, id: 2,
@ -51,7 +52,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: new Date(), date: new Date(),
rotate: true, rotate: true,
repeatType: "Every week" repeatType: "Every week",
}, },
{ {
id: 3, id: 3,
@ -60,7 +61,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
date: new Date(Date.now() + 86400000), date: new Date(Date.now() + 86400000),
points: 40, points: 40,
rotate: false, rotate: false,
repeatType: "Every week" repeatType: "Every week",
}, },
{ {
id: 4, id: 4,
@ -68,7 +69,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: new Date(Date.now() + 86400000), date: new Date(Date.now() + 86400000),
rotate: false, rotate: false,
repeatType: "Once a Month" repeatType: "Once a Month",
}, },
{ {
id: 5, id: 5,
@ -76,7 +77,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: new Date(Date.now() + 2 * 86400000), date: new Date(Date.now() + 2 * 86400000),
rotate: true, rotate: true,
repeatType: "Once a Month" repeatType: "Once a Month",
}, },
{ {
id: 6, id: 6,
@ -84,7 +85,8 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: new Date(Date.now() + 3 * 86400000), date: new Date(Date.now() + 3 * 86400000),
rotate: false, rotate: false,
repeatType: "Once a year" repeatType: "Once a year",
points: 50,
}, },
{ {
id: 7, id: 7,
@ -92,7 +94,7 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: null, date: null,
rotate: false, rotate: false,
repeatType: "None" repeatType: "None",
}, },
{ {
id: 8, id: 8,
@ -100,14 +102,34 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
done: false, done: false,
date: null, date: null,
rotate: false, 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>) => { const updateToDo = (id: number, changes: Partial<IToDo>) => {
setToDos((prevToDos) => setToDos((prevToDos) =>
prevToDos.map((toDo) => (toDo.id === id ? { ...toDo, ...changes } : toDo)) prevToDos.map((toDo) => (toDo.id === id ? { ...toDo, ...changes } : toDo))
); );
calculateMaxPoints();
}; };
const addToDo = (newToDo: IToDo) => { const addToDo = (newToDo: IToDo) => {
@ -118,10 +140,11 @@ export const ToDosContextProvider: FC<{ children: ReactNode }> = ({
id: prevToDos.length ? prevToDos[prevToDos.length - 1].id + 1 : 0, id: prevToDos.length ? prevToDos[prevToDos.length - 1].id + 1 : 0,
}, },
]); ]);
calculateMaxPoints();
}; };
return ( return (
<ToDosContext.Provider value={{ toDos, updateToDo, addToDo }}> <ToDosContext.Provider value={{ toDos, updateToDo, addToDo, maxPoints }}>
{children} {children}
</ToDosContext.Provider> </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 { useMutation } from "react-query";
import auth from "@react-native-firebase/auth"; import auth from "@react-native-firebase/auth";
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
import { ProfileType } from "@/contexts/AuthContext"; import { ProfileType } from "@/contexts/AuthContext";
import { useSetUserData } from "./useSetUserData";
export const useSignUp = () => { export const useSignUp = () => {
const {mutateAsync: updateUserData} = useUpdateUserData() const { mutateAsync: setUserData } = useSetUserData();
return useMutation({ return useMutation({
mutationKey: ["signUp"], mutationKey: ["signUp"],
mutationFn: async ({email, password, firstName, lastName}: { email: string, password: string, firstName: string, lastName: string }) => { mutationFn: async ({
const res = await auth().createUserWithEmailAndPassword(email, password); email,
await updateUserData({newUserData: {userType: ProfileType.PARENT, firstName: firstName, lastName: lastName}, customUser: res.user}); 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);
} }
}); });
} },
});
};

274
package-lock.json generated
View File

@ -32,6 +32,7 @@
"expo-web-browser": "~13.0.3", "expo-web-browser": "~13.0.3",
"firebase-admin": "^12.3.1", "firebase-admin": "^12.3.1",
"firebase-functions": "^5.1.0", "firebase-functions": "^5.1.0",
"fuzzysort": "^3.0.2",
"jotai": "^2.9.1", "jotai": "^2.9.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
@ -2522,9 +2523,9 @@
} }
}, },
"node_modules/@expo/config-plugins": { "node_modules/@expo/config-plugins": {
"version": "8.0.8", "version": "8.0.9",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.8.tgz", "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.9.tgz",
"integrity": "sha512-Fvu6IO13EUw0R9WeqxUO37FkM62YJBNcZb9DyJAOgMz7Ez/vaKQGEjKt9cwT+Q6uirtCATMgaq6VWAW7YW8xXw==", "integrity": "sha512-dNCG45C7BbDPV9MdWvCbsFtJtVn4w/TJbb5b7Yr6FA8HYIlaaVM0wqUMzTPmGj54iYXw8X/Vge8uCPxg7RWgeA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@expo/config-types": "^51.0.0-unreleased", "@expo/config-types": "^51.0.0-unreleased",
@ -3449,9 +3450,9 @@
} }
}, },
"node_modules/@expo/vector-icons": { "node_modules/@expo/vector-icons": {
"version": "14.0.2", "version": "14.0.3",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.2.tgz", "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-14.0.3.tgz",
"integrity": "sha512-70LpmXQu4xa8cMxjp1fydgRPsalefnHaXLzIwaHMEzcZhnyjw2acZz8azRrZOslPVAWlxItOa2Dd7WtD/kI+CA==", "integrity": "sha512-UJAKOXPPi6ez/1QZfoFVopCH3+c12Sw+T+IIVkvONCEN7zjN1fLxxWHkZ7Spz4WO5EH2ObtaJfCe/k4rw+ftxA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"prop-types": "^15.8.1" "prop-types": "^15.8.1"
@ -9379,9 +9380,9 @@
} }
}, },
"node_modules/body-parser": { "node_modules/body-parser": {
"version": "1.20.2", "version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bytes": "3.1.2", "bytes": "3.1.2",
@ -9392,7 +9393,7 @@
"http-errors": "2.0.0", "http-errors": "2.0.0",
"iconv-lite": "0.4.24", "iconv-lite": "0.4.24",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"qs": "6.11.0", "qs": "6.13.0",
"raw-body": "2.5.2", "raw-body": "2.5.2",
"type-is": "~1.6.18", "type-is": "~1.6.18",
"unpipe": "1.0.0" "unpipe": "1.0.0"
@ -11522,24 +11523,24 @@
} }
}, },
"node_modules/expo": { "node_modules/expo": {
"version": "51.0.30", "version": "51.0.34",
"resolved": "https://registry.npmjs.org/expo/-/expo-51.0.30.tgz", "resolved": "https://registry.npmjs.org/expo/-/expo-51.0.34.tgz",
"integrity": "sha512-eo4T2TGnKyDkLryY6lYuGVV1FZyWa1FG7Ns0aqfZ7N/PFQYDfbowVwkcEWbRppSzqSfjGXYqvVV4WZNFbpgZZw==", "integrity": "sha512-l2oi+hIj/ph3qGcvM54Nyd2uF3Zq5caVmSg7AXfBUgtvcdv5Pj1EI/2xCXP9tfMNQo351CWyOwBkTGjv+GdrLg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.0", "@babel/runtime": "^7.20.0",
"@expo/cli": "0.18.29", "@expo/cli": "0.18.29",
"@expo/config": "9.0.3", "@expo/config": "9.0.3",
"@expo/config-plugins": "8.0.8", "@expo/config-plugins": "8.0.9",
"@expo/metro-config": "0.18.11", "@expo/metro-config": "0.18.11",
"@expo/vector-icons": "^14.0.0", "@expo/vector-icons": "^14.0.3",
"babel-preset-expo": "~11.0.14", "babel-preset-expo": "~11.0.14",
"expo-asset": "~10.0.10", "expo-asset": "~10.0.10",
"expo-file-system": "~17.0.1", "expo-file-system": "~17.0.1",
"expo-font": "~12.0.9", "expo-font": "~12.0.10",
"expo-keep-awake": "~13.0.2", "expo-keep-awake": "~13.0.2",
"expo-modules-autolinking": "1.11.2", "expo-modules-autolinking": "1.11.2",
"expo-modules-core": "1.12.22", "expo-modules-core": "1.12.24",
"fbemitter": "^3.0.0", "fbemitter": "^3.0.0",
"whatwg-url-without-unicode": "8.0.0-3" "whatwg-url-without-unicode": "8.0.0-3"
}, },
@ -11703,9 +11704,9 @@
} }
}, },
"node_modules/expo-font": { "node_modules/expo-font": {
"version": "12.0.9", "version": "12.0.10",
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.9.tgz", "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-12.0.10.tgz",
"integrity": "sha512-seTCyf0tbgkAnp3ZI9ZfK9QVtURQUgFnuj+GuJ5TSnN0XsOtVe1s2RxTvmMgkfuvfkzcjJ69gyRpsZS1cC8hjw==", "integrity": "sha512-Q1i2NuYri3jy32zdnBaHHCya1wH1yMAsI+3CCmj9zlQzlhsS9Bdwcj2W3c5eU5FvH2hsNQy4O+O1NnM6o/pDaQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fontfaceobserver": "^2.1.0" "fontfaceobserver": "^2.1.0"
@ -11877,9 +11878,9 @@
} }
}, },
"node_modules/expo-modules-core": { "node_modules/expo-modules-core": {
"version": "1.12.22", "version": "1.12.24",
"resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.22.tgz", "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.24.tgz",
"integrity": "sha512-MX9qJRuVszyuGksOZ1QkMcUGcAZ1o2AmDigkQAl9yxqFtwEpBOxELs3rXeQol7WiEabvK+bERTF9LtSTDCVCYw==", "integrity": "sha512-3geIe2ecizlp7l26iY8Nmc59z2d1RUC5nQZtI9iJoi5uHEUV/zut8e4zRLFVnZb8KOcMcEDsrvaBL5DPnqdfpg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"invariant": "^2.2.4" "invariant": "^2.2.4"
@ -12055,37 +12056,37 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.19.2", "version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.2", "body-parser": "1.20.3",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.6.0", "cookie": "0.6.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"etag": "~1.8.1", "etag": "~1.8.1",
"finalhandler": "1.2.0", "finalhandler": "1.3.1",
"fresh": "0.5.2", "fresh": "0.5.2",
"http-errors": "2.0.0", "http-errors": "2.0.0",
"merge-descriptors": "1.0.1", "merge-descriptors": "1.0.3",
"methods": "~1.1.2", "methods": "~1.1.2",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"path-to-regexp": "0.1.7", "path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7", "proxy-addr": "~2.0.7",
"qs": "6.11.0", "qs": "6.13.0",
"range-parser": "~1.2.1", "range-parser": "~1.2.1",
"safe-buffer": "5.2.1", "safe-buffer": "5.2.1",
"send": "0.18.0", "send": "0.19.0",
"serve-static": "1.15.0", "serve-static": "1.16.2",
"setprototypeof": "1.2.0", "setprototypeof": "1.2.0",
"statuses": "2.0.1", "statuses": "2.0.1",
"type-is": "~1.6.18", "type-is": "~1.6.18",
@ -12111,14 +12112,23 @@
"ms": "2.0.0" "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": { "node_modules/express/node_modules/finalhandler": {
"version": "1.2.0", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"debug": "2.6.9", "debug": "2.6.9",
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"on-finished": "2.4.1", "on-finished": "2.4.1",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
@ -12129,6 +12139,18 @@
"node": ">= 0.8" "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": { "node_modules/express/node_modules/ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -12147,6 +12169,45 @@
"node": ">= 0.8" "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": { "node_modules/express/node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -12832,6 +12893,12 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/gaxios": {
"version": "6.7.1", "version": "6.7.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
@ -17495,10 +17562,13 @@
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/merge-descriptors": { "node_modules/merge-descriptors": {
"version": "1.0.1", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT" "license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
@ -18906,9 +18976,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.7", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-type": { "node_modules/path-type": {
@ -19300,12 +19370,12 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.11.0", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
"side-channel": "^1.0.4" "side-channel": "^1.0.6"
}, },
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@ -20631,20 +20701,116 @@
} }
}, },
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.15.0", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"encodeurl": "~1.0.2", "encodeurl": "~2.0.0",
"escape-html": "~1.0.3", "escape-html": "~1.0.3",
"parseurl": "~1.3.3", "parseurl": "~1.3.3",
"send": "0.18.0" "send": "0.19.0"
}, },
"engines": { "engines": {
"node": ">= 0.8.0" "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": { "node_modules/set-blocking": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",

View File

@ -39,6 +39,7 @@
"expo-web-browser": "~13.0.3", "expo-web-browser": "~13.0.3",
"firebase-admin": "^12.3.1", "firebase-admin": "^12.3.1",
"firebase-functions": "^5.1.0", "firebase-functions": "^5.1.0",
"fuzzysort": "^3.0.2",
"jotai": "^2.9.1", "jotai": "^2.9.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",

1408
yarn.lock

File diff suppressed because it is too large Load Diff