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 {ActivityIndicator, ScrollView, StyleSheet} from "react-native"; import {TouchableOpacity} from "react-native-gesture-handler"; 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 {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 {settingsPageIndex} from "../calendar/atoms"; import CalendarSettingsDialog from "./calendar_components/CalendarSettingsDialog"; import {useClearTokens} from "@/hooks/firebase/useClearTokens"; 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 [firstDayOfWeek, setFirstDayOfWeek] = useState( profileData?.firstDayOfWeek ?? ExpoLocalization.getCalendars()[0].firstWeekday === 1 ? "Mondays" : "Sundays" ); const [isModalVisible, setModalVisible] = useState(false); const [selectedService, setSelectedService] = useState< "google" | "outlook" | "apple" >("google"); const [selectedEmail, setSelectedEmail] = useState(""); const showConfirmationDialog = ( serviceName: "google" | "outlook" | "apple", email: string ) => { setSelectedService(serviceName); setSelectedEmail(email); setModalVisible(true); }; const handleConfirm = () => { clearToken(selectedService, selectedEmail); setModalVisible(false); }; const handleCancel = () => { setModalVisible(false); }; const [selectedColor, setSelectedColor] = useState( profileData?.eventColor ?? colorMap.pink ); const [previousSelectedColor, setPreviousSelectedColor] = useState( profileData?.eventColor ?? colorMap.pink ); 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); } }; 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 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 = (firstDayOfWeek: string) => { setFirstDayOfWeek(firstDayOfWeek); debouncedUpdateFirstDayOfWeek( firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays" ); }; const handleChangeColor = (color: string) => { setPreviousSelectedColor(selectedColor); setSelectedColor(color); 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)}> 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 handleChangeFirstDayOfWeek("Sundays")} /> Sundays {" "} (default) handleChangeFirstDayOfWeek("Mondays")} /> Mondays Add Calendar