mirror of
https://github.com/urosran/cally.git
synced 2025-08-25 13:49:39 +00:00
Event creation
This commit is contained in:
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;
|
||||
|
Reference in New Issue
Block a user