Merge branch 'dev'

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

View File

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

View File

@ -1,6 +1,3 @@
import { Text, View } from "react-native-ui-lib";
import GroceryList from "@/components/pages/grocery/GroceryList";
import AddGroceryItem from "@/components/pages/grocery/AddGroceryItem";
import { GroceryProvider } from "@/contexts/GroceryContext"; import { 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

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

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

@ -1,260 +1,320 @@
import { import {
Avatar, Avatar,
Colors, Colors,
DateTimePicker, DateTimePicker,
LoaderScreen, LoaderScreen,
Modal, Modal,
Picker, Picker,
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";
import {useState} from "react"; import { useState } from "react";
import {MaterialIcons} from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import {PickerMultiValue} from "react-native-ui-lib/src/components/picker/types"; import { PickerMultiValue } from "react-native-ui-lib/src/components/picker/types";
import {useAuthContext} from "@/contexts/AuthContext"; import { useAuthContext } from "@/contexts/AuthContext";
import {useCreateEvent} from "@/hooks/firebase/useCreateEvent"; import { useCreateEvent } from "@/hooks/firebase/useCreateEvent";
import {EventData} from "@/hooks/firebase/types/eventData"; import { EventData } from "@/hooks/firebase/types/eventData";
const daysOfWeek = [ const daysOfWeek = [
{label: "Monday", value: "monday"}, { label: "Monday", value: "monday" },
{label: "Tuesday", value: "tuesday"}, { label: "Tuesday", value: "tuesday" },
{label: "Wednesday", value: "wednesday"}, { label: "Wednesday", value: "wednesday" },
{label: "Thursday", value: "thursday"}, { label: "Thursday", value: "thursday" },
{label: "Friday", value: "friday"}, { label: "Friday", value: "friday" },
{label: "Saturday", value: "saturday"}, { label: "Saturday", value: "saturday" },
{label: "Sunday", value: "sunday"}, { label: "Sunday", value: "sunday" },
]; ];
export const ManuallyAddEventModal = ({show, close, initialDate}: { show: boolean, close: () => void, initialDate?: Date }) => { export const ManuallyAddEventModal = ({
const {user} = useAuthContext() show,
const insets = useSafeAreaInsets(); close,
}: {
show: boolean;
close: () => void;
}) => {
const { user } = useAuthContext();
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(initialDate ?? new Date()); const [startTime, setStartTime] = useState(initialDate ?? new Date());
const [endTime, setEndTime] = useState(new Date()) const [endTime, setEndTime] = useState(new Date());
const [startDate, setStartDate] = useState(new Date()); const [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",
}); });
}; };
const combineDateAndTime = (date: Date, time: Date): Date => { const combineDateAndTime = (date: Date, time: Date): Date => {
const combined = new Date(date); const combined = new Date(date);
combined.setHours(time.getHours()); combined.setHours(time.getHours());
combined.setMinutes(time.getMinutes()); combined.setMinutes(time.getMinutes());
combined.setSeconds(0); combined.setSeconds(0);
combined.setMilliseconds(0); combined.setMilliseconds(0);
return combined; return combined;
}; };
const handleSave = async () => { const handleSave = async () => {
let finalStartDate: Date; let finalStartDate: Date;
let finalEndDate: Date; let finalEndDate: Date;
if (isAllDay) { if (isAllDay) {
finalStartDate = new Date(startDate); finalStartDate = new Date(startDate);
finalStartDate.setHours(0, 0, 0, 0); finalStartDate.setHours(0, 0, 0, 0);
finalEndDate = new Date(startDate); finalEndDate = new Date(startDate);
finalEndDate.setHours(23, 59, 59, 999); finalEndDate.setHours(23, 59, 59, 999);
} else { } else {
finalStartDate = combineDateAndTime(startDate, startTime); finalStartDate = combineDateAndTime(startDate, startTime);
finalEndDate = combineDateAndTime(endDate, endTime); finalEndDate = combineDateAndTime(endDate, endTime);
}
const eventData: Partial<EventData> = {
title,
startDate: finalStartDate,
endDate: finalEndDate,
repeatDays: repeatInterval.map(x => x.toString()),
allDay: isAllDay
};
await createEvent(eventData)
close();
};
const getRepeatLabel = () => {
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 isEveryDay = allDays.every(day => selectedDays.includes(day));
if (isEveryDay) {
return "Every day";
} 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)
.join(", ");
}
};
if (isLoading && !isError) {
return (
<Modal
visible={show}
animationType="slide"
onRequestClose={close}
transparent={false}
>
<LoaderScreen message={'Saving event...'} color={Colors.grey40}/>
</Modal>
)
} }
const eventData: Partial<EventData> = {
title,
startDate: finalStartDate,
endDate: finalEndDate,
repeatDays: repeatInterval.map((x) => x.toString()),
allDay: isAllDay,
};
await createEvent(eventData);
close();
};
const getRepeatLabel = () => {
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 isEveryDay = allDays.every((day) => selectedDays.includes(day));
if (isEveryDay) {
return "Every day";
} 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)
.join(", ");
}
};
if (isLoading && !isError) {
return ( return (
<Modal <Modal
visible={show} visible={show}
animationType="slide" animationType="slide"
onRequestClose={close} onRequestClose={close}
transparent={false} transparent={false}
> >
<View style={{ <LoaderScreen message={"Saving event..."} color={Colors.grey40} />
flex: 1, </Modal>
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}}>
<TouchableOpacity onPress={close}>
<Text style={{color: "#007bff"}}>Cancel</Text>
</TouchableOpacity>
<Text style={{fontWeight: "bold", fontSize: 16}}>Add event</Text>
<TouchableOpacity onPress={handleSave}>
<Text style={{color: "#007bff"}}>Save</Text>
</TouchableOpacity>
</View>
<ScrollView contentContainerStyle={{paddingHorizontal: 16, paddingTop: 10}}>
<View style={{marginVertical: 10}}>
<TextField
placeholder={'Title'}
floatingPlaceholder
value={title}
onChangeText={setTitle}
showCharCounter
maxLength={200}
fieldStyle={{
borderBottomWidth: 1,
borderBottomColor: 'black',
borderStyle: 'solid',
}}
/>
</View>
<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>
</View>
<Switch
value={isAllDay}
onValueChange={(value) => setIsAllDay(value)}
/>
</View>
<View style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20
}}>
<DateTimePicker
mode="date"
dateFormatter={formatDateTime}
value={startDate}
onChange={setStartDate}
/>
{!isAllDay && (
<DateTimePicker
mode="time"
value={startTime}
onChange={setStartTime}
/>
)}
</View>
{!isAllDay && (
<View style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20
}}>
<DateTimePicker
mode="date"
dateFormatter={formatDateTime}
value={endDate}
onChange={setEndDate}
/>
<DateTimePicker
mode="time"
value={endTime}
onChange={setEndTime}
/>
</View>
)}
<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}}
mode={Picker.modes.MULTI}
getLabel={getRepeatLabel}
>
{daysOfWeek.map((option) => (
<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}}>
<Text>Other</Text>
<Text style={{color: "gray"}}>{user?.email}</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView>
</View>
</Modal>
); );
}
return (
<Modal
visible={show}
animationType="slide"
onRequestClose={close}
transparent={false}
>
<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,
}}
>
<TouchableOpacity onPress={close}>
<Text style={{ color: "#007bff" }}>Cancel</Text>
</TouchableOpacity>
<Text style={{ fontWeight: "bold", fontSize: 16 }}>Add event</Text>
<TouchableOpacity onPress={handleSave}>
<Text style={{ color: "#007bff" }}>Save</Text>
</TouchableOpacity>
</View>
<ScrollView
contentContainerStyle={{ paddingHorizontal: 16, paddingTop: 10 }}
>
<View style={{ marginVertical: 10 }}>
<TextField
placeholder={"Title"}
floatingPlaceholder
value={title}
onChangeText={setTitle}
showCharCounter
maxLength={200}
fieldStyle={{
borderBottomWidth: 1,
borderBottomColor: "black",
borderStyle: "solid",
}}
/>
</View>
<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>
</View>
<Switch
value={isAllDay}
onValueChange={(value) => setIsAllDay(value)}
/>
</View>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
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={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
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" />
<Picker
value={repeatInterval}
//@ts-ignore
onChange={(items: PickerMultiValue) => setRepeatInterval(items)}
placeholder="Doest not repeat"
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>
</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 }}>
<Text>Other</Text>
<Text style={{ color: "gray" }}>{user?.email}</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView>
</View>
</Modal>
);
}; };

