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

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,49 +8,55 @@ 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";
import {useState} from "react";
import {MaterialIcons} from "@expo/vector-icons";
import {PickerMultiValue} from "react-native-ui-lib/src/components/picker/types";
import {useAuthContext} from "@/contexts/AuthContext";
import {useCreateEvent} from "@/hooks/firebase/useCreateEvent";
import {EventData} from "@/hooks/firebase/types/eventData";
import { ScrollView, TouchableOpacity } from "react-native-gesture-handler";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useState } from "react";
import { MaterialIcons } from "@expo/vector-icons";
import { PickerMultiValue } from "react-native-ui-lib/src/components/picker/types";
import { useAuthContext } from "@/contexts/AuthContext";
import { useCreateEvent } from "@/hooks/firebase/useCreateEvent";
import { EventData } from "@/hooks/firebase/types/eventData";
const daysOfWeek = [
{label: "Monday", value: "monday"},
{label: "Tuesday", value: "tuesday"},
{label: "Wednesday", value: "wednesday"},
{label: "Thursday", value: "thursday"},
{label: "Friday", value: "friday"},
{label: "Saturday", value: "saturday"},
{label: "Sunday", value: "sunday"},
{ label: "Monday", value: "monday" },
{ label: "Tuesday", value: "tuesday" },
{ label: "Wednesday", value: "wednesday" },
{ label: "Thursday", value: "thursday" },
{ label: "Friday", value: "friday" },
{ label: "Saturday", value: "saturday" },
{ label: "Sunday", value: "sunday" },
];
export const ManuallyAddEventModal = ({show, close}: { show: boolean, close: () => void }) => {
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(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}: { show: boolean, close: ()
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}: { show: boolean, close: ()
onRequestClose={close}
transparent={false}
>
<LoaderScreen message={'Saving event...'} color={Colors.grey40}/>
<LoaderScreen message={"Saving event..."} color={Colors.grey40} />
</Modal>
)
);
}
return (
@ -131,28 +149,38 @@ export const ManuallyAddEventModal = ({show, close}: { show: boolean, close: ()
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>
<Text style={{ color: "#007bff" }}>Cancel</Text>
</TouchableOpacity>
<Text style={{fontWeight: "bold", fontSize: 16}}>Add event</Text>
<Text style={{ fontWeight: "bold", fontSize: 16 }}>Add event</Text>
<TouchableOpacity onPress={handleSave}>
<Text style={{color: "#007bff"}}>Save</Text>
<Text style={{ color: "#007bff" }}>Save</Text>
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={{paddingHorizontal: 16, paddingTop: 10}}>
<View style={{marginVertical: 10}}>
<ScrollView
contentContainerStyle={{ paddingHorizontal: 16, paddingTop: 10 }}
>
<View style={{ marginVertical: 10 }}>
<TextField
placeholder={'Title'}
placeholder={"Title"}
floatingPlaceholder
value={title}
onChangeText={setTitle}
@ -160,22 +188,23 @@ export const ManuallyAddEventModal = ({show, close}: { show: boolean, close: ()
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
}}>
<View style={{flexDirection: "row", alignItems: "center"}}>
<MaterialIcons name="schedule" size={24} color="gray"/>
<Text style={{marginLeft: 10}}>All-day</Text>
marginBottom: 20,
}}
>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<MaterialIcons name="schedule" size={24} color="gray" />
<Text style={{ marginLeft: 10 }}>All-day</Text>
</View>
<Switch
value={isAllDay}
@ -183,73 +212,104 @@ export const ManuallyAddEventModal = ({show, close}: { show: boolean, close: ()
/>
</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}}>
<MaterialIcons name="repeat" size={24} color="gray"/>
<View
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 20,
}}
>
<MaterialIcons name="repeat" size={24} color="gray" />
<Picker
value={repeatInterval}
//@ts-ignore
onChange={(items: PickerMultiValue) => setRepeatInterval(items)}
placeholder="Doest not repeat"
style={{marginLeft: 10, flex: 1}}
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}}>
<MaterialIcons name="person-add" size={24} color="gray"/>
<TouchableOpacity style={{marginLeft: 10, flexDirection: "row", alignItems: "center", flex: 1}}>
<Avatar size={40} backgroundColor={Colors.yellow10}/>
<View style={{marginLeft: 10}}>
<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,
}}
>
<Avatar size={40} backgroundColor={Colors.yellow10} />
<View style={{ marginLeft: 10 }}>
<Text>Other</Text>
<Text style={{color: "gray"}}>{user?.email}</Text>
<Text style={{ color: "gray" }}>{user?.email}</Text>
</View>
</TouchableOpacity>
</View>

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

@ -31,6 +31,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,6 +47,7 @@ const GroceryList = () => {
);
useEffect(() => {
if(submit){
if (title?.length > 2 && title?.length <= 25) {
addGrocery({
id: 0,
@ -58,7 +60,14 @@ const GroceryList = () => {
});
setIsAddingGrocery(false);
setSubmitted(false);
setTitle("");
}
}
}, [submit]);
useEffect(() => {
/**/
}, [category]);
useEffect(() => {
@ -67,7 +76,7 @@ const GroceryList = () => {
}, [groceries]);
return (
<View marginH-20>
<View marginH-20 marginB-20>
<HeaderTemplate
message={"Welcome to your grocery list"}
isWelcome={false}
@ -101,7 +110,7 @@ const GroceryList = () => {
</Text>
</View>
<Button
backgroundColor='transparent'
backgroundColor="transparent"
paddingH-10
iconSource={() => (
<MaterialIcons name="person-add-alt" size={24} color="gray" />
@ -157,7 +166,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,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,8 +2,10 @@ 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 { maxPoints } = useToDosContext();
return (
<View
backgroundColor="white"
@ -30,12 +32,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"
>
<Button backgroundColor="transparent">
<Text style={{ textDecorationLine: "underline", color: "#05a8b6" }}>
View your full progress report here
</Text>

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 { useMutation } from "react-query";
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 = () => {
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);
}
});
}
},
});
};

274
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",
@ -2522,9 +2523,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 +3450,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 +9380,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 +9393,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 +11523,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 +11704,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 +11878,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 +12056,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 +12112,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 +12139,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 +12169,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 +12893,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",
@ -17495,10 +17562,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 +18976,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 +19370,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"
@ -20631,20 +20701,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

@ -39,6 +39,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",

1408
yarn.lock

File diff suppressed because it is too large Load Diff