From d2b46ad97747bd0b415fdaaee50ac102980beaba Mon Sep 17 00:00:00 2001
From: ivic00 <102467664+ivic00@users.noreply.github.com>
Date: Thu, 17 Oct 2024 19:57:41 +0200
Subject: [PATCH 1/9] added focus, ui changes
---
assets/svgs/ArrowRightIcon.tsx | 19 +
assets/svgs/CircledXIcon.tsx | 22 +
assets/svgs/EmailIcon.tsx | 17 +
assets/svgs/QRIcon.tsx | 20 +
components/pages/brain_dump/AddBrainDump.tsx | 74 +-
components/pages/brain_dump/MoveBrainDump.tsx | 11 +-
.../pages/calendar/ManuallyAddEventModal.tsx | 2 +
components/pages/grocery/CategoryDropdown.tsx | 2 +-
components/pages/grocery/EditGroceryItem.tsx | 4 +-
components/pages/main/SignUpPage.tsx | 25 +-
components/pages/settings/SettingsPage.tsx | 141 +--
.../settings/user_settings_views/MyGroup.tsx | 841 ++++++++++--------
.../settings/user_settings_views/UserMenu.tsx | 2 +-
components/pages/todos/AddChoreDialog.tsx | 25 +-
14 files changed, 729 insertions(+), 476 deletions(-)
create mode 100644 assets/svgs/ArrowRightIcon.tsx
create mode 100644 assets/svgs/CircledXIcon.tsx
create mode 100644 assets/svgs/EmailIcon.tsx
create mode 100644 assets/svgs/QRIcon.tsx
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/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)}>
+
+
@@ -192,6 +191,10 @@ const styles = StyleSheet.create({
fontSize: 22,
fontFamily: "Manrope_500Medium",
},
+ description:{
+ fontFamily: "Manrope_400Regular",
+ fontSize: 14,
+ }
});
export default MoveBrainDump;
diff --git a/components/pages/calendar/ManuallyAddEventModal.tsx b/components/pages/calendar/ManuallyAddEventModal.tsx
index ab2b19e..ed979f0 100644
--- a/components/pages/calendar/ManuallyAddEventModal.tsx
+++ b/components/pages/calendar/ManuallyAddEventModal.tsx
@@ -225,6 +225,7 @@ export const ManuallyAddEventModal = ({
{
setTitle(text);
}}
@@ -232,6 +233,7 @@ export const ManuallyAddEventModal = ({
style={{ fontFamily: "Manrope_500Medium", fontSize: 22 }}
paddingT-15
paddingL-30
+ returnKeyType="next"
/>
diff --git a/components/pages/grocery/CategoryDropdown.tsx b/components/pages/grocery/CategoryDropdown.tsx
index 7cb787a..a84eadb 100644
--- a/components/pages/grocery/CategoryDropdown.tsx
+++ b/components/pages/grocery/CategoryDropdown.tsx
@@ -23,7 +23,7 @@ const CategoryDropdown = (props: {
padding: 10,
}}
>
- {category}
+ {category}
))}
diff --git a/components/pages/grocery/EditGroceryItem.tsx b/components/pages/grocery/EditGroceryItem.tsx
index cfdb56d..73d994c 100644
--- a/components/pages/grocery/EditGroceryItem.tsx
+++ b/components/pages/grocery/EditGroceryItem.tsx
@@ -1,7 +1,8 @@
import {Text, View} from "react-native";
-import React, {useEffect, useRef} from "react";
+import React, {useEffect, useRef, useState} from "react";
import {TextField, TextFieldRef} from "react-native-ui-lib";
import {GroceryCategory, useGroceryContext,} from "@/contexts/GroceryContext";
+import CategoryDropdown from "./CategoryDropdown";
interface IEditGrocery {
id?: string;
@@ -17,6 +18,7 @@ interface IEditGrocery {
const EditGroceryItem = ({editGrocery}: { editGrocery: IEditGrocery }) => {
const {fuzzyMatchGroceryCategory} = useGroceryContext();
const inputRef = useRef(null);
+ const [category, setCategory] = useState(GroceryCategory.None);
useEffect(() => {
if (editGrocery.setCategory)
diff --git a/components/pages/main/SignUpPage.tsx b/components/pages/main/SignUpPage.tsx
index 5c843f9..e48e351 100644
--- a/components/pages/main/SignUpPage.tsx
+++ b/components/pages/main/SignUpPage.tsx
@@ -1,10 +1,11 @@
-import React, { useState } from "react";
+import React, { useRef, useState } from "react";
import {
Button,
ButtonSize,
Checkbox,
Text,
TextField,
+ TextFieldRef,
TouchableOpacity,
View,
} from "react-native-ui-lib";
@@ -13,7 +14,13 @@ import { ProfileType } from "@/contexts/AuthContext";
import { StyleSheet } from "react-native";
import { AntDesign } from "@expo/vector-icons";
-const SignUpPage = ({setTab}: { setTab: React.Dispatch> }) => {
+const SignUpPage = ({
+ setTab,
+}: {
+ setTab: React.Dispatch<
+ React.SetStateAction<"register" | "login" | "reset-password">
+ >;
+}) => {
const [email, setEmail] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
@@ -24,6 +31,10 @@ const SignUpPage = ({setTab}: { setTab: React.Dispatch(false);
const { mutateAsync: signUp } = useSignUp();
+ const lnameRef = useRef(null);
+ const emailRef = useRef(null);
+ const passwordRef = useRef(null);
+
const handleSignUp = async () => {
await signUp({ email, password, firstName, lastName });
};
@@ -36,24 +47,34 @@ const SignUpPage = ({setTab}: { setTab: React.DispatchPlease enter your details.
{lnameRef.current?.focus()}}
+ blurOnSubmit={false}
/>
{emailRef.current?.focus()}}
+ blurOnSubmit={false}
/>
{passwordRef.current?.focus()}}
+ blurOnSubmit={false}
/>
{
const [selectedPage, setSelectedPage] = useState(0);
return (
-
- {selectedPage == 0 && (
-
-
- )}
- {selectedPage == pageIndex.calendar && (
-
- )}
- {selectedPage == pageIndex.chore && (
-
- )}
- {selectedPage == pageIndex.user && (
-
- )}
-
+
+ To-Do Reward Settings
+
+
+
+ }
+ onPress={() => setSelectedPage(pageIndex.chore)}
+ />
+
+
+
+ Cally Privacy Policy
+
+
+
+ }
+ />
+
+ )}
+ {selectedPage == pageIndex.calendar && (
+
+ )}
+ {selectedPage == pageIndex.chore && (
+
+ )}
+ {selectedPage == pageIndex.user && (
+
+ )}
+
);
};
@@ -92,14 +105,14 @@ export default SettingsPage;
const styles = StyleSheet.create({
mainBtn: {
- width: "100%",
+ width: 311,
justifyContent: "flex-start",
marginBottom: 20,
- height: 60,
+ height: 57.61,
},
- label:{
+ label: {
fontFamily: "Poppins_400Regular",
fontSize: 14.71,
- textAlignVertical: 'center'
- }
+ textAlignVertical: "center",
+ },
});
diff --git a/components/pages/settings/user_settings_views/MyGroup.tsx b/components/pages/settings/user_settings_views/MyGroup.tsx
index 20b202f..b815560 100644
--- a/components/pages/settings/user_settings_views/MyGroup.tsx
+++ b/components/pages/settings/user_settings_views/MyGroup.tsx
@@ -1,392 +1,505 @@
import {
- Avatar,
- Button,
- Card,
- Colors,
- Dialog,
- FloatingButton,
- PanningProvider,
- Picker,
- Text,
- TextField,
- TouchableOpacity,
- View,
+ Avatar,
+ Button,
+ Card,
+ Colors,
+ Dialog,
+ FloatingButton,
+ PanningProvider,
+ Picker,
+ Text,
+ TextField,
+ TextFieldRef,
+ TouchableOpacity,
+ View,
} from "react-native-ui-lib";
-import React, {useState} from "react";
-import {ScrollView, StyleSheet} from "react-native";
-import {PickerSingleValue} from "react-native-ui-lib/src/components/picker/types";
-import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
-import {ProfileType} from "@/contexts/AuthContext";
-import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
+import React, { useEffect, useRef, useState } from "react";
+import { Dimensions, ScrollView, StyleSheet } from "react-native";
+import { PickerSingleValue } from "react-native-ui-lib/src/components/picker/types";
+import { useCreateSubUser } from "@/hooks/firebase/useCreateSubUser";
+import { ProfileType } from "@/contexts/AuthContext";
+import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
-import {uuidv4} from "@firebase/util";
+import { uuidv4 } from "@firebase/util";
+import QRIcon from "@/assets/svgs/QRIcon";
+import EmailIcon from "@/assets/svgs/EmailIcon";
+import CircledXIcon from "@/assets/svgs/CircledXIcon";
+import ProfileIcon from "@/assets/svgs/ProfileIcon";
+import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
const MyGroup = () => {
- const [showAddUserDialog, setShowAddUserDialog] = useState(false);
- const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
- const [selectedStatus, setSelectedStatus] = useState<
- string | PickerSingleValue
- >(ProfileType.CHILD);
- const [firstName, setFirstName] = useState("");
- const [lastName, setLastName] = useState("");
- const [email, setEmail] = useState("");
+ const [showAddUserDialog, setShowAddUserDialog] = useState(false);
+ const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
+ const [selectedStatus, setSelectedStatus] = useState<
+ string | PickerSingleValue
+ >(ProfileType.CHILD);
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [email, setEmail] = useState("");
- const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
+ const lNameRef = useRef(null);
+ const emailRef = useRef(null);
- const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
- const {data: familyMembers} = useGetFamilyMembers(true);
+ const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
- const parents =
- familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
- const children =
- familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
- const caregivers =
- familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
- const familyDevices =
- familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ?? [];
+ const { mutateAsync: createSubUser, isLoading, isError } = useCreateSubUser();
+ const { data: familyMembers } = useGetFamilyMembers(true);
- const handleCreateSubUser = async () => {
- if (!firstName || (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)) {
- console.error("First name and last name are required");
- return;
- }
+ const parents =
+ familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
+ const children =
+ familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
+ const caregivers =
+ familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
+ const familyDevices =
+ familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ??
+ [];
- if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
- console.error("Email is required for non-family device users");
- return;
- }
+ const handleCreateSubUser = async () => {
+ if (
+ !firstName ||
+ (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)
+ ) {
+ console.error("First name and last name are required");
+ return;
+ }
- if (email && !email.includes("@")) {
- console.error("Invalid email address");
- return;
- }
+ if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
+ console.error("Email is required for non-family device users");
+ return;
+ }
- const res = await createSubUser({
- firstName,
- lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName,
- email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`,
- password: uuidv4(),
- userType: selectedStatus as ProfileType,
- });
- console.log(res)
+ if (email && !email.includes("@")) {
+ console.error("Invalid email address");
+ return;
+ }
- if (!isError) {
- setShowNewUserInfoDialog(false);
+ const res = await createSubUser({
+ firstName,
+ lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName,
+ email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`,
+ password: uuidv4(),
+ userType: selectedStatus as ProfileType,
+ });
+ console.log(res);
- if(res?.data?.userId) {
- setTimeout(() => {
- setShowQRCodeDialog(res.data.userId)
- }, 500)
- }
- }
+ if (!isError) {
+ setShowNewUserInfoDialog(false);
+ if (res?.data?.userId) {
+ setTimeout(() => {
+ setShowQRCodeDialog(res.data.userId);
+ }, 500);
+ }
+ }
+ };
- };
+ useEffect(() => {
+ setFirstName("");
+ setLastName("");
+ setEmail("");
+ }, [])
+
- return (
-
-
-
- {!parents.length && !children.length && !caregivers.length && (
-
- {isLoading ? "Loading...." : "No user devices added"}
-
- )}
+ return (
+
+
+
+ {!parents.length && !children.length && !caregivers.length && (
+
+ {isLoading ? "Loading...." : "No user devices added"}
+
+ )}
- {(!!parents.length || !!children.length) && (
- <>
-
- Family
-
- {[...parents, ...children]?.map((member, index) => (
-
-
-
-
- {member.firstName} {member.lastName}
-
-
- {member.userType === ProfileType.PARENT
- ? "Admin (You)"
- : "Child"}
-
-
-
-
-
- setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
-
- ))}
- >
- )}
-
- {!!caregivers.length && (
- <>
-
- Caregivers
-
- {caregivers?.map((member) => (
-
-
-
-
- {member.firstName} {member.lastName}
-
-
- Caregiver
-
-
-
- setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
-
- ))}
- >
- )}
-
- {!!familyDevices.length && (
- <>
-
- Family Devices
-
- {familyDevices?.map((member, index) => (
-
-
-
-
- {member.firstName}
-
-
- Family Device
-
-
-
- setShowQRCodeDialog("")} showQRCodeDialog={showQRCodeDialog === member?.uid} userId={member?.uid!}/>
-
- ))}
- >
- )}
-
-
-
- setShowAddUserDialog(true),
- }}
- />
-
-
-
-
-
- );
+ ))}
+ >
+ )}
+
+ {!!caregivers.length && (
+ <>
+
+ Caregivers
+
+ {caregivers?.map((member) => (
+
+
+
+
+ {member.firstName} {member.lastName}
+
+
+ Caregiver
+
+
+
+ setShowQRCodeDialog("")}
+ showQRCodeDialog={showQRCodeDialog === member?.uid}
+ userId={member?.uid!}
+ />
+
+ ))}
+ >
+ )}
+
+ {!!familyDevices.length && (
+ <>
+
+ Family Devices
+
+ {familyDevices?.map((member, index) => (
+
+
+
+ {member.firstName}
+
+ Family Device
+
+
+
+ setShowQRCodeDialog("")}
+ showQRCodeDialog={showQRCodeDialog === member?.uid}
+ userId={member?.uid!}
+ />
+
+ ))}
+ >
+ )}
+
+
+
+ setShowAddUserDialog(true),
+ style: styles.bottomButton,
+ }}
+ />
+
+
+
+
+
+ );
};
const styles = StyleSheet.create({
- card: {
- marginVertical: 15,
- backgroundColor: "white",
- width: "100%",
- borderRadius: 15,
- padding: 20,
- },
- familyCard: {
- marginBottom: 10,
- borderRadius: 10,
- backgroundColor: Colors.white,
- width: "100%",
- },
- inputField: {
- borderRadius: 50,
- paddingVertical: 12,
- paddingHorizontal: 16,
- backgroundColor: Colors.grey80,
- marginBottom: 16,
- borderColor: Colors.grey50,
- borderWidth: 1,
- height: 40,
- },
- picker: {
- borderRadius: 50,
- paddingVertical: 12,
- paddingHorizontal: 16,
- backgroundColor: Colors.grey80,
- marginBottom: 16,
- borderColor: Colors.grey50,
- borderWidth: 1,
- marginTop: -20,
- height: 40,
- },
- label: {
- marginBottom: 5,
- fontSize: 12,
- color: Colors.grey40,
- },
- dialogCard: {
- borderRadius: 10,
- gap: 10,
- },
- subTit: {
- fontFamily: "Manrope_500Medium",
- fontSize: 15,
- },
+ dialogBtn: {
+ height: 47,
+ width: 279,
+ },
+ dialogTitle: { fontFamily: "Manrope_600SemiBold", fontSize: 22 },
+ dialogBackBtn: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 15,
+ color: "#a7a7a7",
+ },
+ card: {
+ marginVertical: 15,
+ backgroundColor: "white",
+ width: "100%",
+ borderRadius: 15,
+ padding: 20,
+ },
+ bottomButton: {
+ position: "absolute",
+ bottom: 80,
+ width: "100%",
+ },
+ familyCard: {
+ marginBottom: 10,
+ borderRadius: 10,
+ backgroundColor: Colors.white,
+ width: "100%",
+ },
+ inputField: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 13,
+ color: "#565656",
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ height: 40,
+ },
+ picker: {
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ marginTop: -20,
+ height: 40,
+ },
+ label: {
+ marginBottom: 5,
+ fontSize: 12,
+ color: Colors.grey40,
+ },
+ dialogCard: {
+ borderRadius: 10,
+ gap: 10,
+ },
+ subTit: {
+ fontFamily: "Manrope_500Medium",
+ fontSize: 15,
+ },
+ dialogBtnLbl: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 15,
+ color: "white",
+ },
+ divider: { height: 0.7, backgroundColor: "#e6e6e6", width: "100%" },
+ jakarta12: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 12,
+ color: "#a1a1a1",
+ },
+ jakarta13: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 13,
+ },
});
export default MyGroup;
diff --git a/components/pages/settings/user_settings_views/UserMenu.tsx b/components/pages/settings/user_settings_views/UserMenu.tsx
index ed01179..74f4bfc 100644
--- a/components/pages/settings/user_settings_views/UserMenu.tsx
+++ b/components/pages/settings/user_settings_views/UserMenu.tsx
@@ -30,7 +30,7 @@ const UserMenu = ({
customContent={
- Show Login QR Code
+ Show Login QR Code
}
diff --git a/components/pages/todos/AddChoreDialog.tsx b/components/pages/todos/AddChoreDialog.tsx
index 8dc80c1..c8f061c 100644
--- a/components/pages/todos/AddChoreDialog.tsx
+++ b/components/pages/todos/AddChoreDialog.tsx
@@ -1,5 +1,5 @@
import { View, Text, Button, Switch } from "react-native-ui-lib";
-import React, { useState } from "react";
+import React, { useRef, useState } from "react";
import PointsSlider from "@/components/shared/PointsSlider";
import { repeatOptions, useToDosContext } from "@/contexts/ToDosContext";
import { Feather, AntDesign, Ionicons } from "@expo/vector-icons";
@@ -14,6 +14,7 @@ import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView
import { Dimensions, StyleSheet } from "react-native";
import DropModalIcon from "@/assets/svgs/DropModalIcon";
import { IToDo } from "@/hooks/firebase/types/todoData";
+import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
interface IAddChoreDialog {
isVisible: boolean;
@@ -111,6 +112,7 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
{
setTodo((oldValue: IToDo) => ({ ...oldValue, title: text }));
@@ -197,25 +199,8 @@ const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
label="Assign"
/>
-
-
-
+
+
Take Turns
From 3f7fc92952cbe1f2fecac791a1e70331d6d871c2 Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sat, 19 Oct 2024 17:20:34 +0200
Subject: [PATCH 2/9] Calendar page refactor
---
app/(auth)/calendar/index.tsx | 15 +-
app/_layout.tsx | 466 +++++----
components/pages/calendar/AddEventDialog.tsx | 322 +++----
components/pages/calendar/CalendarHeader.tsx | 102 ++
components/pages/calendar/CalendarPage.tsx | 211 +----
.../pages/calendar/CalendarViewSwitch.tsx | 156 +--
components/pages/calendar/EditEventDialog.tsx | 581 ++++++------
components/pages/calendar/EventCalendar.tsx | 63 ++
components/pages/calendar/InnerCalendar.tsx | 41 +
.../pages/calendar/ManuallyAddEventModal.tsx | 896 +++++++++---------
components/pages/calendar/atoms.ts | 9 +
components/pages/calendar/constants.ts | 20 +
components/pages/calendar/interfaces.ts | 13 +
components/pages/main/SignInPage.tsx | 30 +-
components/pages/settings/SettingsPage.tsx | 200 ++--
contexts/AuthContext.tsx | 1 +
contexts/CalendarContext.tsx | 194 ----
hooks/firebase/types/eventData.ts | 2 +-
hooks/firebase/useGetEvents.ts | 5 +-
hooks/firebase/useUpdateEvent.ts | 4 +-
ios/cally.xcodeproj/project.pbxproj | 4 +-
21 files changed, 1580 insertions(+), 1755 deletions(-)
create mode 100644 components/pages/calendar/CalendarHeader.tsx
create mode 100644 components/pages/calendar/EventCalendar.tsx
create mode 100644 components/pages/calendar/InnerCalendar.tsx
create mode 100644 components/pages/calendar/atoms.ts
create mode 100644 components/pages/calendar/constants.ts
create mode 100644 components/pages/calendar/interfaces.ts
delete mode 100644 contexts/CalendarContext.tsx
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..9b2e5f8 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,223 @@ 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";
- 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 (__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/components/pages/calendar/AddEventDialog.tsx b/components/pages/calendar/AddEventDialog.tsx
index 4e92c30..e6afdf4 100644
--- a/components/pages/calendar/AddEventDialog.tsx
+++ b/components/pages/calendar/AddEventDialog.tsx
@@ -1,188 +1,172 @@
-import React, { useState } from "react";
-import {
- AntDesign,
- 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 { ManuallyAddEventModal } from "@/components/pages/calendar/ManuallyAddEventModal";
+import React, {useState} from "react";
+import {MaterialIcons,} from "@expo/vector-icons";
+import {Button, Card, Dialog, PanningProvider, Text, View,} from "react-native-ui-lib";
+import {StyleSheet, TouchableOpacity} from "react-native";
import AddChoreDialog from "../todos/AddChoreDialog";
-import { ToDosContextProvider } from "@/contexts/ToDosContext";
+import {ToDosContextProvider} from "@/contexts/ToDosContext";
import UploadImageDialog from "./UploadImageDialog";
import CameraIcon from "@/assets/svgs/CameraIcon";
import CalendarIcon from "@/assets/svgs/CalendarIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
+import {useSetAtom} from "jotai";
+import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
export const AddEventDialog = () => {
- const [show, setShow] = useState(false);
- const [showManualInputModal, setShowManualInputModal] = useState(false);
- const [choreDialogVisible, setChoreDialogVisible] = useState(false);
- const [showUploadDialog, setShowUploadDialog] = useState(false);
+ const [show, setShow] = useState(false);
+ const [choreDialogVisible, setChoreDialogVisible] = useState(false);
+ const [showUploadDialog, setShowUploadDialog] = useState(false);
+ const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
- const handleOpenManualInputModal = () => {
- setShow(false);
- setTimeout(() => {
- setShowManualInputModal(true);
- }, 500);
- };
+ const handleOpenManualInputModal = () => {
+ setShow(false);
+ setTimeout(() => {
+ setSelectedNewEndDate(new Date());
+ }, 500);
+ };
- const handleScanImageDialog = () => {
- setShow(false);
- setTimeout(() => {
- setShowUploadDialog(true);
- }, 100);
- };
+ const handleScanImageDialog = () => {
+ setShow(false);
+ setTimeout(() => {
+ setShowUploadDialog(true);
+ }, 100);
+ };
- return (
-
- <>
- setShow(true)}
- >
-
-
-
- New
-
-
-
+ return (
+
+ <>
+ setShow(true)}
+ >
+
+
+
+ New
+
+
+
-
- );
+ 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}
- >
-
- {
- editEventProps.setIsVisible(false);
- }}
- />
-
- {
- editEventProps.setIsVisible(false);
+ if (!event) return null
+
+ return (
+ setIsVisible(false)}
+ containerStyle={{
+ borderRadius: 10,
+ backgroundColor: "white",
+ width: "100%",
+ alignSelf: "stretch",
+ padding: 0,
+ paddingTop: 4,
+ margin: 0,
}}
- />
-
- {
- try {
- if (event.id) {
- updateEvent(event).then(() => editEventProps.setIsVisible(false));
- }
- } catch (error) {
- console.error(error);
- }
- }}
- />
-
-
- {
- 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, mode) => date.toLocaleTimeString("en-us",
- { hour: "numeric",
- minute: "numeric"
- })}
- mode="time"
- marginR-30
- />
-
-
- {!event.allDay && (
-
-
-
- {
- setEvent((prev) => ({ ...prev, end: date }));
- }}
- />
+ visible={isVisible}
+ >
+
+ {
+ setIsVisible(false);
+ }}
+ />
+
+ {
+ setIsVisible(false);
+ }}
+ />
+
+ {
+ try {
+ if (event.id) {
+ updateEvent(event).then(() => setIsVisible(false));
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }}
+ />
- {
- setEvent((prev) => ({ ...prev, end: date }));
- }}
- dateTimeFormatter={(date, mode) => date.toLocaleTimeString("en-us",
- { hour: "numeric",
- minute: "numeric"
- })}
- mode="time"
- marginR-30
- />
-
- )}
-
-
-
-
-
- Assignees
-
- (
-
- )}
- style={{
- marginLeft: "auto",
- borderRadius: 8,
- backgroundColor: "#ffe8f1",
- borderColor: "#ea156c",
- borderWidth: 1,
- }}
- color="#ea156c"
- label="Assign"
- />
-
-
-
-
-
-
-
-
-
-
- Reminder
-
-
-
- (
-
- )}
- style={{
- marginLeft: "auto",
- borderRadius: 8,
- backgroundColor: "#ffe8f1",
- borderColor: "#ea156c",
- borderWidth: 1,
- }}
- color="#ea156c"
- label="Set Reminder"
- />
-
-
-
-
-
-
-
- Mark as Private
-
-
-
-
- setEvent((prev) => ({ ...prev, private: value }))
- }
- />
-
-
-
-
-
-
-
- Add Details
-
-
-
-
-
- );
+ {
+ 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
+
+ (
+
+ )}
+ style={{
+ marginLeft: "auto",
+ borderRadius: 8,
+ backgroundColor: "#ffe8f1",
+ borderColor: "#ea156c",
+ borderWidth: 1,
+ }}
+ color="#ea156c"
+ label="Assign"
+ />
+
+
+
+
+
+
+
+
+
+
+ Reminder
+
+
+
+ (
+
+ )}
+ style={{
+ marginLeft: "auto",
+ borderRadius: 8,
+ backgroundColor: "#ffe8f1",
+ borderColor: "#ea156c",
+ borderWidth: 1,
+ }}
+ color="#ea156c"
+ label="Set Reminder"
+ />
+
+
+
+
+
+
+
+ Mark as Private
+
+
+
+
+ setEvent((prev) => ({...prev!, private: value}))
+ }
+ />
+
+
+
+
+
+
+
+ Add Details
+
+
+
+
+
+ );
};
export default EditEventDialog;
const styles = StyleSheet.create({
- divider: { height: 1, backgroundColor: "#e4e4e4", marginVertical: 15 },
- gradient: {
- height: "25%",
- position: "absolute",
- bottom: 0,
- width: "100%",
- },
- buttonContainer: {
- position: "absolute",
- bottom: 25,
- width: "100%",
- },
- button: {
- backgroundColor: "rgb(253, 23, 117)",
- paddingVertical: 20,
- },
- topBtn: {
- backgroundColor: "white",
- color: "#05a8b6",
- },
- rotateSwitch: {
- marginLeft: 35,
- marginBottom: 10,
- marginTop: 25,
- },
+ divider: {height: 1, backgroundColor: "#e4e4e4", marginVertical: 15},
+ gradient: {
+ height: "25%",
+ position: "absolute",
+ bottom: 0,
+ width: "100%",
+ },
+ buttonContainer: {
+ position: "absolute",
+ bottom: 25,
+ width: "100%",
+ },
+ button: {
+ backgroundColor: "rgb(253, 23, 117)",
+ paddingVertical: 20,
+ },
+ topBtn: {
+ backgroundColor: "white",
+ color: "#05a8b6",
+ },
+ rotateSwitch: {
+ marginLeft: 35,
+ marginBottom: 10,
+ marginTop: 25,
+ },
});
diff --git a/components/pages/calendar/EventCalendar.tsx b/components/pages/calendar/EventCalendar.tsx
new file mode 100644
index 0000000..98cc566
--- /dev/null
+++ b/components/pages/calendar/EventCalendar.tsx
@@ -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 = 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 (
+ ({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,
+ },
+});
diff --git a/components/pages/calendar/InnerCalendar.tsx b/components/pages/calendar/InnerCalendar.tsx
new file mode 100644
index 0000000..b23a3b3
--- /dev/null
+++ b/components/pages/calendar/InnerCalendar.tsx
@@ -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 (
+ <>
+
+
+ {calendarHeight > 0 && (
+
+ )}
+
+
+
+
+
+
+ >
+ )
+}
\ No newline at end of file
diff --git a/components/pages/calendar/ManuallyAddEventModal.tsx b/components/pages/calendar/ManuallyAddEventModal.tsx
index ed979f0..5e2e748 100644
--- a/components/pages/calendar/ManuallyAddEventModal.tsx
+++ b/components/pages/calendar/ManuallyAddEventModal.tsx
@@ -1,490 +1,482 @@
import {
- Avatar,
- Button,
- ButtonSize,
- Colors,
- DateTimePicker,
- LoaderScreen,
- Modal,
- Picker,
- Switch,
- Text,
- TextField,
- TouchableOpacity,
- View,
+ Button,
+ ButtonSize,
+ Colors,
+ DateTimePicker,
+ LoaderScreen,
+ Modal,
+ Switch,
+ Text,
+ TextField,
+ TouchableOpacity,
+ View,
} from "react-native-ui-lib";
-import { ScrollView } from "react-native-gesture-handler";
-import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { useState } from "react";
-import {
- AntDesign,
- Feather,
- Ionicons,
- 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";
-import { addHours, setDate } from "date-fns";
+import {ScrollView} from "react-native-gesture-handler";
+import {useSafeAreaInsets} from "react-native-safe-area-context";
+import {useState} from "react";
+import {AntDesign, Feather, Ionicons,} from "@expo/vector-icons";
+import {PickerMultiValue} from "react-native-ui-lib/src/components/picker/types";
+import {useCreateEvent} from "@/hooks/firebase/useCreateEvent";
+import {EventData} from "@/hooks/firebase/types/eventData";
+import {addHours} from "date-fns";
import DropModalIcon from "@/assets/svgs/DropModalIcon";
-import { CalendarEvent, useCalendarContext } from "@/contexts/CalendarContext";
-import { repeatOptions } from "@/contexts/ToDosContext";
-import { ImageBackground, StyleSheet } from "react-native";
+import {StyleSheet} from "react-native";
import ClockIcon from "@/assets/svgs/ClockIcon";
import LockIcon from "@/assets/svgs/LockIcon";
import MenuIcon from "@/assets/svgs/MenuIcon";
import CameraIcon from "@/assets/svgs/CameraIcon";
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
+import {useAtom} from "jotai";
+import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
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" },
+ {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,
- initialDate,
-}: {
- show: boolean;
- close: () => void;
- initialDate?: Date;
-}) => {
- const { addEvent } = useCalendarContext();
- const { user } = useAuthContext();
- const insets = useSafeAreaInsets();
+export const ManuallyAddEventModal = () => {
+ const insets = useSafeAreaInsets();
- const [title, setTitle] = useState("");
+ const [selectedNewEventDate, setSelectedNewEndDate] = useAtom(selectedNewEventDateAtom)
- const [isAllDay, setIsAllDay] = useState(false);
- const [isPrivate, setIsPrivate] = useState(false);
- const [startTime, setStartTime] = useState(() => {
- const date = initialDate ?? new Date();
- date.setSeconds(0, 0);
- return date;
- });
- const [endTime, setEndTime] = useState(() => {
- const date = initialDate
- ? addHours(initialDate, 1)
- : addHours(new Date(), 1);
- date.setSeconds(0, 0);
- return date;
- });
-
- const [startDate, setStartDate] = useState(initialDate ?? new Date());
- const [endDate, setEndDate] = useState(initialDate ?? new Date());
-
- const [repeatInterval, setRepeatInterval] = useState([]);
-
- const { mutateAsync: createEvent, isLoading, isError } = useCreateEvent();
-
- const formatDateTime = (date?: Date | string) => {
- if (!date) return undefined;
- return new Date(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.setHours(0, 0, 0, 0));
- finalEndDate = new Date(startDate.setHours(0, 0, 0, 0));
- } else {
- finalStartDate = combineDateAndTime(startDate, startTime);
- finalEndDate = combineDateAndTime(endDate, endTime);
+ const {show, close, initialDate} = {
+ show: !!selectedNewEventDate,
+ close: () => setSelectedNewEndDate(undefined),
+ initialDate: selectedNewEventDate
}
- const eventData: Partial = {
- title: title,
- startDate: finalStartDate,
- endDate: finalEndDate,
- allDay: isAllDay,
+ const [title, setTitle] = useState("");
+
+ const [isAllDay, setIsAllDay] = useState(false);
+ const [isPrivate, setIsPrivate] = useState(false);
+ const [startTime, setStartTime] = useState(() => {
+ const date = initialDate ?? new Date();
+ date.setSeconds(0, 0);
+ return date;
+ });
+ const [endTime, setEndTime] = useState(() => {
+ const date = initialDate
+ ? addHours(initialDate, 1)
+ : addHours(new Date(), 1);
+ date.setSeconds(0, 0);
+ return date;
+ });
+
+ const [startDate, setStartDate] = useState(initialDate ?? new Date());
+ const [endDate, setEndDate] = useState(initialDate ?? new Date());
+
+ const [repeatInterval, setRepeatInterval] = useState([]);
+
+ const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent();
+
+ if (!selectedNewEventDate) return null;
+
+ const formatDateTime = (date?: Date | string) => {
+ if (!date) return undefined;
+ return new Date(date).toLocaleDateString("en-US", {
+ weekday: "long",
+ month: "short",
+ day: "numeric",
+ });
};
- await createEvent(eventData);
+ 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;
+ };
- close();
- };
+ const handleSave = async () => {
+ let finalStartDate: Date;
+ let finalEndDate: Date;
- const getRepeatLabel = () => {
- const selectedDays = repeatInterval;
- const allDays = [
- "sunday",
- "monday",
- "tuesday",
- "wednesday",
- "thursday",
- "friday",
- "saturday",
- ];
- const workDays = ["monday", "tuesday", "wednesday", "thursday", "friday"];
+ if (isAllDay) {
+ finalStartDate = new Date(startDate.setHours(0, 0, 0, 0));
+ finalEndDate = new Date(startDate.setHours(0, 0, 0, 0));
+ } else {
+ finalStartDate = combineDateAndTime(startDate, startTime);
+ finalEndDate = combineDateAndTime(endDate, endTime);
+ }
- const isEveryWorkDay = workDays.every((day) => selectedDays.includes(day));
+ const eventData: Partial = {
+ title: title,
+ startDate: finalStartDate,
+ endDate: finalEndDate,
+ allDay: isAllDay,
+ };
- const isEveryDay = allDays.every((day) => selectedDays.includes(day));
+ await createEvent(eventData);
- 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(", ");
+ 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 (
+
+
+
+ );
}
- };
- if (isLoading && !isError) {
return (
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- Cancel
-
-
-
-
-
- Save
-
-
-
-
- {
- setTitle(text);
- }}
- placeholderTextColor="#2d2d30"
- style={{ fontFamily: "Manrope_500Medium", fontSize: 22 }}
- paddingT-15
- paddingL-30
- returnKeyType="next"
- />
-
-
-
-
-
-
- All day
-
-
-
- setIsAllDay(value)}
- />
-
-
-
-
-
- {
- setStartDate(date);
- }}
- maximumDate={endDate}
- style={{
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 16,
- }}
- marginL-8
- />
-
- setStartTime(date)}
- maximumDate={endTime}
- minuteInterval={5}
- dateTimeFormatter={(date, mode) =>
- date.toLocaleTimeString("en-us", {
- hour: "numeric",
- minute: "numeric",
- })
- }
- mode="time"
+
-
-
- {!isAllDay && (
-
-
-
- {
- setEndDate(date);
- }}
+ >
+
+ >
+
+
+ Cancel
+
+
+
+
+
+ Save
+
+
- setEndTime(date)}
- minimumDate={startTime}
- minuteInterval={5}
- dateTimeFormatter={(date, mode) =>
- date.toLocaleTimeString("en-us", {
- hour: "numeric",
- minute: "numeric",
- })
- }
- mode="time"
- style={{
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 16,
- }}
- marginR-30
+
+ {
+ setTitle(text);
+ }}
+ placeholderTextColor="#2d2d30"
+ style={{fontFamily: "Manrope_500Medium", fontSize: 22}}
+ paddingT-15
+ paddingL-30
+ returnKeyType="next"
+ />
+
+
+
+
+
+
+ All day
+
+
+
+ setIsAllDay(value)}
+ />
+
+
+
+
+
+ {
+ setStartDate(date);
+ }}
+ maximumDate={endDate}
+ style={{
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 16,
+ }}
+ marginL-8
+ />
+
+ setStartTime(date)}
+ maximumDate={endTime}
+ minuteInterval={5}
+ dateTimeFormatter={(date, mode) =>
+ date.toLocaleTimeString("en-us", {
+ hour: "numeric",
+ minute: "numeric",
+ })
+ }
+ mode="time"
+ style={{
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 16,
+ }}
+ marginR-30
+ />
+
+
+ {!isAllDay && (
+
+
+
+ {
+ setEndDate(date);
+ }}
+ style={{
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 16,
+ }}
+ />
+
+ setEndTime(date)}
+ minimumDate={startTime}
+ minuteInterval={5}
+ dateTimeFormatter={(date, mode) =>
+ date.toLocaleTimeString("en-us", {
+ hour: "numeric",
+ minute: "numeric",
+ })
+ }
+ mode="time"
+ style={{
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 16,
+ }}
+ marginR-30
+ />
+
+ )}
+
+
+
+
+
+
+
+ Attendees
+
+ (
+
+ )}
+ style={{
+ marginLeft: "auto",
+ borderRadius: 8,
+ backgroundColor: "#ffe8f1",
+ borderColor: "#ea156c",
+ borderWidth: 1,
+ }}
+ color="#ea156c"
+ label="Add"
+ labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
+ />
+
+
+
+
+
+
+
+
+
+ Reminders
+
+
+
+ (
+
+ )}
+ style={{
+ marginLeft: "auto",
+ borderRadius: 8,
+ backgroundColor: "#ffe8f1",
+ borderColor: "#ea156c",
+ borderWidth: 1,
+ }}
+ labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
+ color="#ea156c"
+ label="Set Reminder"
+ />
+
+
+
+
+
+
+
+ Mark as Private
+
+
+
+ setIsPrivate(value)}
+ />
+
+
+
+
+
+
+
+ Add Details
+
+
+
+
+
+ (
+
+
+
+ )}
/>
-
- )}
-
-
-
-
-
-
-
- Attendees
-
- (
-
- )}
- style={{
- marginLeft: "auto",
- borderRadius: 8,
- backgroundColor: "#ffe8f1",
- borderColor: "#ea156c",
- borderWidth: 1,
- }}
- color="#ea156c"
- label="Add"
- labelStyle={{ fontFamily: "Manrope_600SemiBold", fontSize: 14 }}
- />
-
-
-
-
-
-
-
-
-
- Reminders
-
-
- (
-
- )}
- style={{
- marginLeft: "auto",
- borderRadius: 8,
- backgroundColor: "#ffe8f1",
- borderColor: "#ea156c",
- borderWidth: 1,
- }}
- labelStyle={{ fontFamily: "Manrope_600SemiBold", fontSize: 14 }}
- color="#ea156c"
- label="Set Reminder"
- />
-
-
-
-
-
-
-
- Mark as Private
-
-
-
- setIsPrivate(value)}
- />
-
-
-
-
-
-
-
- Add Details
-
-
-
-
-
- (
-
-
-
- )}
- />
-
-
- );
+
+ );
};
const styles = StyleSheet.create({
- divider: { height: 1, backgroundColor: "#e4e4e4", marginVertical: 15 },
- gradient: {
- height: "25%",
- position: "absolute",
- bottom: 0,
- width: "100%",
- },
- buttonContainer: {
- position: "absolute",
- bottom: 25,
- width: "100%",
- },
- button: {
- backgroundColor: "rgb(253, 23, 117)",
- paddingVertical: 20,
- },
- topBtn: {
- backgroundColor: "white",
- color: "#05a8b6",
- },
- rotateSwitch: {
- marginLeft: 35,
- marginBottom: 10,
- marginTop: 25,
- },
+ divider: {height: 1, backgroundColor: "#e4e4e4", marginVertical: 15},
+ gradient: {
+ height: "25%",
+ position: "absolute",
+ bottom: 0,
+ width: "100%",
+ },
+ buttonContainer: {
+ position: "absolute",
+ bottom: 25,
+ width: "100%",
+ },
+ button: {
+ backgroundColor: "rgb(253, 23, 117)",
+ paddingVertical: 20,
+ },
+ topBtn: {
+ backgroundColor: "white",
+ color: "#05a8b6",
+ },
+ rotateSwitch: {
+ marginLeft: 35,
+ marginBottom: 10,
+ marginTop: 25,
+ },
});
diff --git a/components/pages/calendar/atoms.ts b/components/pages/calendar/atoms.ts
new file mode 100644
index 0000000..6f64eab
--- /dev/null
+++ b/components/pages/calendar/atoms.ts
@@ -0,0 +1,9 @@
+import { atom } from 'jotai';
+import {CalendarEvent} from "@/components/pages/calendar/interfaces";
+
+export const editVisibleAtom = atom(false);
+export const eventForEditAtom = atom(undefined);
+export const isFamilyViewAtom = atom(false);
+export const modeAtom = atom<"week" | "month" | "day">("week");
+export const selectedDateAtom = atom(new Date());
+export const selectedNewEventDateAtom = atom(undefined);
\ No newline at end of file
diff --git a/components/pages/calendar/constants.ts b/components/pages/calendar/constants.ts
new file mode 100644
index 0000000..eb5eefc
--- /dev/null
+++ b/components/pages/calendar/constants.ts
@@ -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",
+];
\ No newline at end of file
diff --git a/components/pages/calendar/interfaces.ts b/components/pages/calendar/interfaces.ts
new file mode 100644
index 0000000..5104bd1
--- /dev/null
+++ b/components/pages/calendar/interfaces.ts
@@ -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;
+}
\ No newline at end of file
diff --git a/components/pages/main/SignInPage.tsx b/components/pages/main/SignInPage.tsx
index 56b9f96..ac2b640 100644
--- a/components/pages/main/SignInPage.tsx
+++ b/components/pages/main/SignInPage.tsx
@@ -1,17 +1,17 @@
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 {StyleSheet} from "react-native";
import Toast from 'react-native-toast-message';
import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
import {Camera, CameraView} from 'expo-camera';
-import {BarCodeScanner} from "expo-barcode-scanner";
-const SignInPage = ({setTab}: { setTab: React.Dispatch> }) => {
+const SignInPage = ({setTab}: {
+ setTab: React.Dispatch>
+}) => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [hasPermission, setHasPermission] = useState(null);
- const [scanned, setScanned] = useState(false);
const [showCameraDialog, setShowCameraDialog] = useState(false);
const {mutateAsync: signIn, error, isError} = useSignIn();
@@ -19,7 +19,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch {
await signIn({email, password});
- if(!isError) {
+ if (!isError) {
Toast.show({
type: "success",
text1: "Login successful!"
@@ -33,10 +33,10 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch {
+ const handleQrCodeScanned = async ({data}: { data: string }) => {
setShowCameraDialog(false);
try {
- await signInWithQrCode({ userId: data });
+ await signInWithQrCode({userId: data});
Toast.show({
type: "success",
text1: "Login successful with QR code!"
@@ -51,9 +51,9 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch void) => {
- const { status } = await Camera.requestCameraPermissionsAsync();
+ const {status} = await Camera.requestCameraPermissionsAsync();
setHasPermission(status === 'granted');
- if(status === 'granted') {
+ if (status === 'granted') {
callback();
}
};
@@ -83,12 +83,12 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch {
getCameraPermissions(() => setShowCameraDialog(true));
- }
- }
+ }
+ }
style={{marginBottom: 20}}
backgroundColor="#fd1775"
/>
- {isError && {`${error}`}}
+ {isError && {`${error?.toString()?.split("]")?.[1]}`}}
@@ -131,7 +131,7 @@ const SignInPage = ({setTab}: { setTab: React.Dispatch
{hasPermission === null ? (
Requesting camera permissions...
@@ -139,7 +139,7 @@ const SignInPage = ({setTab}: { setTab: React.DispatchNo access to camera
) : (
setShowCameraDialog(false)}
backgroundColor="#fd1775"
- style={{ margin: 10 }}
+ style={{margin: 10}}
/>
diff --git a/components/pages/settings/SettingsPage.tsx b/components/pages/settings/SettingsPage.tsx
index 49e1053..09415b8 100644
--- a/components/pages/settings/SettingsPage.tsx
+++ b/components/pages/settings/SettingsPage.tsx
@@ -1,118 +1,118 @@
-import { View, Text, Button } from "react-native-ui-lib";
-import React, { useState } from "react";
-import { StyleSheet } from "react-native";
-import { Entypo, Ionicons, Octicons } from "@expo/vector-icons";
+import {Button, Text, View} from "react-native-ui-lib";
+import React, {useState} from "react";
+import {StyleSheet} from "react-native";
+import {Octicons} from "@expo/vector-icons";
import CalendarSettingsPage from "./CalendarSettingsPage";
import ChoreRewardSettings from "./ChoreRewardSettings";
import UserSettings from "./UserSettings";
-import { AuthContextProvider } from "@/contexts/AuthContext";
import ProfileIcon from "@/assets/svgs/ProfileIcon";
import CalendarIcon from "@/assets/svgs/CalendarIcon";
import PrivacyPolicyIcon from "@/assets/svgs/PrivacyPolicyIcon";
import ArrowRightIcon from "@/assets/svgs/ArrowRightIcon";
const pageIndex = {
- main: 0,
- user: 1,
- calendar: 2,
- chore: 3,
- policy: 4,
+ main: 0,
+ user: 1,
+ calendar: 2,
+ chore: 3,
+ policy: 4,
};
+
const SettingsPage = () => {
- const [selectedPage, setSelectedPage] = useState(0);
- return (
-
- {selectedPage == 0 && (
-
-
-
-
- Manage My Profile
-
-
-
- }
- onPress={() => setSelectedPage(pageIndex.user)}
- />
-
-
-
- Calendar Settings
-
-
-
- }
- onPress={() => {
- setSelectedPage(pageIndex.calendar);
- }}
- />
-
-
-
- To-Do Reward Settings
-
-
-
- }
- onPress={() => setSelectedPage(pageIndex.chore)}
- />
-
-
-
- Cally Privacy Policy
-
-
-
- }
- />
+ const [selectedPage, setSelectedPage] = useState(0);
+ return (
+
+ {selectedPage == 0 && (
+
+
+
+
+ Manage My Profile
+
+
+
+ }
+ onPress={() => setSelectedPage(pageIndex.user)}
+ />
+
+
+
+ Calendar Settings
+
+
+
+ }
+ onPress={() => {
+ setSelectedPage(pageIndex.calendar);
+ }}
+ />
+
+
+
+ To-Do Reward Settings
+
+
+
+ }
+ onPress={() => setSelectedPage(pageIndex.chore)}
+ />
+
+
+
+ Cally Privacy Policy
+
+
+
+ }
+ />
+
+ )}
+ {selectedPage == pageIndex.calendar && (
+
+ )}
+ {selectedPage == pageIndex.chore && (
+
+ )}
+ {selectedPage == pageIndex.user && (
+
+ )}
- )}
- {selectedPage == pageIndex.calendar && (
-
- )}
- {selectedPage == pageIndex.chore && (
-
- )}
- {selectedPage == pageIndex.user && (
-
- )}
-
- );
+ );
};
export default SettingsPage;
const styles = StyleSheet.create({
- mainBtn: {
- width: 311,
- justifyContent: "flex-start",
- marginBottom: 20,
- height: 57.61,
- },
- label: {
- fontFamily: "Poppins_400Regular",
- fontSize: 14.71,
- textAlignVertical: "center",
- },
+ mainBtn: {
+ width: 311,
+ justifyContent: "flex-start",
+ marginBottom: 20,
+ height: 57.61,
+ },
+ label: {
+ fontFamily: "Poppins_400Regular",
+ fontSize: 14.71,
+ textAlignVertical: "center",
+ },
});
diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx
index ce395d6..e6f28cc 100644
--- a/contexts/AuthContext.tsx
+++ b/contexts/AuthContext.tsx
@@ -99,6 +99,7 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
setUser(authUser);
+
if (authUser) {
await refreshProfileData(authUser);
const pushToken = await registerForPushNotificationsAsync();
diff --git a/contexts/CalendarContext.tsx b/contexts/CalendarContext.tsx
deleted file mode 100644
index b084caa..0000000
--- a/contexts/CalendarContext.tsx
+++ /dev/null
@@ -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, id?: number) => void;
-}
-
-// Create the CalendarContext
-const CalendarContext = createContext(
- undefined
-);
-
-// Create a provider component
-export const CalendarProvider: React.FC<{ children: ReactNode }> = ({
- children,
-}) => {
- const [events, setEvents] = useState([
- {
- 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([
- {
- 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, id?: number) => {
- setEvents((prevEvents) =>
- prevEvents.map((event) =>
- event.id === id ? { ...event, ...changes } : event
- )
- );
- };
-
- return (
-
- {children}
-
- );
-};
-
-// 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;
-};
diff --git a/hooks/firebase/types/eventData.ts b/hooks/firebase/types/eventData.ts
index 8fbfd6c..b7c6136 100644
--- a/hooks/firebase/types/eventData.ts
+++ b/hooks/firebase/types/eventData.ts
@@ -10,5 +10,5 @@ export interface EventData {
surpriseEvent?: boolean,
notes?: string,
reminders?: string[]
- id?: string,
+ id?: string | number,
}
\ No newline at end of file
diff --git a/hooks/firebase/useGetEvents.ts b/hooks/firebase/useGetEvents.ts
index 88a6d10..79518c7 100644
--- a/hooks/firebase/useGetEvents.ts
+++ b/hooks/firebase/useGetEvents.ts
@@ -2,9 +2,12 @@ import {useQuery} from "react-query";
import firestore from "@react-native-firebase/firestore";
import {useAuthContext} from "@/contexts/AuthContext";
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 isFamilyView = useAtomValue(isFamilyViewAtom)
return useQuery({
queryKey: ["events", user?.uid, isFamilyView],
diff --git a/hooks/firebase/useUpdateEvent.ts b/hooks/firebase/useUpdateEvent.ts
index 7cf4537..1244026 100644
--- a/hooks/firebase/useUpdateEvent.ts
+++ b/hooks/firebase/useUpdateEvent.ts
@@ -1,10 +1,8 @@
-import {useAuthContext} from "@/contexts/AuthContext";
import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
import {EventData} from "@/hooks/firebase/types/eventData";
export const useUpdateEvent = () => {
- const {user: currentUser} = useAuthContext()
const queryClients = useQueryClient()
return useMutation({
@@ -13,7 +11,7 @@ export const useUpdateEvent = () => {
try {
await firestore()
.collection("Events")
- .doc(eventData.id)
+ .doc(`${eventData.id}`)
.update(eventData);
} catch (e) {
console.error(e)
diff --git a/ios/cally.xcodeproj/project.pbxproj b/ios/cally.xcodeproj/project.pbxproj
index 4c20a6d..aae8a40 100644
--- a/ios/cally.xcodeproj/project.pbxproj
+++ b/ios/cally.xcodeproj/project.pbxproj
@@ -441,7 +441,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = "CallyFamilyPlanner";
+ PRODUCT_NAME = CallyFamilyPlanner;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -472,7 +472,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = "CallyFamilyPlanner";
+ PRODUCT_NAME = CallyFamilyPlanner;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
From 043b80baa951617c54de0684cfe4d4bfc1d8976f Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sat, 19 Oct 2024 18:54:01 +0200
Subject: [PATCH 3/9] Calendar settings page changes
---
.../pages/settings/CalendarSettingsPage.tsx | 867 ++++++++++--------
hooks/firebase/types/profileTypes.ts | 52 +-
hooks/firebase/useUpdateUserData.ts | 39 +-
3 files changed, 522 insertions(+), 436 deletions(-)
diff --git a/components/pages/settings/CalendarSettingsPage.tsx b/components/pages/settings/CalendarSettingsPage.tsx
index 6a55b25..4c36a42 100644
--- a/components/pages/settings/CalendarSettingsPage.tsx
+++ b/components/pages/settings/CalendarSettingsPage.tsx
@@ -1,14 +1,14 @@
-import { AntDesign, Ionicons } from "@expo/vector-icons";
-import React, { useCallback, useEffect, useState } from "react";
-import { Button, Checkbox, Text, View } from "react-native-ui-lib";
-import { ScrollView, StyleSheet } from "react-native";
-import { colorMap } from "@/contexts/SettingsContext";
-import { TouchableOpacity } from "react-native-gesture-handler";
-import { fetchGoogleCalendarEvents } from "@/calendar-integration/google-calendar-utils";
-import { fetchMicrosoftCalendarEvents } from "@/calendar-integration/microsoft-calendar-utils";
-import { useCreateEventFromProvider } from "@/hooks/firebase/useCreateEvent";
-import { useAuthContext } from "@/contexts/AuthContext";
-import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
+import {AntDesign, Ionicons} from "@expo/vector-icons";
+import React, {useCallback, useEffect, useState} from "react";
+import {Button, Checkbox, Text, View} from "react-native-ui-lib";
+import {ScrollView, StyleSheet} from "react-native";
+import {colorMap} from "@/contexts/SettingsContext";
+import {TouchableOpacity} from "react-native-gesture-handler";
+import {fetchGoogleCalendarEvents} from "@/calendar-integration/google-calendar-utils";
+import {fetchMicrosoftCalendarEvents} from "@/calendar-integration/microsoft-calendar-utils";
+import {useCreateEventFromProvider} from "@/hooks/firebase/useCreateEvent";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
import debounce from "debounce";
import AppleIcon from "@/assets/svgs/AppleIcon";
import GoogleIcon from "@/assets/svgs/GoogleIcon";
@@ -16,418 +16,487 @@ import OutlookIcon from "@/assets/svgs/OutlookIcon";
import * as AuthSession from "expo-auth-session";
import * as Google from "expo-auth-session/providers/google";
import * as WebBrowser from "expo-web-browser";
+import {UserProfile} from "@firebase/auth";
const googleConfig = {
- androidClientId:
- "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
- iosClientId:
- "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
- webClientId:
- "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
- scopes: [
- "email",
- "profile",
- "https://www.googleapis.com/auth/calendar.events.owned",
- ],
+ androidClientId:
+ "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
+ iosClientId:
+ "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
+ webClientId:
+ "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com",
+ scopes: [
+ "email",
+ "profile",
+ "https://www.googleapis.com/auth/calendar.events.owned",
+ ],
};
const microsoftConfig = {
- clientId: "13c79071-1066-40a9-9f71-b8c4b138b4af", // Replace with your Microsoft client ID
- redirectUri: AuthSession.makeRedirectUri({ path: "settings" }), // Generate redirect URI automatically for Expo
- scopes: [
- "openid",
- "profile",
- "email",
- "offline_access",
- "Calendars.ReadWrite", // Scope for reading calendar events
- ],
- authorizationEndpoint:
- "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
- tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
+ clientId: "13c79071-1066-40a9-9f71-b8c4b138b4af",
+ redirectUri: AuthSession.makeRedirectUri({path: "settings"}),
+ scopes: [
+ "openid",
+ "profile",
+ "email",
+ "offline_access",
+ "Calendars.ReadWrite",
+ "User.Read"
+ ],
+ authorizationEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
+ tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
};
const CalendarSettingsPage = (props: {
- setSelectedPage: (page: number) => void;
+ setSelectedPage: (page: number) => void;
}) => {
- const [startDate, setStartDate] = useState(false);
- const { profileData } = useAuthContext();
+ const [startDate, setStartDate] = useState(false);
+ const {profileData} = useAuthContext();
- const [selectedColor, setSelectedColor] = useState(
- profileData?.eventColor ?? colorMap.pink
- );
- const [previousSelectedColor, setPreviousSelectedColor] = useState(
- profileData?.eventColor ?? colorMap.pink
- );
-
- const { mutateAsync: createEventFromProvider } = useCreateEventFromProvider();
- const { mutateAsync: updateUserData } = useUpdateUserData();
-
- WebBrowser.maybeCompleteAuthSession();
- const [request, response, promptAsync] = Google.useAuthRequest(googleConfig);
-
- useEffect(() => {
- signInWithGoogle();
- }, [response]);
-
- const fetchAndSaveGoogleEvents = () => {
- console.log("fetch");
- const timeMin = new Date(new Date().setHours(0, 0, 0, 0));
- const timeMax = new Date(
- new Date(new Date().setHours(0, 0, 0, 0)).setDate(timeMin.getDate() + 30)
+ const [selectedColor, setSelectedColor] = useState(
+ profileData?.eventColor ?? colorMap.pink
+ );
+ const [previousSelectedColor, setPreviousSelectedColor] = useState(
+ profileData?.eventColor ?? colorMap.pink
);
- fetchGoogleCalendarEvents(
- profileData?.googleToken,
- timeMin.toISOString().slice(0, -5) + "Z",
- timeMax.toISOString().slice(0, -5) + "Z"
- ).then((response) => {
- response?.forEach((item) => saveData(item));
- });
- };
+ const {mutateAsync: createEventFromProvider} = useCreateEventFromProvider();
+ const {mutateAsync: updateUserData} = useUpdateUserData();
- async function saveData(item: any) {
- await createEventFromProvider(item);
- }
+ WebBrowser.maybeCompleteAuthSession();
+ const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
- const fetchAndSaveMicrosoftEvents = () => {
- const startDateTime = new Date(new Date().setHours(0, 0, 0, 0));
- const endDateTime = new Date(
- new Date(new Date().setHours(0, 0, 0, 0)).setDate(
- startDateTime.getDate() + 30
- )
+ useEffect(() => {
+ signInWithGoogle();
+ }, [response]);
+
+ const fetchAndSaveGoogleEvents = () => {
+ console.log("fetch");
+ const timeMin = new Date(new Date().setHours(0, 0, 0, 0));
+ const timeMax = new Date(
+ new Date(new Date().setHours(0, 0, 0, 0)).setDate(timeMin.getDate() + 30)
+ );
+
+ fetchGoogleCalendarEvents(
+ profileData?.googleToken,
+ timeMin.toISOString().slice(0, -5) + "Z",
+ timeMax.toISOString().slice(0, -5) + "Z"
+ ).then((response) => {
+ response?.forEach((item) => saveData(item));
+ });
+ };
+
+ async function saveData(item: any) {
+ await createEventFromProvider(item);
+ }
+
+ const fetchAndSaveMicrosoftEvents = () => {
+ const startDateTime = new Date(new Date().setHours(0, 0, 0, 0));
+ const endDateTime = new Date(
+ new Date(new Date().setHours(0, 0, 0, 0)).setDate(
+ startDateTime.getDate() + 30
+ )
+ );
+
+ fetchMicrosoftCalendarEvents(
+ profileData?.microsoftToken,
+ startDateTime.toISOString().slice(0, -5) + "Z",
+ endDateTime.toISOString().slice(0, -5) + "Z"
+ ).then((response) => {
+ console.log(response);
+ response?.forEach((item) => saveData(item));
+ });
+ };
+
+ const signInWithGoogle = async () => {
+ try {
+ if (response?.type === "success") {
+ const accessToken = response.authentication?.accessToken;
+
+ const userInfoResponse = await fetch(
+ "https://www.googleapis.com/oauth2/v3/userinfo",
+ {
+ headers: {Authorization: `Bearer ${accessToken}`},
+ }
+ );
+
+ const userInfo = await userInfoResponse.json();
+ const googleMail = userInfo.email;
+
+ await updateUserData({
+ newUserData: {googleToken: accessToken, googleMail: googleMail},
+ });
+ }
+ } catch (error) {
+ console.error("Error during Google sign-in:", error);
+ }
+ };
+
+ const handleMicrosoftSignIn = async () => {
+ try {
+ console.log("Starting Microsoft sign-in...");
+
+ const authRequest = new AuthSession.AuthRequest({
+ clientId: microsoftConfig.clientId,
+ scopes: microsoftConfig.scopes,
+ redirectUri: microsoftConfig.redirectUri,
+ responseType: AuthSession.ResponseType.Code,
+ usePKCE: true, // Enable PKCE
+ });
+
+ console.log("Auth request created:", authRequest);
+
+ const authResult = await authRequest.promptAsync({
+ authorizationEndpoint: microsoftConfig.authorizationEndpoint,
+ });
+
+ console.log("Auth result:", authResult);
+
+ if (authResult.type === "success" && authResult.params?.code) {
+ const code = authResult.params.code;
+ console.log("Authorization code received:", code);
+
+ // Exchange authorization code for tokens
+ const tokenResponse = await fetch(microsoftConfig.tokenEndpoint, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: `client_id=${
+ microsoftConfig.clientId
+ }&redirect_uri=${encodeURIComponent(
+ microsoftConfig.redirectUri
+ )}&grant_type=authorization_code&code=${code}&code_verifier=${
+ authRequest.codeVerifier
+ }&scope=${encodeURIComponent(
+ "https://graph.microsoft.com/Calendars.ReadWrite offline_access User.Read"
+ )}`,
+ });
+
+ console.log("Token response status:", tokenResponse.status);
+
+ if (!tokenResponse.ok) {
+ const errorText = await tokenResponse.text();
+ console.error("Token exchange failed:", errorText);
+ return;
+ }
+
+ const tokenData = await tokenResponse.json();
+ console.log("Token data received:", tokenData);
+
+ if (tokenData?.access_token) {
+ console.log("Access token received, fetching user info...");
+
+ // Fetch user info from Microsoft Graph API to get the email
+ const userInfoResponse = await fetch("https://graph.microsoft.com/v1.0/me", {
+ headers: {
+ Authorization: `Bearer ${tokenData.access_token}`,
+ },
+ });
+
+ const userInfo = await userInfoResponse.json();
+ console.log("User info received:", userInfo);
+
+ if (userInfo.error) {
+ console.error("Error fetching user info:", userInfo.error);
+ } else {
+ const outlookMail = userInfo.mail || userInfo.userPrincipalName;
+
+ // Update user data with Microsoft token and email
+ await updateUserData({
+ newUserData: {microsoftToken: tokenData.access_token, outlookMail: outlookMail},
+ });
+
+ console.log("User data updated successfully.");
+ }
+ }
+ } else {
+ console.warn("Authentication was not successful:", authResult);
+ }
+ } catch (error) {
+ console.error("Error during Microsoft sign-in:", error);
+ }
+ };
+
+ const debouncedUpdateUserData = useCallback(
+ debounce(async (color: string) => {
+ try {
+ await updateUserData({
+ newUserData: {
+ eventColor: color,
+ },
+ });
+ } catch (error) {
+ console.error("Failed to update color:", error);
+ setSelectedColor(previousSelectedColor);
+ }
+ }, 500),
+ []
);
- fetchMicrosoftCalendarEvents(
- profileData?.microsoftToken,
- startDateTime.toISOString().slice(0, -5) + "Z",
- endDateTime.toISOString().slice(0, -5) + "Z"
- ).then((response) => {
- console.log(response);
- response?.forEach((item) => saveData(item));
- });
- };
+ const handleChangeColor = (color: string) => {
+ setPreviousSelectedColor(selectedColor);
+ setSelectedColor(color);
+ debouncedUpdateUserData(color);
+ };
- const signInWithGoogle = async () => {
- try {
- // Attempt to retrieve user information from AsyncStorage
- if (response?.type === "success") {
- console.log(response.authentication);
- await updateUserData({
- newUserData: { googleToken: response.authentication?.accessToken },
- });
- }
- } catch (error) {
- // Handle any errors that occur during AsyncStorage retrieval or other operations
- console.error("Error retrieving user data from AsyncStorage:", error);
- }
- };
-
- const handleMicrosoftSignIn = async () => {
- try {
- console.log("Starting Microsoft sign-in...");
-
- const authRequest = new AuthSession.AuthRequest({
- clientId: microsoftConfig.clientId,
- scopes: microsoftConfig.scopes,
- redirectUri: microsoftConfig.redirectUri,
- responseType: AuthSession.ResponseType.Code,
- usePKCE: true, // Enable PKCE
- });
-
- console.log("Auth request created:", authRequest);
-
- const authResult = await authRequest.promptAsync({
- authorizationEndpoint: microsoftConfig.authorizationEndpoint,
- });
-
- console.log("Auth result:", authResult);
-
- if (authResult.type === "success" && authResult.params?.code) {
- const code = authResult.params.code;
- console.log("Authorization code received:", code);
-
- // Exchange authorization code for tokens
- const tokenResponse = await fetch(microsoftConfig.tokenEndpoint, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded",
- },
- body: `client_id=${
- microsoftConfig.clientId
- }&redirect_uri=${encodeURIComponent(
- microsoftConfig.redirectUri
- )}&grant_type=authorization_code&code=${code}&code_verifier=${
- authRequest.codeVerifier
- }&scope=${encodeURIComponent(
- "https://graph.microsoft.com/Calendars.ReadWrite offline_access"
- )}`,
- });
-
- console.log("Token response status:", tokenResponse.status);
-
- if (!tokenResponse.ok) {
- console.error("Token exchange failed:", await tokenResponse.text());
- return;
+ const clearToken = async (provider: "google" | "outlook" | "apple") => {
+ const newUserData: Partial = {};
+ if (provider === "google") {
+ newUserData.googleToken = null;
+ newUserData.googleMail = null;
+ } else if (provider === "outlook") {
+ newUserData.microsoftToken = null;
+ newUserData.outlookMail = null;
+ } else if (provider === "apple") {
+ newUserData.appleToken = null;
+ newUserData.appleMail = null;
}
+ await updateUserData({newUserData});
+ };
- const tokenData = await tokenResponse.json();
- console.log("Token data received:", tokenData);
-
- if (tokenData?.id_token) {
- console.log("ID token received, updating user data...");
- await updateUserData({
- newUserData: { microsoftToken: tokenData.access_token },
- });
- console.log("User data updated successfully.");
- }
- } else {
- console.warn("Authentication was not successful:", authResult);
- }
- } catch (error) {
- console.error("Error during Microsoft sign-in:", error);
- }
- };
-
- const debouncedUpdateUserData = useCallback(
- debounce(async (color: string) => {
- try {
- await updateUserData({
- newUserData: {
- eventColor: color,
- },
- });
- } catch (error) {
- console.error("Failed to update color:", error);
- setSelectedColor(previousSelectedColor);
- }
- }, 500),
- []
- );
-
- const handleChangeColor = (color: string) => {
- setPreviousSelectedColor(selectedColor);
- setSelectedColor(color);
- debouncedUpdateUserData(color);
- };
-
- return (
-
-
- props.setSelectedPage(0)}>
-
-
-
- Return to main settings
-
-
-
- Calendar settings
-
-
- Event Color Preference
-
-
- handleChangeColor(colorMap.pink)}>
-
- {selectedColor == colorMap.pink && (
-
- )}
-
-
- handleChangeColor(colorMap.orange)}
- >
-
- {selectedColor == colorMap.orange && (
-
- )}
-
-
- handleChangeColor(colorMap.green)}>
-
- {selectedColor == colorMap.green && (
-
- )}
-
-
- handleChangeColor(colorMap.teal)}>
-
- {selectedColor == colorMap.teal && (
-
- )}
-
-
- handleChangeColor(colorMap.purple)}
- >
-
- {selectedColor == colorMap.purple && (
-
- )}
-
-
-
-
-
- Weekly Start Date
-
- setStartDate(true)}
- />
-
- Sundays
-
- {" "}
- (default)
-
-
-
-
- setStartDate(false)}
- />
-
- Mondays
-
-
-
-
- Add Calendar
-
-
- promptAsync()}
- label="Connect Google"
- labelStyle={styles.addCalLbl}
- iconSource={() => (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
-
-
- Connected Calendars
-
-
-
- (
-
-
+ return (
+
+
+ props.setSelectedPage(0)}>
+
+
+
+ Return to main settings
+
+
+
+ Calendar settings
+
+
+ Event Color Preference
+
+
+ handleChangeColor(colorMap.pink)}>
+
+ {selectedColor == colorMap.pink && (
+
+ )}
+
+
+ handleChangeColor(colorMap.orange)}
+ >
+
+ {selectedColor == colorMap.orange && (
+
+ )}
+
+
+ handleChangeColor(colorMap.green)}>
+
+ {selectedColor == colorMap.green && (
+
+ )}
+
+
+ handleChangeColor(colorMap.teal)}>
+
+ {selectedColor == colorMap.teal && (
+
+ )}
+
+
+ handleChangeColor(colorMap.purple)}
+ >
+
+ {selectedColor == colorMap.purple && (
+
+ )}
+
+
+
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- (
-
-
+
+ Weekly Start Date
+
+ setStartDate(true)}
+ />
+
+ Sundays
+
+ {" "}
+ (default)
+
+
+
+
+ setStartDate(false)}
+ />
+
+ Mondays
+
+
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
-
-
-
-
- );
+
+ Add Calendar
+
+
+ !profileData?.googleToken ? promptAsync() : clearToken("google")}
+ label={profileData?.googleToken ? `Disconnect ${profileData.googleMail}` : "Connect Google"}
+ labelStyle={styles.addCalLbl}
+ labelProps={{
+ numberOfLines:2
+ }}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ !profileData?.microsoftToken ? handleMicrosoftSignIn() : clearToken("outlook")}
+ label={profileData?.microsoftToken ? `Disconnect ${profileData.outlookMail}` : "Connect Outlook"}
+ labelStyle={styles.addCalLbl}
+ labelProps={{
+ numberOfLines:2
+ }}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+
+ {(profileData?.googleMail || profileData?.outlookMail || profileData?.appleMail) && (
+ <>
+
+ Connected Calendars
+
+
+
+
+ {!!profileData?.googleMail && (
+ (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )}
+
+ {!!profileData?.outlookMail && (
+ (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )}
+
+
+ >
+ )}
+
+
+ );
};
const styles = StyleSheet.create({
- addCalBtn: {
- backgroundColor: "#ffffff",
- marginBottom: 15,
- justifyContent: "flex-start",
- paddingLeft: 25,
- },
- backBtn: {
- backgroundColor: "red",
- marginLeft: -2,
- justifyContent: "flex-start",
- },
- card: {
- backgroundColor: "white",
- width: "100%",
- padding: 20,
- paddingBottom: 30,
- marginTop: 20,
- borderRadius: 12,
- },
- colorBox: {
- aspectRatio: 1,
- justifyContent: "center",
- alignItems: "center",
- width: 51,
- borderRadius: 12,
- },
- checkbox: {
- borderRadius: 50,
- },
- addCalLbl: {
- fontSize: 16,
- fontFamily: "PlusJakartaSan_500Medium",
- },
- subTitle: {
- fontFamily: "Manrope_600SemiBold",
- fontSize: 18,
- },
- cardTitle: {
- fontFamily: "Manrope_500Medium",
- fontSize: 15,
- },
+ addCalBtn: {
+ backgroundColor: "#ffffff",
+ marginBottom: 15,
+ justifyContent: "flex-start",
+ paddingLeft: 25,
+ },
+ backBtn: {
+ backgroundColor: "red",
+ marginLeft: -2,
+ justifyContent: "flex-start",
+ },
+ card: {
+ backgroundColor: "white",
+ width: "100%",
+ padding: 20,
+ paddingBottom: 30,
+ marginTop: 20,
+ borderRadius: 12,
+ },
+ colorBox: {
+ aspectRatio: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ width: 51,
+ borderRadius: 12,
+ },
+ checkbox: {
+ borderRadius: 50,
+ },
+ addCalLbl: {
+ fontSize: 16,
+ fontFamily: "PlusJakartaSan_500Medium",
+ flexWrap: "wrap",
+ width: "75%",
+ textAlign: "left",
+ lineHeight: 20,
+ overflow: "visible"
+ },
+ subTitle: {
+ fontFamily: "Manrope_600SemiBold",
+ fontSize: 18,
+ },
+ cardTitle: {
+ fontFamily: "Manrope_500Medium",
+ fontSize: 15,
+ },
});
export default CalendarSettingsPage;
diff --git a/hooks/firebase/types/profileTypes.ts b/hooks/firebase/types/profileTypes.ts
index 70d9eb1..a73b9e0 100644
--- a/hooks/firebase/types/profileTypes.ts
+++ b/hooks/firebase/types/profileTypes.ts
@@ -1,39 +1,43 @@
-import { ProfileType } from "@/contexts/AuthContext";
+import {ProfileType} from "@/contexts/AuthContext";
export interface User {
- uid: string;
- email: string | null;
+ uid: string;
+ email: string | null;
}
export interface UserProfile {
- userType: ProfileType;
- firstName: string;
- lastName: string;
- childrenIds?: string[];
- birthday?: Date;
- parentId?: string;
- contact?: string;
- email: string;
- password: string;
- familyId?: string;
- uid?: string;
- googleToken?: string;
- microsoftToken?: string;
- eventColor?: string
+ userType: ProfileType;
+ firstName: string;
+ lastName: string;
+ childrenIds?: string[];
+ birthday?: Date;
+ parentId?: string;
+ contact?: string;
+ email: string;
+ password: string;
+ familyId?: string;
+ uid?: string;
+ googleToken?: string | null;
+ microsoftToken?: string | null;
+ appleToken?: string | null;
+ eventColor?: string | null;
+ googleMail?: string | null;
+ outlookMail?: string | null;
+ appleMail?: string | null;
}
export interface ParentProfile extends UserProfile {
- userType: ProfileType.PARENT;
- childrenIds: string[];
+ userType: ProfileType.PARENT;
+ childrenIds: string[];
}
export interface ChildProfile extends UserProfile {
- userType: ProfileType.CHILD;
- birthday: Date;
- parentId: string;
+ userType: ProfileType.CHILD;
+ birthday: Date;
+ parentId: string;
}
export interface CaregiverProfile extends UserProfile {
- userType: ProfileType.CAREGIVER;
- contact: string;
+ userType: ProfileType.CAREGIVER;
+ contact: string;
}
diff --git a/hooks/firebase/useUpdateUserData.ts b/hooks/firebase/useUpdateUserData.ts
index 266be32..66596f3 100644
--- a/hooks/firebase/useUpdateUserData.ts
+++ b/hooks/firebase/useUpdateUserData.ts
@@ -1,33 +1,46 @@
-import {useAuthContext} from "@/contexts/AuthContext";
-import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
-import {UserProfile} from "@/hooks/firebase/types/profileTypes";
-import {FirebaseAuthTypes} from "@react-native-firebase/auth";
+import { FirebaseAuthTypes } from "@react-native-firebase/auth";
+import { useMutation, useQueryClient } from "react-query";
+import { useAuthContext } from "@/contexts/AuthContext";
+import { UserProfile } from "@/hooks/firebase/types/profileTypes";
export const useUpdateUserData = () => {
- const {user: currentUser, refreshProfileData} = useAuthContext();
- const queryClient = useQueryClient()
+ const { user: currentUser, refreshProfileData } = useAuthContext();
+ const queryClient = useQueryClient();
return useMutation({
mutationKey: ["updateUserData"],
- mutationFn: async ({newUserData, customUser}: {newUserData: Partial, customUser?: FirebaseAuthTypes.User }) => {
+ mutationFn: async ({
+ newUserData,
+ customUser,
+ }: {
+ newUserData: Partial;
+ customUser?: FirebaseAuthTypes.User;
+ }) => {
console.log("Mutation function called with data:", { newUserData, customUser });
const user = currentUser ?? customUser;
if (user) {
console.log("Updating user data for UID:", user.uid);
+
try {
- console.log("New user data:", newUserData);
+ const updatedUserData = Object.fromEntries(
+ Object.entries(newUserData).map(([key, value]) =>
+ [key, value === null ? firestore.FieldValue.delete() : value]
+ )
+ );
+
+ console.log("Updated user data with deletions:", updatedUserData);
await firestore()
.collection("Profiles")
.doc(user.uid)
- .update(newUserData);
+ .update(updatedUserData);
console.log("User data updated successfully, fetching updated profile...");
- await refreshProfileData()
+ await refreshProfileData();
console.log("Profile data updated in context.");
} catch (e) {
@@ -38,7 +51,7 @@ export const useUpdateUserData = () => {
}
},
onSuccess: () => {
- queryClient.invalidateQueries("events")
- }
+ queryClient.invalidateQueries("events");
+ },
});
-};
+};
\ No newline at end of file
From 00b6225a1cf21038a32616715d8e71f818c45f93 Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sat, 19 Oct 2024 19:55:46 +0200
Subject: [PATCH 4/9] Syncing improvement
---
calendar-integration/google-calendar-utils.js | 3 +-
.../microsoft-calendar-utils.js | 3 +-
components/pages/calendar/EventCalendar.tsx | 2 +
.../pages/settings/CalendarSettingsPage.tsx | 78 +++++++++++--------
hooks/firebase/useCreateEvent.ts | 57 +++++++-------
5 files changed, 80 insertions(+), 63 deletions(-)
diff --git a/calendar-integration/google-calendar-utils.js b/calendar-integration/google-calendar-utils.js
index c7d332c..c91dcf8 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, 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,7 @@ export async function fetchGoogleCalendarEvents(token, startDate, endDate) {
startDate: startDateTime,
endDate: endDateTime,
allDay: isAllDay,
+ email
};
googleEvents.push(googleEvent);
});
diff --git a/calendar-integration/microsoft-calendar-utils.js b/calendar-integration/microsoft-calendar-utils.js
index b459a29..cbf5d66 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, startDate, endDate) {
const response = await fetch(
`https://graph.microsoft.com/v1.0/me/calendar/calendarView?startDateTime=${startDate}&endDateTime=${endDate}`,
{
@@ -34,6 +34,7 @@ export async function fetchMicrosoftCalendarEvents(token, startDate, endDate) {
startDate: startDateTime,
endDate: endDateTime,
allDay: item.isAllDay,
+ email
};
diff --git a/components/pages/calendar/EventCalendar.tsx b/components/pages/calendar/EventCalendar.tsx
index 98cc566..9f34d54 100644
--- a/components/pages/calendar/EventCalendar.tsx
+++ b/components/pages/calendar/EventCalendar.tsx
@@ -23,6 +23,8 @@ export const EventCalendar: React.FC = memo(({calendarHeight
const setEventForEdit = useSetAtom(eventForEditAtom)
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
+ console.log("Events: ", events)
+
return (
{
- console.log("fetch");
- const timeMin = new Date(new Date().setHours(0, 0, 0, 0));
- const timeMax = new Date(
- new Date(new Date().setHours(0, 0, 0, 0)).setDate(timeMin.getDate() + 30)
- );
+ const fetchAndSaveGoogleEvents = async (token?: string, email?: string) => {
+ console.log("Fetching Google Calendar events...");
+ const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
+ const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
+ console.log("Token: ", token ?? profileData?.googleToken)
fetchGoogleCalendarEvents(
- profileData?.googleToken,
+ token ?? profileData?.googleToken,
+ email ?? profileData?.googleMail,
timeMin.toISOString().slice(0, -5) + "Z",
timeMax.toISOString().slice(0, -5) + "Z"
- ).then((response) => {
- response?.forEach((item) => saveData(item));
+ ).then(async (response) => {
+ console.log("Google Calendar events fetched:", response);
+ const items = response?.map((item) => {
+ if (item.allDay) {
+ item.startDate = new Date(new Date(item.startDate).setHours(0, 0, 0, 0))
+ item.endDate = item.startDate
+ }
+ return item;
+ }) || [];
+ await createEventsFromProvider(items);
+ }).catch((error) => {
+ console.error("Error fetching Google Calendar events:", error);
});
};
- async function saveData(item: any) {
- await createEventFromProvider(item);
- }
+ const fetchAndSaveMicrosoftEvents = async (token?: string, email?: string) => {
+ const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
+ const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
- const fetchAndSaveMicrosoftEvents = () => {
- const startDateTime = new Date(new Date().setHours(0, 0, 0, 0));
- const endDateTime = new Date(
- new Date(new Date().setHours(0, 0, 0, 0)).setDate(
- startDateTime.getDate() + 30
- )
- );
-
- fetchMicrosoftCalendarEvents(
- profileData?.microsoftToken,
- startDateTime.toISOString().slice(0, -5) + "Z",
- endDateTime.toISOString().slice(0, -5) + "Z"
- ).then((response) => {
+ console.log("Token: ", token ?? profileData?.microsoftToken)
+ fetchMicrosoftCalendarEvents(
+ token ?? profileData?.microsoftToken,
+ email ?? profileData?.outlookMail,
+ timeMin.toISOString().slice(0, -5) + "Z",
+ timeMax.toISOString().slice(0, -5) + "Z"
+ ).then(async (response) => {
console.log(response);
- response?.forEach((item) => saveData(item));
+ const items = response ?? [];
+ await createEventsFromProvider(items);
});
};
@@ -126,6 +131,8 @@ const CalendarSettingsPage = (props: {
await updateUserData({
newUserData: {googleToken: accessToken, googleMail: googleMail},
});
+
+ await fetchAndSaveGoogleEvents(accessToken, googleMail)
}
} catch (error) {
console.error("Error during Google sign-in:", error);
@@ -207,6 +214,7 @@ const CalendarSettingsPage = (props: {
newUserData: {microsoftToken: tokenData.access_token, outlookMail: outlookMail},
});
+ await fetchAndSaveMicrosoftEvents(tokenData.access_token, outlookMail)
console.log("User data updated successfully.");
}
}
@@ -257,7 +265,7 @@ const CalendarSettingsPage = (props: {
return (
-
+
props.setSelectedPage(0)}>
(
@@ -374,7 +382,7 @@ const CalendarSettingsPage = (props: {
label="Connect Apple"
labelStyle={styles.addCalLbl}
labelProps={{
- numberOfLines:2
+ numberOfLines: 2
}}
iconSource={() => (
@@ -390,7 +398,7 @@ const CalendarSettingsPage = (props: {
label={profileData?.microsoftToken ? `Disconnect ${profileData.outlookMail}` : "Connect Outlook"}
labelStyle={styles.addCalLbl}
labelProps={{
- numberOfLines:2
+ numberOfLines: 2
}}
iconSource={() => (
@@ -412,9 +420,10 @@ const CalendarSettingsPage = (props: {
{!!profileData?.googleMail && (
fetchAndSaveGoogleEvents()}
label={`Sync ${profileData?.googleMail}`}
labelStyle={styles.addCalLbl}
+ labelProps={{numberOfLines: 2}}
iconSource={() => (
@@ -428,9 +437,10 @@ const CalendarSettingsPage = (props: {
{!!profileData?.outlookMail && (
fetchAndSaveMicrosoftEvents()}
label={`Sync ${profileData?.outlookMail}`}
labelStyle={styles.addCalLbl}
+ labelProps={{numberOfLines: 2}}
iconSource={() => (
diff --git a/hooks/firebase/useCreateEvent.ts b/hooks/firebase/useCreateEvent.ts
index 09eceaf..f17a042 100644
--- a/hooks/firebase/useCreateEvent.ts
+++ b/hooks/firebase/useCreateEvent.ts
@@ -1,7 +1,7 @@
-import { useAuthContext } from "@/contexts/AuthContext";
-import { useMutation, useQueryClient } from "react-query";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
-import { EventData } from "@/hooks/firebase/types/eventData";
+import {EventData} from "@/hooks/firebase/types/eventData";
export const useCreateEvent = () => {
const {user: currentUser, profileData} = useAuthContext()
@@ -11,7 +11,6 @@ export const useCreateEvent = () => {
mutationKey: ["createEvent"],
mutationFn: async (eventData: Partial) => {
try {
- console.log("CALLLLL")
await firestore()
.collection("Events")
.add({...eventData, creatorId: currentUser?.uid, familyId: profileData?.familyId})
@@ -25,37 +24,41 @@ export const useCreateEvent = () => {
})
}
-export const useCreateEventFromProvider = () => {
- const {user: currentUser} = useAuthContext()
- const queryClients = useQueryClient()
+export const useCreateEventsFromProvider = () => {
+ const {user: currentUser} = useAuthContext();
+ const queryClient = useQueryClient();
return useMutation({
- mutationKey: ["createEventFromProvider"],
- mutationFn: async (eventData: Partial) => {
+ mutationKey: ["createEventsFromProvider"],
+ mutationFn: async (eventDataArray: Partial[]) => {
try {
- const snapshot = await firestore()
- .collection("Events")
- .where("id", "==", eventData.id)
- .get();
+ for (const eventData of eventDataArray) {
+ console.log("Processing EventData: ", eventData);
- if (snapshot.empty) {
- await firestore()
+ const snapshot = await firestore()
.collection("Events")
- .add({...eventData, creatorId: currentUser?.uid})
- } else {
- console.log("ENTER HERE")
- const docId = snapshot.docs[0].id;
- await firestore()
- .collection("Events")
- .doc(docId)
- .update({...eventData, creatorId: currentUser?.uid});
+ .where("id", "==", eventData.id)
+ .get();
+
+ if (snapshot.empty) {
+ await firestore()
+ .collection("Events")
+ .add({...eventData, creatorId: currentUser?.uid});
+ } else {
+ console.log("Event already exists, updating...");
+ const docId = snapshot.docs[0].id;
+ await firestore()
+ .collection("Events")
+ .doc(docId)
+ .set({...eventData, creatorId: currentUser?.uid}, {merge: true});
+ }
}
} catch (e) {
- console.error(e)
+ console.error("Error creating/updating events: ", e);
}
},
onSuccess: () => {
- queryClients.invalidateQueries("events")
+ queryClient.invalidateQueries("events");
}
- })
-}
\ No newline at end of file
+ });
+};
\ No newline at end of file
From 3653400a9267e7b4d331f9954fd5f92ea0cd07a2 Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sat, 19 Oct 2024 22:56:55 +0200
Subject: [PATCH 5/9] Apple calendar sync, timezone, first day of week
additions
---
android/app/src/main/AndroidManifest.xml | 4 +-
android/app/src/main/res/values/strings.xml | 2 +-
android/settings.gradle | 2 +-
app.json | 17 +-
calendar-integration/apple-calendar-utils.js | 41 +
calendar-integration/google-calendar-utils.js | 3 +-
.../microsoft-calendar-utils.js | 3 +-
components/pages/calendar/EventCalendar.tsx | 3 +
components/pages/calendar/InnerCalendar.tsx | 2 +-
.../pages/settings/CalendarSettingsPage.tsx | 243 +++--
.../settings/user_settings_views/MyGroup.tsx | 985 +++++++++---------
.../user_settings_views/MyProfile.tsx | 305 ++++--
hooks/firebase/types/profileTypes.ts | 2 +
hooks/firebase/useGetEvents.ts | 4 +-
hooks/firebase/useSignUp.ts | 2 +
hooks/firebase/useUpdateUserData.ts | 12 +-
hooks/useFetchAndSaveAppleEvents.ts | 32 +
hooks/useFetchAndSaveGoogleEvents.ts | 45 +
hooks/useFetchAndSaveOutlookEvents.ts | 36 +
ios/Podfile.lock | 18 +
ios/cally.xcodeproj/project.pbxproj | 14 +-
ios/cally/Info.plist | 18 +-
ios/cally/cally.entitlements | 4 +
package.json | 7 +-
yarn.lock | 39 +
25 files changed, 1154 insertions(+), 689 deletions(-)
create mode 100644 calendar-integration/apple-calendar-utils.js
create mode 100644 hooks/useFetchAndSaveAppleEvents.ts
create mode 100644 hooks/useFetchAndSaveGoogleEvents.ts
create mode 100644 hooks/useFetchAndSaveOutlookEvents.ts
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 4da6c76..cc3e629 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": "23"
+ "buildNumber": "23",
+ "usesAppleSignIn": true
},
"android": {
"adaptiveIcon": {
@@ -63,7 +64,17 @@
"defaultChannel": "default"
}
],
- "expo-font"
+ [
+ "expo-calendar",
+ {
+ "calendarPermission": "The app needs to access your calendar."
+ }
+ ],
+ [
+ "expo-apple-authentication"
+ ],
+ "expo-font",
+ "expo-localization"
],
"experiments": {
"typedRoutes": true
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 c91dcf8..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, email, 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,7 @@ export async function fetchGoogleCalendarEvents(token, email, 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 cbf5d66..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, email, 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,7 @@ export async function fetchMicrosoftCalendarEvents(token, email, startDate, endD
startDate: startDateTime,
endDate: endDateTime,
allDay: item.isAllDay,
+ familyId,
email
};
diff --git a/components/pages/calendar/EventCalendar.tsx b/components/pages/calendar/EventCalendar.tsx
index 9f34d54..487b097 100644
--- a/components/pages/calendar/EventCalendar.tsx
+++ b/components/pages/calendar/EventCalendar.tsx
@@ -10,6 +10,7 @@ import {
selectedDateAtom,
selectedNewEventDateAtom
} from "@/components/pages/calendar/atoms";
+import {useAuthContext} from "@/contexts/AuthContext";
interface EventCalendarProps {
calendarHeight: number;
@@ -17,6 +18,7 @@ interface EventCalendarProps {
export const EventCalendar: React.FC = memo(({calendarHeight}) => {
const {data: events} = useGetEvents();
+ const {profileData} = useAuthContext()
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
const mode = useAtomValue(modeAtom)
const setEditVisible = useSetAtom(editVisibleAtom)
@@ -35,6 +37,7 @@ export const EventCalendar: React.FC = memo(({calendarHeight
setEditVisible(true);
setEventForEdit(event);
}}
+ weekStartsOn={profileData?.firstDayOfWeek === "Mondays" ? 1 : 0}
height={calendarHeight}
activeDate={selectedDate}
date={selectedDate}
diff --git a/components/pages/calendar/InnerCalendar.tsx b/components/pages/calendar/InnerCalendar.tsx
index b23a3b3..00c0bbf 100644
--- a/components/pages/calendar/InnerCalendar.tsx
+++ b/components/pages/calendar/InnerCalendar.tsx
@@ -20,7 +20,7 @@ export const InnerCalendar = () => {
return (
<>
diff --git a/components/pages/settings/CalendarSettingsPage.tsx b/components/pages/settings/CalendarSettingsPage.tsx
index 2e7cb1c..897dcc4 100644
--- a/components/pages/settings/CalendarSettingsPage.tsx
+++ b/components/pages/settings/CalendarSettingsPage.tsx
@@ -1,12 +1,9 @@
import {AntDesign, Ionicons} from "@expo/vector-icons";
import React, {useCallback, useEffect, useState} from "react";
import {Button, Checkbox, Text, View} from "react-native-ui-lib";
-import {ScrollView, StyleSheet} from "react-native";
+import {ActivityIndicator, ScrollView, StyleSheet} from "react-native";
import {colorMap} from "@/contexts/SettingsContext";
import {TouchableOpacity} from "react-native-gesture-handler";
-import {fetchGoogleCalendarEvents} from "@/calendar-integration/google-calendar-utils";
-import {fetchMicrosoftCalendarEvents} from "@/calendar-integration/microsoft-calendar-utils";
-import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
import {useAuthContext} from "@/contexts/AuthContext";
import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
import debounce from "debounce";
@@ -17,6 +14,11 @@ import * as AuthSession from "expo-auth-session";
import * as Google from "expo-auth-session/providers/google";
import * as WebBrowser from "expo-web-browser";
import {UserProfile} from "@firebase/auth";
+import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
+import {useFetchAndSaveOutlookEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
+import {useFetchAndSaveAppleEvents} from "@/hooks/useFetchAndSaveAppleEvents";
+import * as AppleAuthentication from 'expo-apple-authentication';
+import ExpoLocalization from "expo-localization/src/ExpoLocalization";
const googleConfig = {
androidClientId:
@@ -50,8 +52,8 @@ const microsoftConfig = {
const CalendarSettingsPage = (props: {
setSelectedPage: (page: number) => void;
}) => {
- const [startDate, setStartDate] = useState(false);
const {profileData} = useAuthContext();
+ const [firstDayOfWeek, setFirstDayOfWeek] = useState(profileData?.firstDayOfWeek ?? ExpoLocalization.getCalendars()[0].firstWeekday === 1 ? "Mondays" : "Sundays");
const [selectedColor, setSelectedColor] = useState(
profileData?.eventColor ?? colorMap.pink
@@ -60,8 +62,11 @@ const CalendarSettingsPage = (props: {
profileData?.eventColor ?? colorMap.pink
);
- const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
const {mutateAsync: updateUserData} = useUpdateUserData();
+ const {mutateAsync: fetchAndSaveGoogleEvents, isLoading: isSyncingGoogle} = useFetchAndSaveGoogleEvents();
+ const {mutateAsync: fetchAndSaveOutlookEvents, isLoading: isSyncingOutlook} = useFetchAndSaveOutlookEvents();
+ const {mutateAsync: fetchAndSaveAppleEvents, isLoading: isSyncingApple} = useFetchAndSaveAppleEvents();
+
WebBrowser.maybeCompleteAuthSession();
const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
@@ -70,49 +75,6 @@ const CalendarSettingsPage = (props: {
signInWithGoogle();
}, [response]);
- const fetchAndSaveGoogleEvents = async (token?: string, email?: string) => {
- console.log("Fetching Google Calendar events...");
- const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
- const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
-
- console.log("Token: ", token ?? profileData?.googleToken)
- fetchGoogleCalendarEvents(
- token ?? profileData?.googleToken,
- email ?? profileData?.googleMail,
- timeMin.toISOString().slice(0, -5) + "Z",
- timeMax.toISOString().slice(0, -5) + "Z"
- ).then(async (response) => {
- console.log("Google Calendar events fetched:", response);
- const items = response?.map((item) => {
- if (item.allDay) {
- item.startDate = new Date(new Date(item.startDate).setHours(0, 0, 0, 0))
- item.endDate = item.startDate
- }
- return item;
- }) || [];
- await createEventsFromProvider(items);
- }).catch((error) => {
- console.error("Error fetching Google Calendar events:", error);
- });
- };
-
- const fetchAndSaveMicrosoftEvents = async (token?: string, email?: string) => {
- const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
- const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
-
- console.log("Token: ", token ?? profileData?.microsoftToken)
- fetchMicrosoftCalendarEvents(
- token ?? profileData?.microsoftToken,
- email ?? profileData?.outlookMail,
- timeMin.toISOString().slice(0, -5) + "Z",
- timeMax.toISOString().slice(0, -5) + "Z"
- ).then(async (response) => {
- console.log(response);
- const items = response ?? [];
- await createEventsFromProvider(items);
- });
- };
-
const signInWithGoogle = async () => {
try {
if (response?.type === "success") {
@@ -214,7 +176,7 @@ const CalendarSettingsPage = (props: {
newUserData: {microsoftToken: tokenData.access_token, outlookMail: outlookMail},
});
- await fetchAndSaveMicrosoftEvents(tokenData.access_token, outlookMail)
+ await fetchAndSaveOutlookEvents(tokenData.access_token, outlookMail)
console.log("User data updated successfully.");
}
}
@@ -226,6 +188,39 @@ const CalendarSettingsPage = (props: {
}
};
+ const handleAppleSignIn = async () => {
+ try {
+ console.log("Starting Apple Sign-in...");
+
+ const credential = await AppleAuthentication.signInAsync({
+ requestedScopes: [
+ AppleAuthentication.AppleAuthenticationScope.EMAIL,
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
+ ],
+ });
+
+ console.log("Apple sign-in result:", credential);
+
+ const appleToken = credential.identityToken;
+ const appleMail = credential.email;
+
+ if (appleToken) {
+ console.log("Apple ID token received. Fetch user info if needed...");
+
+ // Example: Store user token and email
+ await updateUserData({
+ newUserData: {appleToken, appleMail},
+ });
+
+ console.log("User data updated with Apple ID token.");
+ } else {
+ console.warn("Apple authentication was not successful or email was hidden.");
+ }
+ } catch (error) {
+ console.error("Error during Apple Sign-in:", error);
+ }
+ };
+
const debouncedUpdateUserData = useCallback(
debounce(async (color: string) => {
try {
@@ -242,6 +237,26 @@ const CalendarSettingsPage = (props: {
[]
);
+ const debouncedUpdateFirstDayOfWeek = useCallback(
+ debounce(async (firstDayOfWeek: string) => {
+ try {
+ await updateUserData({
+ newUserData: {
+ firstDayOfWeek,
+ },
+ });
+ } catch (error) {
+ console.error("Failed to update first day of week:", error);
+ }
+ }, 500),
+ []
+ );
+
+ const handleChangeFirstDayOfWeek = () => {
+ setFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
+ debouncedUpdateFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
+ }
+
const handleChangeColor = (color: string) => {
setPreviousSelectedColor(selectedColor);
setSelectedColor(color);
@@ -333,10 +348,10 @@ const CalendarSettingsPage = (props: {
Weekly Start Date
setStartDate(true)}
+ onValueChange={() => handleChangeFirstDayOfWeek("Sundays")}
/>
Sundays
@@ -348,10 +363,10 @@ const CalendarSettingsPage = (props: {
setStartDate(false)}
+ onValueChange={() => handleChangeFirstDayOfWeek("Mondays")}
/>
Mondays
@@ -379,7 +394,8 @@ const CalendarSettingsPage = (props: {
text70BL
/>
!profileData?.appleToken ? handleAppleSignIn() : clearToken("google")}
+ label={profileData?.appleToken ? `Disconnect ${profileData.appleMail}` : "Connect Apple"}
labelStyle={styles.addCalLbl}
labelProps={{
numberOfLines: 2
@@ -416,40 +432,93 @@ const CalendarSettingsPage = (props: {
Connected Calendars
-
+
{!!profileData?.googleMail && (
- fetchAndSaveGoogleEvents()}
- label={`Sync ${profileData?.googleMail}`}
- labelStyle={styles.addCalLbl}
- labelProps={{numberOfLines: 2}}
- iconSource={() => (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
+ fetchAndSaveGoogleEvents(undefined, undefined)}
+ >
+
+ fetchAndSaveGoogleEvents(undefined, undefined)}
+ label={`Sync ${profileData?.googleMail}`}
+ labelStyle={styles.addCalLbl}
+ labelProps={{numberOfLines: 3}}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+
+ {isSyncingGoogle ? (
+
+ ) : (
+
+ )}
+
+
+ )}
+
+
+ {!!profileData?.appleMail && (
+
+
+ fetchAndSaveAppleEvents(undefined, undefined)}
+ label={`Sync ${profileData?.appleMail}`}
+ labelStyle={styles.addCalLbl}
+ labelProps={{numberOfLines: 3}}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ {isSyncingApple ? (
+
+ ) : (
+
+ )}
+
+
)}
{!!profileData?.outlookMail && (
- fetchAndSaveMicrosoftEvents()}
- label={`Sync ${profileData?.outlookMail}`}
- labelStyle={styles.addCalLbl}
- labelProps={{numberOfLines: 2}}
- iconSource={() => (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
+ fetchAndSaveOutlookEvents(undefined, undefined)}
+ >
+
+ fetchAndSaveOutlookEvents(undefined, undefined)}
+ label={`Sync ${profileData?.outlookMail}`}
+ labelStyle={styles.addCalLbl}
+ labelProps={{numberOfLines: 3}}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ {isSyncingOutlook ? (
+
+ ) : (
+
+ )}
+
+
)}
@@ -480,6 +549,12 @@ const styles = StyleSheet.create({
marginTop: 20,
borderRadius: 12,
},
+ noPaddingCard: {
+ backgroundColor: "white",
+ width: "100%",
+ marginTop: 20,
+ borderRadius: 12,
+ },
colorBox: {
aspectRatio: 1,
justifyContent: "center",
diff --git a/components/pages/settings/user_settings_views/MyGroup.tsx b/components/pages/settings/user_settings_views/MyGroup.tsx
index b815560..de4e2e1 100644
--- a/components/pages/settings/user_settings_views/MyGroup.tsx
+++ b/components/pages/settings/user_settings_views/MyGroup.tsx
@@ -1,505 +1,540 @@
import {
- Avatar,
- Button,
- Card,
- Colors,
- Dialog,
- FloatingButton,
- PanningProvider,
- Picker,
- Text,
- TextField,
- TextFieldRef,
- TouchableOpacity,
- View,
+ Avatar,
+ Button,
+ Card,
+ Colors,
+ Dialog,
+ FloatingButton,
+ PanningProvider,
+ Picker,
+ Text,
+ TextField,
+ TextFieldRef,
+ TouchableOpacity,
+ View,
} from "react-native-ui-lib";
-import React, { useEffect, useRef, useState } from "react";
-import { Dimensions, ScrollView, StyleSheet } from "react-native";
-import { PickerSingleValue } from "react-native-ui-lib/src/components/picker/types";
-import { useCreateSubUser } from "@/hooks/firebase/useCreateSubUser";
-import { ProfileType } from "@/contexts/AuthContext";
-import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
+import React, {useEffect, useRef, useState} from "react";
+import {ScrollView, StyleSheet} from "react-native";
+import {PickerSingleValue} from "react-native-ui-lib/src/components/picker/types";
+import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
+import {ProfileType} from "@/contexts/AuthContext";
+import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
-import { uuidv4 } from "@firebase/util";
+import {uuidv4} from "@firebase/util";
import QRIcon from "@/assets/svgs/QRIcon";
import EmailIcon from "@/assets/svgs/EmailIcon";
import CircledXIcon from "@/assets/svgs/CircledXIcon";
import ProfileIcon from "@/assets/svgs/ProfileIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
+import Ionicons from "@expo/vector-icons/Ionicons";
const MyGroup = () => {
- const [showAddUserDialog, setShowAddUserDialog] = useState(false);
- const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
- const [selectedStatus, setSelectedStatus] = useState<
- string | PickerSingleValue
- >(ProfileType.CHILD);
- const [firstName, setFirstName] = useState("");
- const [lastName, setLastName] = useState("");
- const [email, setEmail] = useState("");
+ const [showAddUserDialog, setShowAddUserDialog] = useState(false);
+ const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
+ const [selectedStatus, setSelectedStatus] = useState<
+ string | PickerSingleValue
+ >(ProfileType.CHILD);
+ const [firstName, setFirstName] = useState("");
+ const [lastName, setLastName] = useState("");
+ const [email, setEmail] = useState("");
- const lNameRef = useRef(null);
- const emailRef = useRef(null);
+ const lNameRef = useRef(null);
+ const emailRef = useRef(null);
- const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
+ const [showQRCodeDialog, setShowQRCodeDialog] = useState("");
- const { mutateAsync: createSubUser, isLoading, isError } = useCreateSubUser();
- const { data: familyMembers } = useGetFamilyMembers(true);
+ const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
+ const {data: familyMembers} = useGetFamilyMembers(true);
- const parents =
- familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
- const children =
- familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
- const caregivers =
- familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
- const familyDevices =
- familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ??
- [];
+ const parents =
+ familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
+ const children =
+ familyMembers?.filter((x) => x.userType === ProfileType.CHILD) ?? [];
+ const caregivers =
+ familyMembers?.filter((x) => x.userType === ProfileType.CAREGIVER) ?? [];
+ const familyDevices =
+ familyMembers?.filter((x) => x.userType === ProfileType.FAMILY_DEVICE) ??
+ [];
- const handleCreateSubUser = async () => {
- if (
- !firstName ||
- (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)
- ) {
- console.error("First name and last name are required");
- return;
- }
+ const handleCreateSubUser = async () => {
+ if (
+ !firstName ||
+ (selectedStatus !== ProfileType.FAMILY_DEVICE && !lastName)
+ ) {
+ console.error("First name and last name are required");
+ return;
+ }
- if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
- console.error("Email is required for non-family device users");
- return;
- }
+ if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
+ console.error("Email is required for non-family device users");
+ return;
+ }
- if (email && !email.includes("@")) {
- console.error("Invalid email address");
- return;
- }
+ if (email && !email.includes("@")) {
+ console.error("Invalid email address");
+ return;
+ }
- const res = await createSubUser({
- firstName,
- lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName,
- email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`,
- password: uuidv4(),
- userType: selectedStatus as ProfileType,
- });
- console.log(res);
+ const res = await createSubUser({
+ firstName,
+ lastName: selectedStatus === ProfileType.FAMILY_DEVICE ? "" : lastName,
+ email: email || `placeholder_${uuidv4().split("-")[0]}@family.device`,
+ password: uuidv4(),
+ userType: selectedStatus as ProfileType,
+ });
+ console.log(res);
- if (!isError) {
- setShowNewUserInfoDialog(false);
+ if (!isError) {
+ setShowNewUserInfoDialog(false);
- if (res?.data?.userId) {
- setTimeout(() => {
- setShowQRCodeDialog(res.data.userId);
- }, 500);
- }
- }
- };
-
- useEffect(() => {
- setFirstName("");
- setLastName("");
- setEmail("");
- }, [])
-
-
- return (
-
-
-
- {!parents.length && !children.length && !caregivers.length && (
-
- {isLoading ? "Loading...." : "No user devices added"}
-
- )}
-
- {(!!parents.length || !!children.length) && (
- <>
-
- Family
-
- {[...parents, ...children]?.map((member, index) => (
-
-
-
-
- {member.firstName} {member.lastName}
-
-
- {member.userType === ProfileType.PARENT
- ? "Admin (You)"
- : "Child"}
-
-
-
-
-
- setShowQRCodeDialog("")}
- showQRCodeDialog={showQRCodeDialog === member?.uid}
- userId={member?.uid!}
- />
-
- ))}
- >
- )}
-
- {!!caregivers.length && (
- <>
-
- Caregivers
-
- {caregivers?.map((member) => (
-
-
-
-
- {member.firstName} {member.lastName}
-
-
- Caregiver
-
-
-
- setShowQRCodeDialog("")}
- showQRCodeDialog={showQRCodeDialog === member?.uid}
- userId={member?.uid!}
- />
-
- ))}
- >
- )}
-
- {!!familyDevices.length && (
- <>
-
- Family Devices
-
- {familyDevices?.map((member, index) => (
-
-
-
- {member.firstName}
-
- Family Device
-
-
-
- setShowQRCodeDialog("")}
- showQRCodeDialog={showQRCodeDialog === member?.uid}
- userId={member?.uid!}
- />
-
- ))}
- >
- )}
-
-
-
- setShowAddUserDialog(true),
- style: styles.bottomButton,
- }}
- />
-
- setShowAddUserDialog(false)}
- panDirection={PanningProvider.Directions.DOWN}
- >
-
-
- Add a new user device
-
-
-
-
-
- Show a QR Code
-
-
- {
- setShowAddUserDialog(false);
- setTimeout(() => {
- setShowNewUserInfoDialog(true);
- }, 500);
- }}
- >
-
-
- Enter email address
-
-
-
- setShowAddUserDialog(false)}
- center
- marginT-30
- >
- Return to user settings
-
-
-
-
- setShowNewUserInfoDialog(false)}
- >
-
-
-
- New User Information
-
- {setShowNewUserInfoDialog(false)}}>
-
-
-
-
-
-
-
- }
- backgroundColor={Colors.grey60}
- style={{ borderRadius: 25 }}
- center
- />
- {}}>
-
- Upload User Profile Photo
-
-
-
-
- Member Status
- setSelectedStatus(item)}
- style={styles.picker}
- showSearch
- floatingPlaceholder
- >
-
-
-
-
-
-
-
- {selectedStatus === ProfileType.FAMILY_DEVICE
- ? "Device Name"
- : "First Name"}
-
- {
+ setShowQRCodeDialog(res.data.userId);
+ }, 500);
}
- value={firstName}
- onChangeText={setFirstName}
- style={styles.inputField}
- onSubmitEditing={() => {lNameRef.current?.focus()}}
- blurOnSubmit={false}
- />
+ }
+ };
- {selectedStatus !== ProfileType.FAMILY_DEVICE && (
- <>
- Last Name
- {emailRef.current?.focus()}}
- blurOnSubmit={false}
- />
- >
- )}
+ useEffect(() => {
+ setFirstName("");
+ setLastName("");
+ setEmail("");
+ }, [])
- {selectedStatus !== ProfileType.FAMILY_DEVICE && (
- <>
- Email Address (Optional)
-
- >
- )}
- }
- onPress={handleCreateSubUser}
- />
-
-
-
- );
+ // @ts-ignore
+ return (
+
+
+
+ {!parents.length && !children.length && !caregivers.length && (
+
+ {isLoading ? "Loading...." : "No user devices added"}
+
+ )}
+
+ {(!!parents.length || !!children.length) && (
+ <>
+
+ Family
+
+ {[...parents, ...children]?.map((member, index) => (
+
+
+
+
+ {member.firstName} {member.lastName}
+
+
+ {member.userType === ProfileType.PARENT
+ ? "Admin (You)"
+ : "Child"}
+
+
+
+
+
+ setShowQRCodeDialog("")}
+ showQRCodeDialog={showQRCodeDialog === member?.uid}
+ userId={member?.uid!}
+ />
+
+ ))}
+ >
+ )}
+
+ {!!caregivers.length && (
+ <>
+
+ Caregivers
+
+ {caregivers?.map((member) => (
+
+
+
+
+ {member.firstName} {member.lastName}
+
+
+ Caregiver
+
+
+
+ setShowQRCodeDialog("")}
+ showQRCodeDialog={showQRCodeDialog === member?.uid}
+ userId={member?.uid!}
+ />
+
+ ))}
+ >
+ )}
+
+ {!!familyDevices.length && (
+ <>
+
+ Family Devices
+
+ {familyDevices?.map((member, index) => (
+
+
+
+ {member.firstName}
+
+ Family Device
+
+
+
+ setShowQRCodeDialog("")}
+ showQRCodeDialog={showQRCodeDialog === member?.uid}
+ userId={member?.uid!}
+ />
+
+ ))}
+ >
+ )}
+
+
+
+ setShowAddUserDialog(true),
+ style: styles.bottomButton,
+ }}
+ />
+
+ setShowAddUserDialog(false)}
+ panDirection={PanningProvider.Directions.DOWN}
+ >
+
+
+ Add a new user device
+
+
+
+
+
+ Show a QR Code
+
+
+ {
+ setShowAddUserDialog(false);
+ setTimeout(() => {
+ setShowNewUserInfoDialog(true);
+ }, 500);
+ }}
+ >
+
+
+ Enter email address
+
+
+
+ setShowAddUserDialog(false)}
+ center
+ marginT-30
+ >
+ Return to user settings
+
+
+
+
+ setShowNewUserInfoDialog(false)}
+ >
+
+
+
+ New User Information
+
+ {
+ setShowNewUserInfoDialog(false)
+ }}>
+
+
+
+
+
+
+
+ }
+ 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"}
+
+ {
+ lNameRef.current?.focus()
+ }}
+ blurOnSubmit={false}
+ />
+
+ {selectedStatus !== ProfileType.FAMILY_DEVICE && (
+ <>
+ Last Name
+ {
+ emailRef.current?.focus()
+ }}
+ blurOnSubmit={false}
+ />
+ >
+ )}
+
+ {selectedStatus !== ProfileType.FAMILY_DEVICE && (
+ <>
+ Email Address (Optional)
+
+ >
+ )}
+
+ }
+ onPress={handleCreateSubUser}
+ />
+
+
+
+ );
};
const styles = StyleSheet.create({
- dialogBtn: {
- height: 47,
- width: 279,
- },
- dialogTitle: { fontFamily: "Manrope_600SemiBold", fontSize: 22 },
- dialogBackBtn: {
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 15,
- color: "#a7a7a7",
- },
- card: {
- marginVertical: 15,
- backgroundColor: "white",
- width: "100%",
- borderRadius: 15,
- padding: 20,
- },
- bottomButton: {
- position: "absolute",
- bottom: 80,
- width: "100%",
- },
- familyCard: {
- marginBottom: 10,
- borderRadius: 10,
- backgroundColor: Colors.white,
- width: "100%",
- },
- inputField: {
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 13,
- color: "#565656",
- borderRadius: 50,
- paddingVertical: 12,
- paddingHorizontal: 16,
- backgroundColor: Colors.grey80,
- marginBottom: 16,
- borderColor: Colors.grey50,
- borderWidth: 1,
- height: 40,
- },
- picker: {
- borderRadius: 50,
- paddingVertical: 12,
- paddingHorizontal: 16,
- backgroundColor: Colors.grey80,
- marginBottom: 16,
- borderColor: Colors.grey50,
- borderWidth: 1,
- marginTop: -20,
- height: 40,
- },
- label: {
- marginBottom: 5,
- fontSize: 12,
- color: Colors.grey40,
- },
- dialogCard: {
- borderRadius: 10,
- gap: 10,
- },
- subTit: {
- fontFamily: "Manrope_500Medium",
- fontSize: 15,
- },
- dialogBtnLbl: {
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 15,
- color: "white",
- },
- divider: { height: 0.7, backgroundColor: "#e6e6e6", width: "100%" },
- jakarta12: {
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 12,
- color: "#a1a1a1",
- },
- jakarta13: {
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 13,
- },
+ dialogBtn: {
+ height: 47,
+ width: 279,
+ },
+ dialogTitle: {fontFamily: "Manrope_600SemiBold", fontSize: 22},
+ dialogBackBtn: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 15,
+ color: "#a7a7a7",
+ },
+ card: {
+ marginVertical: 15,
+ backgroundColor: "white",
+ width: "100%",
+ borderRadius: 15,
+ padding: 20,
+ },
+ bottomButton: {
+ position: "absolute",
+ bottom: 80,
+ width: "100%",
+ },
+ familyCard: {
+ marginBottom: 10,
+ borderRadius: 10,
+ backgroundColor: Colors.white,
+ width: "100%",
+ },
+ inputField: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 13,
+ color: "#565656",
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ height: 40,
+ },
+ picker: {
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ marginTop: -20,
+ height: 40,
+ zIndex: 10,
+ },
+ viewPicker: {
+ borderRadius: 50,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ marginTop: 0,
+ height: 40,
+ zIndex: 10,
+ },
+ inViewPicker: {
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ marginBottom: 16,
+ marginTop: -20,
+ height: 40,
+ zIndex: 10,
+ },
+ label: {
+ marginBottom: 5,
+ fontSize: 12,
+ color: Colors.grey40,
+ },
+ dialogCard: {
+ borderRadius: 10,
+ gap: 10,
+ },
+ subTit: {
+ fontFamily: "Manrope_500Medium",
+ fontSize: 15,
+ },
+ dialogBtnLbl: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 15,
+ color: "white",
+ },
+ divider: {height: 0.7, backgroundColor: "#e6e6e6", width: "100%"},
+ jakarta12: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 12,
+ color: "#a1a1a1",
+ },
+ jakarta13: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 13,
+ },
});
export default MyGroup;
diff --git a/components/pages/settings/user_settings_views/MyProfile.tsx b/components/pages/settings/user_settings_views/MyProfile.tsx
index 609f50f..fe91d08 100644
--- a/components/pages/settings/user_settings_views/MyProfile.tsx
+++ b/components/pages/settings/user_settings_views/MyProfile.tsx
@@ -1,122 +1,207 @@
-import { Text, TextField, View } from "react-native-ui-lib";
-import React, { useState } from "react";
-import { ImageBackground, StyleSheet } from "react-native";
-import { ScrollView } from "react-native-gesture-handler";
-import { useAuthContext } from "@/contexts/AuthContext";
-import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
+import {Colors, Picker, Text, TextField, View} from "react-native-ui-lib";
+import React, {useEffect, useRef, useState} from "react";
+import {ImageBackground, StyleSheet} from "react-native";
+import {ScrollView} from "react-native-gesture-handler";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
+import Ionicons from "@expo/vector-icons/Ionicons";
+import * as tz from 'tzdata';
+import * as Localization from 'expo-localization';
+import debounce from "debounce";
const MyProfile = () => {
- const { user, profileData } = useAuthContext();
+ const {user, profileData} = useAuthContext();
- const [lastName, setLastName] = useState(profileData?.lastName || "");
- const [firstName, setFirstName] = useState(
- profileData?.firstName || ""
- );
+ const [timeZone, setTimeZone] = useState(profileData?.timeZone! ?? Localization.getCalendars()[0].timeZone);
+ const [lastName, setLastName] = useState(profileData?.lastName || "");
+ const [firstName, setFirstName] = useState(
+ profileData?.firstName || ""
+ );
- const { mutateAsync: updateUserData } = useUpdateUserData();
- return (
-
-
- Your Profile
-
-
+ const {mutateAsync: updateUserData} = useUpdateUserData();
+ const isFirstRender = useRef(true);
-
- Change Photo
-
- Remove Photo
-
-
-
- First name
-
- {
- setFirstName(value);
- await updateUserData({ newUserData: { firstName: value } });
- }}
- />
-
- Last name
-
- {
- setLastName(value);
- await updateUserData({ newUserData: { lastName: value } });
- }}
- />
-
- Email address
-
-
-
-
-
- Settings
-
- Time Zone
-
-
-
-
- );
+ const handleUpdateUserData = async () => {
+ await updateUserData({newUserData: {firstName, lastName, timeZone}});
+ }
+
+ const debouncedUserDataUpdate = debounce(handleUpdateUserData, 500);
+
+ useEffect(() => {
+ if (isFirstRender.current) {
+ isFirstRender.current = false;
+ return;
+ }
+ debouncedUserDataUpdate();
+ }, [timeZone, lastName, firstName]);
+
+ return (
+
+
+ Your Profile
+
+
+
+
+ Change Photo
+
+ Remove Photo
+
+
+
+ First name
+
+ {
+ setFirstName(value);
+ }}
+ />
+
+ Last name
+
+ {
+ setLastName(value);
+ }}
+ />
+
+ Email address
+
+
+
+
+
+
+ Settings
+ Time Zone
+
+ {
+ setTimeZone(item as string)
+ }}
+ showSearch
+ floatingPlaceholder
+ style={styles.inViewPicker}
+ trailingAccessory={
+
+
+
+ }
+ >
+ {timeZoneItems}
+
+
+
+
+ );
};
+const timeZoneItems = Object.keys(tz.zones).sort().map((zone) => (
+
+));
+
const styles = StyleSheet.create({
- card: {
- marginVertical: 15,
- backgroundColor: "white",
- width: "100%",
- borderRadius: 12,
- paddingHorizontal: 20,
- paddingVertical: 21,
- },
- pfp: {
- aspectRatio: 1,
- width: 65.54,
- backgroundColor: "green",
- borderRadius: 20,
- },
- txtBox: {
- backgroundColor: "#fafafa",
- borderRadius: 50,
- borderWidth: 2,
- borderColor: "#cecece",
- padding: 15,
- height: 45,
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 13
- },
- subTit: {
- fontFamily: "Manrope_500Medium",
- fontSize: 15,
- },
- label: {
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 12,
- color: "#a1a1a1"
- },
- photoSet:{
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 13.07
- }
+ card: {
+ marginVertical: 15,
+ backgroundColor: "white",
+ width: "100%",
+ borderRadius: 12,
+ paddingHorizontal: 20,
+ paddingVertical: 21,
+ },
+ pfp: {
+ aspectRatio: 1,
+ width: 65.54,
+ backgroundColor: "green",
+ borderRadius: 20,
+ },
+ txtBox: {
+ backgroundColor: "#fafafa",
+ borderRadius: 50,
+ borderWidth: 2,
+ borderColor: "#cecece",
+ padding: 15,
+ height: 45,
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 13
+ },
+ subTit: {
+ fontFamily: "Manrope_500Medium",
+ fontSize: 15,
+ },
+ label: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 12,
+ color: "#a1a1a1"
+ },
+ photoSet: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 13.07
+ },
+ jakarta12: {
+ paddingVertical: 10,
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 12,
+ color: "#a1a1a1",
+ },
+ picker: {
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ marginTop: -20,
+ height: 40,
+ zIndex: 10,
+ },
+ viewPicker: {
+ borderRadius: 50,
+ backgroundColor: Colors.grey80,
+ marginBottom: 16,
+ borderColor: Colors.grey50,
+ borderWidth: 1,
+ marginTop: 0,
+ height: 40,
+ zIndex: 10,
+ },
+ inViewPicker: {
+ borderRadius: 50,
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ marginBottom: 16,
+ marginTop: -20,
+ height: 40,
+ zIndex: 10,
+ },
});
export default MyProfile;
diff --git a/hooks/firebase/types/profileTypes.ts b/hooks/firebase/types/profileTypes.ts
index a73b9e0..8c6ea4a 100644
--- a/hooks/firebase/types/profileTypes.ts
+++ b/hooks/firebase/types/profileTypes.ts
@@ -24,6 +24,8 @@ export interface UserProfile {
googleMail?: string | null;
outlookMail?: string | null;
appleMail?: string | null;
+ timeZone?: string | null;
+ firstDayOfWeek?: string | null;
}
export interface ParentProfile extends UserProfile {
diff --git a/hooks/firebase/useGetEvents.ts b/hooks/firebase/useGetEvents.ts
index 79518c7..74b79b1 100644
--- a/hooks/firebase/useGetEvents.ts
+++ b/hooks/firebase/useGetEvents.ts
@@ -6,7 +6,7 @@ import {useAtomValue} from "jotai";
import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
export const useGetEvents = () => {
- const { user, profileData } = useAuthContext();
+ const {user, profileData} = useAuthContext();
const isFamilyView = useAtomValue(isFamilyViewAtom)
return useQuery({
@@ -43,5 +43,7 @@ export const useGetEvents = () => {
};
}));
},
+ staleTime: Infinity,
+ cacheTime: Infinity
});
};
diff --git a/hooks/firebase/useSignUp.ts b/hooks/firebase/useSignUp.ts
index c9b217b..2464cd0 100644
--- a/hooks/firebase/useSignUp.ts
+++ b/hooks/firebase/useSignUp.ts
@@ -3,6 +3,7 @@ import auth from "@react-native-firebase/auth";
import { ProfileType } from "@/contexts/AuthContext";
import { useSetUserData } from "./useSetUserData";
import {uuidv4} from "@firebase/util";
+import * as Localization from "expo-localization";
export const useSignUp = () => {
const { mutateAsync: setUserData } = useSetUserData();
@@ -30,6 +31,7 @@ export const useSignUp = () => {
firstName: firstName,
lastName: lastName,
familyId: uuidv4(),
+ timeZone: Localization.getCalendars()[0].timeZone,
},
customUser: res.user,
});
diff --git a/hooks/firebase/useUpdateUserData.ts b/hooks/firebase/useUpdateUserData.ts
index 66596f3..28b3363 100644
--- a/hooks/firebase/useUpdateUserData.ts
+++ b/hooks/firebase/useUpdateUserData.ts
@@ -1,11 +1,11 @@
import firestore from "@react-native-firebase/firestore";
-import { FirebaseAuthTypes } from "@react-native-firebase/auth";
-import { useMutation, useQueryClient } from "react-query";
-import { useAuthContext } from "@/contexts/AuthContext";
-import { UserProfile } from "@/hooks/firebase/types/profileTypes";
+import {FirebaseAuthTypes} from "@react-native-firebase/auth";
+import {useMutation, useQueryClient} from "react-query";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {UserProfile} from "@/hooks/firebase/types/profileTypes";
export const useUpdateUserData = () => {
- const { user: currentUser, refreshProfileData } = useAuthContext();
+ const {user: currentUser, refreshProfileData} = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
@@ -17,7 +17,7 @@ export const useUpdateUserData = () => {
newUserData: Partial;
customUser?: FirebaseAuthTypes.User;
}) => {
- console.log("Mutation function called with data:", { newUserData, customUser });
+ console.log("Mutation function called with data:", {newUserData, customUser});
const user = currentUser ?? customUser;
diff --git a/hooks/useFetchAndSaveAppleEvents.ts b/hooks/useFetchAndSaveAppleEvents.ts
new file mode 100644
index 0000000..4493da6
--- /dev/null
+++ b/hooks/useFetchAndSaveAppleEvents.ts
@@ -0,0 +1,32 @@
+import {useMutation} from "react-query";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
+import {fetchiPhoneCalendarEvents} from "@/calendar-integration/apple-calendar-utils";
+
+export const useFetchAndSaveAppleEvents = () => {
+ const {profileData} = useAuthContext();
+ const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
+
+ return useMutation({
+ mutationKey: ["fetchAndSaveAppleEvents"],
+ mutationFn: async (token?: string, email?: string) => {
+ const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
+ const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
+ try {
+ const response = await fetchiPhoneCalendarEvents(
+ profileData?.familyId!,
+ email,
+ timeMin,
+ timeMax
+ );
+
+ console.log(response);
+ const items = response ?? [];
+ await createEventsFromProvider(items);
+ } catch (error) {
+ console.error("Error fetching and saving Apple Calendar events: ", error);
+ throw error;
+ }
+ },
+ });
+};
\ No newline at end of file
diff --git a/hooks/useFetchAndSaveGoogleEvents.ts b/hooks/useFetchAndSaveGoogleEvents.ts
new file mode 100644
index 0000000..7243c72
--- /dev/null
+++ b/hooks/useFetchAndSaveGoogleEvents.ts
@@ -0,0 +1,45 @@
+import {useMutation} from "react-query";
+import {fetchGoogleCalendarEvents} from "@/calendar-integration/google-calendar-utils";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
+
+export const useFetchAndSaveGoogleEvents = () => {
+ const {profileData} = useAuthContext();
+ const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
+
+ return useMutation({
+ mutationKey: ["fetchAndSaveGoogleEvents"],
+ mutationFn: async (token?: string, email?: string) => {
+ console.log("Fetching Google Calendar events...");
+ const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
+ const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 5));
+
+ console.log("Token: ", token ?? profileData?.googleToken);
+
+ try {
+ const response = await fetchGoogleCalendarEvents(
+ token ?? profileData?.googleToken,
+ email ?? profileData?.googleMail,
+ profileData?.familyId,
+ timeMin.toISOString().slice(0, -5) + "Z",
+ timeMax.toISOString().slice(0, -5) + "Z"
+ );
+
+ console.log("Google Calendar events fetched:", response);
+
+ const items = response?.map((item) => {
+ if (item.allDay) {
+ item.startDate = new Date(new Date(item.startDate).setHours(0, 0, 0, 0));
+ item.endDate = item.startDate;
+ }
+ return item;
+ }) || [];
+
+ await createEventsFromProvider(items);
+ } catch (error) {
+ console.error("Error fetching Google Calendar events:", error);
+ throw error; // Ensure errors are propagated to the mutation
+ }
+ },
+ });
+};
\ No newline at end of file
diff --git a/hooks/useFetchAndSaveOutlookEvents.ts b/hooks/useFetchAndSaveOutlookEvents.ts
new file mode 100644
index 0000000..3c53fc5
--- /dev/null
+++ b/hooks/useFetchAndSaveOutlookEvents.ts
@@ -0,0 +1,36 @@
+import { useMutation } from "react-query";
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useCreateEventsFromProvider } from "@/hooks/firebase/useCreateEvent";
+import { fetchMicrosoftCalendarEvents } from "@/calendar-integration/microsoft-calendar-utils";
+
+export const useFetchAndSaveOutlookEvents = () => {
+ const { profileData } = useAuthContext();
+ const { mutateAsync: createEventsFromProvider } = useCreateEventsFromProvider();
+
+ return useMutation({
+ mutationKey: ["fetchAndSaveOutlookEvents"],
+ mutationFn: async (token?: string, email?: string) => {
+ const timeMin = new Date(new Date().setFullYear(new Date().getFullYear() - 1));
+ const timeMax = new Date(new Date().setFullYear(new Date().getFullYear() + 3));
+
+ console.log("Token: ", token ?? profileData?.microsoftToken);
+
+ try {
+ const response = await fetchMicrosoftCalendarEvents(
+ token ?? profileData?.microsoftToken,
+ email ?? profileData?.outlookMail,
+ profileData?.familyId,
+ timeMin.toISOString().slice(0, -5) + "Z",
+ timeMax.toISOString().slice(0, -5) + "Z"
+ );
+
+ console.log(response);
+ const items = response ?? [];
+ await createEventsFromProvider(items);
+ } catch (error) {
+ console.error("Error fetching and saving Outlook events: ", error);
+ throw error;
+ }
+ },
+ });
+};
\ No newline at end of file
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 8f5a0f0..407bbe1 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1190,8 +1190,12 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - ExpoAppleAuthentication (6.4.2):
+ - ExpoModulesCore
- ExpoAsset (10.0.10):
- ExpoModulesCore
+ - ExpoCalendar (13.0.5):
+ - ExpoModulesCore
- ExpoCamera (15.0.16):
- ExpoModulesCore
- ZXingObjC/OneD
@@ -1210,6 +1214,8 @@ PODS:
- ExpoModulesCore
- ExpoKeepAwake (13.0.2):
- ExpoModulesCore
+ - ExpoLocalization (15.0.3):
+ - ExpoModulesCore
- ExpoModulesCore (1.12.24):
- DoubleConversion
- glog
@@ -2802,7 +2808,9 @@ DEPENDENCIES:
- expo-dev-launcher (from `../node_modules/expo-dev-launcher`)
- expo-dev-menu (from `../node_modules/expo-dev-menu`)
- expo-dev-menu-interface (from `../node_modules/expo-dev-menu-interface/ios`)
+ - ExpoAppleAuthentication (from `../node_modules/expo-apple-authentication/ios`)
- ExpoAsset (from `../node_modules/expo-asset/ios`)
+ - ExpoCalendar (from `../node_modules/expo-calendar/ios`)
- ExpoCamera (from `../node_modules/expo-camera/ios`)
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
- ExpoDevice (from `../node_modules/expo-device/ios`)
@@ -2811,6 +2819,7 @@ DEPENDENCIES:
- ExpoHead (from `../node_modules/expo-router/ios`)
- ExpoImagePicker (from `../node_modules/expo-image-picker/ios`)
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
+ - ExpoLocalization (from `../node_modules/expo-localization/ios`)
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
- ExpoSystemUI (from `../node_modules/expo-system-ui/ios`)
- ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`)
@@ -2956,8 +2965,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-dev-menu"
expo-dev-menu-interface:
:path: "../node_modules/expo-dev-menu-interface/ios"
+ ExpoAppleAuthentication:
+ :path: "../node_modules/expo-apple-authentication/ios"
ExpoAsset:
:path: "../node_modules/expo-asset/ios"
+ ExpoCalendar:
+ :path: "../node_modules/expo-calendar/ios"
ExpoCamera:
:path: "../node_modules/expo-camera/ios"
ExpoCrypto:
@@ -2974,6 +2987,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/expo-image-picker/ios"
ExpoKeepAwake:
:path: "../node_modules/expo-keep-awake/ios"
+ ExpoLocalization:
+ :path: "../node_modules/expo-localization/ios"
ExpoModulesCore:
:path: "../node_modules/expo-modules-core"
ExpoSystemUI:
@@ -3142,7 +3157,9 @@ SPEC CHECKSUMS:
expo-dev-launcher: fe4f2c0a0aa627449eeaec5f9f11e04090f97c40
expo-dev-menu: 5b14897ecce3a8cf9e9cf9109344c2c192a3766a
expo-dev-menu-interface: be32c09f1e03833050f0ee290dcc86b3ad0e73e4
+ ExpoAppleAuthentication: 265219fa0ba1110872079f55f56686b9737b0065
ExpoAsset: 323700f291684f110fb55f0d4022a3362ea9f875
+ ExpoCalendar: 135beb39ea3795f854a4ea287a49f74c9203ce51
ExpoCamera: 929be541d1c1319fcf32f9f5d9df8b97804346b5
ExpoCrypto: 156078f266bf28f80ecf5e2a9c3a0d6ffce07a1c
ExpoDevice: fc94f0e42ecdfd897e7590f2874fc64dfa7e9b1c
@@ -3151,6 +3168,7 @@ SPEC CHECKSUMS:
ExpoHead: fcb28a68ed4ba28f177394d2dfb8a0a8824cd103
ExpoImagePicker: 12a420923383ae38dccb069847218f27a3b87816
ExpoKeepAwake: 3b8815d9dd1d419ee474df004021c69fdd316d08
+ ExpoLocalization: f04eeec2e35bed01ab61c72ee1768ec04d093d01
ExpoModulesCore: db3e31e694684f08223d713e89f7648c6d3e04d0
ExpoSystemUI: d4f065a016cae6721b324eb659cdee4d4cf0cb26
ExpoWebBrowser: 7595ccac6938eb65b076385fd23d035db9ecdc8e
diff --git a/ios/cally.xcodeproj/project.pbxproj b/ios/cally.xcodeproj/project.pbxproj
index aae8a40..c9f5a41 100644
--- a/ios/cally.xcodeproj/project.pbxproj
+++ b/ios/cally.xcodeproj/project.pbxproj
@@ -178,7 +178,9 @@
LastUpgradeCheck = 1130;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
+ DevelopmentTeam = MV9C3PHV87;
LastSwiftMigration = 1250;
+ ProvisioningStyle = Automatic;
};
};
};
@@ -302,6 +304,7 @@
"${PODS_CONFIGURATION_BUILD_DIR}/EXUpdates/EXUpdates.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/ExpoLocalization/ExpoLocalization_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseAuth/FirebaseAuth_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseCore/FirebaseCore_Privacy.bundle",
@@ -337,6 +340,7 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXUpdates.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoLocalization_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseAuth_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseCore_Privacy.bundle",
@@ -421,7 +425,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = cally/cally.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = MV9C3PHV87;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
@@ -441,7 +448,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = CallyFamilyPlanner;
+ PRODUCT_NAME = "Cally";
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -457,7 +464,10 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = cally/cally.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = MV9C3PHV87;
INFOPLIST_FILE = cally/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.4;
LD_RUNPATH_SEARCH_PATHS = (
@@ -472,7 +482,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = CallyFamilyPlanner;
+ PRODUCT_NAME = "Cally";
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/ios/cally/Info.plist b/ios/cally/Info.plist
index 7859764..c246950 100644
--- a/ios/cally/Info.plist
+++ b/ios/cally/Info.plist
@@ -4,10 +4,12 @@
CADisableMinimumFrameDurationOnPhone
+ CFBundleAllowMixedLocalizations
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
- Cally - Family Planner
+ Cally.
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -55,12 +57,20 @@
NSAllowsLocalNetworking
+ NSCalendarsFullAccessUsageDescription
+ The app needs to access your calendar.
+ NSCalendarsUsageDescription
+ The app needs to access your calendar.
NSCameraUsageDescription
Allow $(PRODUCT_NAME) to access your camera
NSMicrophoneUsageDescription
Allow $(PRODUCT_NAME) to access your microphone
NSPhotoLibraryUsageDescription
Allow $(PRODUCT_NAME) to access your photos
+ NSRemindersFullAccessUsageDescription
+ Allow $(PRODUCT_NAME) to access your reminders
+ NSRemindersUsageDescription
+ Allow $(PRODUCT_NAME) to access your reminders
NSUserActivityTypes
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
@@ -92,6 +102,12 @@
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
UILaunchStoryboardName
SplashScreen
diff --git a/ios/cally/cally.entitlements b/ios/cally/cally.entitlements
index 018a6e2..3f2ea61 100644
--- a/ios/cally/cally.entitlements
+++ b/ios/cally/cally.entitlements
@@ -4,5 +4,9 @@
aps-environment
development
+ com.apple.developer.applesignin
+
+ Default
+
\ No newline at end of file
diff --git a/package.json b/package.json
index 17b0b52..a729afe 100644
--- a/package.json
+++ b/package.json
@@ -44,9 +44,11 @@
"debounce": "^2.1.1",
"expo": "~51.0.24",
"expo-app-loading": "^2.1.1",
+ "expo-apple-authentication": "~6.4.2",
"expo-auth-session": "^5.5.2",
"expo-barcode-scanner": "~13.0.1",
"expo-build-properties": "~0.12.4",
+ "expo-calendar": "~13.0.5",
"expo-camera": "~15.0.16",
"expo-constants": "~16.0.2",
"expo-dev-client": "~4.0.27",
@@ -54,6 +56,7 @@
"expo-font": "~12.0.10",
"expo-image-picker": "~15.0.7",
"expo-linking": "~6.3.1",
+ "expo-localization": "~15.0.3",
"expo-notifications": "~0.28.18",
"expo-router": "~3.5.20",
"expo-splash-screen": "~0.27.5",
@@ -84,7 +87,9 @@
"react-native-toast-message": "^2.2.1",
"react-native-ui-lib": "^7.27.0",
"react-native-web": "~0.19.10",
- "react-query": "^3.39.3"
+ "react-query": "^3.39.3",
+ "timezonecomplete": "^5.13.1",
+ "tzdata": "^1.0.42"
},
"devDependencies": {
"@babel/core": "^7.20.0",
diff --git a/yarn.lock b/yarn.lock
index bbbd3ac..2c6e8c2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5001,6 +5001,11 @@ expo-app-loading@^2.1.1:
dependencies:
expo-splash-screen "~0.17.0"
+expo-apple-authentication@~6.4.2:
+ version "6.4.2"
+ resolved "https://registry.yarnpkg.com/expo-apple-authentication/-/expo-apple-authentication-6.4.2.tgz#1c2ea4fcbd1de5736483dccd370cdd6b8e3de15d"
+ integrity sha512-X4u1n3Ql1hOpztXHbKNq4I1l4+Ff82gC6RmEeW43Eht7VE6E8PrQBpYKw+JJv8osrCJt7R5O1PZwed6WLN5oig==
+
expo-application@~5.9.0:
version "5.9.1"
resolved "https://registry.npmjs.org/expo-application/-/expo-application-5.9.1.tgz"
@@ -5042,6 +5047,11 @@ expo-build-properties@~0.12.4:
ajv "^8.11.0"
semver "^7.6.0"
+expo-calendar@~13.0.5:
+ version "13.0.5"
+ resolved "https://registry.yarnpkg.com/expo-calendar/-/expo-calendar-13.0.5.tgz#cdc85978cb59d99d6fc9a4fcd2c33d56cc7c8008"
+ integrity sha512-Wkk7eHvlyhWz2csxU6guYA2HFcLUfYpmlsdMy4a6bneBmFqIZG/ldnLKq/lcQ+BCrfI3fOULt3aNdF6SlZtLlw==
+
expo-camera@~15.0.16:
version "15.0.16"
resolved "https://registry.npmjs.org/expo-camera/-/expo-camera-15.0.16.tgz"
@@ -5153,6 +5163,13 @@ expo-linking@~6.3.0, expo-linking@~6.3.1:
expo-constants "~16.0.0"
invariant "^2.2.4"
+expo-localization@~15.0.3:
+ version "15.0.3"
+ resolved "https://registry.yarnpkg.com/expo-localization/-/expo-localization-15.0.3.tgz#772c89b3ab9c925b7eca6911a11ca33980c2b674"
+ integrity sha512-IfcmlKuKRlowR9qIzL0e+nGHBeNoF7l2GQaOJstc7HZiPjNJ4J1R4D53ZNf483dt7JSkTRJBihdTadOtOEjRdg==
+ dependencies:
+ rtl-detect "^1.0.2"
+
expo-manifests@~0.14.0:
version "0.14.3"
resolved "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.14.3.tgz"
@@ -9276,6 +9293,11 @@ rimraf@~2.6.2:
dependencies:
glob "^7.1.3"
+rtl-detect@^1.0.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.1.2.tgz#ca7f0330af5c6bb626c15675c642ba85ad6273c6"
+ integrity sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==
+
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
@@ -10124,6 +10146,13 @@ through@2:
resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
+timezonecomplete@^5.13.1:
+ version "5.13.1"
+ resolved "https://registry.yarnpkg.com/timezonecomplete/-/timezonecomplete-5.13.1.tgz#72c05e82b33013bacc7a38e5d554eafc7914b31f"
+ integrity sha512-41o3TTExXQ03jQML12Tk64b5TYeEy968Umq5vya+08sFWCcYw5fNqrHubD1vj/JGN1NYhFFBCS09rOL3b7nM2w==
+ dependencies:
+ tzdata "1.0.25"
+
tinycolor2@^1.4.1, tinycolor2@^1.4.2:
version "1.6.0"
resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz"
@@ -10313,6 +10342,16 @@ typical@^2.6.0:
resolved "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz"
integrity sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==
+tzdata@1.0.25:
+ version "1.0.25"
+ resolved "https://registry.yarnpkg.com/tzdata/-/tzdata-1.0.25.tgz#e8839033c05761e04ef552242e779777becb13d0"
+ integrity sha512-yAZ/Tv/tBFIPHJGYrOexxW8Swyjszt7rDhIjnIPSqLaP8mzrr3T7D0w4cxQBtToXnQrlFqkEU0stGC/auz0JcQ==
+
+tzdata@^1.0.42:
+ version "1.0.42"
+ resolved "https://registry.yarnpkg.com/tzdata/-/tzdata-1.0.42.tgz#4f278809b50c50e9c865e44969aa7e746b165638"
+ integrity sha512-hVA4V8g27yz1YB4Ty4UliwJlWrFOoFrFBYFMd9rKUlRlaF+9Fl3gyzxF/+MQOtCH50pPE+XZ/bYOYkRnBDscVQ==
+
ua-parser-js@^0.7.33:
version "0.7.39"
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.39.tgz"
From 478341cce4a518be48a61a3bdbf533914ff8ce85 Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sat, 19 Oct 2024 23:40:08 +0200
Subject: [PATCH 6/9] Switch to day view when clicking on days in week and
month view. Refresh tokens every 12 hrs
---
components/pages/calendar/EventCalendar.tsx | 7 ++-
firebase/functions/index.js | 70 ++++++++++++++++++++-
ios/cally/Info.plist | 1 +
3 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/components/pages/calendar/EventCalendar.tsx b/components/pages/calendar/EventCalendar.tsx
index 487b097..ca02e5f 100644
--- a/components/pages/calendar/EventCalendar.tsx
+++ b/components/pages/calendar/EventCalendar.tsx
@@ -20,7 +20,7 @@ export const EventCalendar: React.FC = memo(({calendarHeight
const {data: events} = useGetEvents();
const {profileData} = useAuthContext()
const [selectedDate, setSelectedDate] = useAtom(selectedDateAtom)
- const mode = useAtomValue(modeAtom)
+ const [mode, setMode] = useAtom(modeAtom)
const setEditVisible = useSetAtom(editVisibleAtom)
const setEventForEdit = useSetAtom(eventForEditAtom)
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom)
@@ -41,7 +41,10 @@ export const EventCalendar: React.FC = memo(({calendarHeight
height={calendarHeight}
activeDate={selectedDate}
date={selectedDate}
- onPressCell={setSelectedNewEndDate}
+ onPressCell={mode === "day" ? setSelectedNewEndDate: (date) => {
+ setSelectedDate(date)
+ setMode("day")
+ }}
headerContentStyle={mode === "day" ? styles.dayModeHeader : {}}
onSwipeEnd={(date) => {
setSelectedDate(date);
diff --git a/firebase/functions/index.js b/firebase/functions/index.js
index 4827235..0569dd0 100644
--- a/firebase/functions/index.js
+++ b/firebase/functions/index.js
@@ -11,7 +11,6 @@ const db = admin.firestore();
let expo = new Expo({accessToken: process.env.EXPO_ACCESS_TOKEN});
-// Firestore trigger that listens for new events in the 'Events' collection
exports.sendNotificationOnEventCreation = functions.firestore
.document('Events/{eventId}')
.onCreate(async (snapshot, context) => {
@@ -193,6 +192,73 @@ exports.generateCustomToken = onRequest(async (request, response) => {
}
});
+exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async (context) => {
+ console.log('Running token refresh job...');
+
+ const profilesSnapshot = await db.collection('Profiles').get();
+
+ profilesSnapshot.forEach(async (profileDoc) => {
+ const profileData = profileDoc.data();
+
+ if (profileData.googleToken) {
+ try {
+ const refreshedGoogleToken = await refreshGoogleToken(profileData.googleToken);
+ await profileDoc.ref.update({ googleToken: refreshedGoogleToken });
+ console.log(`Google token updated for user ${profileDoc.id}`);
+ } catch (error) {
+ console.error(`Error refreshing Google token for user ${profileDoc.id}:`, error.message);
+ }
+ }
+
+ if (profileData.microsoftToken) {
+ try {
+ const refreshedMicrosoftToken = await refreshMicrosoftToken(profileData.microsoftToken);
+ await profileDoc.ref.update({ microsoftToken: refreshedMicrosoftToken });
+ console.log(`Microsoft token updated for user ${profileDoc.id}`);
+ } catch (error) {
+ console.error(`Error refreshing Microsoft token for user ${profileDoc.id}:`, error.message);
+ }
+ }
+
+ if (profileData.appleToken) {
+ try {
+ const refreshedAppleToken = await refreshAppleToken(profileData.appleToken);
+ await profileDoc.ref.update({ appleToken: refreshedAppleToken });
+ console.log(`Apple token updated for user ${profileDoc.id}`);
+ } catch (error) {
+ console.error(`Error refreshing Apple token for user ${profileDoc.id}:`, error.message);
+ }
+ }
+ });
+
+ return null;
+});
+
+// Function to refresh Google token
+async function refreshGoogleToken(token) {
+ // Assuming you use OAuth2 token refresh flow
+ const response = await axios.post('https://oauth2.googleapis.com/token', {
+ grant_type: 'refresh_token',
+ refresh_token: token, // Add refresh token stored previously
+ client_id: 'YOUR_GOOGLE_CLIENT_ID',
+ client_secret: 'YOUR_GOOGLE_CLIENT_SECRET',
+ });
+
+ return response.data.access_token; // Return new access token
+}
+
+async function refreshMicrosoftToken(token) {
+ const response = await axios.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
+ grant_type: 'refresh_token',
+ refresh_token: token, // Add refresh token stored previously
+ client_id: 'YOUR_MICROSOFT_CLIENT_ID',
+ client_secret: 'YOUR_MICROSOFT_CLIENT_SECRET',
+ scope: 'https://graph.microsoft.com/Calendars.ReadWrite offline_access',
+ });
+
+ return response.data.access_token; // Return new access token
+}
+
async function getPushTokensForEvent() {
const usersRef = db.collection('Profiles');
const snapshot = await usersRef.get();
@@ -223,4 +289,4 @@ async function getPushTokensForFamilyExcludingCreator(familyId, creatorId) {
});
return pushTokens;
-}
+}
\ No newline at end of file
diff --git a/ios/cally/Info.plist b/ios/cally/Info.plist
index c246950..919ffbc 100644
--- a/ios/cally/Info.plist
+++ b/ios/cally/Info.plist
@@ -108,6 +108,7 @@
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
UILaunchStoryboardName
SplashScreen
From d87d67eae50d61da792559ee1c18e1251df3be4c Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sun, 20 Oct 2024 00:53:09 +0200
Subject: [PATCH 7/9] Added attendees
---
.../pages/calendar/ManuallyAddEventModal.tsx | 62 +++++++++----
components/shared/AssigneesDisplay.tsx | 90 +++++++++++++++----
hooks/firebase/types/eventData.ts | 1 +
hooks/firebase/types/profileTypes.ts | 1 +
hooks/firebase/useGetEvents.ts | 3 +-
5 files changed, 120 insertions(+), 37 deletions(-)
diff --git a/components/pages/calendar/ManuallyAddEventModal.tsx b/components/pages/calendar/ManuallyAddEventModal.tsx
index 5e2e748..f82188a 100644
--- a/components/pages/calendar/ManuallyAddEventModal.tsx
+++ b/components/pages/calendar/ManuallyAddEventModal.tsx
@@ -5,6 +5,8 @@ import {
DateTimePicker,
LoaderScreen,
Modal,
+ Picker,
+ PickerModes,
Switch,
Text,
TextField,
@@ -28,6 +30,7 @@ import CameraIcon from "@/assets/svgs/CameraIcon";
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
import {useAtom} from "jotai";
import {selectedNewEventDateAtom} from "@/components/pages/calendar/atoms";
+import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
const daysOfWeek = [
{label: "Monday", value: "monday"},
@@ -70,9 +73,13 @@ export const ManuallyAddEventModal = () => {
const [startDate, setStartDate] = useState(initialDate ?? new Date());
const [endDate, setEndDate] = useState(initialDate ?? new Date());
+ const [selectedAttendees, setSelectedAttendees] = useState([]);
+
const [repeatInterval, setRepeatInterval] = useState([]);
const {mutateAsync: createEvent, isLoading, isError} = useCreateEvent();
+ const {data: members} = useGetFamilyMembers(true)
+
if (!selectedNewEventDate) return null;
@@ -111,6 +118,7 @@ export const ManuallyAddEventModal = () => {
startDate: finalStartDate,
endDate: finalEndDate,
allDay: isAllDay,
+ attendees: selectedAttendees,
};
await createEvent(eventData);
@@ -338,27 +346,45 @@ export const ManuallyAddEventModal = () => {
>
Attendees
- (
-
- )}
- style={{
- marginLeft: "auto",
- borderRadius: 8,
- backgroundColor: "#ffe8f1",
- borderColor: "#ea156c",
- borderWidth: 1,
- }}
- color="#ea156c"
- label="Add"
- labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
- />
+
+ setSelectedAttendees(value as string[] ?? [])}
+ style={{marginLeft: "auto"}}
+ mode={PickerModes.MULTI}
+ renderInput={() =>
+ (
+
+ )}
+ style={{
+ marginLeft: "auto",
+ borderRadius: 8,
+ backgroundColor: "#ffe8f1",
+ borderColor: "#ea156c",
+ borderWidth: 1,
+ }}
+ color="#ea156c"
+ label="Add"
+ labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
+ />
+ }>
+ {members?.map((member) => (
+
+ ))}
+
+
-
+
+
diff --git a/components/shared/AssigneesDisplay.tsx b/components/shared/AssigneesDisplay.tsx
index 82c028c..9316394 100644
--- a/components/shared/AssigneesDisplay.tsx
+++ b/components/shared/AssigneesDisplay.tsx
@@ -1,23 +1,77 @@
import React from "react";
-import { ImageBackground, StyleSheet } from "react-native";
-import { View } from "react-native-ui-lib";
+import {ImageBackground, StyleSheet} from "react-native";
+import {Text, TouchableOpacity, View} from "react-native-ui-lib";
import RemoveAssigneeBtn from "./RemoveAssigneeBtn";
+import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
-const AssigneesDisplay = () => {
- return (
-
- }
- />
- }
- />
-
- );
+const AssigneesDisplay = ({selectedAttendees, setSlectedAttendees}: {
+ selectedAttendees: string[],
+ setSlectedAttendees: (value: React.SetStateAction) => void
+}) => {
+ const {data: members} = useGetFamilyMembers(true);
+
+ const selectedMembers = members?.filter((x) => selectedAttendees.includes(x?.uid!));
+
+ const getInitials = (firstName: string, lastName: string) => {
+ return `${firstName.charAt(0)}${lastName.charAt(0)}`;
+ };
+
+ const removeAttendee = (uid: string) => {
+ setSlectedAttendees((prev) => prev.filter((x) => x !== uid));
+ }
+
+ return (
+
+ {selectedMembers?.map((member) => (
+ removeAttendee(member.uid!)}>
+ {member?.pfp ? (
+ }
+ />
+ ) : (
+
+
+ {getInitials(member.firstName, member.lastName)}
+
+
+ )}
+
+
+ ))}
+
+ {selectedAttendees.length === 0 && No attendees added}
+
+ );
};
-export default AssigneesDisplay;
+const styles = StyleSheet.create({
+ assigneeWrapper: {
+ position: 'relative',
+ width: 58.08,
+ aspectRatio: 1,
+ },
+ image: {
+ aspectRatio: 1,
+ width: '100%',
+ borderRadius: 100, // Makes the image circular
+ overflow: 'hidden',
+ },
+ initialsCircle: {
+ backgroundColor: '#ccc',
+ justifyContent: 'center',
+ alignItems: 'center',
+ borderRadius: 100, // Circular shape
+ width: '100%',
+ height: '100%',
+ },
+ initialsText: {
+ color: '#fff',
+ fontSize: 24,
+ fontWeight: 'bold',
+ },
+});
+
+export default AssigneesDisplay;
\ No newline at end of file
diff --git a/hooks/firebase/types/eventData.ts b/hooks/firebase/types/eventData.ts
index b7c6136..616ff19 100644
--- a/hooks/firebase/types/eventData.ts
+++ b/hooks/firebase/types/eventData.ts
@@ -11,4 +11,5 @@ export interface EventData {
notes?: string,
reminders?: string[]
id?: string | number,
+ attendees?: string[]
}
\ No newline at end of file
diff --git a/hooks/firebase/types/profileTypes.ts b/hooks/firebase/types/profileTypes.ts
index 8c6ea4a..64bdd84 100644
--- a/hooks/firebase/types/profileTypes.ts
+++ b/hooks/firebase/types/profileTypes.ts
@@ -17,6 +17,7 @@ export interface UserProfile {
password: string;
familyId?: string;
uid?: string;
+ pfp?: string;
googleToken?: string | null;
microsoftToken?: string | null;
appleToken?: string | null;
diff --git a/hooks/firebase/useGetEvents.ts b/hooks/firebase/useGetEvents.ts
index 74b79b1..d1f8c15 100644
--- a/hooks/firebase/useGetEvents.ts
+++ b/hooks/firebase/useGetEvents.ts
@@ -14,7 +14,8 @@ export const useGetEvents = () => {
queryFn: async () => {
const eventsQuery = firestore()
.collection("Events")
- .where("creatorId", "==", user?.uid);
+ .where("creatorId", "==", user?.uid)
+ .where("attendees", "array-contains", user?.uid);
if (isFamilyView) {
eventsQuery.where("familyID", "==", profileData?.familyId);
From 00583ac1150bc49d62f920c3e3b9173302cf369b Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sun, 20 Oct 2024 01:44:20 +0200
Subject: [PATCH 8/9] Various updates....
---
app.json | 5 +-
app/_layout.tsx | 8 +
.../settings/user_settings_views/MyGroup.tsx | 255 ++++++++++--------
hooks/firebase/useGetEvents.ts | 68 +++--
ios/Podfile | 1 +
ios/Podfile.lock | 21 +-
ios/ReactNativeKeyboardManager.swift | 7 +
ios/cally.xcodeproj/project.pbxproj | 14 +-
.../xcshareddata/xcschemes/cally.xcscheme | 6 +-
ios/cally/Info.plist | 1 +
package.json | 1 +
plugins/withPodfile.js | 42 +++
yarn.lock | 5 +
13 files changed, 280 insertions(+), 154 deletions(-)
create mode 100644 ios/ReactNativeKeyboardManager.swift
create mode 100644 plugins/withPodfile.js
diff --git a/app.json b/app.json
index cc3e629..f6dccd7 100644
--- a/app.json
+++ b/app.json
@@ -16,7 +16,7 @@
"supportsTablet": true,
"bundleIdentifier": "com.cally.app",
"googleServicesFile": "./ios/GoogleService-Info.plist",
- "buildNumber": "23",
+ "buildNumber": "30",
"usesAppleSignIn": true
},
"android": {
@@ -74,7 +74,8 @@
"expo-apple-authentication"
],
"expo-font",
- "expo-localization"
+ "expo-localization",
+ "./plugins/withPodfile"
],
"experiments": {
"typedRoutes": true
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 9b2e5f8..fb8d977 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -52,11 +52,19 @@ 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';
SplashScreen.preventAutoHideAsync();
const queryClient = new QueryClient();
+
+if (Platform.OS === 'ios') {
+ KeyboardManager.setEnable(true);
+ KeyboardManager.setToolbarPreviousNextButtonEnable(true);
+}
+
if (__DEV__) {
// functions().useEmulator("localhost", 5001);
// firestore().useEmulator("localhost", 5471);
diff --git a/components/pages/settings/user_settings_views/MyGroup.tsx b/components/pages/settings/user_settings_views/MyGroup.tsx
index de4e2e1..7164082 100644
--- a/components/pages/settings/user_settings_views/MyGroup.tsx
+++ b/components/pages/settings/user_settings_views/MyGroup.tsx
@@ -5,6 +5,7 @@ import {
Colors,
Dialog,
FloatingButton,
+ KeyboardAwareScrollView,
PanningProvider,
Picker,
Text,
@@ -27,6 +28,7 @@ import CircledXIcon from "@/assets/svgs/CircledXIcon";
import ProfileIcon from "@/assets/svgs/ProfileIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import Ionicons from "@expo/vector-icons/Ionicons";
+import {PreviousNextView} from "react-native-keyboard-manager";
const MyGroup = () => {
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
@@ -299,136 +301,151 @@ const MyGroup = () => {
visible={showNewUserInfoDialog}
onDismiss={() => setShowNewUserInfoDialog(false)}
>
-
-
-
- New User Information
-
- {
- setShowNewUserInfoDialog(false)
- }}>
-
-
-
-
+
+
+
+
+
+ New User Information
+
+ {
+ setShowNewUserInfoDialog(false)
+ }}>
+
+
+
+
-
-
- }
- backgroundColor={Colors.grey60}
- style={{borderRadius: 25}}
- center
- />
- {
- }}>
-
- 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)}
- showSearch
- floatingPlaceholder
- style={styles.inViewPicker}
- trailingAccessory={
-
-
-
- }
- >
-
-
-
-
-
-
-
-
- {selectedStatus === ProfileType.FAMILY_DEVICE
- ? "Device Name"
- : "First Name"}
-
- {
- lNameRef.current?.focus()
- }}
- blurOnSubmit={false}
- />
-
- {selectedStatus !== ProfileType.FAMILY_DEVICE && (
- <>
- Last Name
{
- emailRef.current?.focus()
+ 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)
+
+ >
+ )}
+
+ }
+ onPress={handleCreateSubUser}
/>
- >
- )}
-
- }
- onPress={handleCreateSubUser}
- />
-
+
+
+
);
diff --git a/hooks/firebase/useGetEvents.ts b/hooks/firebase/useGetEvents.ts
index d1f8c15..ae45c89 100644
--- a/hooks/firebase/useGetEvents.ts
+++ b/hooks/firebase/useGetEvents.ts
@@ -12,37 +12,57 @@ export const useGetEvents = () => {
return useQuery({
queryKey: ["events", user?.uid, isFamilyView],
queryFn: async () => {
- const eventsQuery = firestore()
- .collection("Events")
- .where("creatorId", "==", user?.uid)
- .where("attendees", "array-contains", user?.uid);
+ const db = firestore();
+
+ const userId = user?.uid; // Assuming user is defined
+ const familyId = profileData?.familyId; // Assuming profileData is defined
+ let allEvents = [];
if (isFamilyView) {
- eventsQuery.where("familyID", "==", profileData?.familyId);
+ const familyQuery = db.collection("Events").where("familyID", "==", familyId);
+ const familySnapshot = await familyQuery.get();
+ const familyEvents = familySnapshot.docs.map(doc => doc.data());
+
+ allEvents = [...familyEvents];
+ } else {
+ const creatorQuery = db.collection("Events").where("creatorId", "==", userId);
+ const attendeeQuery = db.collection("Events").where("attendees", "array-contains", userId);
+
+ const [creatorSnapshot, attendeeSnapshot] = await Promise.all([
+ creatorQuery.get(),
+ attendeeQuery.get(),
+ ]);
+
+ const creatorEvents = creatorSnapshot.docs.map(doc => doc.data());
+ const attendeeEvents = attendeeSnapshot.docs.map(doc => doc.data());
+
+ allEvents = [...creatorEvents, ...attendeeEvents];
}
- const snapshot = await eventsQuery.get();
+ allEvents = allEvents.filter((event, index, self) =>
+ index === self.findIndex(e => e.id === event.id)
+ );
- return await Promise.all(snapshot.docs.map(async (doc) => {
- const data = doc.data();
+ return await Promise.all(
+ allEvents.map(async (event) => {
+ const profileSnapshot = await db
+ .collection("Profiles")
+ .doc(event.creatorId)
+ .get();
- const profileSnapshot = await firestore()
- .collection("Profiles")
- .doc(data.creatorId)
- .get();
+ const profileData = profileSnapshot.data();
+ const eventColor = profileData?.eventColor || colorMap.pink; // Default color if not found
- const profileData = profileSnapshot.data();
- const eventColor: string = profileData?.eventColor || colorMap.pink // Default color if not found
-
- return {
- id: doc.id,
- title: data.title,
- start: new Date(data.startDate.seconds * 1000),
- end: new Date(data.endDate.seconds * 1000),
- hideHours: data.allDay,
- eventColor: eventColor,
- };
- }));
+ return {
+ id: event.id,
+ title: event.title,
+ start: new Date(event.startDate.seconds * 1000),
+ end: new Date(event.endDate.seconds * 1000),
+ hideHours: event.allDay,
+ eventColor: eventColor,
+ };
+ })
+ );
},
staleTime: Infinity,
cacheTime: Infinity
diff --git a/ios/Podfile b/ios/Podfile
index edd194e..6a08a06 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -14,6 +14,7 @@ install! 'cocoapods',
prepare_react_native_project!
target 'cally' do
+ pod 'IQKeyboardManagerSwift', :git => 'https://github.com/douglasjunior/IQKeyboardManager.git', :branch => 'react-native-keyboard-manager'
use_expo_modules!
config = use_native_modules!
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 407bbe1..f8b463d 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1495,6 +1495,7 @@ PODS:
- hermes-engine (0.74.3):
- hermes-engine/Pre-built (= 0.74.3)
- hermes-engine/Pre-built (0.74.3)
+ - IQKeyboardManagerSwift (6.5.16)
- leveldb-library (1.22.5)
- nanopb (2.30909.1):
- nanopb/decode (= 2.30909.1)
@@ -2688,6 +2689,10 @@ PODS:
- React-logger (= 0.74.3)
- React-perflogger (= 0.74.3)
- React-utils (= 0.74.3)
+ - ReactNativeKeyboardManager (6.5.16-0):
+ - IQKeyboardManagerSwift (= 6.5.16)
+ - React-Core
+ - React-RCTText
- ReactNativeUiLib (4.2.0):
- React
- RecaptchaInterop (100.0.0)
@@ -2831,6 +2836,7 @@ DEPENDENCIES:
- fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
+ - IQKeyboardManagerSwift (from `https://github.com/douglasjunior/IQKeyboardManager.git`, branch `react-native-keyboard-manager`)
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
@@ -2883,6 +2889,7 @@ DEPENDENCIES:
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
+ - ReactNativeKeyboardManager (from `../node_modules/react-native-keyboard-manager`)
- ReactNativeUiLib (from `../node_modules/react-native-ui-lib`)
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
@@ -3012,6 +3019,9 @@ EXTERNAL SOURCES:
hermes-engine:
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
:tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85
+ IQKeyboardManagerSwift:
+ :branch: react-native-keyboard-manager
+ :git: https://github.com/douglasjunior/IQKeyboardManager.git
RCT-Folly:
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
RCTDeprecation:
@@ -3112,6 +3122,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
+ ReactNativeKeyboardManager:
+ :path: "../node_modules/react-native-keyboard-manager"
ReactNativeUiLib:
:path: "../node_modules/react-native-ui-lib"
RNDateTimePicker:
@@ -3137,6 +3149,11 @@ EXTERNAL SOURCES:
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
+CHECKOUT OPTIONS:
+ IQKeyboardManagerSwift:
+ :commit: 718cbed77cdd5ecd8b779afe543ba5b2df45b40a
+ :git: https://github.com/douglasjunior/IQKeyboardManager.git
+
SPEC CHECKSUMS:
abseil: d121da9ef7e2ff4cab7666e76c5a3e0915ae08c3
AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa
@@ -3201,6 +3218,7 @@ SPEC CHECKSUMS:
gRPC-Core: eee4be35df218649fe66d721a05a7f27a28f069b
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
hermes-engine: 1f547997900dd0752dc0cc0ae6dd16173c49e09b
+ IQKeyboardManagerSwift: 90ba81812fbbd6694924a95a271fa3affdf04a14
leveldb-library: e8eadf9008a61f9e1dde3978c086d2b6d9b9dc28
nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
@@ -3256,6 +3274,7 @@ SPEC CHECKSUMS:
React-runtimescheduler: e4ad653e1d2f5ff40ba047446cacde009694f0ed
React-utils: 6f7ac39d9a0de447d4334bb25d144a28c0c5d8c9
ReactCommon: 4a09c7d8a06e93c1e2e988a3b9f3db3d2449f2fc
+ ReactNativeKeyboardManager: 704d89bde3cb1e0f432bc273a44eec96eab9d90f
ReactNativeUiLib: deb877cd9b36cf5cad3c72b226bb330060681351
RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14
@@ -3273,6 +3292,6 @@ SPEC CHECKSUMS:
Yoga: bd92064a0d558be92786820514d74fc4dddd1233
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
-PODFILE CHECKSUM: 50f618790da7cbbfd5c5e988b7f9370bd45d34a6
+PODFILE CHECKSUM: ae388457578eb44dbbdba1451a584b59f3bc21dd
COCOAPODS: 1.15.2
diff --git a/ios/ReactNativeKeyboardManager.swift b/ios/ReactNativeKeyboardManager.swift
new file mode 100644
index 0000000..a96080a
--- /dev/null
+++ b/ios/ReactNativeKeyboardManager.swift
@@ -0,0 +1,7 @@
+//
+// ReactNativeKeyboardManager.swift
+// cally
+//
+// Created by Milan Paunovic on 20.10.24..
+//
+
diff --git a/ios/cally.xcodeproj/project.pbxproj b/ios/cally.xcodeproj/project.pbxproj
index c9f5a41..311c8c3 100644
--- a/ios/cally.xcodeproj/project.pbxproj
+++ b/ios/cally.xcodeproj/project.pbxproj
@@ -21,7 +21,7 @@
/* Begin PBXFileReference section */
103D20271F044483964A389F /* cally-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "cally-Bridging-Header.h"; path = "cally/cally-Bridging-Header.h"; sourceTree = ""; };
- 13B07F961A680F5B00A75B9A /* cally.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = cally.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 13B07F961A680F5B00A75B9A /* Cally.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cally.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = cally/AppDelegate.h; sourceTree = ""; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = cally/AppDelegate.mm; sourceTree = ""; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = cally/Images.xcassets; sourceTree = ""; };
@@ -35,6 +35,7 @@
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
F20F68FCCB33056D70B2396B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = cally/PrivacyInfo.xcprivacy; sourceTree = ""; };
+ F3A90F152CC474F700DDA353 /* ReactNativeKeyboardManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactNativeKeyboardManager.swift; sourceTree = ""; };
F56C9EADA6FA4AEAA71245EB /* GoogleService-Info.plist */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "cally/GoogleService-Info.plist"; sourceTree = ""; };
FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-cally/ExpoModulesProvider.swift"; sourceTree = ""; };
/* End PBXFileReference section */
@@ -88,6 +89,7 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
+ F3A90F152CC474F700DDA353 /* ReactNativeKeyboardManager.swift */,
13B07FAE1A68108700A75B9A /* cally */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
@@ -103,7 +105,7 @@
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
- 13B07F961A680F5B00A75B9A /* cally.app */,
+ 13B07F961A680F5B00A75B9A /* Cally.app */,
);
name = Products;
sourceTree = "";
@@ -166,7 +168,7 @@
);
name = cally;
productName = cally;
- productReference = 13B07F961A680F5B00A75B9A /* cally.app */;
+ productReference = 13B07F961A680F5B00A75B9A /* Cally.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -317,6 +319,7 @@
"${PODS_CONFIGURATION_BUILD_DIR}/GTMSessionFetcher/GTMSessionFetcher_Core_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleDataTransport/GoogleDataTransport_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUtilities/GoogleUtilities_Privacy.bundle",
+ "${PODS_CONFIGURATION_BUILD_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/PromisesSwift/Promises_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift/ReachabilitySwift.bundle",
@@ -353,6 +356,7 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMSessionFetcher_Core_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleDataTransport_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleUtilities_Privacy.bundle",
+ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/IQKeyboardManagerSwift.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Promises_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ReachabilitySwift.bundle",
@@ -448,7 +452,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = "Cally";
+ PRODUCT_NAME = Cally;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -482,7 +486,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = "Cally";
+ PRODUCT_NAME = Cally;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/ios/cally.xcodeproj/xcshareddata/xcschemes/cally.xcscheme b/ios/cally.xcodeproj/xcshareddata/xcschemes/cally.xcscheme
index 098cf8c..96c3bb1 100644
--- a/ios/cally.xcodeproj/xcshareddata/xcschemes/cally.xcscheme
+++ b/ios/cally.xcodeproj/xcshareddata/xcschemes/cally.xcscheme
@@ -15,7 +15,7 @@
@@ -55,7 +55,7 @@
@@ -72,7 +72,7 @@
diff --git a/ios/cally/Info.plist b/ios/cally/Info.plist
index 919ffbc..1e3bcc8 100644
--- a/ios/cally/Info.plist
+++ b/ios/cally/Info.plist
@@ -109,6 +109,7 @@
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
UILaunchStoryboardName
SplashScreen
diff --git a/package.json b/package.json
index a729afe..92e580b 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
"react-native-calendars": "^1.1306.0",
"react-native-gesture-handler": "~2.16.1",
"react-native-gifted-charts": "^1.4.41",
+ "react-native-keyboard-manager": "^6.5.16-0",
"react-native-linear-gradient": "^2.8.3",
"react-native-onboarding-swiper": "^1.3.0",
"react-native-qrcode-svg": "^6.3.2",
diff --git a/plugins/withPodfile.js b/plugins/withPodfile.js
new file mode 100644
index 0000000..fd3a65e
--- /dev/null
+++ b/plugins/withPodfile.js
@@ -0,0 +1,42 @@
+const { withDangerousMod, withPlugins } = require('@expo/config-plugins');
+
+const fs = require('fs');
+const path = require('path');
+
+async function readFile(path) {
+ return fs.promises.readFile(path, 'utf8');
+}
+
+async function saveFile(path, content) {
+ return fs.promises.writeFile(path, content, 'utf8');
+}
+
+module.exports = (config) =>
+ withPlugins(config, [
+ (config) => {
+ return withDangerousMod(config, [
+ 'iOS',
+ async (config) => {
+ const file = path.join(config.modRequest.platformProjectRoot, 'Podfile');
+
+ /*
+ * You need to remove the line before adding it.
+ * If you don't do this and you run `expo prebuild` in a dirt project
+ * your file will have the same line added twice
+ */
+ const contents = (await readFile(file)).replace(
+ /pod 'IQKeyboardManagerSwift', :git => 'https:\/\/github.com\/douglasjunior\/IQKeyboardManager.git', :branch => 'react-native-keyboard-manager'\n\n/g,
+ '',
+ );
+ /*
+ * Now re-adds the content
+ */
+ await saveFile(
+ file,
+ `pod 'IQKeyboardManagerSwift', :git => 'https://github.com/douglasjunior/IQKeyboardManager.git', :branch => 'react-native-keyboard-manager'\n\n${contents}`,
+ );
+ return config;
+ },
+ ]);
+ },
+ ]);
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 2c6e8c2..087dffe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8872,6 +8872,11 @@ react-native-helmet-async@2.0.4:
react-fast-compare "^3.2.2"
shallowequal "^1.1.0"
+react-native-keyboard-manager@^6.5.16-0:
+ version "6.5.16-0"
+ resolved "https://registry.yarnpkg.com/react-native-keyboard-manager/-/react-native-keyboard-manager-6.5.16-0.tgz#ead536f01ade296f483cbf3ecaf3026cff702c9c"
+ integrity sha512-vdNp7PZ0hfxeJXqi7RaHTriQgZqIFKaG3Kx8e4Bdtkne8y9wQ1gGsiAfee+dmmo2kWZaDSbBB7CCKJwRIy5zGg==
+
react-native-linear-gradient@^2.8.3:
version "2.8.3"
resolved "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz"
From c63e47bae8fff65fb3af4b7f4d20115e3fbdc28e Mon Sep 17 00:00:00 2001
From: Milan Paunovic
Date: Sun, 20 Oct 2024 01:50:54 +0200
Subject: [PATCH 9/9] bundle updates
---
app.json | 2 +-
ios/cally.xcodeproj/project.pbxproj | 4 ++--
ios/cally/Info.plist | 3 ++-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/app.json b/app.json
index f6dccd7..c250b65 100644
--- a/app.json
+++ b/app.json
@@ -16,7 +16,7 @@
"supportsTablet": true,
"bundleIdentifier": "com.cally.app",
"googleServicesFile": "./ios/GoogleService-Info.plist",
- "buildNumber": "30",
+ "buildNumber": "31",
"usesAppleSignIn": true
},
"android": {
diff --git a/ios/cally.xcodeproj/project.pbxproj b/ios/cally.xcodeproj/project.pbxproj
index 311c8c3..1a86576 100644
--- a/ios/cally.xcodeproj/project.pbxproj
+++ b/ios/cally.xcodeproj/project.pbxproj
@@ -452,7 +452,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = Cally;
+ PRODUCT_NAME = "Cally";
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -486,7 +486,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = Cally;
+ PRODUCT_NAME = "Cally";
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/ios/cally/Info.plist b/ios/cally/Info.plist
index 1e3bcc8..8571d8d 100644
--- a/ios/cally/Info.plist
+++ b/ios/cally/Info.plist
@@ -47,7 +47,7 @@
CFBundleVersion
- 23
+ 31
LSRequiresIPhoneOS
NSAppTransportSecurity
@@ -110,6 +110,7 @@
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
UILaunchStoryboardName
SplashScreen