Calendar page refactor

This commit is contained in:
Milan Paunovic
2024-10-19 17:20:34 +02:00
parent d2b46ad977
commit 3f7fc92952
21 changed files with 1580 additions and 1755 deletions

View File

@ -1,14 +1,11 @@
import React from "react"; import React from "react";
import { CalendarProvider } from "@/contexts/CalendarContext"; // Import the new CalendarPage component
import CalendarPage from "@/components/pages/calendar/CalendarPage"; import CalendarPage from "@/components/pages/calendar/CalendarPage";
import {SettingsContextProvider} from "@/contexts/SettingsContext"; import {SettingsContextProvider} from "@/contexts/SettingsContext";
export default function Screen() { export default function Screen() {
return ( return (
<SettingsContextProvider> <SettingsContextProvider>
<CalendarProvider>
<CalendarPage/> <CalendarPage/>
</CalendarProvider>
</SettingsContextProvider> </SettingsContextProvider>
); );
} }

View File

@ -1,7 +1,6 @@
import React, {useEffect} from "react"; import React, {useEffect} from "react";
import {DefaultTheme, ThemeProvider} from "@react-navigation/native"; import {DefaultTheme, ThemeProvider} from "@react-navigation/native";
import { import {
useFonts,
Manrope_200ExtraLight, Manrope_200ExtraLight,
Manrope_300Light, Manrope_300Light,
Manrope_400Regular, Manrope_400Regular,
@ -9,21 +8,22 @@ import {
Manrope_600SemiBold, Manrope_600SemiBold,
Manrope_700Bold, Manrope_700Bold,
Manrope_800ExtraBold, Manrope_800ExtraBold,
useFonts,
} from "@expo-google-fonts/manrope"; } from "@expo-google-fonts/manrope";
import { import {
PlusJakartaSans_200ExtraLight, PlusJakartaSans_200ExtraLight,
PlusJakartaSans_300Light,
PlusJakartaSans_400Regular,
PlusJakartaSans_500Medium,
PlusJakartaSans_600SemiBold,
PlusJakartaSans_700Bold,
PlusJakartaSans_800ExtraBold,
PlusJakartaSans_200ExtraLight_Italic, PlusJakartaSans_200ExtraLight_Italic,
PlusJakartaSans_300Light,
PlusJakartaSans_300Light_Italic, PlusJakartaSans_300Light_Italic,
PlusJakartaSans_400Regular,
PlusJakartaSans_400Regular_Italic, PlusJakartaSans_400Regular_Italic,
PlusJakartaSans_500Medium,
PlusJakartaSans_500Medium_Italic, PlusJakartaSans_500Medium_Italic,
PlusJakartaSans_600SemiBold,
PlusJakartaSans_600SemiBold_Italic, PlusJakartaSans_600SemiBold_Italic,
PlusJakartaSans_700Bold,
PlusJakartaSans_700Bold_Italic, PlusJakartaSans_700Bold_Italic,
PlusJakartaSans_800ExtraBold,
PlusJakartaSans_800ExtraBold_Italic, PlusJakartaSans_800ExtraBold_Italic,
} from "@expo-google-fonts/plus-jakarta-sans"; } from "@expo-google-fonts/plus-jakarta-sans";
import { import {
@ -51,24 +51,16 @@ import * as SplashScreen from "expo-splash-screen";
import "react-native-reanimated"; import "react-native-reanimated";
import {AuthContextProvider} from "@/contexts/AuthContext"; import {AuthContextProvider} from "@/contexts/AuthContext";
import {QueryClient, QueryClientProvider} from "react-query"; import {QueryClient, QueryClientProvider} from "react-query";
import { import {TextProps, ThemeManager, Toast, Typography,} from "react-native-ui-lib";
ThemeManager,
Typography,
Toast,
TextProps,
} from "react-native-ui-lib";
import functions from "@react-native-firebase/functions";
import auth from "@react-native-firebase/auth";
import firestore from "@react-native-firebase/firestore";
SplashScreen.preventAutoHideAsync(); SplashScreen.preventAutoHideAsync();
const queryClient = new QueryClient(); const queryClient = new QueryClient();
if (__DEV__) { if (__DEV__) {
functions().useEmulator("localhost", 5001); // functions().useEmulator("localhost", 5001);
firestore().useEmulator("localhost", 5471); // firestore().useEmulator("localhost", 5471);
auth().useEmulator("http://localhost:9099"); // auth().useEmulator("http://localhost:9099");
} }
type TextStyleBase = type TextStyleBase =
@ -236,7 +228,7 @@ export default function RootLayout() {
ThemeManager.setComponentTheme( ThemeManager.setComponentTheme(
"Text", "Text",
(props: ExtendedTextProps, context: unknown) => { (props: ExtendedTextProps) => {
const textStyle = ( const textStyle = (
Object.keys(props) as Array<keyof ExtendedTextProps> Object.keys(props) as Array<keyof ExtendedTextProps>
).find((key) => typographies[key as TextStyle]) as ).find((key) => typographies[key as TextStyle]) as

View File

@ -1,38 +1,26 @@
import React, {useState} from "react"; import React, {useState} from "react";
import { import {MaterialIcons,} from "@expo/vector-icons";
AntDesign, import {Button, Card, Dialog, PanningProvider, Text, View,} from "react-native-ui-lib";
Feather,
MaterialCommunityIcons,
MaterialIcons,
} from "@expo/vector-icons";
import {
Button,
ButtonSize,
Card,
Dialog,
PanningProvider,
Text,
View,
} from "react-native-ui-lib";
import {StyleSheet, TouchableOpacity} from "react-native"; import {StyleSheet, TouchableOpacity} from "react-native";
import { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
import AddChoreDialog from "../todos/AddChoreDialog"; import AddChoreDialog from "../todos/AddChoreDialog";
import {ToDosContextProvider} from "@/contexts/ToDosContext"; import {ToDosContextProvider} from "@/contexts/ToDosContext";
import UploadImageDialog from "./UploadImageDialog"; import UploadImageDialog from "./UploadImageDialog";
import CameraIcon from "@/assets/svgs/CameraIcon"; import CameraIcon from "@/assets/svgs/CameraIcon";
import CalendarIcon from "@/assets/svgs/CalendarIcon"; import CalendarIcon from "@/assets/svgs/CalendarIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon"; import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import {useSetAtom} from "jotai";
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
export const AddEventDialog = () => { export const AddEventDialog = () => {
const [show, setShow] = useState(false); const [show, setShow] = useState(false);
const [showManualInputModal, setShowManualInputModal] = useState(false);
const [choreDialogVisible, setChoreDialogVisible] = useState<boolean>(false); const [choreDialogVisible, setChoreDialogVisible] = useState<boolean>(false);
const [showUploadDialog, setShowUploadDialog] = useState<boolean>(false); const [showUploadDialog, setShowUploadDialog] = useState<boolean>(false);
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
const handleOpenManualInputModal = () => { const handleOpenManualInputModal = () => {
setShow(false); setShow(false);
setTimeout(() => { setTimeout(() => {
setShowManualInputModal(true); setSelectedNewEndDate(new Date());
}, 500); }, 500);
}; };
@ -147,10 +135,6 @@ export const AddEventDialog = () => {
isVisible={choreDialogVisible} isVisible={choreDialogVisible}
setIsVisible={setChoreDialogVisible} setIsVisible={setChoreDialogVisible}
/> />
<ManuallyAddEventModal
show={showManualInputModal}
close={() => setShowManualInputModal(false)}
/>
<UploadImageDialog <UploadImageDialog
show={showUploadDialog} show={showUploadDialog}
setShow={setShowUploadDialog} setShow={setShowUploadDialog}

View File

@ -0,0 +1,102 @@
import React, {memo} from 'react';
import {Picker, PickerModes, SegmentedControl, Text, View} from "react-native-ui-lib";
import {MaterialIcons} from "@expo/vector-icons";
import {modeMap, months} from './constants';
import {StyleSheet} from "react-native";
import {useAtom} from "jotai";
import {modeAtom, selectedDateAtom} from "@/components/pages/calendar/atoms";
export const CalendarHeader = memo(() => {
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
const [mode, setMode] = useAtom(modeAtom)
const handleSegmentChange = (index: number) => {
const selectedMode = modeMap.get(index);
if (selectedMode) {
setMode(selectedMode as "day" | "week" | "month");
}
};
const handleMonthChange = (month: string) => {
const currentDay = selectedDate.getDate();
const currentYear = selectedDate.getFullYear();
const newMonthIndex = months.indexOf(month);
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
setSelectedDate(updatedDate);
};
return (
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: 10,
paddingVertical: 8,
borderRadius: 20,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
backgroundColor: "white",
marginBottom: 10,
}}
>
<View row centerV gap-3>
<Text style={{fontFamily: "Manrope_500Medium", fontSize: 17}}>
{selectedDate.getFullYear()}
</Text>
<Picker
value={months[selectedDate.getMonth()]}
placeholder={"Select Month"}
style={{fontFamily: "Manrope_500Medium", fontSize: 17}}
mode={PickerModes.SINGLE}
onChange={(itemValue) => handleMonthChange(itemValue as string)}
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"}/>}
topBarProps={{
title: selectedDate.getFullYear().toString(),
titleStyle: {fontFamily: "Manrope_500Medium", fontSize: 17},
}}
>
{months.map((month) => (
<Picker.Item key={month} label={month} value={month}/>
))}
</Picker>
</View>
<View>
<SegmentedControl
segments={[{label: "D"}, {label: "W"}, {label: "M"}]}
backgroundColor="#ececec"
inactiveColor="#919191"
activeBackgroundColor="#ea156c"
activeColor="white"
outlineColor="white"
outlineWidth={3}
segmentLabelStyle={styles.segmentslblStyle}
onChangeIndex={handleSegmentChange}
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
/>
</View>
</View>
);
});
const styles = StyleSheet.create({
segmentslblStyle: {
fontSize: 12,
fontFamily: "Manrope_600SemiBold",
},
calHeader: {
borderWidth: 0,
},
dayModeHeader: {
alignSelf: "flex-start",
justifyContent: "space-between",
alignContent: "center",
width: 38,
right: 42,
},
});

View File

@ -1,100 +1,9 @@
import React, { useRef, useState } from "react"; import React from "react";
import { LayoutChangeEvent, StyleSheet } from "react-native"; import {View,} from "react-native-ui-lib";
import { Calendar } from "react-native-big-calendar";
import {
Picker,
PickerModes,
SegmentedControl,
View,
} from "react-native-ui-lib";
import { MaterialIcons } from "@expo/vector-icons";
import { AddEventDialog } from "@/components/pages/calendar/AddEventDialog";
import HeaderTemplate from "@/components/shared/HeaderTemplate"; import HeaderTemplate from "@/components/shared/HeaderTemplate";
import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch"; import {InnerCalendar} from "@/components/pages/calendar/InnerCalendar";
import { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
import { CalendarEvent } from "@/contexts/CalendarContext";
import { useSettingsContext } from "@/contexts/SettingsContext";
import EditEventDialog from "./EditEventDialog";
import { useGetEvents } from "@/hooks/firebase/useGetEvents";
import { Text } from "react-native-ui-lib";
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 CalendarPage() { export default function CalendarPage() {
const { calendarColor } = useSettingsContext();
const [editVisible, setEditVisible] = useState<boolean>(false);
const [eventForEdit, setEventForEdit] = useState<CalendarEvent>();
const styles = StyleSheet.create({
segmentslblStyle: {
fontSize: 12,
fontFamily: "Manrope_600SemiBold",
},
calHeader: {
borderWidth: 0,
},
dayModeHeader: {
alignSelf: "flex-start",
justifyContent: "space-between",
alignContent: "center",
width: 38,
right: 42,
},
});
const [isFamilyView, setIsFamilyView] = useState<boolean>(false);
const [calendarHeight, setCalendarHeight] = useState(0);
const [mode, setMode] = useState<"week" | "month" | "day">("week");
const [selectedDate, setSelectedDate] = useState<Date>(new Date());
const [selectedNewEventDate, setSelectedNewEndDate] = useState<
Date | undefined
>(undefined);
const calendarContainerRef = useRef(null);
const { data: events } = useGetEvents(isFamilyView);
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 "day" | "week" | "month");
}
};
const handleMonthChange = (month: string) => {
const currentDay = selectedDate.getDate();
const currentYear = selectedDate.getFullYear();
const newMonthIndex = months.indexOf(month);
const updatedDate = new Date(currentYear, newMonthIndex, currentDay);
setSelectedDate(updatedDate);
};
return ( return (
<View <View
style={{flex: 1, height: "100%", padding: 10}} style={{flex: 1, height: "100%", padding: 10}}
@ -103,105 +12,9 @@ export default function CalendarPage() {
> >
<HeaderTemplate <HeaderTemplate
message={"Let's get your week started!"} message={"Let's get your week started!"}
isWelcome={true} isWelcome
/>
<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: "white",
marginBottom: 10,
}}
>
<View row centerV gap-3>
<Text style={{ fontFamily: "Manrope_500Medium", fontSize: 17 }}>
{selectedDate.getFullYear()}
</Text>
<Picker
value={months[selectedDate.getMonth()]} // Get the month from the date
placeholder={"Select Month"}
style={{ fontFamily: "Manrope_500Medium", fontSize: 17 }}
mode={PickerModes.SINGLE}
onChange={(itemValue) => handleMonthChange(itemValue as string)}
trailingAccessory={<MaterialIcons name={"keyboard-arrow-down"} />}
topBarProps={{
title: selectedDate.getFullYear().toString(),
titleStyle: { fontFamily: "Manrope_500Medium", fontSize: 17 },
}}
>
{months.map((month) => (
<Picker.Item key={month} label={month} value={month} />
))}
</Picker>
</View>
<View>
<SegmentedControl
segments={[{ label: "D" }, { label: "W" }, { label: "M" }]}
backgroundColor="#ececec"
inactiveColor="#919191"
activeBackgroundColor="#ea156c"
activeColor="white"
outlineColor="white"
outlineWidth={3}
segmentLabelStyle={styles.segmentslblStyle}
onChangeIndex={handleSegmentChange}
initialIndex={mode === "day" ? 0 : mode === "week" ? 1 : 2}
/>
</View>
</View>
{calendarHeight > 0 && (
<Calendar
bodyContainerStyle={styles.calHeader}
mode={mode}
events={isFamilyView ? events ?? [] : events ?? []}
eventCellStyle={(event) => ({ backgroundColor: event.eventColor })}
onPressEvent={(event) => {
setEditVisible(true);
setEventForEdit(event);
}}
height={calendarHeight}
activeDate={selectedDate}
date={selectedDate}
onPressCell={setSelectedNewEndDate}
headerContentStyle={mode === "day" ? styles.dayModeHeader : {}}
onSwipeEnd={(date) => {
setSelectedDate(date);
}}
/>
)}
</View>
<CalendarViewSwitch viewSwitch={setIsFamilyView} />
<AddEventDialog />
{eventForEdit && (
<EditEventDialog
isVisible={editVisible}
setIsVisible={() => {
setEditVisible(!editVisible);
}}
event={eventForEdit}
/>
)}
<ManuallyAddEventModal
key={`${selectedNewEventDate}`}
initialDate={selectedNewEventDate}
show={!!selectedNewEventDate}
close={() => setSelectedNewEndDate(undefined)}
/> />
<InnerCalendar/>
</View> </View>
); );
} }

View File

@ -1,13 +1,13 @@
import { View, Text, Button, TouchableOpacity } from "react-native-ui-lib"; import {Text, TouchableOpacity, View} from "react-native-ui-lib";
import React, {useState} from "react"; import React, {useState} from "react";
import { MaterialIcons } from "@expo/vector-icons";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import {useSetAtom} from "jotai";
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
interface ICalendarViewProps {
viewSwitch: (value: boolean) => void; const CalendarViewSwitch = () => {
}
const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
const [calView, setCalView] = useState<boolean>(false); const [calView, setCalView] = useState<boolean>(false);
const viewSwitch = useSetAtom(isFamilyViewAtom)
return ( return (
<View <View
@ -34,7 +34,7 @@ const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
setCalView(true); setCalView(true);
calendarViewProps.viewSwitch(true); viewSwitch(true);
}} }}
> >
<View <View
@ -53,7 +53,7 @@ const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => {
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
setCalView(false); setCalView(false);
calendarViewProps.viewSwitch(false); viewSwitch(false);
}} }}
> >
<View <View

View File

@ -1,42 +1,31 @@
import { View, Text, Button, Switch } from "react-native-ui-lib"; import {Button, ButtonSize, DateTimePicker, Dialog, Switch, Text, TextField, View} from "react-native-ui-lib";
import React, { useEffect, useState } from "react"; import React from "react";
import { Feather, AntDesign, Ionicons } from "@expo/vector-icons"; import {AntDesign, Feather, Ionicons} from "@expo/vector-icons";
import {
Dialog,
TextField,
DateTimePicker,
Picker,
ButtonSize,
} from "react-native-ui-lib";
import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView"; import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import DropModalIcon from "@/assets/svgs/DropModalIcon"; import DropModalIcon from "@/assets/svgs/DropModalIcon";
import { CalendarEvent } from "@/contexts/CalendarContext";
import ClockIcon from "@/assets/svgs/ClockIcon"; import ClockIcon from "@/assets/svgs/ClockIcon";
import LockIcon from "@/assets/svgs/LockIcon"; import LockIcon from "@/assets/svgs/LockIcon";
import MenuIcon from "@/assets/svgs/MenuIcon"; import MenuIcon from "@/assets/svgs/MenuIcon";
import {useUpdateEvent} from "@/hooks/firebase/useUpdateEvent"; import {useUpdateEvent} from "@/hooks/firebase/useUpdateEvent";
import {editVisibleAtom, eventForEditAtom} from "@/components/pages/calendar/atoms";
import {useAtom} from "jotai";
interface IEditEventDialog {
event: CalendarEvent; const EditEventDialog = () => {
isVisible: boolean; const [isVisible, setIsVisible] = useAtom(editVisibleAtom)
setIsVisible: (value: boolean) => void; const [event, setEvent] = useAtom(eventForEditAtom)
}
const EditEventDialog = (editEventProps: IEditEventDialog) => {
const [event, setEvent] = useState<CalendarEvent>(editEventProps.event);
const {mutateAsync: updateEvent} = useUpdateEvent(); const {mutateAsync: updateEvent} = useUpdateEvent();
useEffect(() => { if (!event) return null
setEvent(editEventProps.event);
}, [editEventProps.isVisible]);
return ( return (
<Dialog <Dialog
bottom={true} bottom={true}
height={"90%"} height={"90%"}
panDirection={PanningDirectionsEnum.DOWN} panDirection={PanningDirectionsEnum.DOWN}
onDismiss={() => editEventProps.setIsVisible(false)} onDismiss={() => setIsVisible(false)}
containerStyle={{ containerStyle={{
borderRadius: 10, borderRadius: 10,
backgroundColor: "white", backgroundColor: "white",
@ -46,7 +35,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
paddingTop: 4, paddingTop: 4,
margin: 0, margin: 0,
}} }}
visible={editEventProps.isVisible} visible={isVisible}
> >
<View row spread> <View row spread>
<Button <Button
@ -54,13 +43,13 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
style={styles.topBtn} style={styles.topBtn}
label="Cancel" label="Cancel"
onPress={() => { onPress={() => {
editEventProps.setIsVisible(false); setIsVisible(false);
}} }}
/> />
<View marginT-12> <View marginT-12>
<DropModalIcon <DropModalIcon
onPress={() => { onPress={() => {
editEventProps.setIsVisible(false); setIsVisible(false);
}} }}
/> />
</View> </View>
@ -71,7 +60,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
onPress={() => { onPress={() => {
try { try {
if (event.id) { if (event.id) {
updateEvent(event).then(() => editEventProps.setIsVisible(false)); updateEvent(event).then(() => setIsVisible(false));
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -85,7 +74,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
value={event.title} value={event.title}
onChangeText={(text) => { onChangeText={(text) => {
setEvent((prevEvent) => ({ setEvent((prevEvent) => ({
...prevEvent, ...prevEvent!,
title: text, title: text,
})); }));
}} }}
@ -110,7 +99,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
marginL-10 marginL-10
value={event.allDay} value={event.allDay}
onValueChange={(value) => onValueChange={(value) =>
setEvent((prev) => ({ ...prev, allDay: value })) setEvent((prev) => ({...prev!, allDay: value}))
} }
/> />
</View> </View>
@ -125,7 +114,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
marginL-8 marginL-8
maximumDate={event.end} maximumDate={event.end}
onChange={(date) => { onChange={(date) => {
setEvent((prev) => ({ ...prev, start: date })); setEvent((prev) => ({...prev!, start: date}));
}} }}
/> />
</View> </View>
@ -133,11 +122,12 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
text70 text70
value={event.start} value={event.start}
onChange={(date) => { onChange={(date) => {
setEvent((prev) => ({ ...prev, start: date })); setEvent((prev) => ({...prev!, start: date}));
}} }}
maximumDate={event.end} maximumDate={event.end}
dateTimeFormatter={(date, mode) => date.toLocaleTimeString("en-us", dateTimeFormatter={(date) => date.toLocaleTimeString("en-us",
{ hour: "numeric", {
hour: "numeric",
minute: "numeric" minute: "numeric"
})} })}
mode="time" mode="time"
@ -155,7 +145,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
text70 text70
marginL-8 marginL-8
onChange={(date) => { onChange={(date) => {
setEvent((prev) => ({ ...prev, end: date })); setEvent((prev) => ({...prev!, end: date}));
}} }}
/> />
</View> </View>
@ -164,10 +154,11 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
value={event.end} value={event.end}
minimumDate={event.start} minimumDate={event.start}
onChange={(date) => { onChange={(date) => {
setEvent((prev) => ({ ...prev, end: date })); setEvent((prev) => ({...prev!, end: date}));
}} }}
dateTimeFormatter={(date, mode) => date.toLocaleTimeString("en-us", dateTimeFormatter={(date) => date.toLocaleTimeString("en-us",
{ hour: "numeric", {
hour: "numeric",
minute: "numeric" minute: "numeric"
})} })}
mode="time" mode="time"
@ -262,7 +253,7 @@ const EditEventDialog = (editEventProps: IEditEventDialog) => {
marginL-10 marginL-10
value={event.private} value={event.private}
onValueChange={(value) => onValueChange={(value) =>
setEvent((prev) => ({ ...prev, private: value })) setEvent((prev) => ({...prev!, private: value}))
} }
/> />
</View> </View>

View File

@ -0,0 +1,63 @@
import React, {memo} from 'react';
import {Calendar} from "react-native-big-calendar";
import {StyleSheet} from "react-native";
import {useGetEvents} from "@/hooks/firebase/useGetEvents";
import {useAtom, useAtomValue, useSetAtom} from "jotai";
import {
editVisibleAtom,
eventForEditAtom,
modeAtom,
selectedDateAtom,
selectedNewEventDateAtom
} from "@/components/pages/calendar/atoms";
interface EventCalendarProps {
calendarHeight: number;
}
export const EventCalendar: React.FC<EventCalendarProps> = memo(({calendarHeight}) => {
const {data: events} = useGetEvents();
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
const mode = useAtomValue(modeAtom)
const setEditVisible = useSetAtom(editVisibleAtom)
const setEventForEdit = useSetAtom(eventForEditAtom)
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
return (
<Calendar
bodyContainerStyle={styles.calHeader}
mode={mode}
events={events ?? []}
eventCellStyle={(event) => ({backgroundColor: event.eventColor})}
onPressEvent={(event) => {
setEditVisible(true);
setEventForEdit(event);
}}
height={calendarHeight}
activeDate={selectedDate}
date={selectedDate}
onPressCell={setSelectedNewEndDate}
headerContentStyle={mode === "day" ? styles.dayModeHeader : {}}
onSwipeEnd={(date) => {
setSelectedDate(date);
}}
/>
);
});
const styles = StyleSheet.create({
segmentslblStyle: {
fontSize: 12,
fontFamily: "Manrope_600SemiBold",
},
calHeader: {
borderWidth: 0,
},
dayModeHeader: {
alignSelf: "flex-start",
justifyContent: "space-between",
alignContent: "center",
width: 38,
right: 42,
},
});

View File

@ -0,0 +1,41 @@
import {View} from "react-native-ui-lib";
import React, {useRef, useState} from "react";
import {LayoutChangeEvent} from "react-native";
import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch";
import {AddEventDialog} from "@/components/pages/calendar/AddEventDialog";
import EditEventDialog from "@/components/pages/calendar/EditEventDialog";
import {ManuallyAddEventModal} from "@/components/pages/calendar/ManuallyAddEventModal";
import {CalendarHeader} from "@/components/pages/calendar/CalendarHeader";
import {EventCalendar} from "@/components/pages/calendar/EventCalendar";
export const InnerCalendar = () => {
const [calendarHeight, setCalendarHeight] = useState(0);
const calendarContainerRef = useRef(null);
const onLayout = (event: LayoutChangeEvent) => {
const {height} = event.nativeEvent.layout;
setCalendarHeight(height);
};
return (
<>
<View
style={{flex: 1, backgroundColor: "#fff", borderRadius: 30}}
ref={calendarContainerRef}
onLayout={onLayout}
>
<CalendarHeader/>
{calendarHeight > 0 && (
<EventCalendar
calendarHeight={calendarHeight}
/>
)}
</View>
<CalendarViewSwitch/>
<AddEventDialog/>
<EditEventDialog/>
<ManuallyAddEventModal/>
</>
)
}

View File

@ -1,12 +1,10 @@
import { import {
Avatar,
Button, Button,
ButtonSize, ButtonSize,
Colors, Colors,
DateTimePicker, DateTimePicker,
LoaderScreen, LoaderScreen,
Modal, Modal,
Picker,
Switch, Switch,
Text, Text,
TextField, TextField,
@ -16,26 +14,20 @@ import {
import {ScrollView} from "react-native-gesture-handler"; import {ScrollView} 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 { import {AntDesign, Feather, Ionicons,} from "@expo/vector-icons";
AntDesign,
Feather,
Ionicons,
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 {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";
import { addHours, setDate } from "date-fns"; import {addHours} from "date-fns";
import DropModalIcon from "@/assets/svgs/DropModalIcon"; import DropModalIcon from "@/assets/svgs/DropModalIcon";
import { CalendarEvent, useCalendarContext } from "@/contexts/CalendarContext"; import {StyleSheet} from "react-native";
import { repeatOptions } from "@/contexts/ToDosContext";
import { ImageBackground, StyleSheet } from "react-native";
import ClockIcon from "@/assets/svgs/ClockIcon"; import ClockIcon from "@/assets/svgs/ClockIcon";
import LockIcon from "@/assets/svgs/LockIcon"; import LockIcon from "@/assets/svgs/LockIcon";
import MenuIcon from "@/assets/svgs/MenuIcon"; import MenuIcon from "@/assets/svgs/MenuIcon";
import CameraIcon from "@/assets/svgs/CameraIcon"; import CameraIcon from "@/assets/svgs/CameraIcon";
import AssigneesDisplay from "@/components/shared/AssigneesDisplay"; import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
import {useAtom} from "jotai";
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
const daysOfWeek = [ const daysOfWeek = [
{label: "Monday", value: "monday"}, {label: "Monday", value: "monday"},
@ -47,19 +39,17 @@ const daysOfWeek = [
{label: "Sunday", value: "sunday"}, {label: "Sunday", value: "sunday"},
]; ];
export const ManuallyAddEventModal = ({ export const ManuallyAddEventModal = () => {
show,
close,
initialDate,
}: {
show: boolean;
close: () => void;
initialDate?: Date;
}) => {
const { addEvent } = useCalendarContext();
const { user } = useAuthContext();
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const [selectedNewEventDate, setSelectedNewEndDate] = useAtom(selectedNewEventDateAtom)
const {show, close, initialDate} = {
show: !!selectedNewEventDate,
close: () => setSelectedNewEndDate(undefined),
initialDate: selectedNewEventDate
}
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
const [isAllDay, setIsAllDay] = useState(false); const [isAllDay, setIsAllDay] = useState(false);
@ -84,6 +74,8 @@ export const ManuallyAddEventModal = ({
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent(); const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent();
if (!selectedNewEventDate) return null;
const formatDateTime = (date?: Date | string) => { const formatDateTime = (date?: Date | string) => {
if (!date) return undefined; if (!date) return undefined;
return new Date(date).toLocaleDateString("en-US", { return new Date(date).toLocaleDateString("en-US", {

View File

@ -0,0 +1,9 @@
import { atom } from 'jotai';
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
export const editVisibleAtom = atom<boolean>(false);
export const eventForEditAtom = atom<CalendarEvent | undefined>(undefined);
export const isFamilyViewAtom = atom<boolean>(false);
export const modeAtom = atom<"week" | "month" | "day">("week");
export const selectedDateAtom = atom<Date>(new Date());
export const selectedNewEventDateAtom = atom<Date | undefined>(undefined);

View File

@ -0,0 +1,20 @@
export const modeMap = new Map([
[0, "day"],
[1, "week"],
[2, "month"],
]);
export const months = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];

View File

@ -0,0 +1,13 @@
export interface CalendarEvent {
id?: number | string; // Unique identifier for the event
user?: string;
title: string; // Event title or name
description?: string; // Optional description for the event
start: Date; // Start date and time of the event
end: Date; // End date and time of the event
location?: string; // Optional event location
allDay?: boolean; // Specifies if the event lasts all day
eventColor?: string; // Optional color to represent the event
participants?: string[]; // Optional list of participants or attendees
private?: boolean;
}

View File

@ -1,17 +1,17 @@
import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib"; import {Button, ButtonSize, Dialog, Text, TextField, View} from "react-native-ui-lib";
import React, {useEffect, useState} from "react"; import React, {useState} from "react";
import {useSignIn} from "@/hooks/firebase/useSignIn"; import {useSignIn} from "@/hooks/firebase/useSignIn";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import Toast from 'react-native-toast-message'; import Toast from 'react-native-toast-message';
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode"; import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
import {Camera, CameraView} from 'expo-camera'; import {Camera, CameraView} from 'expo-camera';
import {BarCodeScanner} from "expo-barcode-scanner";
const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">> }) => { const SignInPage = ({setTab}: {
setTab: React.Dispatch<React.SetStateAction<"register" | "login" | "reset-password">>
}) => {
const [email, setEmail] = useState<string>(""); const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>(""); const [password, setPassword] = useState<string>("");
const [hasPermission, setHasPermission] = useState<boolean | null>(null); const [hasPermission, setHasPermission] = useState<boolean | null>(null);
const [scanned, setScanned] = useState<boolean>(false);
const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false); const [showCameraDialog, setShowCameraDialog] = useState<boolean>(false);
const {mutateAsync: signIn, error, isError} = useSignIn(); const {mutateAsync: signIn, error, isError} = useSignIn();
@ -88,7 +88,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch<React.SetStateAction<"re
style={{marginBottom: 20}} style={{marginBottom: 20}}
backgroundColor="#fd1775" backgroundColor="#fd1775"
/> />
{isError && <Text center style={{marginBottom: 20}}>{`${error}`}</Text>} {isError && <Text center style={{marginBottom: 20}}>{`${error?.toString()?.split("]")?.[1]}`}</Text>}
<View row centerH marginB-5 gap-5> <View row centerH marginB-5 gap-5>
<Text text70> <Text text70>

View File

@ -1,11 +1,10 @@
import { View, Text, Button } from "react-native-ui-lib"; import {Button, Text, View} from "react-native-ui-lib";
import React, {useState} from "react"; import React, {useState} from "react";
import {StyleSheet} from "react-native"; import {StyleSheet} from "react-native";
import { Entypo, Ionicons, Octicons } from "@expo/vector-icons"; import {Octicons} from "@expo/vector-icons";
import CalendarSettingsPage from "./CalendarSettingsPage"; import CalendarSettingsPage from "./CalendarSettingsPage";
import ChoreRewardSettings from "./ChoreRewardSettings"; import ChoreRewardSettings from "./ChoreRewardSettings";
import UserSettings from "./UserSettings"; import UserSettings from "./UserSettings";
import { AuthContextProvider } from "@/contexts/AuthContext";
import ProfileIcon from "@/assets/svgs/ProfileIcon"; import ProfileIcon from "@/assets/svgs/ProfileIcon";
import CalendarIcon from "@/assets/svgs/CalendarIcon"; import CalendarIcon from "@/assets/svgs/CalendarIcon";
import PrivacyPolicyIcon from "@/assets/svgs/PrivacyPolicyIcon"; import PrivacyPolicyIcon from "@/assets/svgs/PrivacyPolicyIcon";
@ -18,6 +17,7 @@ const pageIndex = {
chore: 3, chore: 3,
policy: 4, policy: 4,
}; };
const SettingsPage = () => { const SettingsPage = () => {
const [selectedPage, setSelectedPage] = useState<number>(0); const [selectedPage, setSelectedPage] = useState<number>(0);
return ( return (

View File

@ -99,6 +99,7 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => { const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
setUser(authUser); setUser(authUser);
if (authUser) { if (authUser) {
await refreshProfileData(authUser); await refreshProfileData(authUser);
const pushToken = await registerForPushNotificationsAsync(); const pushToken = await registerForPushNotificationsAsync();

View File

@ -1,194 +0,0 @@
// CalendarContext.tsx
import React, { createContext, useContext, useState, ReactNode } from "react";
// Define the CalendarEvent interface
export interface CalendarEvent {
id?: number | string; // Unique identifier for the event
user?: string;
title: string; // Event title or name
description?: string; // Optional description for the event
start: Date; // Start date and time of the event
end: Date; // End date and time of the event
location?: string; // Optional event location
allDay?: boolean; // Specifies if the event lasts all day
color?: string; // Optional color to represent the event
participants?: string[]; // Optional list of participants or attendees
private?: boolean;
}
// Define the context type
interface CalendarContextType {
events: CalendarEvent[];
familyEvents: CalendarEvent[];
addEvent: (event: CalendarEvent) => void; // Function to add an event
removeEvent: (id: number) => void; // Function to remove an event by ID
updateEvent: (changes: Partial<CalendarEvent>, id?: number) => void;
}
// Create the CalendarContext
const CalendarContext = createContext<CalendarContextType | undefined>(
undefined
);
// Create a provider component
export const CalendarProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const [events, setEvents] = useState<CalendarEvent[]>([
{
id: 1,
title: "Team Meeting",
description: "Discuss project milestones and deadlines.",
start: new Date("2024-09-15T10:00:00"),
end: new Date("2024-09-15T11:00:00"),
location: "Office Conference Room",
allDay: false,
color: "#FF5733",
participants: ["Alice", "Bob", "Charlie"],
},
{
id: 2,
title: "Doctor's Appointment",
description: "Annual check-up with Dr. Smith.",
start: new Date("2024-09-20T14:30:00"),
end: new Date("2024-09-20T15:30:00"),
location: "Health Clinic",
allDay: false,
color: "#33FF57",
participants: ["You"],
},
{
id: 3,
title: "Birthday Party",
description: "Celebrating Sarah's 30th birthday.",
start: new Date("2024-09-25T18:00:00"),
end: new Date("2024-09-25T21:00:00"),
location: "Sarah's House",
allDay: false,
color: "#3357FF",
participants: ["You", "Sarah", "Tom", "Lily"],
},
{
id: 4,
title: "Project Deadline",
description: "Final submission for the project.",
start: new Date("2024-10-01T00:00:00"),
end: new Date("2024-10-01T23:59:00"),
location: "Online",
allDay: false,
color: "#FF33A1",
participants: ["You"],
},
{
id: 5,
title: "Halloween Costume Party",
description: "Join us for a spooky night of fun!",
start: new Date("2024-10-31T19:00:00"),
end: new Date("2024-10-31T23:00:00"),
location: "Downtown Club",
allDay: false,
color: "#FFB733",
participants: ["You", "Friends"],
},
]);
const [familyEvents, setFamilyEvents] = useState<CalendarEvent[]>([
{
id: 1,
user: "jakesId",
title: "Team Meeting",
description: "Discuss project milestones and deadlines.",
start: new Date("2024-09-10T10:00:00"),
end: new Date("2024-09-10T11:00:00"),
location: "Office Conference Room",
allDay: false,
color: "#FF5733",
participants: ["Alice", "Bob", "Charlie"],
},
{
id: 2,
user: "mikesId",
title: "Doctor's Appointment",
description: "Annual check-up with Dr. Smith.",
start: new Date("2024-09-21T14:30:00"),
end: new Date("2024-09-21T15:30:00"),
location: "Health Clinic",
allDay: false,
color: "#33FF57",
participants: ["You"],
},
{
id: 3,
user: "jakesId",
title: "Birthday Party",
description: "Celebrating Sarah's 30th birthday.",
start: new Date("2024-09-5T18:00:00"),
end: new Date("2024-09-5T21:00:00"),
location: "Sarah's House",
allDay: false,
color: "#3357FF",
participants: ["You", "Sarah", "Tom", "Lily"],
},
{
id: 4,
user: "davidsId",
title: "Project Deadline",
description: "Final submission for the project.",
start: new Date("2024-10-03T00:00:00"),
end: new Date("2024-10-03T23:59:00"),
location: "Online",
allDay: false,
color: "#FF33A1",
participants: ["You"],
},
{
id: 5,
user: "jakesId",
title: "Halloween Costume Party",
description: "Join us for a spooky night of fun!",
start: new Date("2024-10-02T19:00:00"),
end: new Date("2024-10-02T23:00:00"),
location: "Downtown Club",
allDay: false,
color: "#FFB733",
participants: ["You", "Friends"],
},
]);
// Function to add an event
const addEvent = (event: CalendarEvent) => {
event.id = events.length + 1;
setEvents((prevEvents) => [...prevEvents, event]);
};
// Function to remove an event by ID
const removeEvent = (id: number) => {
setEvents((prevEvents) => prevEvents.filter((event) => event.id !== id));
};
// Function to update an event
const updateEvent = ( changes: Partial<CalendarEvent>, id?: number) => {
setEvents((prevEvents) =>
prevEvents.map((event) =>
event.id === id ? { ...event, ...changes } : event
)
);
};
return (
<CalendarContext.Provider
value={{ events, addEvent, removeEvent, updateEvent, familyEvents }}
>
{children}
</CalendarContext.Provider>
);
};
// Custom hook to use the CalendarContext
export const useCalendarContext = () => {
const context = useContext(CalendarContext);
if (!context) {
throw new Error("useCalendar must be used within a CalendarProvider");
}
return context;
};

View File

@ -10,5 +10,5 @@ export interface EventData {
surpriseEvent?: boolean, surpriseEvent?: boolean,
notes?: string, notes?: string,
reminders?: string[] reminders?: string[]
id?: string, id?: string | number,
} }

View File

@ -2,9 +2,12 @@ import {useQuery} from "react-query";
import firestore from "@react-native-firebase/firestore"; import firestore from "@react-native-firebase/firestore";
import {useAuthContext} from "@/contexts/AuthContext"; import {useAuthContext} from "@/contexts/AuthContext";
import {colorMap} from "@/contexts/SettingsContext"; import {colorMap} from "@/contexts/SettingsContext";
import {useAtomValue} from "jotai";
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
export const useGetEvents = (isFamilyView: boolean) => { export const useGetEvents = () => {
const { user, profileData } = useAuthContext(); const { user, profileData } = useAuthContext();
const isFamilyView = useAtomValue(isFamilyViewAtom)
return useQuery({ return useQuery({
queryKey: ["events", user?.uid, isFamilyView], queryKey: ["events", user?.uid, isFamilyView],

View File

@ -1,10 +1,8 @@
import {useAuthContext} from "@/contexts/AuthContext";
import {useMutation, useQueryClient} from "react-query"; import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore"; import firestore from "@react-native-firebase/firestore";
import {EventData} from "@/hooks/firebase/types/eventData"; import {EventData} from "@/hooks/firebase/types/eventData";
export const useUpdateEvent = () => { export const useUpdateEvent = () => {
const {user: currentUser} = useAuthContext()
const queryClients = useQueryClient() const queryClients = useQueryClient()
return useMutation({ return useMutation({
@ -13,7 +11,7 @@ export const useUpdateEvent = () => {
try { try {
await firestore() await firestore()
.collection("Events") .collection("Events")
.doc(eventData.id) .doc(`${eventData.id}`)
.update(eventData); .update(eventData);
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -441,7 +441,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app; PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
PRODUCT_NAME = "CallyFamilyPlanner"; PRODUCT_NAME = CallyFamilyPlanner;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -472,7 +472,7 @@
); );
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app; PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
PRODUCT_NAME = "CallyFamilyPlanner"; PRODUCT_NAME = CallyFamilyPlanner;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";