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 {useCreateEventsFromProvider} 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"; 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", ], }; 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 = (props: { setSelectedPage: (page: number) => void; }) => { 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: createEventsFromProvider} = useCreateEventsFromProvider(); const {mutateAsync: updateUserData} = useUpdateUserData(); WebBrowser.maybeCompleteAuthSession(); const [_, response, promptAsync] = Google.useAuthRequest(googleConfig); useEffect(() => { 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") { 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}, }); await fetchAndSaveGoogleEvents(accessToken, 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}, }); await fetchAndSaveMicrosoftEvents(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 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); }; 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}); }; 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