View File

@ -1,28 +1,60 @@
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";
const EditGroceryItem = (props: {title: string, setTitle: (value: string) => void, setCategory: (category: GroceryCategory) => void}) => { interface IEditGrocery {
return ( id?: number;
<View title: string;
style={{ setTitle: (value: string) => void;
backgroundColor: "white", setCategory?: (category: GroceryCategory) => void;
width: "100%", category: GroceryCategory;
borderRadius: 25, setSubmit?: (value: boolean) => void;
padding: 15, updateCategory?: (id: number, changes: Partial<IGrocery>) => void;
}} closeEdit?: (value: boolean) => void;
>
<TextField
placeholder="Grocery"
value={props.title}
onChangeText={(value) => props.setTitle(value)}
maxLength={25}
/>
<CategoryDropdown setSelectedCategory={props.setCategory} />
</View>
)
} }
export default EditGroceryItem const EditGroceryItem = ({ editGrocery }: { editGrocery: IEditGrocery }) => {
const { fuzzyMatchGroceryCategory } = useGroceryContext();
useEffect(() => {
if (editGrocery.setCategory)
editGrocery.setCategory(fuzzyMatchGroceryCategory(editGrocery.title));
}, [editGrocery.title]);
return (
<View
style={{
backgroundColor: "white",
width: "100%",
borderRadius: 25,
padding: 15,
}}
>
<TextField
placeholder="Grocery"
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}
/>
<Text>{editGrocery.category}</Text>
</View>
);
};
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,49 +66,24 @@ 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>
<ListItem.Part right containerStyle={{ paddingEnd: 20 }}> <ListItem.Part right containerStyle={{ paddingEnd: 20 }}>

