Calendar settings page changes

This commit is contained in:
Milan Paunovic
2024-10-19 18:54:01 +02:00
parent 3f7fc92952
commit 043b80baa9
3 changed files with 522 additions and 436 deletions

View File

@ -16,6 +16,7 @@ import OutlookIcon from "@/assets/svgs/OutlookIcon";
import * as AuthSession from "expo-auth-session"; import * as AuthSession from "expo-auth-session";
import * as Google from "expo-auth-session/providers/google"; import * as Google from "expo-auth-session/providers/google";
import * as WebBrowser from "expo-web-browser"; import * as WebBrowser from "expo-web-browser";
import {UserProfile} from "@firebase/auth";
const googleConfig = { const googleConfig = {
androidClientId: androidClientId:
@ -32,17 +33,17 @@ const googleConfig = {
}; };
const microsoftConfig = { const microsoftConfig = {
clientId: "13c79071-1066-40a9-9f71-b8c4b138b4af", // Replace with your Microsoft client ID clientId: "13c79071-1066-40a9-9f71-b8c4b138b4af",
redirectUri: AuthSession.makeRedirectUri({ path: "settings" }), // Generate redirect URI automatically for Expo redirectUri: AuthSession.makeRedirectUri({path: "settings"}),
scopes: [ scopes: [
"openid", "openid",
"profile", "profile",
"email", "email",
"offline_access", "offline_access",
"Calendars.ReadWrite", // Scope for reading calendar events "Calendars.ReadWrite",
"User.Read"
], ],
authorizationEndpoint: authorizationEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token", tokenEndpoint: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
}; };
@ -63,7 +64,7 @@ const CalendarSettingsPage = (props: {
const {mutateAsync: updateUserData} = useUpdateUserData(); const {mutateAsync: updateUserData} = useUpdateUserData();
WebBrowser.maybeCompleteAuthSession(); WebBrowser.maybeCompleteAuthSession();
const [request, response, promptAsync] = Google.useAuthRequest(googleConfig); const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
useEffect(() => { useEffect(() => {
signInWithGoogle(); signInWithGoogle();
@ -109,16 +110,25 @@ const CalendarSettingsPage = (props: {
const signInWithGoogle = async () => { const signInWithGoogle = async () => {
try { try {
// Attempt to retrieve user information from AsyncStorage
if (response?.type === "success") { if (response?.type === "success") {
console.log(response.authentication); 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({ await updateUserData({
newUserData: { googleToken: response.authentication?.accessToken }, newUserData: {googleToken: accessToken, googleMail: googleMail},
}); });
} }
} catch (error) { } catch (error) {
// Handle any errors that occur during AsyncStorage retrieval or other operations console.error("Error during Google sign-in:", error);
console.error("Error retrieving user data from AsyncStorage:", error);
} }
}; };
@ -159,27 +169,47 @@ const CalendarSettingsPage = (props: {
)}&grant_type=authorization_code&code=${code}&code_verifier=${ )}&grant_type=authorization_code&code=${code}&code_verifier=${
authRequest.codeVerifier authRequest.codeVerifier
}&scope=${encodeURIComponent( }&scope=${encodeURIComponent(
"https://graph.microsoft.com/Calendars.ReadWrite offline_access" "https://graph.microsoft.com/Calendars.ReadWrite offline_access User.Read"
)}`, )}`,
}); });
console.log("Token response status:", tokenResponse.status); console.log("Token response status:", tokenResponse.status);
if (!tokenResponse.ok) { if (!tokenResponse.ok) {
console.error("Token exchange failed:", await tokenResponse.text()); const errorText = await tokenResponse.text();
console.error("Token exchange failed:", errorText);
return; return;
} }
const tokenData = await tokenResponse.json(); const tokenData = await tokenResponse.json();
console.log("Token data received:", tokenData); console.log("Token data received:", tokenData);
if (tokenData?.id_token) { if (tokenData?.access_token) {
console.log("ID token received, updating user data..."); console.log("Access token received, fetching user info...");
await updateUserData({
newUserData: { microsoftToken: tokenData.access_token }, // 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."); console.log("User data updated successfully.");
} }
}
} else { } else {
console.warn("Authentication was not successful:", authResult); console.warn("Authentication was not successful:", authResult);
} }
@ -210,6 +240,21 @@ const CalendarSettingsPage = (props: {
debouncedUpdateUserData(color); debouncedUpdateUserData(color);
}; };
const clearToken = async (provider: "google" | "outlook" | "apple") => {
const newUserData: Partial<UserProfile> = {};
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 ( return (
<ScrollView> <ScrollView>
<View marginH-30> <View marginH-30>
@ -310,9 +355,12 @@ const CalendarSettingsPage = (props: {
</Text> </Text>
<Button <Button
onPress={() => promptAsync()} onPress={() => !profileData?.googleToken ? promptAsync() : clearToken("google")}
label="Connect Google" label={profileData?.googleToken ? `Disconnect ${profileData.googleMail}` : "Connect Google"}
labelStyle={styles.addCalLbl} labelStyle={styles.addCalLbl}
labelProps={{
numberOfLines:2
}}
iconSource={() => ( iconSource={() => (
<View marginR-15> <View marginR-15>
<GoogleIcon/> <GoogleIcon/>
@ -325,6 +373,9 @@ const CalendarSettingsPage = (props: {
<Button <Button
label="Connect Apple" label="Connect Apple"
labelStyle={styles.addCalLbl} labelStyle={styles.addCalLbl}
labelProps={{
numberOfLines:2
}}
iconSource={() => ( iconSource={() => (
<View marginR-15> <View marginR-15>
<AppleIcon/> <AppleIcon/>
@ -335,9 +386,12 @@ const CalendarSettingsPage = (props: {
text70BL text70BL
/> />
<Button <Button
onPress={handleMicrosoftSignIn} onPress={() => !profileData?.microsoftToken ? handleMicrosoftSignIn() : clearToken("outlook")}
label="Connect Outlook" label={profileData?.microsoftToken ? `Disconnect ${profileData.outlookMail}` : "Connect Outlook"}
labelStyle={styles.addCalLbl} labelStyle={styles.addCalLbl}
labelProps={{
numberOfLines:2
}}
iconSource={() => ( iconSource={() => (
<View marginR-15> <View marginR-15>
<OutlookIcon/> <OutlookIcon/>
@ -348,14 +402,18 @@ const CalendarSettingsPage = (props: {
text70BL text70BL
/> />
{(profileData?.googleMail || profileData?.outlookMail || profileData?.appleMail) && (
<>
<Text style={styles.subTitle} marginT-30 marginB-20> <Text style={styles.subTitle} marginT-30 marginB-20>
Connected Calendars Connected Calendars
</Text> </Text>
<View style={styles.card}> <View style={styles.card}>
<View style={{marginTop: 20}}> <View style={{marginTop: 20}}>
{!!profileData?.googleMail && (
<Button <Button
onPress={fetchAndSaveGoogleEvents} onPress={fetchAndSaveGoogleEvents}
label="Sync Google" label={`Sync ${profileData?.googleMail}`}
labelStyle={styles.addCalLbl} labelStyle={styles.addCalLbl}
iconSource={() => ( iconSource={() => (
<View marginR-15> <View marginR-15>
@ -366,9 +424,12 @@ const CalendarSettingsPage = (props: {
color="black" color="black"
text70BL text70BL
/> />
)}
{!!profileData?.outlookMail && (
<Button <Button
onPress={fetchAndSaveMicrosoftEvents} onPress={fetchAndSaveMicrosoftEvents}
label="Sync Outlook" label={`Sync ${profileData?.outlookMail}`}
labelStyle={styles.addCalLbl} labelStyle={styles.addCalLbl}
iconSource={() => ( iconSource={() => (
<View marginR-15> <View marginR-15>
@ -379,8 +440,11 @@ const CalendarSettingsPage = (props: {
color="black" color="black"
text70BL text70BL
/> />
)}
</View> </View>
</View> </View>
</>
)}
</View> </View>
</ScrollView> </ScrollView>
); );
@ -419,6 +483,11 @@ const styles = StyleSheet.create({
addCalLbl: { addCalLbl: {
fontSize: 16, fontSize: 16,
fontFamily: "PlusJakartaSan_500Medium", fontFamily: "PlusJakartaSan_500Medium",
flexWrap: "wrap",
width: "75%",
textAlign: "left",
lineHeight: 20,
overflow: "visible"
}, },
subTitle: { subTitle: {
fontFamily: "Manrope_600SemiBold", fontFamily: "Manrope_600SemiBold",

View File

@ -17,9 +17,13 @@ export interface UserProfile {
password: string; password: string;
familyId?: string; familyId?: string;
uid?: string; uid?: string;
googleToken?: string; googleToken?: string | null;
microsoftToken?: string; microsoftToken?: string | null;
eventColor?: string appleToken?: string | null;
eventColor?: string | null;
googleMail?: string | null;
outlookMail?: string | null;
appleMail?: string | null;
} }
export interface ParentProfile extends UserProfile { export interface ParentProfile extends UserProfile {

View File

@ -1,33 +1,46 @@
import {useAuthContext} from "@/contexts/AuthContext";
import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore"; import firestore from "@react-native-firebase/firestore";
import {UserProfile} from "@/hooks/firebase/types/profileTypes";
import { FirebaseAuthTypes } from "@react-native-firebase/auth"; import { FirebaseAuthTypes } from "@react-native-firebase/auth";
import { useMutation, useQueryClient } from "react-query";
import { useAuthContext } from "@/contexts/AuthContext";
import { UserProfile } from "@/hooks/firebase/types/profileTypes";
export const useUpdateUserData = () => { export const useUpdateUserData = () => {
const { user: currentUser, refreshProfileData } = useAuthContext(); const { user: currentUser, refreshProfileData } = useAuthContext();
const queryClient = useQueryClient() const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationKey: ["updateUserData"], mutationKey: ["updateUserData"],
mutationFn: async ({newUserData, customUser}: {newUserData: Partial<UserProfile>, customUser?: FirebaseAuthTypes.User }) => { mutationFn: async ({
newUserData,
customUser,
}: {
newUserData: Partial<UserProfile>;
customUser?: FirebaseAuthTypes.User;
}) => {
console.log("Mutation function called with data:", { newUserData, customUser }); console.log("Mutation function called with data:", { newUserData, customUser });
const user = currentUser ?? customUser; const user = currentUser ?? customUser;
if (user) { if (user) {
console.log("Updating user data for UID:", user.uid); console.log("Updating user data for UID:", user.uid);
try { try {
console.log("New user data:", newUserData); const updatedUserData = Object.fromEntries(
Object.entries(newUserData).map(([key, value]) =>
[key, value === null ? firestore.FieldValue.delete() : value]
)
);
console.log("Updated user data with deletions:", updatedUserData);
await firestore() await firestore()
.collection("Profiles") .collection("Profiles")
.doc(user.uid) .doc(user.uid)
.update(newUserData); .update(updatedUserData);
console.log("User data updated successfully, fetching updated profile..."); console.log("User data updated successfully, fetching updated profile...");
await refreshProfileData() await refreshProfileData();
console.log("Profile data updated in context."); console.log("Profile data updated in context.");
} catch (e) { } catch (e) {
@ -38,7 +51,7 @@ export const useUpdateUserData = () => {
} }
}, },
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries("events") queryClient.invalidateQueries("events");
} },
}); });
}; };