diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7c03a96..9c43dd4 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,10 +1,12 @@ + + @@ -22,7 +24,7 @@ - + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 4713052..e5e1871 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Cally - Family Planner + Cally. contain false light diff --git a/android/settings.gradle b/android/settings.gradle index d40bc72..86a6fdf 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,4 @@ -rootProject.name = 'Cally - Family Planner' +rootProject.name = 'Cally.' dependencyResolutionManagement { versionCatalogs { diff --git a/app.json b/app.json index 6c7bf14..c250b65 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { "expo": { - "name": "Cally - Family Planner", + "name": "Cally.", "slug": "cally", "version": "1.0.0", "orientation": "portrait", @@ -16,7 +16,8 @@ "supportsTablet": true, "bundleIdentifier": "com.cally.app", "googleServicesFile": "./ios/GoogleService-Info.plist", - "buildNumber": "28" + "buildNumber": "31", + "usesAppleSignIn": true }, "android": { "adaptiveIcon": { @@ -63,7 +64,18 @@ "defaultChannel": "default" } ], - "expo-font" + [ + "expo-calendar", + { + "calendarPermission": "The app needs to access your calendar." + } + ], + [ + "expo-apple-authentication" + ], + "expo-font", + "expo-localization", + "./plugins/withPodfile" ], "experiments": { "typedRoutes": true diff --git a/app/(auth)/calendar/index.tsx b/app/(auth)/calendar/index.tsx index 4c4da19..ba50b83 100644 --- a/app/(auth)/calendar/index.tsx +++ b/app/(auth)/calendar/index.tsx @@ -1,14 +1,11 @@ import React from "react"; -import { CalendarProvider } from "@/contexts/CalendarContext"; // Import the new CalendarPage component import CalendarPage from "@/components/pages/calendar/CalendarPage"; -import { SettingsContextProvider } from "@/contexts/SettingsContext"; +import {SettingsContextProvider} from "@/contexts/SettingsContext"; export default function Screen() { - return ( - - - - - - ); + return ( + + + + ); } diff --git a/app/_layout.tsx b/app/_layout.tsx index 42e1941..fb8d977 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -1,170 +1,6 @@ -import React, { useEffect } from "react"; -import { DefaultTheme, ThemeProvider } from "@react-navigation/native"; +import React, {useEffect} from "react"; +import {DefaultTheme, ThemeProvider} from "@react-navigation/native"; import { - useFonts, - Manrope_200ExtraLight, - Manrope_300Light, - Manrope_400Regular, - Manrope_500Medium, - Manrope_600SemiBold, - Manrope_700Bold, - Manrope_800ExtraBold, -} from "@expo-google-fonts/manrope"; -import { - PlusJakartaSans_200ExtraLight, - PlusJakartaSans_300Light, - PlusJakartaSans_400Regular, - PlusJakartaSans_500Medium, - PlusJakartaSans_600SemiBold, - PlusJakartaSans_700Bold, - PlusJakartaSans_800ExtraBold, - PlusJakartaSans_200ExtraLight_Italic, - PlusJakartaSans_300Light_Italic, - PlusJakartaSans_400Regular_Italic, - PlusJakartaSans_500Medium_Italic, - PlusJakartaSans_600SemiBold_Italic, - PlusJakartaSans_700Bold_Italic, - PlusJakartaSans_800ExtraBold_Italic, -} from "@expo-google-fonts/plus-jakarta-sans"; -import { - Poppins_100Thin, - Poppins_100Thin_Italic, - Poppins_200ExtraLight, - Poppins_200ExtraLight_Italic, - Poppins_300Light, - Poppins_300Light_Italic, - Poppins_400Regular, - Poppins_400Regular_Italic, - Poppins_500Medium, - Poppins_500Medium_Italic, - Poppins_600SemiBold, - Poppins_600SemiBold_Italic, - Poppins_700Bold, - Poppins_700Bold_Italic, - Poppins_800ExtraBold, - Poppins_800ExtraBold_Italic, - Poppins_900Black, - Poppins_900Black_Italic, -} from "@expo-google-fonts/poppins"; -import { Stack } from "expo-router"; -import * as SplashScreen from "expo-splash-screen"; -import "react-native-reanimated"; -import { AuthContextProvider } from "@/contexts/AuthContext"; -import { QueryClient, QueryClientProvider } from "react-query"; -import { - 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(); - -const queryClient = new QueryClient(); - -if (__DEV__) { - functions().useEmulator("localhost", 5001); - firestore().useEmulator("localhost", 5471); - auth().useEmulator("http://localhost:9099"); -} - -type TextStyleBase = - | "text10" - | "text20" - | "text30" - | "text40" - | "text50" - | "text60" - | "text70" - | "text80" - | "text90" - | "text100"; -type TextStyleModifier = "R" | "M" | "BO" | "H" | "BL" | "L"; -type TextStyle = TextStyleBase | `${TextStyleBase}${TextStyleModifier}`; - -type TextStyleProps = { - [K in TextStyle]?: boolean; -}; - -type ExtendedTextProps = TextProps & TextStyleProps; - -interface FontStyle { - fontFamily: string; - fontSize: number; -} - -const getManropeFontStyle = (style: TextStyle): FontStyle => { - let fontFamily: string; - let fontSize: number; - - if (style.includes("L") || style.includes("BL")) - fontFamily = "Manrope_300Light"; - else if (style.includes("R")) fontFamily = "Manrope_400Regular"; - else if (style.includes("M")) fontFamily = "Manrope_500Medium"; - else if (style.includes("BO") || style.includes("H")) - fontFamily = "Manrope_700Bold"; - else { - const baseStyle = style.slice(0, 6) as TextStyleBase; - switch (baseStyle) { - case "text10": - case "text20": - fontFamily = "Manrope_700Bold"; - break; - case "text30": - case "text40": - fontFamily = "Manrope_600SemiBold"; - break; - case "text50": - fontFamily = "Manrope_400Regular"; - break; - default: - fontFamily = "Manrope_300Light"; - } - } - - switch (style.slice(0, 6) as TextStyleBase) { - case "text10": - fontSize = 64; - break; - case "text20": - fontSize = 50; - break; - case "text30": - fontSize = 36; - break; - case "text40": - fontSize = 28; - break; - case "text50": - fontSize = 24; - break; - case "text60": - fontSize = 20; - break; - case "text70": - fontSize = 16; - break; - case "text80": - fontSize = 14; - break; - case "text90": - fontSize = 12; - break; - case "text100": - fontSize = 10; - break; - default: - fontSize = 16; - } - - return { fontFamily, fontSize }; -}; - -export default function RootLayout() { - const [loaded] = useFonts({ Manrope_200ExtraLight, Manrope_300Light, Manrope_400Regular, @@ -172,20 +8,25 @@ export default function RootLayout() { Manrope_600SemiBold, Manrope_700Bold, Manrope_800ExtraBold, + useFonts, +} from "@expo-google-fonts/manrope"; +import { PlusJakartaSans_200ExtraLight, - PlusJakartaSans_300Light, - PlusJakartaSans_400Regular, - PlusJakartaSans_500Medium, - PlusJakartaSans_600SemiBold, - PlusJakartaSans_700Bold, - PlusJakartaSans_800ExtraBold, PlusJakartaSans_200ExtraLight_Italic, + PlusJakartaSans_300Light, PlusJakartaSans_300Light_Italic, + PlusJakartaSans_400Regular, PlusJakartaSans_400Regular_Italic, + PlusJakartaSans_500Medium, PlusJakartaSans_500Medium_Italic, + PlusJakartaSans_600SemiBold, PlusJakartaSans_600SemiBold_Italic, + PlusJakartaSans_700Bold, PlusJakartaSans_700Bold_Italic, + PlusJakartaSans_800ExtraBold, PlusJakartaSans_800ExtraBold_Italic, +} from "@expo-google-fonts/plus-jakarta-sans"; +import { Poppins_100Thin, Poppins_100Thin_Italic, Poppins_200ExtraLight, @@ -204,72 +45,231 @@ export default function RootLayout() { Poppins_800ExtraBold_Italic, Poppins_900Black, Poppins_900Black_Italic, - }); +} from "@expo-google-fonts/poppins"; +import {Stack} from "expo-router"; +import * as SplashScreen from "expo-splash-screen"; +import "react-native-reanimated"; +import {AuthContextProvider} from "@/contexts/AuthContext"; +import {QueryClient, QueryClientProvider} from "react-query"; +import {TextProps, ThemeManager, Toast, Typography,} from "react-native-ui-lib"; +import {Platform} from 'react-native'; +import KeyboardManager from 'react-native-keyboard-manager'; - useEffect(() => { - if (loaded) { - SplashScreen.hideAsync(); +SplashScreen.preventAutoHideAsync(); - const typographies: Partial> = {}; - ( - [ - "text10", - "text20", - "text30", - "text40", - "text50", - "text60", - "text70", - "text80", - "text90", - "text100", - ] as const - ).forEach((baseStyle) => { - typographies[baseStyle] = getManropeFontStyle(baseStyle); - (["R", "M", "BO", "H", "BL", "L"] as const).forEach((modifier) => { - const style = `${baseStyle}${modifier}` as TextStyle; - typographies[style] = getManropeFontStyle(style); - }); - }); +const queryClient = new QueryClient(); - Typography.loadTypographies(typographies); - ThemeManager.setComponentTheme( - "Text", - (props: ExtendedTextProps, context: unknown) => { - const textStyle = ( - Object.keys(props) as Array - ).find((key) => typographies[key as TextStyle]) as - | TextStyle - | undefined; - - return { - style: [ - Typography.text50, - textStyle ? typographies[textStyle] : undefined, - ], - }; - } - ); - } - }, [loaded]); - - if (!loaded) { - return null; - } - - return ( - - - - - - - - - - - - - ); +if (Platform.OS === 'ios') { + KeyboardManager.setEnable(true); + KeyboardManager.setToolbarPreviousNextButtonEnable(true); +} + +if (__DEV__) { + // functions().useEmulator("localhost", 5001); + // firestore().useEmulator("localhost", 5471); + // auth().useEmulator("http://localhost:9099"); +} + +type TextStyleBase = + | "text10" + | "text20" + | "text30" + | "text40" + | "text50" + | "text60" + | "text70" + | "text80" + | "text90" + | "text100"; +type TextStyleModifier = "R" | "M" | "BO" | "H" | "BL" | "L"; +type TextStyle = TextStyleBase | `${TextStyleBase}${TextStyleModifier}`; + +type TextStyleProps = { + [K in TextStyle]?: boolean; +}; + +type ExtendedTextProps = TextProps & TextStyleProps; + +interface FontStyle { + fontFamily: string; + fontSize: number; +} + +const getManropeFontStyle = (style: TextStyle): FontStyle => { + let fontFamily: string; + let fontSize: number; + + if (style.includes("L") || style.includes("BL")) + fontFamily = "Manrope_300Light"; + else if (style.includes("R")) fontFamily = "Manrope_400Regular"; + else if (style.includes("M")) fontFamily = "Manrope_500Medium"; + else if (style.includes("BO") || style.includes("H")) + fontFamily = "Manrope_700Bold"; + else { + const baseStyle = style.slice(0, 6) as TextStyleBase; + switch (baseStyle) { + case "text10": + case "text20": + fontFamily = "Manrope_700Bold"; + break; + case "text30": + case "text40": + fontFamily = "Manrope_600SemiBold"; + break; + case "text50": + fontFamily = "Manrope_400Regular"; + break; + default: + fontFamily = "Manrope_300Light"; + } + } + + switch (style.slice(0, 6) as TextStyleBase) { + case "text10": + fontSize = 64; + break; + case "text20": + fontSize = 50; + break; + case "text30": + fontSize = 36; + break; + case "text40": + fontSize = 28; + break; + case "text50": + fontSize = 24; + break; + case "text60": + fontSize = 20; + break; + case "text70": + fontSize = 16; + break; + case "text80": + fontSize = 14; + break; + case "text90": + fontSize = 12; + break; + case "text100": + fontSize = 10; + break; + default: + fontSize = 16; + } + + return {fontFamily, fontSize}; +}; + +export default function RootLayout() { + const [loaded] = useFonts({ + Manrope_200ExtraLight, + Manrope_300Light, + Manrope_400Regular, + Manrope_500Medium, + Manrope_600SemiBold, + Manrope_700Bold, + Manrope_800ExtraBold, + PlusJakartaSans_200ExtraLight, + PlusJakartaSans_300Light, + PlusJakartaSans_400Regular, + PlusJakartaSans_500Medium, + PlusJakartaSans_600SemiBold, + PlusJakartaSans_700Bold, + PlusJakartaSans_800ExtraBold, + PlusJakartaSans_200ExtraLight_Italic, + PlusJakartaSans_300Light_Italic, + PlusJakartaSans_400Regular_Italic, + PlusJakartaSans_500Medium_Italic, + PlusJakartaSans_600SemiBold_Italic, + PlusJakartaSans_700Bold_Italic, + PlusJakartaSans_800ExtraBold_Italic, + Poppins_100Thin, + Poppins_100Thin_Italic, + Poppins_200ExtraLight, + Poppins_200ExtraLight_Italic, + Poppins_300Light, + Poppins_300Light_Italic, + Poppins_400Regular, + Poppins_400Regular_Italic, + Poppins_500Medium, + Poppins_500Medium_Italic, + Poppins_600SemiBold, + Poppins_600SemiBold_Italic, + Poppins_700Bold, + Poppins_700Bold_Italic, + Poppins_800ExtraBold, + Poppins_800ExtraBold_Italic, + Poppins_900Black, + Poppins_900Black_Italic, + }); + + useEffect(() => { + if (loaded) { + SplashScreen.hideAsync(); + + const typographies: Partial> = {}; + ( + [ + "text10", + "text20", + "text30", + "text40", + "text50", + "text60", + "text70", + "text80", + "text90", + "text100", + ] as const + ).forEach((baseStyle) => { + typographies[baseStyle] = getManropeFontStyle(baseStyle); + (["R", "M", "BO", "H", "BL", "L"] as const).forEach((modifier) => { + const style = `${baseStyle}${modifier}` as TextStyle; + typographies[style] = getManropeFontStyle(style); + }); + }); + + Typography.loadTypographies(typographies); + + ThemeManager.setComponentTheme( + "Text", + (props: ExtendedTextProps) => { + const textStyle = ( + Object.keys(props) as Array + ).find((key) => typographies[key as TextStyle]) as + | TextStyle + | undefined; + + return { + style: [ + Typography.text50, + textStyle ? typographies[textStyle] : undefined, + ], + }; + } + ); + } + }, [loaded]); + + if (!loaded) { + return null; + } + + return ( + + + + + + + + + + + + + ); } diff --git a/assets/svgs/ArrowRightIcon.tsx b/assets/svgs/ArrowRightIcon.tsx new file mode 100644 index 0000000..37e85cf --- /dev/null +++ b/assets/svgs/ArrowRightIcon.tsx @@ -0,0 +1,19 @@ +import * as React from "react" +import Svg, { SvgProps, Path } from "react-native-svg" +const ArrowRightIcon = (props: SvgProps) => ( + + + +) +export default ArrowRightIcon diff --git a/assets/svgs/CircledXIcon.tsx b/assets/svgs/CircledXIcon.tsx new file mode 100644 index 0000000..9759ef8 --- /dev/null +++ b/assets/svgs/CircledXIcon.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; +import Svg, { SvgProps, Path } from "react-native-svg"; +const CircledXIcon = (props: SvgProps) => ( + + + + +); +export default CircledXIcon; diff --git a/assets/svgs/EmailIcon.tsx b/assets/svgs/EmailIcon.tsx new file mode 100644 index 0000000..47d6e00 --- /dev/null +++ b/assets/svgs/EmailIcon.tsx @@ -0,0 +1,17 @@ +import * as React from "react" +import Svg, { SvgProps, Path } from "react-native-svg" +const EmailIcon = (props: SvgProps) => ( + + + +) +export default EmailIcon diff --git a/assets/svgs/QRIcon.tsx b/assets/svgs/QRIcon.tsx new file mode 100644 index 0000000..f7be63d --- /dev/null +++ b/assets/svgs/QRIcon.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +import Svg, { SvgProps, Path } from "react-native-svg" +const QRIcon = (props: SvgProps) => ( + + + +) +export default QRIcon diff --git a/calendar-integration/apple-calendar-utils.js b/calendar-integration/apple-calendar-utils.js new file mode 100644 index 0000000..779a4ab --- /dev/null +++ b/calendar-integration/apple-calendar-utils.js @@ -0,0 +1,41 @@ +import * as Calendar from 'expo-calendar'; + +export async function fetchiPhoneCalendarEvents(familyId, email, startDate, endDate) { + try { + const {status} = await Calendar.requestCalendarPermissionsAsync(); + if (status !== 'granted') { + throw new Error("Calendar permission not granted"); + } + + const defaultCalendarSource = await Calendar.getDefaultCalendarAsync(); + + if (!defaultCalendarSource) { + throw new Error("No calendar found"); + } + + const events = await Calendar.getEventsAsync( + [defaultCalendarSource.id], + startDate, + endDate + ); + + return events.map((event) => { + let isAllDay = event.allDay || false; + const startDateTime = new Date(event.startDate); + const endDateTime = new Date(event.endDate); + + return { + id: event.id, + title: event.title, + startDate: startDateTime, + endDate: endDateTime, + allDay: isAllDay, + familyId, + email + }; + }); + } catch (error) { + console.error("Error fetching iPhone Calendar events: ", error); + throw error; + } +} \ No newline at end of file diff --git a/calendar-integration/google-calendar-utils.js b/calendar-integration/google-calendar-utils.js index c7d332c..26a0e26 100644 --- a/calendar-integration/google-calendar-utils.js +++ b/calendar-integration/google-calendar-utils.js @@ -1,4 +1,4 @@ -export async function fetchGoogleCalendarEvents(token, startDate, endDate) { +export async function fetchGoogleCalendarEvents(token, email, familyId, startDate, endDate) { console.log(token); const response = await fetch( `https://www.googleapis.com/calendar/v3/calendars/primary/events?single_events=true&time_min=${startDate}&time_max=${endDate}`, @@ -45,6 +45,8 @@ export async function fetchGoogleCalendarEvents(token, startDate, endDate) { startDate: startDateTime, endDate: endDateTime, allDay: isAllDay, + familyId, + email }; googleEvents.push(googleEvent); }); diff --git a/calendar-integration/microsoft-calendar-utils.js b/calendar-integration/microsoft-calendar-utils.js index b459a29..8ff09ce 100644 --- a/calendar-integration/microsoft-calendar-utils.js +++ b/calendar-integration/microsoft-calendar-utils.js @@ -1,4 +1,4 @@ -export async function fetchMicrosoftCalendarEvents(token, startDate, endDate) { +export async function fetchMicrosoftCalendarEvents(token, email, familyId, startDate, endDate) { const response = await fetch( `https://graph.microsoft.com/v1.0/me/calendar/calendarView?startDateTime=${startDate}&endDateTime=${endDate}`, { @@ -34,6 +34,8 @@ export async function fetchMicrosoftCalendarEvents(token, startDate, endDate) { startDate: startDateTime, endDate: endDateTime, allDay: item.isAllDay, + familyId, + email }; diff --git a/components/pages/brain_dump/AddBrainDump.tsx b/components/pages/brain_dump/AddBrainDump.tsx index 00bc3f2..a134f5d 100644 --- a/components/pages/brain_dump/AddBrainDump.tsx +++ b/components/pages/brain_dump/AddBrainDump.tsx @@ -1,17 +1,24 @@ -import { View, Text, Button, TextField } from "react-native-ui-lib"; -import React, { useEffect, useState } from "react"; +import { + View, + Text, + Button, + TextField, + TextFieldRef, + TouchableOpacity, +} from "react-native-ui-lib"; +import React, { useEffect, useState, useRef } from "react"; import { Dialog } from "react-native-ui-lib"; import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView"; -import CloseXIcon from "@/assets/svgs/CloseXIcon"; -import { Dimensions, StyleSheet } from "react-native"; +import { Dimensions, Keyboard, StyleSheet } from "react-native"; + import DropModalIcon from "@/assets/svgs/DropModalIcon"; -import MenuIcon from "@/assets/svgs/MenuIcon"; import { useBrainDumpContext } from "@/contexts/DumpContext"; interface IAddBrainDumpProps { isVisible: boolean; setIsVisible: (value: boolean) => void; } + const AddBrainDump = ({ addBrainDumpProps, }: { @@ -20,7 +27,10 @@ const AddBrainDump = ({ const { addBrainDump } = useBrainDumpContext(); const [dumpTitle, setDumpTitle] = useState(""); const [dumpDesc, setDumpDesc] = useState(""); - const { width, height } = Dimensions.get("screen"); + const { width } = Dimensions.get("screen"); + + // Refs for the two TextFields + const descriptionRef = useRef(null); useEffect(() => { setDumpDesc(""); @@ -34,14 +44,7 @@ const AddBrainDump = ({ width={width} panDirection={PanningDirectionsEnum.DOWN} onDismiss={() => addBrainDumpProps.setIsVisible(false)} - containerStyle={{ - borderTopRightRadius: 15, - borderTopLeftRadius: 15, - backgroundColor: "white", - padding: 0, - paddingTop: 3, - margin: 0, - }} + containerStyle={styles.dialogContainer} visible={addBrainDumpProps.isVisible} > @@ -53,16 +56,15 @@ const AddBrainDump = ({ addBrainDumpProps.setIsVisible(false); }} /> - addBrainDumpProps.setIsVisible(false)} - /> + addBrainDumpProps.setIsVisible(false)}> + + + return ( + + <> + - setShow(false)} - panDirection={PanningProvider.Directions.DOWN} - center - > - - - Create a new event - + setShow(false)} + panDirection={PanningProvider.Directions.DOWN} + center + > + + + Create a new event + - - - - setShowManualInputModal(false)} - /> - - - - ); + setShow(false)}> + + Go back to calendar + + + + + + + + + ); }; const styles = StyleSheet.create({ - modalTitle: { - fontSize: 22, - fontFamily: "Manrope_600SemiBold", - marginBottom: 16, - }, - bottomText: { - marginTop: 20, - color: "#999999", - fontSize: 13.53, - fontFamily: "Poppins_500Medium", - }, - dialogCard: { - paddingHorizontal: 40, - paddingTop: 35, - paddingBottom: 20, - justifyContent: "center", - alignItems: "center", - borderRadius: 20, - }, - btnLabel: { - fontSize: 15, - fontFamily: "PlusJakartaSans_500Medium", - }, - btnIcon: { marginRight: 10 }, + modalTitle: { + fontSize: 22, + fontFamily: "Manrope_600SemiBold", + marginBottom: 16, + }, + bottomText: { + marginTop: 20, + color: "#999999", + fontSize: 13.53, + fontFamily: "Poppins_500Medium", + }, + dialogCard: { + paddingHorizontal: 40, + paddingTop: 35, + paddingBottom: 20, + justifyContent: "center", + alignItems: "center", + borderRadius: 20, + }, + btnLabel: { + fontSize: 15, + fontFamily: "PlusJakartaSans_500Medium", + }, + btnIcon: {marginRight: 10}, }); diff --git a/components/pages/calendar/CalendarHeader.tsx b/components/pages/calendar/CalendarHeader.tsx new file mode 100644 index 0000000..d2d221a --- /dev/null +++ b/components/pages/calendar/CalendarHeader.tsx @@ -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 ( + + + + {selectedDate.getFullYear()} + + handleMonthChange(itemValue as string)} + trailingAccessory={} + topBarProps={{ + title: selectedDate.getFullYear().toString(), + titleStyle: {fontFamily: "Manrope_500Medium", fontSize: 17}, + }} + > + {months.map((month) => ( + + ))} + + + + + + + + ); +}); + +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, + }, +}); \ No newline at end of file diff --git a/components/pages/calendar/CalendarPage.tsx b/components/pages/calendar/CalendarPage.tsx index 0cfd397..f118eaa 100644 --- a/components/pages/calendar/CalendarPage.tsx +++ b/components/pages/calendar/CalendarPage.tsx @@ -1,207 +1,20 @@ -import React, { useRef, useState } from "react"; -import { LayoutChangeEvent, StyleSheet } from "react-native"; -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 React from "react"; +import {View,} from "react-native-ui-lib"; import HeaderTemplate from "@/components/shared/HeaderTemplate"; -import CalendarViewSwitch from "@/components/pages/calendar/CalendarViewSwitch"; -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", -]; +import {InnerCalendar} from "@/components/pages/calendar/InnerCalendar"; export default function CalendarPage() { - const { calendarColor } = useSettingsContext(); - const [editVisible, setEditVisible] = useState(false); - const [eventForEdit, setEventForEdit] = useState(); - - 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(false); - const [calendarHeight, setCalendarHeight] = useState(0); - const [mode, setMode] = useState<"week" | "month" | "day">("week"); - const [selectedDate, setSelectedDate] = useState(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 ( - - - {selectedDate.getFullYear()} - - handleMonthChange(itemValue as string)} - trailingAccessory={} - topBarProps={{ - title: selectedDate.getFullYear().toString(), - titleStyle: { fontFamily: "Manrope_500Medium", fontSize: 17 }, - }} - > - {months.map((month) => ( - - ))} - - - - - - + - - {calendarHeight > 0 && ( - ({ 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); - }} - /> - )} - - - - {eventForEdit && ( - { - setEditVisible(!editVisible); - }} - event={eventForEdit} - /> - )} - - setSelectedNewEndDate(undefined)} - /> - - ); + ); } diff --git a/components/pages/calendar/CalendarViewSwitch.tsx b/components/pages/calendar/CalendarViewSwitch.tsx index 2bbc376..ac46a97 100644 --- a/components/pages/calendar/CalendarViewSwitch.tsx +++ b/components/pages/calendar/CalendarViewSwitch.tsx @@ -1,90 +1,90 @@ -import { View, Text, Button, TouchableOpacity } from "react-native-ui-lib"; -import React, { useState } from "react"; -import { MaterialIcons } from "@expo/vector-icons"; -import { StyleSheet } from "react-native"; +import {Text, TouchableOpacity, View} from "react-native-ui-lib"; +import React, {useState} from "react"; +import {StyleSheet} from "react-native"; +import {useSetAtom} from "jotai"; +import {isFamilyViewAtom} from "@/components/pages/calendar/atoms"; -interface ICalendarViewProps { - viewSwitch: (value: boolean) => void; -} -const CalendarViewSwitch = (calendarViewProps: ICalendarViewProps) => { - const [calView, setCalView] = useState(false); - return ( - - { - setCalView(true); - calendarViewProps.viewSwitch(true); - }} - > +const CalendarViewSwitch = () => { + const [calView, setCalView] = useState(false); + const viewSwitch = useSetAtom(isFamilyViewAtom) + + return ( - - Family View - - - + { + setCalView(true); + viewSwitch(true); + }} + > + + + Family View + + + - { - setCalView(false); - calendarViewProps.viewSwitch(false); - }} - > - - - My View - + { + setCalView(false); + viewSwitch(false); + }} + > + + + My View + + + - - - ); + ); }; export default CalendarViewSwitch; const styles = StyleSheet.create({ - switchBtnActive: { - backgroundColor: "#a1a1a1", - borderRadius: 50, - }, - switchBtn: { - backgroundColor: "white", - borderRadius: 50, - }, - switchTxt:{ - fontSize: 16, - fontFamily: 'Manrope_600SemiBold' - } + switchBtnActive: { + backgroundColor: "#a1a1a1", + borderRadius: 50, + }, + switchBtn: { + backgroundColor: "white", + borderRadius: 50, + }, + switchTxt: { + fontSize: 16, + fontFamily: 'Manrope_600SemiBold' + } }); diff --git a/components/pages/calendar/EditEventDialog.tsx b/components/pages/calendar/EditEventDialog.tsx index 25a67a4..56b73b1 100644 --- a/components/pages/calendar/EditEventDialog.tsx +++ b/components/pages/calendar/EditEventDialog.tsx @@ -1,312 +1,303 @@ -import { View, Text, Button, Switch } from "react-native-ui-lib"; -import React, { useEffect, useState } from "react"; -import { Feather, AntDesign, 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 { StyleSheet } from "react-native"; +import {Button, ButtonSize, DateTimePicker, Dialog, Switch, Text, TextField, View} from "react-native-ui-lib"; +import React from "react"; +import {AntDesign, Feather, Ionicons} from "@expo/vector-icons"; +import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView"; +import {StyleSheet} from "react-native"; import DropModalIcon from "@/assets/svgs/DropModalIcon"; -import { CalendarEvent } from "@/contexts/CalendarContext"; import ClockIcon from "@/assets/svgs/ClockIcon"; import LockIcon from "@/assets/svgs/LockIcon"; 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; - isVisible: boolean; - setIsVisible: (value: boolean) => void; -} -const EditEventDialog = (editEventProps: IEditEventDialog) => { - const [event, setEvent] = useState(editEventProps.event); - const { mutateAsync: updateEvent } = useUpdateEvent(); +const EditEventDialog = () => { + const [isVisible, setIsVisible] = useAtom(editVisibleAtom) + const [event, setEvent] = useAtom(eventForEditAtom) - useEffect(() => { - setEvent(editEventProps.event); - }, [editEventProps.isVisible]); + const {mutateAsync: updateEvent} = useUpdateEvent(); - return ( - editEventProps.setIsVisible(false)} - containerStyle={{ - borderRadius: 10, - backgroundColor: "white", - width: "100%", - alignSelf: "stretch", - padding: 0, - paddingTop: 4, - margin: 0, - }} - visible={editEventProps.isVisible} - > - - - ); + { + setEvent((prevEvent) => ({ + ...prevEvent!, + title: text, + })); + }} + placeholderTextColor="#2d2d30" + text60R + marginT-15 + marginL-30 + /> + + + + + + + All day + + + + + setEvent((prev) => ({...prev!, allDay: value})) + } + /> + + + + + + + { + setEvent((prev) => ({...prev!, start: date})); + }} + /> + + { + setEvent((prev) => ({...prev!, start: date})); + }} + maximumDate={event.end} + dateTimeFormatter={(date) => date.toLocaleTimeString("en-us", + { + hour: "numeric", + minute: "numeric" + })} + mode="time" + marginR-30 + /> + + + {!event.allDay && ( + + + + { + setEvent((prev) => ({...prev!, end: date})); + }} + /> + + { + setEvent((prev) => ({...prev!, end: date})); + }} + dateTimeFormatter={(date) => date.toLocaleTimeString("en-us", + { + hour: "numeric", + minute: "numeric" + })} + mode="time" + marginR-30 + /> + + )} + + + + + + + Assignees + + - setShowAddUserDialog(false)} center> - Return to user settings + setShowAddUserDialog(false)} + center + marginT-30 + > + Return to user settings @@ -248,98 +301,167 @@ const MyGroup = () => { visible={showNewUserInfoDialog} onDismiss={() => setShowNewUserInfoDialog(false)} > - - - New User Information - setShowAddUserDialog(false)}> - X - - + + + + + + New User Information + + { + setShowNewUserInfoDialog(false) + }}> + + + + - - - { - }}> - - Upload User Profile Photo + + + } + backgroundColor={Colors.grey60} + style={{borderRadius: 25}} + center + /> + { + }}> + + Upload User Profile Photo + + + + + Member Status + + setSelectedStatus(item)} + showSearch + floatingPlaceholder + style={styles.inViewPicker} + trailingAccessory={ + + + + } + > + + + + + + + + + {selectedStatus === ProfileType.FAMILY_DEVICE + ? "Device Name" + : "First Name"} - - - - Member Status - setSelectedStatus(item)} - style={styles.picker} - showSearch - floatingPlaceholder - > - - - - - - - - {selectedStatus === ProfileType.FAMILY_DEVICE ? "Device Name" : "First Name"} - - - - {selectedStatus !== ProfileType.FAMILY_DEVICE && ( - <> - Last Name { + lNameRef.current?.focus() + }} + blurOnSubmit={false} + returnKeyType="next" /> - - )} - {selectedStatus !== ProfileType.FAMILY_DEVICE && ( - <> - Email Address (Optional) - + Last Name + { + emailRef.current?.focus() + }} + blurOnSubmit={false} + returnKeyType="next" + + /> + + )} + + {selectedStatus !== ProfileType.FAMILY_DEVICE && ( + <> + Email Address (Optional) + + + )} + +