View File

@ -12,6 +12,7 @@ import HeaderTemplate from "@/components/shared/HeaderTemplate";
import CategoryDropdown from "./CategoryDropdown"; import CategoryDropdown from "./CategoryDropdown";
import { MaterialIcons } from "@expo/vector-icons"; import { MaterialIcons } from "@expo/vector-icons";
import EditGroceryItem from "./EditGroceryItem"; import EditGroceryItem from "./EditGroceryItem";
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
const GroceryList = () => { const GroceryList = () => {
const { const {
@ -21,6 +22,7 @@ const GroceryList = () => {
setIsAddingGrocery, setIsAddingGrocery,
addGrocery, addGrocery,
} = useGroceryContext(); } = useGroceryContext();
const { profileData } = useAuthContext();
const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>( const [approvedGroceries, setapprovedGroceries] = useState<IGrocery[]>(
groceries.filter((item) => item.approved === true) groceries.filter((item) => item.approved === true)
); );
@ -31,6 +33,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,19 +49,27 @@ const GroceryList = () => {
); );
useEffect(() => { useEffect(() => {
if (title?.length > 2 && title?.length <= 25) { if (submit) {
addGrocery({ if (title?.length > 2 && title?.length <= 25) {
id: 0, addGrocery({
title: title, id: 0,
category: category, title: title,
approved: false, category: category,
recurring: false, approved: profileData?.userType === ProfileType.PARENT ? true : false,
frequency: GroceryFrequency.Never, recurring: false,
bought: false, frequency: GroceryFrequency.Never,
}); bought: false,
});
setIsAddingGrocery(false); setIsAddingGrocery(false);
setSubmitted(false);
setTitle("");
}
} }
}, [submit]);
useEffect(() => {
/**/
}, [category]); }, [category]);
useEffect(() => { useEffect(() => {
@ -67,7 +78,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,8 +112,8 @@ 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 +168,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,145 +1,150 @@
import React, {useState} from "react"; import React, { useState } from "react";
import {Button, ButtonSize, Text, TextField, View,} from "react-native-ui-lib"; import {
import {useSignUp} from "@/hooks/firebase/useSignUp"; Button,
import {ProfileType} from "@/contexts/AuthContext"; ButtonSize,
import {StyleSheet} from "react-native"; Checkbox,
import {AntDesign} from "@expo/vector-icons"; Text,
TextField,
TouchableOpacity,
View,
} from "react-native-ui-lib";
import { useSignUp } from "@/hooks/firebase/useSignUp";
import { ProfileType } from "@/contexts/AuthContext";
import { StyleSheet } from "react-native";
import { AntDesign } from "@expo/vector-icons";
const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => { const SignUpPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => {
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [firstName, setFirstName] = useState<string>(""); const [firstName, setFirstName] = useState<string>("");
const [lastName, setLastName] = useState<string>(""); const [lastName, setLastName] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [isParent, setIsParent] = useState<boolean>(true);
const [isChild, setIsChild] = useState<boolean>(false);
const [isCaregiver, setIsCaregiver] = useState<boolean>(false);
const [profileType, setProfileType] = useState<ProfileType>(
ProfileType.PARENT
);
const {mutateAsync: signUp} = useSignUp();
const handleSignUp = async () => { const [isPasswordVisible, setIsPasswordVisible] = useState<boolean>(false);
await signUp({email, password, firstName, lastName}); const [allowFaceID, setAllowFaceID] = useState<boolean>(false);
}; const [acceptTerms, setAcceptTerms] = useState<boolean>(false);
const { mutateAsync: signUp } = useSignUp();
return ( const handleSignUp = async () => {
<View padding-10> await signUp({ email, password, firstName, lastName });
<Text text30 center> };
Get started with Kali
</Text>
<Text center>Please enter your details.</Text>
<TextField
marginT-60
placeholder="First name"
value={firstName}
onChangeText={setFirstName}
style={styles.textfield}
/>
<TextField
placeholder="Last name"
value={lastName}
onChangeText={setLastName}
style={styles.textfield}
/>
<TextField
placeholder="Email"
value={email}
onChangeText={setEmail}
style={styles.textfield}
/>
<TextField
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
style={styles.textfield}
/>
<Button
label="Register"
onPress={handleSignUp}
style={{marginBottom: 10, backgroundColor: "#fd1775"}}
/>
<Button
label="Sign up with Google"
backgroundColor="white"
color="black"
iconSource={() => (
<AntDesign
name="google"
size={24}
color="black"
style={{marginRight: 15}}
/>
)}
/>
{/*<Text style={{ marginBottom: 10 }}>Choose Profile Type:</Text>
<Checkbox
label="Parent"
value={isParent}
onValueChange={(value) => {
setIsParent(value);
setProfileType(ProfileType.PARENT);
if (value) {
setIsChild(false);
setIsCaregiver(false);
}
}}
style={{ marginBottom: 10 }}
/>
<Checkbox
label="Child"
value={isChild}
onValueChange={(value) => {
setIsChild(value);
setProfileType(ProfileType.CHILD);
if (value) {
setIsParent(false);
setIsCaregiver(false);
}
}}
style={{ marginBottom: 10 }}
/>
<Checkbox
label="Caregiver"
value={isCaregiver}
onValueChange={(value) => {
setIsCaregiver(value);
setProfileType(ProfileType.CAREGIVER);
if (value) {
setIsParent(false);
setIsChild(false);
}
}}
/>*/}
<View row centerH marginT-10 marginB-5 gap-5>
<Text text70 center>
Already have an account?
</Text>
return (
<Button <View padding-10 height={"100%"} flexG>
label="Sign In" <Text text30 center>
flexS Get started with Kali
margin-0 </Text>
link <Text center>Please enter your details.</Text>
color="#fd1775" <TextField
size={ButtonSize.small} marginT-60
text70 placeholder="First name"
onPress={() => setTab("login")} value={firstName}
/> onChangeText={setFirstName}
</View> style={styles.textfield}
/>
<TextField
placeholder="Last name"
value={lastName}
onChangeText={setLastName}
style={styles.textfield}
/>
<TextField
placeholder="Email"
value={email}
onChangeText={setEmail}
style={styles.textfield}
/>
<TextField
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry={!isPasswordVisible}
style={styles.textfield}
trailingAccessory={
<TouchableOpacity
onPress={() => setIsPasswordVisible(!isPasswordVisible)}
>
<AntDesign
name={isPasswordVisible ? "eye" : "eyeo"}
size={24}
color="gray"
/>
</TouchableOpacity>
}
/>
<View gap-10 marginH-10>
<View row centerV>
<Checkbox
value={allowFaceID}
onValueChange={(value) => {
setAllowFaceID(value);
}}
/>
<Text text90R marginL-10>
Allow FaceID for login in future
</Text>
</View> </View>
); <View row centerV>
<Checkbox
value={acceptTerms}
onValueChange={(value) => setAcceptTerms(value)}
/>
<View row>
<Text text90R marginL-10>
I accept the
</Text>
<TouchableOpacity>
<Text text90 style={{ textDecorationLine: "underline" }}>
{" "}
terms and conditions
</Text>
</TouchableOpacity>
<Text text90R> and </Text>
<TouchableOpacity>
<Text text90 style={{ textDecorationLine: "underline" }}>
{" "}
privacy policy
</Text>
</TouchableOpacity>
</View>
</View>
</View>
<View style={styles.bottomView}>
<Button
label="Register"
onPress={handleSignUp}
style={{ marginBottom: 10, backgroundColor: "#fd1775" }}
/>
<View row centerH marginT-10 marginB-5 gap-5>
<Text text70 center>
Already have an account?
</Text>
<Button
label="Sign In"
flexS
margin-0
link
color="#fd1775"
size={ButtonSize.small}
text70
onPress={() => setTab("login")}
/>
</View>
</View>
</View>
);
}; };
export default SignUpPage; export default SignUpPage;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
textfield: { textfield: {
backgroundColor: "white", backgroundColor: "white",
marginVertical: 10, marginVertical: 10,
padding: 30, padding: 30,
height: 45, height: 45,
borderRadius: 50, borderRadius: 50,
}, },
//mora da se izmeni kako treba
bottomView: { marginTop: 150 },
}); });

View File

@ -1,7 +1,12 @@
import { View, Text, TouchableOpacity } from "react-native-ui-lib"; import { 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,51 +14,57 @@ 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 }}
<TouchableOpacity onPress={() => props.setSelectedPage(0)}> >
<View row marginT-20 marginL-20 marginB-35 centerV> <View style={{ flex: 1 }}>
<Ionicons name="chevron-back" size={22} color="#979797" /> <TouchableOpacity onPress={() => props.setSelectedPage(0)}>
<Text text70 color="#979797"> <View row marginT-20 marginL-20 marginB-35 centerV>
Return to main settings <Ionicons name="chevron-back" size={22} color="#979797" />
<Text text70 color="#979797">
Return to main settings
</Text>
</View>
</TouchableOpacity>
<View marginH-20>
<Text text60R marginB-25>
User Management
</Text> </Text>
<View style={styles.buttonSwitch} spread row>
<TouchableOpacity
onPress={() => setSelectedView(true)}
centerV
centerH
style={
selectedView == true ? styles.btnSelected : styles.btnNot
}
>
<View>
<Text text70 color={selectedView ? "white" : "black"}>
My Profile
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setSelectedView(false)}
centerV
centerH
style={
selectedView == false ? styles.btnSelected : styles.btnNot
}
>
<View>
<Text text70 color={!selectedView ? "white" : "black"}>
My Group
</Text>
</View>
</TouchableOpacity>
</View>
{selectedView && <MyProfile />}
{!selectedView && <MyGroup />}
</View> </View>
</TouchableOpacity>
<View marginH-20>
<Text text60R marginB-25>
User Management
</Text>
<View style={styles.buttonSwitch} spread row>
<TouchableOpacity
onPress={() => setSelectedView(true)}
centerV
centerH
style={selectedView == true ? styles.btnSelected : styles.btnNot}
>
<View>
<Text text70 color={selectedView ? "white" : "black"}>
My Profile
</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => setSelectedView(false)}
centerV
centerH
style={selectedView == false ? styles.btnSelected : styles.btnNot}
>
<View>
<Text text70 color={!selectedView ? "white" : "black"}>
My Group
</Text>
</View>
</TouchableOpacity>
</View>
{selectedView && <MyProfile />}
{!selectedView && <MyGroup />}
</View> </View>
</ScrollView> </ScrollView>
</View>
); );
}; };

