mirror of
https://github.com/urosran/cally.git
synced 2025-07-10 07:07:16 +00:00
Event creation
This commit is contained in:
@ -1,12 +1,69 @@
|
||||
import React from 'react';
|
||||
|
||||
import {Drawer} from "expo-router/drawer";
|
||||
import {useSignOut} from "@/hooks/firebase/useSignOut";
|
||||
import {DrawerContentScrollView, DrawerItem, DrawerItemList} from "@react-navigation/drawer";
|
||||
|
||||
export default function TabLayout() {
|
||||
return (
|
||||
<Drawer
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
}}/>
|
||||
);
|
||||
const {mutateAsync: signOut} = useSignOut()
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
initialRouteName={"index"}
|
||||
screenOptions={{
|
||||
headerShown: true,
|
||||
}}
|
||||
drawerContent={props => {
|
||||
return (
|
||||
<DrawerContentScrollView {...props}>
|
||||
<DrawerItemList {...props} />
|
||||
<DrawerItem label="Logout" onPress={() => signOut()}/>
|
||||
</DrawerContentScrollView>
|
||||
)
|
||||
}}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="index"
|
||||
options={{
|
||||
drawerLabel: "Calendar",
|
||||
title: "Calendar",
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="calendar"
|
||||
options={{
|
||||
drawerLabel: "Calendar",
|
||||
title: "Calendar",
|
||||
drawerItemStyle: {display: "none"}
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="brain_dump"
|
||||
options={{
|
||||
drawerLabel: "Brain Dump",
|
||||
title: "Brain Dump",
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="grocery"
|
||||
options={{
|
||||
drawerLabel: "Grocery",
|
||||
title: "Grocery",
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="reminders"
|
||||
options={{
|
||||
drawerLabel: "Reminders",
|
||||
title: "Reminders",
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="todos"
|
||||
options={{
|
||||
drawerLabel: "To-Do",
|
||||
title: "To-Do",
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,123 @@
|
||||
import {View} from "react-native-ui-lib";
|
||||
import React, {useRef, useState} from "react";
|
||||
import {LayoutChangeEvent} from "react-native";
|
||||
import {Calendar} from "react-native-big-calendar";
|
||||
import {Button, Picker, PickerModes, SegmentedControl, Text, View} from "react-native-ui-lib";
|
||||
import {MaterialIcons} from "@expo/vector-icons";
|
||||
import {AddEventDialog} from "@/components/pages/calendar/AddEventDialog";
|
||||
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
|
||||
|
||||
const modeMap = new Map([
|
||||
[0, "day"],
|
||||
[1, "week"],
|
||||
[2, "month"]
|
||||
]);
|
||||
|
||||
const months = [
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
|
||||
export default function Screen() {
|
||||
const [calendarHeight, setCalendarHeight] = useState(0);
|
||||
const [mode, setMode] = useState<"week" | "month" | "day">("week");
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
|
||||
|
||||
const calendarContainerRef = useRef(null);
|
||||
const { data: events} = useGetEvents()
|
||||
|
||||
const onLayout = (event: LayoutChangeEvent) => {
|
||||
const {height} = event.nativeEvent.layout;
|
||||
setCalendarHeight(height);
|
||||
};
|
||||
|
||||
const handleSegmentChange = (index: number) => {
|
||||
const selectedMode = modeMap.get(index);
|
||||
if (selectedMode) {
|
||||
setMode(selectedMode as "week" | "month" | "day");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMonthChange = (month: string) => {
|
||||
const currentYear = selectedDate.getFullYear();
|
||||
const currentDay = selectedDate.getDate();
|
||||
const newMonthIndex = months.indexOf(month);
|
||||
|
||||
// Update the date with the new month while preserving the day and year
|
||||
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
|
||||
setSelectedDate(updatedDate);
|
||||
};
|
||||
|
||||
console.log(events)
|
||||
|
||||
return (
|
||||
<View/>
|
||||
)
|
||||
}
|
||||
<View style={{flex: 1, height: "100%", padding: 10}}>
|
||||
<View style={{height: 60, justifyContent: "space-evenly", alignItems: "flex-start"}}>
|
||||
<Text>Welcome Dalia</Text>
|
||||
<Text>Let's get your week started!</Text>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30}}
|
||||
ref={calendarContainerRef}
|
||||
onLayout={onLayout}
|
||||
>
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
borderBottomLeftRadius: 0,
|
||||
borderBottomRightRadius: 0,
|
||||
backgroundColor: "#f9f9f9",
|
||||
marginBottom: 10,
|
||||
shadowColor: "#000",
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 5,
|
||||
elevation: 3,
|
||||
}}>
|
||||
<Picker
|
||||
value={months[selectedDate.getMonth()]} // Get the month from the date
|
||||
placeholder={"Select Month"}
|
||||
mode={PickerModes.SINGLE}
|
||||
onChange={(itemValue) => handleMonthChange(itemValue as string)}
|
||||
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"}/>}
|
||||
>
|
||||
{months.map((month) => (
|
||||
<Picker.Item key={month} label={month} value={month}/>
|
||||
))}
|
||||
</Picker>
|
||||
|
||||
<View>
|
||||
<SegmentedControl
|
||||
segments={[
|
||||
{label: "D"},
|
||||
{label: "W"},
|
||||
{label: "M"}
|
||||
]}
|
||||
onChangeIndex={handleSegmentChange}
|
||||
initialIndex={[...modeMap.entries()].find(([_, value]) => value === mode)?.[0] || 1}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{calendarHeight > 0 && (
|
||||
<Calendar
|
||||
mode={mode}
|
||||
events={events ?? []}
|
||||
height={calendarHeight}
|
||||
activeDate={selectedDate}
|
||||
onSwipeEnd={(newDate) => {
|
||||
console.log(newDate)
|
||||
setSelectedDate(newDate);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<AddEventDialog/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -1,70 +1,3 @@
|
||||
import { Image, StyleSheet, Platform } from 'react-native';
|
||||
import Screen from "@/app/(auth)/calendar";
|
||||
|
||||
import { HelloWave } from '@/components/HelloWave';
|
||||
import ParallaxScrollView from '@/components/ParallaxScrollView';
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
||||
headerImage={
|
||||
<Image
|
||||
source={require('@/assets/images/partial-react-logo.png')}
|
||||
style={styles.reactLogo}
|
||||
/>
|
||||
}>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Welcome!</ThemedText>
|
||||
<HelloWave />
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
|
||||
<ThemedText>
|
||||
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
|
||||
Press{' '}
|
||||
<ThemedText type="defaultSemiBold">
|
||||
{Platform.select({ ios: 'cmd + d', android: 'cmd + m' })}
|
||||
</ThemedText>{' '}
|
||||
to open developer tools.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
|
||||
<ThemedText>
|
||||
Tap the Explore tab to learn more about what's included in this starter app.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
|
||||
<ThemedText>
|
||||
When you're ready, run{' '}
|
||||
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
|
||||
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
});
|
||||
export default Screen
|
@ -1,5 +0,0 @@
|
||||
import {Stack} from "expo-router";
|
||||
|
||||
export default function StackLayout () {
|
||||
return <Stack screenOptions={{headerShown: false}}/>
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Button, TextInput } from "react-native";
|
||||
import { Checkbox, Picker, TextField, View, Text } from "react-native-ui-lib";
|
||||
import { useCreateSubUser } from "@/hooks/firebase/useCreateSubUser";
|
||||
import { UserProfile } from "@/hooks/firebase/types/profileTypes";
|
||||
import { uuidv4 } from "@firebase/util";
|
||||
import { useGetChildrenByParentId } from "@/hooks/firebase/useGetChildrenByParentId";
|
||||
import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
|
||||
import SignInPage from "../../../components/pages/main/SignInPage";
|
||||
import Entry from "@/components/pages/main/Entry";
|
||||
import { useSignUp } from "@/hooks/firebase/useSignUp";
|
||||
import { useSignOut } from "@/hooks/firebase/useSignOut";
|
||||
|
||||
const Screen: React.FC = () => {
|
||||
const { user, profileType, profileData } = useAuthContext();
|
||||
|
||||
const { data: children } = useGetChildrenByParentId();
|
||||
|
||||
const { mutateAsync: createSubUser } = useCreateSubUser();
|
||||
const { mutateAsync: signOut } = useSignOut();
|
||||
|
||||
const createNewSubUser = async (userProfile: UserProfile) => {
|
||||
await createSubUser({ ...userProfile, email: `${uuidv4()}@test.com` });
|
||||
// createSubUser({
|
||||
// email,
|
||||
// password,
|
||||
// userType: profileType!,
|
||||
// name: "",
|
||||
// contact: "+381628334",
|
||||
// ...child
|
||||
// })
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
{user ? (
|
||||
<View paddingH-20 marginT-20>
|
||||
{profileType === ProfileType.PARENT && <Text>Parent</Text>}
|
||||
{profileType === ProfileType.CHILD && <Text>Child</Text>}
|
||||
{profileType === ProfileType.CAREGIVER && <Text>Caregiver</Text>}
|
||||
<Button title="Sign Out" onPress={async() => {await signOut()}} />
|
||||
</View>
|
||||
) : (
|
||||
<Entry />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Screen;
|
@ -0,0 +1,5 @@
|
||||
import {Stack} from "expo-router";
|
||||
|
||||
export default function Layout() {
|
||||
return <Stack screenOptions={{title: "Login"}}/>
|
||||
}
|
7
app/(unauth)/index.tsx
Normal file
7
app/(unauth)/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import Entry from "@/components/pages/main/Entry";
|
||||
|
||||
export default function Screen() {
|
||||
return (
|
||||
<Entry/>
|
||||
)
|
||||
}
|
@ -1,15 +1,16 @@
|
||||
import { Link, Stack } from 'expo-router';
|
||||
import {Link, Stack, usePathname} from 'expo-router';
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
|
||||
export default function NotFoundScreen() {
|
||||
const route = usePathname()
|
||||
return (
|
||||
<>
|
||||
<Stack.Screen options={{ title: 'Oops!' }} />
|
||||
<ThemedView style={styles.container}>
|
||||
<ThemedText type="title">This screen doesn't exist.</ThemedText>
|
||||
<ThemedText type="title">This screen doesn't exist. ({route})</ThemedText>
|
||||
<Link href="/" style={styles.link}>
|
||||
<ThemedText type="link">Go to home screen!</ThemedText>
|
||||
</Link>
|
||||
|
89
components/pages/calendar/AddEventDialog.tsx
Normal file
89
components/pages/calendar/AddEventDialog.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import React, {useState} from "react";
|
||||
import {MaterialIcons} from "@expo/vector-icons";
|
||||
import {Button, Card, Dialog, PanningProvider, Text, View} from "react-native-ui-lib";
|
||||
import {TouchableOpacity} from "react-native";
|
||||
import {ManuallyAddEventModal} from "@/components/pages/calendar/ManuallyAddEventModal";
|
||||
|
||||
export const AddEventDialog = () => {
|
||||
const [show, setShow] = useState(false);
|
||||
const [showManualInputModal, setShowManualInputModal] = useState(false);
|
||||
|
||||
const handleOpenManualInputModal = () => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
setShowManualInputModal(true);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
height: 60,
|
||||
width: 60,
|
||||
borderRadius: 30,
|
||||
backgroundColor: "#fff",
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
enableShadow
|
||||
iconSource={() => <MaterialIcons name="add" size={30}/>}
|
||||
onPress={() => setShow(true)}
|
||||
/>
|
||||
|
||||
<Dialog
|
||||
visible={show}
|
||||
onDismiss={() => setShow(false)}
|
||||
panDirection={PanningProvider.Directions.DOWN}
|
||||
center
|
||||
>
|
||||
<Card style={{padding: 20, justifyContent: 'center', alignItems: "center"}}>
|
||||
<Text text60>Create a new event</Text>
|
||||
|
||||
<View style={{marginTop: 20, alignItems: 'center'}}>
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#007bff",
|
||||
}}
|
||||
onPress={handleOpenManualInputModal}
|
||||
>
|
||||
<Text style={{color: "white"}}>Manually Input</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#007bff",
|
||||
opacity: 0.5
|
||||
}}
|
||||
disabled
|
||||
>
|
||||
<Text style={{color: "white"}}>Scan an Image</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
backgroundColor: "#007bff",
|
||||
opacity: 0.5
|
||||
}}
|
||||
disabled
|
||||
>
|
||||
<Text style={{color: "white"}}>Paste in text</Text>
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity onPress={() => setShow(false)}>
|
||||
<Text style={{marginTop: 20, color: "#007bff"}}>Go back</Text>
|
||||
</TouchableOpacity>
|
||||
</Card>
|
||||
</Dialog>
|
||||
|
||||
<ManuallyAddEventModal show={showManualInputModal} close={() => setShowManualInputModal(false)}/>
|
||||
</>
|
||||
)
|
||||
}
|
260
components/pages/calendar/ManuallyAddEventModal.tsx
Normal file
260
components/pages/calendar/ManuallyAddEventModal.tsx
Normal file
@ -0,0 +1,260 @@
|
||||
import {
|
||||
Avatar,
|
||||
Colors,
|
||||
DateTimePicker,
|
||||
LoaderScreen,
|
||||
Modal,
|
||||
Picker,
|
||||
Switch,
|
||||
Text,
|
||||
TextField,
|
||||
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";
|
||||
|
||||
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"},
|
||||
];
|
||||
|
||||
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 [startDate, setStartDate] = useState(new Date());
|
||||
const [endDate, setEndDate] = useState(new Date())
|
||||
|
||||
const [repeatInterval, setRepeatInterval] = useState<PickerMultiValue>([]);
|
||||
|
||||
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent()
|
||||
|
||||
const formatDateTime = (date: Date) => {
|
||||
return date.toLocaleDateString('en-US', {
|
||||
weekday: 'long',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
};
|
||||
|
||||
const combineDateAndTime = (date: Date, time: Date): Date => {
|
||||
const combined = new Date(date);
|
||||
combined.setHours(time.getHours());
|
||||
combined.setMinutes(time.getMinutes());
|
||||
combined.setSeconds(0);
|
||||
combined.setMilliseconds(0);
|
||||
return combined;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
let finalStartDate: Date;
|
||||
let finalEndDate: Date;
|
||||
|
||||
if (isAllDay) {
|
||||
finalStartDate = new Date(startDate);
|
||||
finalStartDate.setHours(0, 0, 0, 0);
|
||||
|
||||
finalEndDate = new Date(startDate);
|
||||
finalEndDate.setHours(23, 59, 59, 999);
|
||||
} else {
|
||||
finalStartDate = combineDateAndTime(startDate, startTime);
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
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}
|
||||
/>
|
||||
{!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>
|
||||
);
|
||||
};
|
@ -1,41 +1,49 @@
|
||||
import { View, Text, Button } from "react-native-ui-lib";
|
||||
import { TextInput } from "react-native";
|
||||
import { View, Text, Button, TextField } from "react-native-ui-lib";
|
||||
import React, { useState } from "react";
|
||||
import { useSignIn } from "@/hooks/firebase/useSignIn";
|
||||
|
||||
const SignInPage = (props: {
|
||||
setRegister: () => any;
|
||||
}) => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const SignInPage = (props: { setRegister: () => any }) => {
|
||||
const [email, setEmail] = useState<string>("");
|
||||
const [password, setPassword] = useState<string>("");
|
||||
|
||||
const { mutateAsync: signIn } = useSignIn();
|
||||
const { mutateAsync: signIn, error, isError } = useSignIn();
|
||||
|
||||
const handleSignIn = async () => {
|
||||
await signIn({email, password});
|
||||
}
|
||||
const handleSignIn = async () => {
|
||||
await signIn({ email, password });
|
||||
};
|
||||
|
||||
return (
|
||||
<View marginH-20>
|
||||
<TextInput placeholder="Email" value={email} onChangeText={setEmail} />
|
||||
<TextInput
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
/>
|
||||
<Button label="Login" onPress={handleSignIn} />
|
||||
<Text>Don't have an account?</Text>
|
||||
<Button
|
||||
onPress={props.setRegister}
|
||||
label="Sign Up"
|
||||
link
|
||||
padding-0
|
||||
margin-0
|
||||
left
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View padding-10>
|
||||
<TextField
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
style={{ marginBottom: 10 }}
|
||||
floatingPlaceholder
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
style={{ marginBottom: 10 }}
|
||||
floatingPlaceholder
|
||||
/>
|
||||
<Button label="Login" onPress={handleSignIn} style={{ marginBottom: 20 }} />
|
||||
{isError && (
|
||||
<Text center style={{ marginBottom: 20 }}>{`${error}`}</Text>
|
||||
)}
|
||||
<Text center style={{ marginBottom: 5 }}>Don't have an account?</Text>
|
||||
<Button
|
||||
onPress={props.setRegister}
|
||||
label="Sign Up"
|
||||
link
|
||||
padding-0
|
||||
margin-0
|
||||
left
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignInPage;
|
||||
|
@ -1,83 +1,96 @@
|
||||
import { TextInput } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import { Checkbox, Button, View, Text } from "react-native-ui-lib";
|
||||
import {Checkbox, Button, View, Text, TextField} from "react-native-ui-lib";
|
||||
import { useSignUp } from "@/hooks/firebase/useSignUp";
|
||||
import { ProfileType } from "@/contexts/AuthContext";
|
||||
|
||||
const SignUpPage = (props: { unsetRegister: () => any }) => {
|
||||
const [email, setEmail] = 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 [email, setEmail] = 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 () => {
|
||||
await signUp({ email, password });
|
||||
};
|
||||
const handleSignUp = async () => {
|
||||
await signUp({ email, password });
|
||||
};
|
||||
|
||||
return (
|
||||
<View marginH-20>
|
||||
<TextInput placeholder="Email" value={email} onChangeText={setEmail} />
|
||||
<TextInput
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
/>
|
||||
<Button label="Register" onPress={handleSignUp} />
|
||||
<Text>Choose Profile Type:</Text>
|
||||
<Checkbox
|
||||
label="Parent"
|
||||
value={isParent}
|
||||
onValueChange={(value) => {
|
||||
setIsParent(value);
|
||||
setProfileType(ProfileType.PARENT);
|
||||
if (value) {
|
||||
setIsChild(false);
|
||||
setIsCaregiver(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Child"
|
||||
value={isChild}
|
||||
onValueChange={(value) => {
|
||||
setIsChild(value);
|
||||
setProfileType(ProfileType.CHILD);
|
||||
if (value) {
|
||||
setIsParent(false);
|
||||
setIsCaregiver(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Caregiver"
|
||||
value={isCaregiver}
|
||||
onValueChange={(value) => {
|
||||
setIsCaregiver(value);
|
||||
setProfileType(ProfileType.CAREGIVER);
|
||||
if (value) {
|
||||
setIsParent(false);
|
||||
setIsChild(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Text>
|
||||
Already have an account?
|
||||
<Button
|
||||
label="Sign In"
|
||||
margin-0
|
||||
link
|
||||
text200
|
||||
onPress={props.unsetRegister}
|
||||
/>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View padding-10>
|
||||
<TextField
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
style={{ marginBottom: 10 }}
|
||||
floatingPlaceholder
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secureTextEntry
|
||||
style={{ marginBottom: 10 }}
|
||||
floatingPlaceholder
|
||||
/>
|
||||
<Button
|
||||
label="Register"
|
||||
onPress={handleSignUp}
|
||||
style={{ marginBottom: 10 }}
|
||||
/>
|
||||
<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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Text center style={{ marginBottom: 5, marginTop: 10 }}>
|
||||
Already have an account?
|
||||
</Text>
|
||||
<Button
|
||||
label="Sign In"
|
||||
margin-0
|
||||
link
|
||||
text200
|
||||
onPress={props.unsetRegister}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpPage;
|
||||
|
@ -67,7 +67,7 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && user) {
|
||||
replace({pathname: "/(auth)"})
|
||||
replace({pathname: "/(auth)/calendar"})
|
||||
} else if (ready && !user) {
|
||||
replace({pathname: "/(unauth)"})
|
||||
}
|
||||
|
13
hooks/firebase/types/eventData.ts
Normal file
13
hooks/firebase/types/eventData.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export interface EventData {
|
||||
title: string,
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
allDay: boolean,
|
||||
repeatDays: string[],
|
||||
creatorId: string[],
|
||||
userIds: string[],
|
||||
timeZone?: string,
|
||||
surpriseEvent?: boolean,
|
||||
notes?: string,
|
||||
reminders?: string[]
|
||||
}
|
25
hooks/firebase/useCreateEvent.ts
Normal file
25
hooks/firebase/useCreateEvent.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import {EventData} from "@/hooks/firebase/types/eventData";
|
||||
|
||||
export const useCreateEvent = () => {
|
||||
const {user: currentUser} = useAuthContext()
|
||||
const queryClients = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ["createEvent"],
|
||||
mutationFn: async (eventData: Partial<EventData>) => {
|
||||
try {
|
||||
await firestore()
|
||||
.collection("Events")
|
||||
.add({...eventData, creatorId: currentUser?.uid})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClients.invalidateQueries("events")
|
||||
}
|
||||
})
|
||||
}
|
31
hooks/firebase/useGetEvents.ts
Normal file
31
hooks/firebase/useGetEvents.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import {useQuery} from "react-query";
|
||||
import firestore from "@react-native-firebase/firestore";
|
||||
import {ReactElement} from "react";
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {ICalendarEventBase} from "react-native-big-calendar";
|
||||
|
||||
export const useGetEvents = () => {
|
||||
const {user} = useAuthContext()
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["events", user?.uid],
|
||||
queryFn: async () => {
|
||||
const snapshot = await firestore()
|
||||
.collection("Events")
|
||||
.where("creatorId", "==", user?.uid)
|
||||
.get();
|
||||
|
||||
const events: ICalendarEventBase[] = snapshot.docs.map((doc) => {
|
||||
const data = doc.data();
|
||||
return {
|
||||
title: data.title,
|
||||
start: new Date(data.startDate.seconds * 1000),
|
||||
end: new Date(data.endDate.seconds * 1000),
|
||||
hideHours: data.allDay,
|
||||
};
|
||||
});
|
||||
|
||||
return events;
|
||||
}
|
||||
})
|
||||
}
|
@ -14,18 +14,12 @@ export const useUpdateUserData = () => {
|
||||
|
||||
if (user) {
|
||||
try {
|
||||
console.log("yall don't even");
|
||||
console.log(newUserData)
|
||||
await firestore()
|
||||
.collection("Profiles")
|
||||
.doc(user.uid)
|
||||
.set(newUserData);
|
||||
|
||||
console.log("wtf")
|
||||
const profileData = await firestore().collection("Profiles").doc(user?.uid!).get()
|
||||
console.log("wtf222")
|
||||
|
||||
console.log(profileData)
|
||||
setProfileData(profileData.data() as UserProfile)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -959,18 +959,18 @@ PODS:
|
||||
- EXJSONUtils (0.13.1)
|
||||
- EXManifests (0.14.3):
|
||||
- ExpoModulesCore
|
||||
- Expo (51.0.29):
|
||||
- Expo (51.0.30):
|
||||
- ExpoModulesCore
|
||||
- expo-dev-client (4.0.23):
|
||||
- expo-dev-client (4.0.24):
|
||||
- EXManifests
|
||||
- expo-dev-launcher
|
||||
- expo-dev-menu
|
||||
- expo-dev-menu-interface
|
||||
- EXUpdatesInterface
|
||||
- expo-dev-launcher (4.0.25):
|
||||
- expo-dev-launcher (4.0.26):
|
||||
- DoubleConversion
|
||||
- EXManifests
|
||||
- expo-dev-launcher/Main (= 4.0.25)
|
||||
- expo-dev-launcher/Main (= 4.0.26)
|
||||
- expo-dev-menu
|
||||
- expo-dev-menu-interface
|
||||
- ExpoModulesCore
|
||||
@ -996,7 +996,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- expo-dev-launcher/Main (4.0.25):
|
||||
- expo-dev-launcher/Main (4.0.26):
|
||||
- DoubleConversion
|
||||
- EXManifests
|
||||
- expo-dev-launcher/Unsafe
|
||||
@ -1025,7 +1025,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- expo-dev-launcher/Unsafe (4.0.25):
|
||||
- expo-dev-launcher/Unsafe (4.0.26):
|
||||
- DoubleConversion
|
||||
- EXManifests
|
||||
- expo-dev-menu
|
||||
@ -1053,10 +1053,10 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- expo-dev-menu (5.0.19):
|
||||
- expo-dev-menu (5.0.20):
|
||||
- DoubleConversion
|
||||
- expo-dev-menu/Main (= 5.0.19)
|
||||
- expo-dev-menu/ReactNativeCompatibles (= 5.0.19)
|
||||
- expo-dev-menu/Main (= 5.0.20)
|
||||
- expo-dev-menu/ReactNativeCompatibles (= 5.0.20)
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.01.01.00)
|
||||
@ -1077,7 +1077,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- expo-dev-menu-interface (1.8.3)
|
||||
- expo-dev-menu/Main (5.0.19):
|
||||
- expo-dev-menu/Main (5.0.20):
|
||||
- DoubleConversion
|
||||
- EXManifests
|
||||
- expo-dev-menu-interface
|
||||
@ -1103,7 +1103,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- expo-dev-menu/ReactNativeCompatibles (5.0.19):
|
||||
- expo-dev-menu/ReactNativeCompatibles (5.0.20):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@ -1124,7 +1124,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- expo-dev-menu/SafeAreaView (5.0.19):
|
||||
- expo-dev-menu/SafeAreaView (5.0.20):
|
||||
- DoubleConversion
|
||||
- ExpoModulesCore
|
||||
- glog
|
||||
@ -1146,7 +1146,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- expo-dev-menu/Vendored (5.0.19):
|
||||
- expo-dev-menu/Vendored (5.0.20):
|
||||
- DoubleConversion
|
||||
- expo-dev-menu/SafeAreaView
|
||||
- glog
|
||||
@ -1178,7 +1178,7 @@ PODS:
|
||||
- ExpoModulesCore
|
||||
- ExpoKeepAwake (13.0.2):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (1.12.21):
|
||||
- ExpoModulesCore (1.12.22):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@ -3009,17 +3009,17 @@ SPEC CHECKSUMS:
|
||||
EXConstants: 409690fbfd5afea964e5e9d6c4eb2c2b59222c59
|
||||
EXJSONUtils: 30c17fd9cc364d722c0946a550dfbf1be92ef6a4
|
||||
EXManifests: c1fab4c3237675e7b0299ea8df0bcb14baca4f42
|
||||
Expo: 2c1f878fb356a11e8c32eb391f97eb7abfb21e55
|
||||
expo-dev-client: 924e474dc9eccf507260a22c5a8301bed0497453
|
||||
expo-dev-launcher: 5f27c01458fd729684cc49a345f4c9ca052f4864
|
||||
expo-dev-menu: 9ff772f6918e5fdfd73f31cc7534b853925f3d47
|
||||
Expo: 61b2953ad6afa979729f639c5992c182e8eb9040
|
||||
expo-dev-client: 44e9bb8afbf444bc380c606475030fe8929de203
|
||||
expo-dev-launcher: 012fd9aea425d902b5404e75e58d0bacf8e2542f
|
||||
expo-dev-menu: 045ace71676316ecac9bff8c2ac34fa4d8ef8392
|
||||
expo-dev-menu-interface: be32c09f1e03833050f0ee290dcc86b3ad0e73e4
|
||||
ExpoAsset: 323700f291684f110fb55f0d4022a3362ea9f875
|
||||
ExpoFileSystem: 80bfe850b1f9922c16905822ecbf97acd711dc51
|
||||
ExpoFont: e7f2275c10ca8573c991e007329ad6bf98086485
|
||||
ExpoHead: fcb28a68ed4ba28f177394d2dfb8a0a8824cd103
|
||||
ExpoKeepAwake: 3b8815d9dd1d419ee474df004021c69fdd316d08
|
||||
ExpoModulesCore: 620690a98d712d142e0acce5933419b05a63214a
|
||||
ExpoModulesCore: 470e4a326c045a3b78c172e3e62d922e3df52a41
|
||||
ExpoSystemUI: d4f065a016cae6721b324eb659cdee4d4cf0cb26
|
||||
ExpoWebBrowser: 7595ccac6938eb65b076385fd23d035db9ecdc8e
|
||||
EXSplashScreen: d8b3c547b9b18a41d80c6f6b274c4c26664febd4
|
||||
|
@ -42,6 +42,7 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.74.3",
|
||||
"react-native-big-calendar": "^4.14.0",
|
||||
"react-native-calendars": "^1.1306.0",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-reanimated": "~3.10.1",
|
||||
|
Reference in New Issue
Block a user