mirror of
https://github.com/urosran/cally.git
synced 2025-07-15 01:35:22 +00:00
Calendar settings page changes
This commit is contained in:
@ -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<boolean>(false);
|
||||
const { profileData } = useAuthContext();
|
||||
const [startDate, setStartDate] = useState<boolean>(false);
|
||||
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();
|
||||
|
||||
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<string>(
|
||||
profileData?.eventColor ?? colorMap.pink
|
||||
);
|
||||
const [previousSelectedColor, setPreviousSelectedColor] = useState<string>(
|
||||
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<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});
|
||||
};
|
||||
|
||||
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={14}
|
||||
color="#979797"
|
||||
style={{ paddingBottom: 3 }}
|
||||
/>
|
||||
<Text
|
||||
style={{ fontFamily: "Poppins_400Regular", fontSize: 14.71 }}
|
||||
color="#979797"
|
||||
>
|
||||
Return to main settings
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.subTitle}>Calendar settings</Text>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle} 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 style={styles.cardTitle}>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>
|
||||
<Text style={styles.subTitle} marginT-30 marginB-25>
|
||||
Add Calendar
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
onPress={() => promptAsync()}
|
||||
label="Connect Google"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
label="Connect Apple"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={handleMicrosoftSignIn}
|
||||
label="Connect Outlook"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon />
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
|
||||
<Text style={styles.subTitle} marginT-30 marginB-20>
|
||||
Connected Calendars
|
||||
</Text>
|
||||
<View style={styles.card}>
|
||||
<View style={{ marginTop: 20 }}>
|
||||
<Button
|
||||
onPress={fetchAndSaveGoogleEvents}
|
||||
label="Sync Google"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon />
|
||||
return (
|
||||
<ScrollView>
|
||||
<View marginH-30>
|
||||
<TouchableOpacity onPress={() => props.setSelectedPage(0)}>
|
||||
<View row marginT-20 marginB-35 centerV>
|
||||
<Ionicons
|
||||
name="chevron-back"
|
||||
size={14}
|
||||
color="#979797"
|
||||
style={{paddingBottom: 3}}
|
||||
/>
|
||||
<Text
|
||||
style={{fontFamily: "Poppins_400Regular", fontSize: 14.71}}
|
||||
color="#979797"
|
||||
>
|
||||
Return to main settings
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.subTitle}>Calendar settings</Text>
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle} 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>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={fetchAndSaveMicrosoftEvents}
|
||||
label="Sync Outlook"
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon />
|
||||
<View style={styles.card}>
|
||||
<Text style={styles.cardTitle}>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>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
<Text style={styles.subTitle} marginT-30 marginB-25>
|
||||
Add Calendar
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
onPress={() => !profileData?.googleToken ? promptAsync() : clearToken("google")}
|
||||
label={profileData?.googleToken ? `Disconnect ${profileData.googleMail}` : "Connect Google"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines:2
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
label="Connect Apple"
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines:2
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<AppleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
<Button
|
||||
onPress={() => !profileData?.microsoftToken ? handleMicrosoftSignIn() : clearToken("outlook")}
|
||||
label={profileData?.microsoftToken ? `Disconnect ${profileData.outlookMail}` : "Connect Outlook"}
|
||||
labelStyle={styles.addCalLbl}
|
||||
labelProps={{
|
||||
numberOfLines:2
|
||||
}}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
|
||||
{(profileData?.googleMail || profileData?.outlookMail || profileData?.appleMail) && (
|
||||
<>
|
||||
<Text style={styles.subTitle} marginT-30 marginB-20>
|
||||
Connected Calendars
|
||||
</Text>
|
||||
|
||||
<View style={styles.card}>
|
||||
<View style={{marginTop: 20}}>
|
||||
{!!profileData?.googleMail && (
|
||||
<Button
|
||||
onPress={fetchAndSaveGoogleEvents}
|
||||
label={`Sync ${profileData?.googleMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<GoogleIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)}
|
||||
|
||||
{!!profileData?.outlookMail && (
|
||||
<Button
|
||||
onPress={fetchAndSaveMicrosoftEvents}
|
||||
label={`Sync ${profileData?.outlookMail}`}
|
||||
labelStyle={styles.addCalLbl}
|
||||
iconSource={() => (
|
||||
<View marginR-15>
|
||||
<OutlookIcon/>
|
||||
</View>
|
||||
)}
|
||||
style={styles.addCalBtn}
|
||||
color="black"
|
||||
text70BL
|
||||
/>
|
||||
)}
|
||||
</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: 12,
|
||||
},
|
||||
colorBox: {
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 51,
|
||||
borderRadius: 12,
|
||||
},
|
||||
checkbox: {
|
||||
borderRadius: 50,
|
||||
},
|
||||
addCalLbl: {
|
||||
fontSize: 16,
|
||||
fontFamily: "PlusJakartaSan_500Medium",
|
||||
},
|
||||
subTitle: {
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 18,
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
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: 12,
|
||||
},
|
||||
colorBox: {
|
||||
aspectRatio: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: 51,
|
||||
borderRadius: 12,
|
||||
},
|
||||
checkbox: {
|
||||
borderRadius: 50,
|
||||
},
|
||||
addCalLbl: {
|
||||
fontSize: 16,
|
||||
fontFamily: "PlusJakartaSan_500Medium",
|
||||
flexWrap: "wrap",
|
||||
width: "75%",
|
||||
textAlign: "left",
|
||||
lineHeight: 20,
|
||||
overflow: "visible"
|
||||
},
|
||||
subTitle: {
|
||||
fontFamily: "Manrope_600SemiBold",
|
||||
fontSize: 18,
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: "Manrope_500Medium",
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
|
||||
export default CalendarSettingsPage;
|
||||
|
@ -1,39 +1,43 @@
|
||||
import { ProfileType } from "@/contexts/AuthContext";
|
||||
import {ProfileType} from "@/contexts/AuthContext";
|
||||
|
||||
export interface User {
|
||||
uid: string;
|
||||
email: string | null;
|
||||
uid: string;
|
||||
email: string | null;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
userType: ProfileType;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
childrenIds?: string[];
|
||||
birthday?: Date;
|
||||
parentId?: string;
|
||||
contact?: string;
|
||||
email: string;
|
||||
password: string;
|
||||
familyId?: string;
|
||||
uid?: string;
|
||||
googleToken?: string;
|
||||
microsoftToken?: string;
|
||||
eventColor?: string
|
||||
userType: ProfileType;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
childrenIds?: string[];
|
||||
birthday?: Date;
|
||||
parentId?: string;
|
||||
contact?: string;
|
||||
email: string;
|
||||
password: string;
|
||||
familyId?: string;
|
||||
uid?: string;
|
||||
googleToken?: string | null;
|
||||
microsoftToken?: string | null;
|
||||
appleToken?: string | null;
|
||||
eventColor?: string | null;
|
||||
googleMail?: string | null;
|
||||
outlookMail?: string | null;
|
||||
appleMail?: string | null;
|
||||
}
|
||||
|
||||
export interface ParentProfile extends UserProfile {
|
||||
userType: ProfileType.PARENT;
|
||||
childrenIds: string[];
|
||||
userType: ProfileType.PARENT;
|
||||
childrenIds: string[];
|
||||
}
|
||||
|
||||
export interface ChildProfile extends UserProfile {
|
||||
userType: ProfileType.CHILD;
|
||||
birthday: Date;
|
||||
parentId: string;
|
||||
userType: ProfileType.CHILD;
|
||||
birthday: Date;
|
||||
parentId: string;
|
||||
}
|
||||
|
||||
export interface CaregiverProfile extends UserProfile {
|
||||
userType: ProfileType.CAREGIVER;
|
||||
contact: string;
|
||||
userType: ProfileType.CAREGIVER;
|
||||
contact: string;
|
||||
}
|
||||
|
@ -1,33 +1,46 @@
|
||||
import {useAuthContext} from "@/contexts/AuthContext";
|
||||
import {useMutation, useQueryClient} from "react-query";
|
||||
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 = () => {
|
||||
const {user: currentUser, refreshProfileData} = useAuthContext();
|
||||
const queryClient = useQueryClient()
|
||||
const { user: currentUser, refreshProfileData } = useAuthContext();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
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 });
|
||||
|
||||
const user = currentUser ?? customUser;
|
||||
|
||||
if (user) {
|
||||
console.log("Updating user data for UID:", user.uid);
|
||||
|
||||
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()
|
||||
.collection("Profiles")
|
||||
.doc(user.uid)
|
||||
.update(newUserData);
|
||||
.update(updatedUserData);
|
||||
|
||||
console.log("User data updated successfully, fetching updated profile...");
|
||||
|
||||
await refreshProfileData()
|
||||
await refreshProfileData();
|
||||
|
||||
console.log("Profile data updated in context.");
|
||||
} catch (e) {
|
||||
@ -38,7 +51,7 @@ export const useUpdateUserData = () => {
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries("events")
|
||||
}
|
||||
queryClient.invalidateQueries("events");
|
||||
},
|
||||
});
|
||||
};
|
Reference in New Issue
Block a user