View File

@ -2,13 +2,14 @@ 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 = ({children}: {children?: React.ReactNode}) => {
const { maxPoints } = useToDosContext();
return ( return (
<View <View
backgroundColor="white" backgroundColor="white"
marginH-25 marginB-5
marginB-30
padding-15 padding-15
style={{ borderRadius: 22 }} style={{ borderRadius: 22 }}
> >
@ -30,16 +31,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 {children}
backgroundColor="transparent"
>
<Text style={{ textDecorationLine: "underline", color: "#05a8b6" }}>
View your full progress report here
</Text>
</Button>
</View> </View>
</View> </View>
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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);
}
});
},
});
};

317
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",
@ -39,12 +40,13 @@
"react-native-big-calendar": "^4.14.0", "react-native-big-calendar": "^4.14.0",
"react-native-calendars": "^1.1306.0", "react-native-calendars": "^1.1306.0",
"react-native-gesture-handler": "~2.16.1", "react-native-gesture-handler": "~2.16.1",
"react-native-gifted-charts": "^1.4.41",
"react-native-linear-gradient": "^2.8.3", "react-native-linear-gradient": "^2.8.3",
"react-native-onboarding-swiper": "^1.3.0", "react-native-onboarding-swiper": "^1.3.0",
"react-native-reanimated": "~3.10.1", "react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5", "react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1", "react-native-screens": "3.31.1",
"react-native-svg": "^15.6.0", "react-native-svg": "^15.7.1",
"react-native-ui-lib": "^7.27.0", "react-native-ui-lib": "^7.27.0",
"react-native-web": "~0.19.10", "react-native-web": "~0.19.10",
"react-query": "^3.39.3" "react-query": "^3.39.3"
@ -2522,9 +2524,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 +3451,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 +9381,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 +9394,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 +11524,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 +11705,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 +11879,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 +12057,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 +12113,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 +12140,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 +12170,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 +12894,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",
@ -12998,6 +13066,16 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/gifted-charts-core": {
"version": "0.1.41",
"resolved": "https://registry.npmjs.org/gifted-charts-core/-/gifted-charts-core-0.1.41.tgz",
"integrity": "sha512-17xzJ0kJw+BVbxX7J1JmDHHJt/Yp1n+h0sTrC6eK4rQg9Hzw0EC/MJcv5fLJzg6dkVo+3wQ3anWXL5itapP+nQ==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/glob": { "node_modules/glob": {
"version": "7.2.3", "version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@ -17495,10 +17573,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 +18987,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 +19381,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"
@ -19614,6 +19695,30 @@
"react-native": "*" "react-native": "*"
} }
}, },
"node_modules/react-native-gifted-charts": {
"version": "1.4.41",
"resolved": "https://registry.npmjs.org/react-native-gifted-charts/-/react-native-gifted-charts-1.4.41.tgz",
"integrity": "sha512-UQ3E6ork24VFVTOhgAAbGkGplYB89mRjNEEPJKRAzwSYoJ1Gjtt1pleuY7OKpMb5TQychreFBA2oVBrwTLdeKw==",
"license": "MIT",
"dependencies": {
"gifted-charts-core": "0.1.41"
},
"peerDependencies": {
"expo-linear-gradient": "*",
"react": "*",
"react-native": "*",
"react-native-linear-gradient": "*",
"react-native-svg": "*"
},
"peerDependenciesMeta": {
"expo-linear-gradient": {
"optional": true
},
"react-native-linear-gradient": {
"optional": true
}
}
},
"node_modules/react-native-helmet-async": { "node_modules/react-native-helmet-async": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/react-native-helmet-async/-/react-native-helmet-async-2.0.4.tgz", "resolved": "https://registry.npmjs.org/react-native-helmet-async/-/react-native-helmet-async-2.0.4.tgz",
@ -19716,9 +19821,9 @@
} }
}, },
"node_modules/react-native-svg": { "node_modules/react-native-svg": {
"version": "15.6.0", "version": "15.7.1",
"resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.6.0.tgz", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.7.1.tgz",
"integrity": "sha512-TUtR+h+yi1ODsd8FHdom1TpjfWOmnaK5pri5rnSBXnMqpzq8o2zZfonHTjPX+nS3wb/Pu2XsoARgYaHNjVWXhQ==", "integrity": "sha512-Xc11L4t6/DtmUwrQqHR7S45Qy3cIWpcfGlmEatVeZ9c1N8eAK79heJmGRgCOVrXESrrLEHfP/AYGf0BGyrvV6A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"css-select": "^5.1.0", "css-select": "^5.1.0",
@ -20631,20 +20736,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

@ -51,6 +51,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",
@ -58,12 +59,13 @@
"react-native-big-calendar": "^4.14.0", "react-native-big-calendar": "^4.14.0",
"react-native-calendars": "^1.1306.0", "react-native-calendars": "^1.1306.0",
"react-native-gesture-handler": "~2.16.1", "react-native-gesture-handler": "~2.16.1",
"react-native-gifted-charts": "^1.4.41",
"react-native-linear-gradient": "^2.8.3", "react-native-linear-gradient": "^2.8.3",
"react-native-onboarding-swiper": "^1.3.0", "react-native-onboarding-swiper": "^1.3.0",
"react-native-reanimated": "~3.10.1", "react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.5", "react-native-safe-area-context": "4.10.5",
"react-native-screens": "3.31.1", "react-native-screens": "3.31.1",
"react-native-svg": "^15.6.0", "react-native-svg": "^15.7.1",
"react-native-toast-message": "^2.2.1", "react-native-toast-message": "^2.2.1",
"react-native-ui-lib": "^7.27.0", "react-native-ui-lib": "^7.27.0",
"react-native-web": "~0.19.10", "react-native-web": "~0.19.10",

1430
yarn.lock

File diff suppressed because it is too large Load Diff