diff --git a/app/(unauth)/cal_sync.tsx b/app/(unauth)/cal_sync.tsx
new file mode 100644
index 0000000..0e9f15b
--- /dev/null
+++ b/app/(unauth)/cal_sync.tsx
@@ -0,0 +1,246 @@
+import {SafeAreaView} from "react-native-safe-area-context";
+import {Button, Text, View} from "react-native-ui-lib";
+import React from "react";
+import {useCalSync} from "@/hooks/useCalSync";
+import GoogleIcon from "@/assets/svgs/GoogleIcon";
+import AppleIcon from "@/assets/svgs/AppleIcon";
+import OutlookIcon from "@/assets/svgs/OutlookIcon";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {StyleSheet} from "react-native";
+
+export default function Screen() {
+ const {profileData, setRedirectOverride} = useAuthContext()
+ const {handleStartGoogleSignIn, handleAppleSignIn, handleMicrosoftSignIn} = useCalSync()
+
+ const hasSomeCalendarsSynced =
+ !!profileData?.appleAccounts || !!profileData?.microsoftAccounts || !!profileData?.googleAccounts
+
+ return (
+
+
+
+
+ Let's get started!
+
+
+ Add your calendar below to sync events to your Cally calendar
+
+
+
+
+ {!profileData?.googleAccounts && (
+
+
+
+
+
+ setRedirectOverride(false)}
+ marginT-50
+ labelStyle={{
+ fontFamily: "PlusJakartaSans_600SemiBold",
+ fontSize: 16,
+ }}
+ style={{height: 50}}
+ backgroundColor="#fd1775"
+ />
+
+
+
+ )
+}
+
+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,
+ },
+ noPaddingCard: {
+ backgroundColor: "white",
+ width: "100%",
+ 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,
+ },
+});
diff --git a/app/(unauth)/onboarding_flow/index.tsx b/app/(unauth)/connect.tsx
similarity index 80%
rename from app/(unauth)/onboarding_flow/index.tsx
rename to app/(unauth)/connect.tsx
index 8d09129..7abd451 100644
--- a/app/(unauth)/onboarding_flow/index.tsx
+++ b/app/(unauth)/connect.tsx
@@ -1,5 +1,5 @@
import Entry from "@/components/pages/main/Entry";
export default function Screen() {
- return ;
+ return ;
}
diff --git a/app/(unauth)/get_started.tsx b/app/(unauth)/get_started.tsx
new file mode 100644
index 0000000..44cc11f
--- /dev/null
+++ b/app/(unauth)/get_started.tsx
@@ -0,0 +1,166 @@
+import {SafeAreaView} from "react-native-safe-area-context";
+import {Button, Colors, Dialog, LoaderScreen, Text, View} from "react-native-ui-lib";
+import React, {useState} from "react";
+import {useRouter} from "expo-router";
+import QRIcon from "@/assets/svgs/QRIcon";
+import Toast from "react-native-toast-message";
+import {Camera, CameraView} from "expo-camera";
+import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
+
+export default function Screen() {
+ const router = useRouter()
+ const [hasPermission, setHasPermission] = useState(null);
+ const [showCameraDialog, setShowCameraDialog] = useState(false);
+
+ const {mutateAsync: signInWithQrCode, isLoading} = useLoginWithQrCode();
+
+ const handleQrCodeScanned = async ({data}: { data: string }) => {
+ setShowCameraDialog(false);
+ try {
+ await signInWithQrCode({userId: data});
+ Toast.show({
+ type: "success",
+ text1: "Login successful with QR code!",
+ });
+ } catch (err) {
+ Toast.show({
+ type: "error",
+ text1: "Error logging in with QR code",
+ text2: `${err}`,
+ });
+ }
+ };
+
+ const getCameraPermissions = async (callback: () => void) => {
+ const {status} = await Camera.requestCameraPermissionsAsync();
+ setHasPermission(status === "granted");
+ if (status === "granted") {
+ callback();
+ }
+ };
+
+ const handleOpenQrCodeDialog = () => {
+ getCameraPermissions(() => setShowCameraDialog(true));
+ }
+
+ return (
+
+
+
+
+ Get started with Cally
+
+
+
+
+
+ }
+ onPress={handleOpenQrCodeDialog}
+ style={{height: 50}}
+ color={Colors.black}
+ backgroundColor={Colors.white}
+ />
+ {/* GOOGLE LOGIN HERE */}
+
+
+
+
+
+ or
+
+
+
+
+
+ router.push("/(unauth)/sign_up")}
+ style={{height: 50, borderStyle: "solid", borderColor: "#E2E2E2", borderWidth: 2}}
+ color={Colors.black}
+ backgroundColor={"transparent"}
+ />
+
+
+
+
+
+
+
+
+ Already have an account?
+
+
+ router.push("/(unauth)/sign_in")}
+ labelStyle={[
+ {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 16,
+ color: "#919191",
+ },
+ {fontSize: 16, textDecorationLine: "none", color: "#fd1775"},
+ ]}
+ />
+
+
+
+ {/* Legacy, move into separate component */}
+ {/* Camera Dialog */}
+
+
+
+ {isLoading && (
+
+ )}
+
+ )
+}
diff --git a/app/(unauth)/index.tsx b/app/(unauth)/index.tsx
index 6bd604c..25d0cd0 100644
--- a/app/(unauth)/index.tsx
+++ b/app/(unauth)/index.tsx
@@ -1,10 +1,44 @@
-import Entry from "@/components/pages/main/Entry";
import {SafeAreaView} from "react-native-safe-area-context";
+import {Button, Image, Text, View} from "react-native-ui-lib";
+import React from "react";
+import {useRouter} from "expo-router";
export default function Screen() {
+ const router = useRouter()
+
return (
-
-
+
+
+
+
+
+
+
+
+ Welcome to Cally
+
+
+ Lightening Mental Loads,
+ One Family at a Time
+
+
+
+
+
+
+ router.push("/(unauth)/get_started")}
+ style={{height: 50}}
+ backgroundColor="#fd1775"
+ />
+
+
)
-}
\ No newline at end of file
+}
diff --git a/app/(unauth)/onboarding_flow/_layout.tsx b/app/(unauth)/onboarding_flow/_layout.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/app/(unauth)/reset_password.tsx b/app/(unauth)/reset_password.tsx
new file mode 100644
index 0000000..ce263f1
--- /dev/null
+++ b/app/(unauth)/reset_password.tsx
@@ -0,0 +1,6 @@
+import React from "react";
+import {ResetPasswordPage} from "@/components/pages/main/ResetPasswordPage";
+
+export default function Screen() {
+ return
+}
diff --git a/app/(unauth)/sign_in.tsx b/app/(unauth)/sign_in.tsx
new file mode 100644
index 0000000..454fbef
--- /dev/null
+++ b/app/(unauth)/sign_in.tsx
@@ -0,0 +1,6 @@
+import SignInPage from "@/components/pages/main/SignInPage";
+import React from "react";
+
+export default function Screen() {
+ return
+}
diff --git a/app/(unauth)/sign_up.tsx b/app/(unauth)/sign_up.tsx
new file mode 100644
index 0000000..d653f37
--- /dev/null
+++ b/app/(unauth)/sign_up.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import SignUpPage from "@/components/pages/main/SignUpPage";
+
+export default function Screen() {
+ return (
+
+ )
+}
diff --git a/components/pages/calendar/EventCalendar.tsx b/components/pages/calendar/EventCalendar.tsx
index 87069fe..54a6832 100644
--- a/components/pages/calendar/EventCalendar.tsx
+++ b/components/pages/calendar/EventCalendar.tsx
@@ -13,7 +13,7 @@ import {
import {useAuthContext} from "@/contexts/AuthContext";
import {CalendarEvent} from "@/components/pages/calendar/interfaces";
import {Text} from "react-native-ui-lib";
-import { isWithinInterval, subDays, addDays, compareAsc } from "date-fns";
+import {addDays, compareAsc, isWithinInterval, subDays} from "date-fns";
interface EventCalendarProps {
calendarHeight: number;
@@ -37,21 +37,10 @@ export const EventCalendar: React.FC = React.memo(
const setEventForEdit = useSetAtom(eventForEditAtom);
const setSelectedNewEndDate = useSetAtom(selectedNewEventDateAtom);
- const [isRendering, setIsRendering] = useState(true);
const [offsetMinutes, setOffsetMinutes] = useState(getTotalMinutes());
const todaysDate = new Date();
- useEffect(() => {
- if (events && mode) {
- setIsRendering(true);
- const timeout = setTimeout(() => {
- setIsRendering(false);
- }, 10);
- return () => clearTimeout(timeout);
- }
- }, [events, mode]);
-
const handlePressEvent = useCallback(
(event: CalendarEvent) => {
if (mode === "day" || mode === "week") {
@@ -129,7 +118,7 @@ export const EventCalendar: React.FC = React.memo(
}, [mode]);
- const { enrichedEvents, filteredEvents } = useMemo(() => {
+ const {enrichedEvents, filteredEvents} = useMemo(() => {
const startTime = Date.now(); // Start timer
const startOffset = mode === "month" ? 40 : mode === "week" ? 10 : 1;
@@ -156,16 +145,15 @@ export const EventCalendar: React.FC = React.memo(
overlapCount: 0
});
- // Sort events for this dateKey from oldest to newest by event.start
acc[dateKey].sort((a, b) => compareAsc(a.start, b.start));
return acc;
}, {} as Record);
- const endTime = Date.now(); // End timer
+ const endTime = Date.now();
console.log("memoizedEvents computation time:", endTime - startTime, "ms");
- return { enrichedEvents, filteredEvents };
+ return {enrichedEvents, filteredEvents};
}, [events, selectedDate, mode]);
const renderCustomDateForMonth = (date: Date) => {
@@ -220,7 +208,7 @@ export const EventCalendar: React.FC = React.memo(
setOffsetMinutes(getTotalMinutes());
}, [events, mode]);
- if (isLoading || isRendering) {
+ if (isLoading) {
return (
diff --git a/components/pages/calendar/ManuallyAddEventModal.tsx b/components/pages/calendar/ManuallyAddEventModal.tsx
index ee9b86a..e4564c4 100644
--- a/components/pages/calendar/ManuallyAddEventModal.tsx
+++ b/components/pages/calendar/ManuallyAddEventModal.tsx
@@ -103,6 +103,7 @@ export const ManuallyAddEventModal = () => {
const {mutateAsync: createEvent, isLoading: isAdding, isError} = useCreateEvent();
const {data: members} = useGetFamilyMembers(true);
+ const titleRef = useRef(null)
const isLoading = isDeleting || isAdding
@@ -135,6 +136,14 @@ export const ManuallyAddEventModal = () => {
setRepeatInterval([]);
}, [editEvent, selectedNewEventDate]);
+ useEffect(() => {
+ if(show && !editEvent) {
+ setTimeout(() => {
+ titleRef?.current?.focus()
+ }, 500);
+ }
+ }, [selectedNewEventDate]);
+
if (!show) return null;
const formatDateTime = (date?: Date | string) => {
@@ -342,6 +351,7 @@ export const ManuallyAddEventModal = () => {
{
setTitle(text);
diff --git a/components/pages/main/ResetPasswordPage.tsx b/components/pages/main/ResetPasswordPage.tsx
index 737903d..ef3f1b2 100644
--- a/components/pages/main/ResetPasswordPage.tsx
+++ b/components/pages/main/ResetPasswordPage.tsx
@@ -1,11 +1,9 @@
-import {Button, ButtonSize, Text, TextField, View} from "react-native-ui-lib";
+import {Button, Text, TextField, View} from "react-native-ui-lib";
import React, {useState} from "react";
-import {useSignIn} from "@/hooks/firebase/useSignIn";
import {StyleSheet} from "react-native";
import {useResetPassword} from "@/hooks/firebase/useResetPassword";
-import {isLoading} from "expo-font";
-export const ResetPasswordPage = ({setTab}: { setTab: React.Dispatch> }) => {
+export const ResetPasswordPage = () => {
const [email, setEmail] = useState("");
const {mutateAsync: resetPassword, error, isError, isLoading} = useResetPassword();
diff --git a/components/pages/main/SignInPage.tsx b/components/pages/main/SignInPage.tsx
index fc70c85..b3b0718 100644
--- a/components/pages/main/SignInPage.tsx
+++ b/components/pages/main/SignInPage.tsx
@@ -2,7 +2,7 @@ import {
Button,
ButtonSize,
Colors,
- Dialog,
+ KeyboardAwareScrollView,
LoaderScreen,
Text,
TextField,
@@ -13,29 +13,20 @@ import React, {useRef, useState} from "react";
import {useSignIn} from "@/hooks/firebase/useSignIn";
import {KeyboardAvoidingView, Platform, StyleSheet} from "react-native";
import Toast from "react-native-toast-message";
-import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
-import {Camera, CameraView} from "expo-camera";
import KeyboardManager from "react-native-keyboard-manager";
+import {SafeAreaView} from "react-native-safe-area-context";
+import {useRouter} from "expo-router";
KeyboardManager.setEnableAutoToolbar(true);
-const SignInPage = ({
- setTab,
- }: {
- setTab: React.Dispatch<
- React.SetStateAction<"register" | "login" | "reset-password">
- >;
-}) => {
+const SignInPage = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
- const [hasPermission, setHasPermission] = useState(null);
- const [showCameraDialog, setShowCameraDialog] = useState(false);
const passwordRef = useRef(null);
- const {mutateAsync: signIn, error, isError, isLoading: isSigninIn} = useSignIn();
- const {mutateAsync: signInWithQrCode, isLoading: isQRCodeLoggingIn} = useLoginWithQrCode();
+ const {mutateAsync: signIn, error, isError, isLoading} = useSignIn();
- const isLoading = isSigninIn || isQRCodeLoggingIn
+ const router = useRouter()
const handleSignIn = async () => {
await signIn({email, password});
@@ -53,169 +44,117 @@ const SignInPage = ({
}
};
- const handleQrCodeScanned = async ({data}: { data: string }) => {
- setShowCameraDialog(false);
- try {
- await signInWithQrCode({userId: data});
- Toast.show({
- type: "success",
- text1: "Login successful with QR code!",
- });
- } catch (err) {
- Toast.show({
- type: "error",
- text1: "Error logging in with QR code",
- text2: `${err}`,
- });
- }
- };
-
- const getCameraPermissions = async (callback: () => void) => {
- const {status} = await Camera.requestCameraPermissionsAsync();
- setHasPermission(status === "granted");
- if (status === "granted") {
- callback();
- }
- };
-
return (
-
-
- {
- // Move focus to the description field
- passwordRef.current?.focus();
- }}
- />
-
-
- {
- getCameraPermissions(() => setShowCameraDialog(true));
- }}
- style={{marginBottom: 20, height: 50}}
- backgroundColor="#fd1775"
- />
+
+
+
+
+
+ Jump back into Cally
+
+
+ Please enter your details.
+
+
- {isError && (
- {`${
- error?.toString()?.split("]")?.[1]
- }`}
- )}
+
+ {
+ // Move focus to the description field
+ passwordRef.current?.focus();
+ }}
+ />
+
+
+
+
-
- Don't have an account?
setTab("register")}
- label="Sign Up"
- labelStyle={[
- styles.jakartaMedium,
- {textDecorationLine: "none", color: "#fd1575"},
- ]}
- link
- size={ButtonSize.xSmall}
- padding-0
- margin-0
- text70
- left
- color="#fd1775"
- />
-
-
-
- Forgot your password?
- setTab("reset-password")}
- label="Reset password"
- labelStyle={[
- styles.jakartaMedium,
- {textDecorationLine: "none", color: "#fd1575"},
- ]}
- link
- size={ButtonSize.xSmall}
- padding-0
- margin-0
- text70
- left
- avoidInnerPadding
- color="#fd1775"
- />
-
-
-
- {/* Camera Dialog */}
-
+ {isError && (
+ {`${
+ error?.toString()?.split("]")?.[1]
+ }`}
+ )}
- {isLoading && (
-
- )}
+
+ Don't have an account?
+ router.replace("/(unauth)/sign_up")}
+ label="Sign Up"
+ labelStyle={[
+ styles.jakartaMedium,
+ {textDecorationLine: "none", color: "#fd1575"},
+ ]}
+ link
+ size={ButtonSize.xSmall}
+ padding-0
+ margin-0
+ text70
+ left
+ color="#fd1775"
+ />
+
-
+ {/**/}
+ {/* Forgot your password?*/}
+ {/* router.replace("/(unauth)/sign_up")}*/}
+ {/* label="Reset password"*/}
+ {/* labelStyle={[*/}
+ {/* styles.jakartaMedium,*/}
+ {/* {textDecorationLine: "none", color: "#fd1575"},*/}
+ {/* ]}*/}
+ {/* link*/}
+ {/* size={ButtonSize.xSmall}*/}
+ {/* padding-0*/}
+ {/* margin-0*/}
+ {/* text70*/}
+ {/* left*/}
+ {/* avoidInnerPadding*/}
+ {/* color="#fd1775"*/}
+ {/* />*/}
+ {/**/}
+
+ {isLoading && (
+
+ )}
+
+
+
);
};
diff --git a/components/pages/main/SignUpPage.tsx b/components/pages/main/SignUpPage.tsx
index e994a3f..32f83b1 100644
--- a/components/pages/main/SignUpPage.tsx
+++ b/components/pages/main/SignUpPage.tsx
@@ -3,6 +3,9 @@ import {
Button,
ButtonSize,
Checkbox,
+ Colors,
+ KeyboardAwareScrollView,
+ LoaderScreen,
Text,
TextField,
TextFieldRef,
@@ -10,19 +13,15 @@ import {
View,
} from "react-native-ui-lib";
import {useSignUp} from "@/hooks/firebase/useSignUp";
-import {StyleSheet} from "react-native";
+import {KeyboardAvoidingView, StyleSheet} from "react-native";
import {AntDesign} from "@expo/vector-icons";
import KeyboardManager from "react-native-keyboard-manager";
+import {SafeAreaView} from "react-native-safe-area-context";
+import {useRouter} from "expo-router";
KeyboardManager.setEnableAutoToolbar(true);
-const SignUpPage = ({
- setTab,
- }: {
- setTab: React.Dispatch<
- React.SetStateAction<"register" | "login" | "reset-password">
- >;
-}) => {
+const SignUpPage = () => {
const [email, setEmail] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
@@ -31,170 +30,196 @@ const SignUpPage = ({
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const [allowFaceID, setAllowFaceID] = useState(false);
const [acceptTerms, setAcceptTerms] = useState(false);
- const {mutateAsync: signUp} = useSignUp();
+ const {mutateAsync: signUp, isLoading} = useSignUp();
const lnameRef = useRef(null);
const emailRef = useRef(null);
const passwordRef = useRef(null);
+ const router = useRouter()
+
const handleSignUp = async () => {
await signUp({email, password, firstName, lastName});
+ router.replace("/(unauth)/cal_sync")
};
return (
-
- Get started with Cally
-
- Please enter your details.
-
- {
- lnameRef.current?.focus();
- }}
- blurOnSubmit={false}
- accessibilityLabel="First name input"
- accessibilityHint="Enter your first name"
- accessible
- returnKeyType="next"
- textContentType="givenName"
- importantForAccessibility="yes"
- />
- {
- emailRef.current?.focus();
- }}
- blurOnSubmit={false}
- accessibilityLabel="Last name input"
- accessibilityHint="Enter your last name"
- accessible
- returnKeyType="next"
- textContentType="familyName"
- importantForAccessibility="yes"
- />
- {
- passwordRef.current?.focus();
- }}
- />
-
- setIsPasswordVisible(!isPasswordVisible)}
- >
-
-
- }
- />
-
-
-
- {
- setAllowFaceID(value);
- }}
- />
-
- Allow FaceID for login in future
-
-
-
- setAcceptTerms(value)}
- />
-
-
- I accept the
+
+
+
+
+
+ Get started with Cally
-
-
- {" "}
- terms and conditions
+
+ Please enter your details.
+
+
+
+
+ {
+ lnameRef.current?.focus();
+ }}
+ blurOnSubmit={false}
+ accessibilityLabel="First name input"
+ accessibilityHint="Enter your first name"
+ accessible
+ returnKeyType="next"
+ textContentType="givenName"
+ importantForAccessibility="yes"
+ />
+ {
+ emailRef.current?.focus();
+ }}
+ blurOnSubmit={false}
+ accessibilityLabel="Last name input"
+ accessibilityHint="Enter your last name"
+ accessible
+ returnKeyType="next"
+ textContentType="familyName"
+ importantForAccessibility="yes"
+ />
+ {
+ passwordRef.current?.focus();
+ }}
+ />
+
+
+ setIsPasswordVisible(!isPasswordVisible)}
+ >
+
+
+ }
+ />
+
+
+
+
+
+
+ {
+ setAllowFaceID(value);
+ }}
+ />
+
+ Allow FaceID for login in future
-
- and
-
-
- {" "}
- privacy policy
+
+
+ setAcceptTerms(value)}
+ />
+
+
+ I accept the
+
+
+
+ {" "}
+ terms and conditions
+
+
+ and
+
+
+ {" "}
+ privacy policy
+
+
+
+
+
+
+
+
+
+
+
+
+ Already have an account?
-
+
+ router.replace("/(unauth)/sign_in")}
+ />
+
-
-
-
-
-
-
- Already have an account?
-
+
- setTab("login")}
- />
-
-
-
+ {isLoading && (
+
+ )}
+
);
};
@@ -211,8 +236,6 @@ const styles = StyleSheet.create({
fontSize: 13,
color: "#919191",
},
- //mora da se izmeni kako treba
- bottomView: {marginTop: "auto", marginBottom: 30, marginTop: "auto"},
jakartaLight: {
fontFamily: "PlusJakartaSans_300Light",
fontSize: 13,
diff --git a/components/pages/settings/CalendarSettingsPage.tsx b/components/pages/settings/CalendarSettingsPage.tsx
index 4fe4dfd..fd314fb 100644
--- a/components/pages/settings/CalendarSettingsPage.tsx
+++ b/components/pages/settings/CalendarSettingsPage.tsx
@@ -1,5 +1,5 @@
import {AntDesign, Ionicons} from "@expo/vector-icons";
-import React, {useCallback, useEffect, useState} from "react";
+import React, {useCallback, useState} from "react";
import {Button, Checkbox, Text, View} from "react-native-ui-lib";
import {ActivityIndicator, ScrollView, StyleSheet} from "react-native";
import {TouchableOpacity} from "react-native-gesture-handler";
@@ -9,56 +9,19 @@ import debounce from "debounce";
import AppleIcon from "@/assets/svgs/AppleIcon";
import GoogleIcon from "@/assets/svgs/GoogleIcon";
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 {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";
import {colorMap} from "@/constants/colorMap";
-import {useAtom} from "jotai";
+import {useSetAtom} from "jotai";
import {settingsPageIndex} from "../calendar/atoms";
import CalendarSettingsDialog from "./calendar_components/CalendarSettingsDialog";
import {useClearTokens} from "@/hooks/firebase/useClearTokens";
+import {useCalSync} from "@/hooks/useCalSync";
-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",
- ],
- extraParams: {
- access_type: "offline",
- },
-};
-
-const microsoftConfig = {
- 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 = () => {
const {profileData} = useAuthContext();
- const [pageIndex, setPageIndex] = useAtom(settingsPageIndex);
+ const setPageIndex = useSetAtom(settingsPageIndex);
+
const [firstDayOfWeek, setFirstDayOfWeek] = useState(
profileData?.firstDayOfWeek ??
ExpoLocalization.getCalendars()[0].firstWeekday === 1
@@ -80,8 +43,8 @@ const CalendarSettingsPage = () => {
setModalVisible(true);
};
- const handleConfirm = () => {
- clearToken(selectedService, selectedEmail);
+ const handleConfirm = async () => {
+ await clearToken({email: selectedEmail, provider: selectedService});
setModalVisible(false);
};
@@ -98,196 +61,21 @@ const CalendarSettingsPage = () => {
const {mutateAsync: updateUserData} = useUpdateUserData();
const {mutateAsync: clearToken} = useClearTokens();
- 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);
-
- useEffect(() => {
- signInWithGoogle();
- }, [response]);
-
- const signInWithGoogle = async () => {
- try {
- if (response?.type === "success") {
- const { accessToken, refreshToken } = response?.authentication!;
-
- const userInfoResponse = await fetch(
- "https://www.googleapis.com/oauth2/v3/userinfo",
- {
- headers: { Authorization: `Bearer ${accessToken}` },
- }
- );
-
- const userInfo = await userInfoResponse.json();
- const googleMail = userInfo.email;
-
- let googleAccounts = profileData?.googleAccounts || {};
- const updatedGoogleAccounts = {
- ...googleAccounts,
- [googleMail]: { accessToken, refreshToken },
- };
-
- await updateUserData({
- newUserData: { googleAccounts: updatedGoogleAccounts },
- });
-
- await fetchAndSaveGoogleEvents({
- token: accessToken,
- email: 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;
-
- let microsoftAccounts = profileData?.microsoftAccounts;
- const updatedMicrosoftAccounts = microsoftAccounts
- ? {...microsoftAccounts, [outlookMail]: tokenData.access_token}
- : {[outlookMail]: tokenData.access_token};
-
- await updateUserData({
- newUserData: {microsoftAccounts: updatedMicrosoftAccounts},
- });
-
- await fetchAndSaveOutlookEvents(
- tokenData.access_token,
- 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 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);
-
- alert(JSON.stringify(credential))
-
- const appleToken = credential.identityToken;
- const appleMail = credential.email!;
-
-
- if (appleToken) {
- console.log("Apple ID token received. Fetch user info if needed...");
-
- let appleAcounts = profileData?.appleAccounts;
- const updatedAppleAccounts = appleAcounts
- ? {...appleAcounts, [appleMail]: appleToken}
- : {[appleMail]: appleToken};
-
- await updateUserData({
- newUserData: {appleAccounts: updatedAppleAccounts},
- });
-
- console.log("User data updated with Apple ID token.");
- await fetchAndSaveAppleEvents({token: appleToken, email: appleMail!});
- } else {
- console.warn(
- "Apple authentication was not successful or email was hidden."
- );
- }
- } catch (error) {
- console.error("Error during Apple Sign-in:", error);
- }
- };
+ isSyncingGoogle,
+ isSyncingOutlook,
+ isConnectedToGoogle,
+ isConnectedToMicrosoft,
+ isConnectedToApple,
+ handleAppleSignIn,
+ isSyncingApple,
+ handleMicrosoftSignIn,
+ fetchAndSaveOutlookEvents,
+ fetchAndSaveGoogleEvents,
+ handleStartGoogleSignIn,
+ fetchAndSaveAppleEvents
+ } = useCalSync()
const debouncedUpdateUserData = useCallback(
debounce(async (color: string) => {
@@ -331,37 +119,6 @@ const CalendarSettingsPage = () => {
debouncedUpdateUserData(color);
};
- let isConnectedToGoogle = false;
- if (profileData?.googleAccounts) {
- Object.values(profileData?.googleAccounts).forEach((item) => {
- if (item !== null) {
- isConnectedToGoogle = true;
- return;
- }
- });
- }
-
- let isConnectedToMicrosoft = false;
- const microsoftAccounts = profileData?.microsoftAccounts;
- if (microsoftAccounts) {
- Object.values(profileData?.microsoftAccounts).forEach((item) => {
- if (item !== null) {
- isConnectedToMicrosoft = true;
- return;
- }
- });
- }
-
- let isConnectedToApple = false;
- if (profileData?.appleAccounts) {
- Object.values(profileData?.appleAccounts).forEach((item) => {
- if (item !== null) {
- isConnectedToApple = true;
- return;
- }
- });
- }
-
return (
setPageIndex(0)}>
@@ -462,7 +219,7 @@ const CalendarSettingsPage = () => {
promptAsync()}
+ onPress={() => handleStartGoogleSignIn()}
label={profileData?.googleAccounts ? "Connect another Google account" : "Connect Google account"}
labelStyle={styles.addCalLbl}
labelProps={{
diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx
index e0a2115..8483f67 100644
--- a/contexts/AuthContext.tsx
+++ b/contexts/AuthContext.tsx
@@ -8,7 +8,7 @@ import {UserProfile} from "@/hooks/firebase/types/profileTypes";
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
-import { Platform } from 'react-native';
+import {Platform} from 'react-native';
import {useQueryClient} from "react-query";
@@ -24,6 +24,7 @@ interface IAuthContext {
profileType?: ProfileType,
profileData?: UserProfile,
setProfileData: (profileData: UserProfile) => void,
+ setRedirectOverride: (val: boolean) => void,
refreshProfileData: () => Promise
}
@@ -50,11 +51,11 @@ async function registerForPushNotificationsAsync() {
}
if (Device.isDevice) {
- const { status: existingStatus } = await Notifications.getPermissionsAsync();
+ const {status: existingStatus} = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
- const { status } = await Notifications.requestPermissionsAsync();
+ const {status} = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
@@ -72,7 +73,7 @@ async function registerForPushNotificationsAsync() {
}
try {
- const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data;
+ const token = (await Notifications.getExpoPushTokenAsync({projectId})).data;
console.log('Push Token:', token);
return token;
} catch (error) {
@@ -91,24 +92,28 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
const [initializing, setInitializing] = useState(true);
const [profileType, setProfileType] = useState(undefined);
const [profileData, setProfileData] = useState(undefined);
+ const [redirectOverride, setRedirectOverride] = useState(false);
+
const {replace} = useRouter();
const ready = !initializing;
const queryClient = useQueryClient();
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
- setUser(authUser);
+ if (!redirectOverride) {
+ setUser(authUser);
- if (authUser) {
- await refreshProfileData(authUser);
- const pushToken = await registerForPushNotificationsAsync();
- if (pushToken) {
- await savePushTokenToFirestore(authUser.uid, pushToken);
+ if (authUser) {
+ await refreshProfileData(authUser);
+ const pushToken = await registerForPushNotificationsAsync();
+ if (pushToken) {
+ await savePushTokenToFirestore(authUser.uid, pushToken);
+ }
}
- }
- if (initializing) setInitializing(false);
+ if (initializing) setInitializing(false);
+ }
};
const refreshProfileData = async (user?: FirebaseAuthTypes.User) => {
@@ -152,12 +157,12 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
}, [initializing]);
useEffect(() => {
- if (ready && user) {
+ if (ready && user && !redirectOverride) {
replace({pathname: "/(auth)/calendar"});
- } else if (ready && !user) {
+ } else if (ready && !user && !redirectOverride) {
replace({pathname: "/(unauth)"});
}
- }, [user, ready]);
+ }, [user, ready, redirectOverride]);
useEffect(() => {
const sub = Notifications.addNotificationReceivedListener(notification => {
@@ -175,7 +180,8 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
}
return (
-
+
{children}
);
diff --git a/hooks/firebase/useLoginWithQrCode.ts b/hooks/firebase/useLoginWithQrCode.ts
index 13cc44b..d28c456 100644
--- a/hooks/firebase/useLoginWithQrCode.ts
+++ b/hooks/firebase/useLoginWithQrCode.ts
@@ -1,12 +1,16 @@
import {useMutation} from "react-query";
import functions, {FirebaseFunctionsTypes} from '@react-native-firebase/functions';
import auth from "@react-native-firebase/auth";
+import {useAuthContext} from "@/contexts/AuthContext";
export const useLoginWithQrCode = () => {
+ const {setRedirectOverride} = useAuthContext()
+
return useMutation({
mutationKey: ["loginWithQrCode"],
mutationFn: async ({userId}: { userId: string }) => {
try {
+ setRedirectOverride(true)
const res = await functions().httpsCallable("generateCustomToken")({userId}) as FirebaseFunctionsTypes.HttpsCallableResult<{
token: string
}>
diff --git a/hooks/firebase/useSignUp.ts b/hooks/firebase/useSignUp.ts
index 2464cd0..4326297 100644
--- a/hooks/firebase/useSignUp.ts
+++ b/hooks/firebase/useSignUp.ts
@@ -1,44 +1,47 @@
-import { useMutation } from "react-query";
+import {useMutation} from "react-query";
import auth from "@react-native-firebase/auth";
-import { ProfileType } from "@/contexts/AuthContext";
-import { useSetUserData } from "./useSetUserData";
+import {ProfileType, useAuthContext} 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();
+ const {setRedirectOverride} = useAuthContext()
+ const {mutateAsync: setUserData} = useSetUserData();
- return useMutation({
- mutationKey: ["signUp"],
- mutationFn: async ({
- email,
- password,
- firstName,
- lastName,
- }: {
- email: string;
- password: string;
- firstName: string;
- lastName: string;
- }) => {
- await auth()
- .createUserWithEmailAndPassword(email, password)
- .then(async (res) => {
- try {
- await setUserData({
- newUserData: {
- userType: ProfileType.PARENT,
- firstName: firstName,
- lastName: lastName,
- familyId: uuidv4(),
- timeZone: Localization.getCalendars()[0].timeZone,
- },
- customUser: res.user,
- });
- } catch (error) {
- console.error(error);
- }
- });
- },
- });
+ return useMutation({
+ mutationKey: ["signUp"],
+ mutationFn: async ({
+ email,
+ password,
+ firstName,
+ lastName,
+ }: {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName: string;
+ }) => {
+ setRedirectOverride(true)
+
+ await auth()
+ .createUserWithEmailAndPassword(email, password)
+ .then(async (res) => {
+ try {
+ await setUserData({
+ newUserData: {
+ userType: ProfileType.PARENT,
+ firstName: firstName,
+ lastName: lastName,
+ familyId: uuidv4(),
+ timeZone: Localization.getCalendars()[0].timeZone,
+ },
+ customUser: res.user,
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ });
+ },
+ });
};
diff --git a/hooks/useCalSync.ts b/hooks/useCalSync.ts
new file mode 100644
index 0000000..76c2dff
--- /dev/null
+++ b/hooks/useCalSync.ts
@@ -0,0 +1,288 @@
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useEffect} from "react";
+import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
+import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
+import {useFetchAndSaveOutlookEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
+import {useFetchAndSaveAppleEvents} from "@/hooks/useFetchAndSaveAppleEvents";
+import * as WebBrowser from "expo-web-browser";
+import * as Google from "expo-auth-session/providers/google";
+import * as AuthSession from "expo-auth-session";
+import * as AppleAuthentication from "expo-apple-authentication";
+
+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",
+ ],
+ extraParams: {
+ access_type: "offline",
+ },
+};
+
+const microsoftConfig = {
+ 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",
+};
+
+export const useCalSync = () => {
+ const {profileData} = useAuthContext();
+
+ 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);
+
+ useEffect(() => {
+ signInWithGoogle();
+ }, [response]);
+
+ const signInWithGoogle = async () => {
+ try {
+ if (response?.type === "success") {
+ const {accessToken, refreshToken} = response?.authentication!;
+
+ const userInfoResponse = await fetch(
+ "https://www.googleapis.com/oauth2/v3/userinfo",
+ {
+ headers: {Authorization: `Bearer ${accessToken}`},
+ }
+ );
+
+ const userInfo = await userInfoResponse.json();
+ const googleMail = userInfo.email;
+
+ let googleAccounts = profileData?.googleAccounts || {};
+ const updatedGoogleAccounts = {
+ ...googleAccounts,
+ [googleMail]: {accessToken, refreshToken},
+ };
+
+ await updateUserData({
+ newUserData: {googleAccounts: updatedGoogleAccounts},
+ });
+
+ await fetchAndSaveGoogleEvents({
+ token: accessToken,
+ email: 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;
+
+ let microsoftAccounts = profileData?.microsoftAccounts;
+ const updatedMicrosoftAccounts = microsoftAccounts
+ ? {...microsoftAccounts, [outlookMail]: tokenData.access_token}
+ : {[outlookMail]: tokenData.access_token};
+
+ await updateUserData({
+ newUserData: {microsoftAccounts: updatedMicrosoftAccounts},
+ });
+
+ await fetchAndSaveOutlookEvents(
+ tokenData.access_token,
+ 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 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);
+
+ alert(JSON.stringify(credential))
+
+ const appleToken = credential.identityToken;
+ const appleMail = credential.email!;
+
+
+ if (appleToken) {
+ console.log("Apple ID token received. Fetch user info if needed...");
+
+ let appleAcounts = profileData?.appleAccounts;
+ const updatedAppleAccounts = appleAcounts
+ ? {...appleAcounts, [appleMail]: appleToken}
+ : {[appleMail]: appleToken};
+
+ await updateUserData({
+ newUserData: {appleAccounts: updatedAppleAccounts},
+ });
+
+ console.log("User data updated with Apple ID token.");
+ await fetchAndSaveAppleEvents({token: appleToken, email: appleMail!});
+ } else {
+ console.warn(
+ "Apple authentication was not successful or email was hidden."
+ );
+ }
+ } catch (error) {
+ console.error("Error during Apple Sign-in:", error);
+ }
+ };
+
+
+ let isConnectedToGoogle = false;
+ if (profileData?.googleAccounts) {
+ Object.values(profileData?.googleAccounts).forEach((item) => {
+ if (item !== null) {
+ isConnectedToGoogle = true;
+ return;
+ }
+ });
+ }
+
+ let isConnectedToMicrosoft = false;
+ const microsoftAccounts = profileData?.microsoftAccounts;
+ if (microsoftAccounts) {
+ Object.values(profileData?.microsoftAccounts).forEach((item) => {
+ if (item !== null) {
+ isConnectedToMicrosoft = true;
+ return;
+ }
+ });
+ }
+
+ let isConnectedToApple = false;
+ if (profileData?.appleAccounts) {
+ Object.values(profileData?.appleAccounts).forEach((item) => {
+ if (item !== null) {
+ isConnectedToApple = true;
+ return;
+ }
+ });
+ }
+
+
+ return {
+ handleAppleSignIn,
+ handleMicrosoftSignIn,
+ handleGoogleSignIn: signInWithGoogle,
+ handleStartGoogleSignIn: promptAsync,
+ fetchAndSaveOutlookEvents,
+ fetchAndSaveAppleEvents,
+ fetchAndSaveGoogleEvents,
+ isConnectedToApple,
+ isConnectedToMicrosoft,
+ isConnectedToGoogle,
+ isSyncingOutlook,
+ isSyncingGoogle,
+ isSyncingApple
+ }
+}
\ No newline at end of file