Files
cally/components/pages/settings/CalendarSettingsPage.tsx
2024-10-10 02:00:14 +02:00

403 lines
16 KiB
TypeScript

import {AntDesign, Ionicons} from "@expo/vector-icons";
import React, {useCallback, 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 {GoogleSignin} from "@react-native-google-signin/google-signin";
import * as AuthSession from "expo-auth-session";
import debounce from "debounce";
GoogleSignin.configure({
webClientId:
"406146460310-hjadmfa1gg4ptaouira5rkhu0djlo5ut.apps.googleusercontent.com",
scopes: ["profile", "email"], // Note: add calendar scope
});
const GoogleLogin = async () => {
return await GoogleSignin.signIn();
};
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"
};
const CalendarSettingsPage = (props: {
setSelectedPage: (page: number) => void;
}) => {
const [startDate, setStartDate] = useState<boolean>(true);
const {profileData} = useAuthContext();
const [selectedColor, setSelectedColor] = useState<string>(profileData?.eventColor ?? colorMap.pink);
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(profileData?.eventColor ?? colorMap.pink);
const {mutateAsync: createEventFromProvider} = useCreateEventFromProvider();
const {mutateAsync: updateUserData} = useUpdateUserData();
const fetchAndSaveGoogleEvents = () => {
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 handleGoogleLogin = async () => {
try {
const response = await GoogleLogin();
if (response) {
const googleUserData = response.data;
let idToken = googleUserData?.idToken;
if (idToken) {
await updateUserData({newUserData: {googleToken: idToken}});
}
}
} catch (apiError) {
console.log(apiError || "Something went wrong");
}
};
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 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 (
<ScrollView>
<View marginH-30>
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
<View row marginT-20 marginB-35 centerV>
<Ionicons name="chevron-back" size={22} color="#979797"/>
<Text text70 color="#979797">
Return to main settings
</Text>
</View>
</TouchableOpacity>
<Text text60R>Calendar settings</Text>
<View style={styles.card}>
<Text text70 marginB-14>
Event Color Preference
</Text>
<View row spread>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.pink)}>
<View style={styles.colorBox} backgroundColor={colorMap.pink}>
{selectedColor == colorMap.pink && (
<AntDesign name="check" size={30} color="white"/>
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.orange)}>
<View style={styles.colorBox} backgroundColor={colorMap.orange}>
{selectedColor == colorMap.orange && (
<AntDesign name="check" size={30} color="white"/>
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.green)}>
<View style={styles.colorBox} backgroundColor={colorMap.green}>
{selectedColor == colorMap.green && (
<AntDesign name="check" size={30} color="white"/>
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.teal)}>
<View style={styles.colorBox} backgroundColor={colorMap.teal}>
{selectedColor == colorMap.teal && (
<AntDesign name="check" size={30} color="white"/>
)}
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleChangeColor(colorMap.purple)}>
<View style={styles.colorBox} backgroundColor={colorMap.purple}>
{selectedColor == colorMap.purple && (
<AntDesign name="check" size={30} color="white"/>
)}
</View>
</TouchableOpacity>
</View>
</View>
<View style={styles.card}>
<Text text70>Weekly Start Date</Text>
<View row marginV-5 marginT-20>
<Checkbox
value={startDate}
style={styles.checkbox}
color="#ea156d"
onValueChange={() => setStartDate(true)}
/>
<View row marginL-8>
<Text text70>Sundays</Text>
<Text text70 color="gray">
{" "}
(default)
</Text>
</View>
</View>
<View row marginV-5>
<Checkbox
value={!startDate}
style={styles.checkbox}
color="#ea156d"
onValueChange={() => setStartDate(false)}
/>
<Text text70 marginL-8>
Mondays
</Text>
</View>
</View>
<View style={styles.card}>
<Text text70>Add Calendar</Text>
<View style={{marginTop: 20}}>
<Button
label={"Connect Google"}
iconSource={() => (
<View
backgroundColor="#ededed"
width={40}
height={40}
style={{borderRadius: 50}}
marginR-10
centerV
centerH
>
<Ionicons name="logo-google" size={22} color="#979797"/>
</View>
)}
backgroundColor="white"
color="#464039"
borderRadius={15}
onPress={handleGoogleLogin}
/>
<Button
label={"Connect Microsoft"}
iconSource={() => (
<View
backgroundColor="#ededed"
width={40}
height={40}
style={{borderRadius: 50}}
marginR-10
centerV
centerH
>
<Ionicons name="logo-microsoft" size={22} color="#979797"/>
</View>
)}
backgroundColor="white"
color="#464039"
borderRadius={15}
onPress={handleMicrosoftSignIn}
/>
</View>
</View>
<View style={styles.card}>
<Text text70>Calendars</Text>
<View style={{marginTop: 20}}>
<Button
label={"Sync Outlook"}
iconSource={() => (
<View
backgroundColor="#ededed"
width={40}
height={40}
style={{borderRadius: 50}}
marginR-10
centerV
centerH
>
<Ionicons name="logo-microsoft" size={22} color="#979797"/>
</View>
)}
backgroundColor="white"
color="#464039"
borderRadius={15}
onPress={fetchAndSaveMicrosoftEvents}
/>
{profileData?.googleToken !== undefined && (
<Button
label={"Sync Google"}
iconSource={() => (
<View
backgroundColor="#ededed"
width={40}
height={40}
style={{borderRadius: 50}}
marginR-10
centerV
centerH
>
<Ionicons name="logo-google" size={22} color="#979797"/>
</View>
)}
backgroundColor="white"
color="#464039"
borderRadius={15}
onPress={fetchAndSaveGoogleEvents}
/>
)}
</View>
</View>
</View>
</ScrollView>
);
};
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: 20,
},
colorBox: {
aspectRatio: 1,
justifyContent: "center",
alignItems: "center",
width: 50,
borderRadius: 12,
},
checkbox: {
borderRadius: 50,
},
});
export default CalendarSettingsPage;