mirror of
https://github.com/urosran/cally.git
synced 2025-07-15 09:45:20 +00:00
Calendar settings page changes
This commit is contained in:
@ -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",
|
||||||
|
@ -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 {
|
||||||
|
@ -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");
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
Reference in New Issue
Block a user