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 - - -