diff --git a/app.json b/app.json
index 9ad190e..7413002 100644
--- a/app.json
+++ b/app.json
@@ -16,7 +16,7 @@
"supportsTablet": true,
"bundleIdentifier": "com.cally.app",
"googleServicesFile": "./ios/GoogleService-Info.plist",
- "buildNumber": "40",
+ "buildNumber": "60",
"usesAppleSignIn": true
},
"android": {
diff --git a/app/(auth)/_layout.tsx b/app/(auth)/_layout.tsx
index 5edd485..13d04c3 100644
--- a/app/(auth)/_layout.tsx
+++ b/app/(auth)/_layout.tsx
@@ -6,8 +6,8 @@ import {
DrawerItem,
DrawerItemList,
} from "@react-navigation/drawer";
-import { Button, View, Text, ButtonSize, Constants } from "react-native-ui-lib";
-import { StyleSheet } from "react-native";
+import { Button, View, Text, ButtonSize } from "react-native-ui-lib";
+import { Dimensions, ImageBackground, StyleSheet } from "react-native";
import Feather from "@expo/vector-icons/Feather";
import DrawerButton from "@/components/shared/DrawerButton";
import {
@@ -24,9 +24,21 @@ import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon";
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon";
import ViewSwitch from "@/components/pages/(tablet_pages)/ViewSwitch";
+import { useAtom, useSetAtom } from "jotai";
+import {
+ isFamilyViewAtom,
+ settingsPageIndex,
+ toDosPageIndex,
+ userSettingsView,
+} from "@/components/pages/calendar/atoms";
+import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon";
export default function TabLayout() {
const { mutateAsync: signOut } = useSignOut();
+ const setIsFamilyView = useSetAtom(isFamilyViewAtom);
+ const setPageIndex = useSetAtom(settingsPageIndex);
+ const setUserView = useSetAtom(userSettingsView);
+ const setToDosIndex = useSetAtom(toDosPageIndex);
return (
,
}}
drawerContent={(props) => {
return (
-
-
+
+
+
Welcome to Cally
props.navigation.navigate("calendar")}
+ pressFunc={() => {
+ props.navigation.navigate("calendar");
+ setPageIndex(0);
+ setToDosIndex(0);
+ setUserView(true);
+ setIsFamilyView(false);
+ }}
icon={}
/>
props.navigation.navigate("grocery")}
+ pressFunc={() => {
+ props.navigation.navigate("grocery");
+ setPageIndex(0);
+ setToDosIndex(0);
+ setUserView(true);
+ setIsFamilyView(false);
+ }}
icon={}
/>
+ {
+ props.navigation.navigate("feedback");
+ setPageIndex(0);
+ setToDosIndex(0);
+ setUserView(true);
+ setIsFamilyView(false);
+ }}
+ icon={}
+ />
{/* props.navigation.navigate("todos")}
+ pressFunc={() => {
+ props.navigation.navigate("todos");
+ setPageIndex(0);
+ setToDosIndex(0);
+ setUserView(true);
+ setIsFamilyView(false);
+ }}
icon={}
/>
props.navigation.navigate("brain_dump")}
+ pressFunc={() => {
+ props.navigation.navigate("brain_dump");
+ setPageIndex(0);
+ setToDosIndex(0);
+ setUserView(true);
+ setIsFamilyView(false);
+ }}
icon={}
/>
{/* signOut()} />*/}
);
}
diff --git a/app/(auth)/feedback/_layout.tsx b/app/(auth)/feedback/_layout.tsx
new file mode 100644
index 0000000..0990eb8
--- /dev/null
+++ b/app/(auth)/feedback/_layout.tsx
@@ -0,0 +1,5 @@
+import {Stack} from "expo-router";
+
+export default function StackLayout () {
+ return
+}
\ No newline at end of file
diff --git a/app/(auth)/feedback/index.tsx b/app/(auth)/feedback/index.tsx
new file mode 100644
index 0000000..feb51f7
--- /dev/null
+++ b/app/(auth)/feedback/index.tsx
@@ -0,0 +1,13 @@
+import FeedbackPage from "@/components/pages/feedback/FeedbackPage";
+import { FeedbackProvider } from "@/contexts/FeedbackContext";
+import { View } from "react-native-ui-lib";
+
+export default function Screen() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/app/(unauth)/cal_sync.tsx b/app/(unauth)/cal_sync.tsx
new file mode 100644
index 0000000..0e9f15b
--- /dev/null
+++ b/app/(unauth)/cal_sync.tsx
@@ -0,0 +1,246 @@
+import {SafeAreaView} from "react-native-safe-area-context";
+import {Button, Text, View} from "react-native-ui-lib";
+import React from "react";
+import {useCalSync} from "@/hooks/useCalSync";
+import GoogleIcon from "@/assets/svgs/GoogleIcon";
+import AppleIcon from "@/assets/svgs/AppleIcon";
+import OutlookIcon from "@/assets/svgs/OutlookIcon";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {StyleSheet} from "react-native";
+
+export default function Screen() {
+ const {profileData, setRedirectOverride} = useAuthContext()
+ const {handleStartGoogleSignIn, handleAppleSignIn, handleMicrosoftSignIn} = useCalSync()
+
+ const hasSomeCalendarsSynced =
+ !!profileData?.appleAccounts || !!profileData?.microsoftAccounts || !!profileData?.googleAccounts
+
+ return (
+
+
+
+
+ Let's get started!
+
+
+ Add your calendar below to sync events to your Cally calendar
+
+
+
+
+ {!profileData?.googleAccounts && (
+ handleStartGoogleSignIn()}
+ label={"Connect Google account"}
+ labelStyle={styles.addCalLbl}
+ labelProps={{
+ numberOfLines: 2,
+ }}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )}
+
+ {profileData?.googleAccounts
+ ? Object.keys(profileData?.googleAccounts)?.map((googleMail) => {
+ const googleToken = profileData?.googleAccounts?.[googleMail]?.accessToken;
+ return (
+ googleToken && (
+ (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )
+ );
+ })
+ : null}
+
+ {!profileData?.appleAccounts && (
+ handleAppleSignIn()}
+ label={"Connect Apple"}
+ labelStyle={styles.addCalLbl}
+ labelProps={{
+ numberOfLines: 2,
+ }}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )}
+
+ {profileData?.appleAccounts
+ ? Object.keys(profileData?.appleAccounts)?.map((appleEmail) => {
+ const appleToken = profileData?.appleAccounts?.[appleEmail!];
+ return (
+ appleToken && (
+ (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )
+ );
+ })
+ : null}
+
+ {!profileData?.microsoftAccounts && (
+ handleMicrosoftSignIn()}
+ label={"Connect Outlook"}
+ labelStyle={styles.addCalLbl}
+ labelProps={{
+ numberOfLines: 2,
+ }}
+ iconSource={() => (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )}
+
+ {profileData?.microsoftAccounts
+ ? Object.keys(profileData?.microsoftAccounts)?.map(
+ (microsoftEmail) => {
+ const microsoftToken =
+ profileData?.microsoftAccounts?.[microsoftEmail];
+ return (
+ microsoftToken && (
+ (
+
+
+
+ )}
+ style={styles.addCalBtn}
+ color="black"
+ text70BL
+ />
+ )
+ );
+ }
+ )
+ : null}
+
+
+
+
+
+ setRedirectOverride(false)}
+ marginT-50
+ labelStyle={{
+ fontFamily: "PlusJakartaSans_600SemiBold",
+ fontSize: 16,
+ }}
+ style={{height: 50}}
+ backgroundColor="#fd1775"
+ />
+
+
+
+ )
+}
+
+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,
+ },
+ noPaddingCard: {
+ backgroundColor: "white",
+ width: "100%",
+ 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,
+ },
+});
diff --git a/app/(unauth)/onboarding_flow/index.tsx b/app/(unauth)/connect.tsx
similarity index 80%
rename from app/(unauth)/onboarding_flow/index.tsx
rename to app/(unauth)/connect.tsx
index 8d09129..7abd451 100644
--- a/app/(unauth)/onboarding_flow/index.tsx
+++ b/app/(unauth)/connect.tsx
@@ -1,5 +1,5 @@
import Entry from "@/components/pages/main/Entry";
export default function Screen() {
- return ;
+ return ;
}
diff --git a/app/(unauth)/get_started.tsx b/app/(unauth)/get_started.tsx
new file mode 100644
index 0000000..f5788f2
--- /dev/null
+++ b/app/(unauth)/get_started.tsx
@@ -0,0 +1,169 @@
+import {SafeAreaView} from "react-native-safe-area-context";
+import {Button, Colors, Dialog, LoaderScreen, Text, View} from "react-native-ui-lib";
+import React, {useCallback, useState} from "react";
+import {useRouter} from "expo-router";
+import QRIcon from "@/assets/svgs/QRIcon";
+import {Camera, CameraView} from "expo-camera";
+import {useLoginWithQrCode} from "@/hooks/firebase/useLoginWithQrCode";
+import {useAuthContext} from "@/contexts/AuthContext";
+import debounce from "debounce";
+
+export default function Screen() {
+ const router = useRouter()
+ const {setRedirectOverride} = useAuthContext()
+ const [hasPermission, setHasPermission] = useState(null);
+ const [showCameraDialog, setShowCameraDialog] = useState(false);
+
+ const {mutateAsync: signInWithQrCode, isLoading} = useLoginWithQrCode();
+
+ const debouncedRouterReplace = useCallback(
+ debounce(() => {
+ router.push("/(unauth)/cal_sync");
+ }, 300),
+ []
+ );
+
+ const handleQrCodeScanned = async ({data}: { data: string }) => {
+ setShowCameraDialog(false);
+ setRedirectOverride(true);
+ try {
+ await signInWithQrCode({userId: data});
+ debouncedRouterReplace()
+ } catch (err) {
+ console.log(err)
+ }
+ };
+
+ const getCameraPermissions = async (callback: () => void) => {
+ const {status} = await Camera.requestCameraPermissionsAsync();
+ setHasPermission(status === "granted");
+ if (status === "granted") {
+ callback();
+ }
+ };
+
+ const handleOpenQrCodeDialog = () => {
+ getCameraPermissions(() => setShowCameraDialog(true));
+ }
+
+ return (
+
+
+
+
+ Get started with Cally
+
+
+
+
+
+ }
+ onPress={handleOpenQrCodeDialog}
+ style={{height: 50}}
+ color={Colors.black}
+ backgroundColor={Colors.white}
+ />
+ {/* GOOGLE LOGIN HERE */}
+
+
+
+
+
+ or
+
+
+
+
+
+ router.push("/(unauth)/sign_up")}
+ style={{height: 50, borderStyle: "solid", borderColor: "#E2E2E2", borderWidth: 2}}
+ color={Colors.black}
+ backgroundColor={"transparent"}
+ />
+
+
+
+
+
+
+
+
+ Already have an account?
+
+
+ router.push("/(unauth)/sign_in")}
+ labelStyle={[
+ {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 16,
+ color: "#919191",
+ },
+ {fontSize: 16, textDecorationLine: "none", color: "#fd1775"},
+ ]}
+ />
+
+
+
+ {/* Legacy, move into separate component */}
+ {/* Camera Dialog */}
+
+
+
+ {isLoading && (
+
+ )}
+
+ )
+}
diff --git a/app/(unauth)/index.tsx b/app/(unauth)/index.tsx
index 6bd604c..25d0cd0 100644
--- a/app/(unauth)/index.tsx
+++ b/app/(unauth)/index.tsx
@@ -1,10 +1,44 @@
-import Entry from "@/components/pages/main/Entry";
import {SafeAreaView} from "react-native-safe-area-context";
+import {Button, Image, Text, View} from "react-native-ui-lib";
+import React from "react";
+import {useRouter} from "expo-router";
export default function Screen() {
+ const router = useRouter()
+
return (
-
-
+
+
+
+
+
+
+
+
+ Welcome to Cally
+
+
+ Lightening Mental Loads,
+ One Family at a Time
+
+
+
+
+
+
+ router.push("/(unauth)/get_started")}
+ style={{height: 50}}
+ backgroundColor="#fd1775"
+ />
+
+
)
-}
\ No newline at end of file
+}
diff --git a/app/(unauth)/onboarding_flow/_layout.tsx b/app/(unauth)/onboarding_flow/_layout.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/app/(unauth)/reset_password.tsx b/app/(unauth)/reset_password.tsx
new file mode 100644
index 0000000..ce263f1
--- /dev/null
+++ b/app/(unauth)/reset_password.tsx
@@ -0,0 +1,6 @@
+import React from "react";
+import {ResetPasswordPage} from "@/components/pages/main/ResetPasswordPage";
+
+export default function Screen() {
+ return
+}
diff --git a/app/(unauth)/sign_in.tsx b/app/(unauth)/sign_in.tsx
new file mode 100644
index 0000000..454fbef
--- /dev/null
+++ b/app/(unauth)/sign_in.tsx
@@ -0,0 +1,6 @@
+import SignInPage from "@/components/pages/main/SignInPage";
+import React from "react";
+
+export default function Screen() {
+ return
+}
diff --git a/app/(unauth)/sign_up.tsx b/app/(unauth)/sign_up.tsx
new file mode 100644
index 0000000..d653f37
--- /dev/null
+++ b/app/(unauth)/sign_up.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import SignUpPage from "@/components/pages/main/SignUpPage";
+
+export default function Screen() {
+ return (
+
+ )
+}
diff --git a/assets/svgs/CloseXIcon.tsx b/assets/svgs/CloseXIcon.tsx
index b072267..b040fe6 100644
--- a/assets/svgs/CloseXIcon.tsx
+++ b/assets/svgs/CloseXIcon.tsx
@@ -5,13 +5,14 @@ const CloseXIcon: React.FC = (props) => (
width={15}
height={15}
fill="none"
+ viewBox="0 0 15 15"
{...props}
>
diff --git a/assets/svgs/FeedbackNavIcon.tsx b/assets/svgs/FeedbackNavIcon.tsx
new file mode 100644
index 0000000..0f4d67b
--- /dev/null
+++ b/assets/svgs/FeedbackNavIcon.tsx
@@ -0,0 +1,20 @@
+import * as React from "react"
+import Svg, { SvgProps, Path } from "react-native-svg"
+const FeedbackNavIcon = (props: SvgProps) => (
+
+)
+export default FeedbackNavIcon
diff --git a/assets/svgs/OutlookIcon.tsx b/assets/svgs/OutlookIcon.tsx
index 719d0de..1e0626a 100644
--- a/assets/svgs/OutlookIcon.tsx
+++ b/assets/svgs/OutlookIcon.tsx
@@ -3,8 +3,8 @@ import Svg, { Path, LinearGradient, Stop, SvgProps } from "react-native-svg";
const OutlookIcon: React.FC = (props) => (
);
};
@@ -161,11 +244,33 @@ export default SignUpPage;
const styles = StyleSheet.create({
textfield: {
backgroundColor: "white",
- marginVertical: 10,
+ marginVertical: 8,
padding: 30,
- height: 45,
+ height: 44,
borderRadius: 50,
+ fontFamily: "PlusJakartaSans_300Light",
+ fontSize: 13,
+ color: "#919191",
+ },
+ jakartaLight: {
+ fontFamily: "PlusJakartaSans_300Light",
+ fontSize: 13,
+ color: "#919191",
+ },
+ jakartaMedium: {
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 13,
+ color: "#919191",
+ textDecorationLine: "underline",
+ },
+ title: { fontFamily: "Manrope_600SemiBold", fontSize: 34, marginTop: 50 },
+ subtitle: { fontFamily: "PlusJakartaSans_400Regular", fontSize: 16 },
+ check: {
+ borderRadius: 3,
+ aspectRatio: 1,
+ width: 18,
+ color: "#919191",
+ borderColor: "#919191",
+ borderWidth: 1,
},
- //mora da se izmeni kako treba
- bottomView: { marginTop: 150 },
});
diff --git a/components/pages/settings/CalendarSettingsPage.tsx b/components/pages/settings/CalendarSettingsPage.tsx
index a2e012b..46ff557 100644
--- a/components/pages/settings/CalendarSettingsPage.tsx
+++ b/components/pages/settings/CalendarSettingsPage.tsx
@@ -1,5 +1,5 @@
import {AntDesign, Ionicons} from "@expo/vector-icons";
-import React, {useCallback, useEffect, useState} from "react";
+import React, {useCallback, useState} from "react";
import {Button, Checkbox, Text, View} from "react-native-ui-lib";
import {ActivityIndicator, ScrollView, StyleSheet} from "react-native";
import {TouchableOpacity} from "react-native-gesture-handler";
@@ -9,51 +9,49 @@ import debounce from "debounce";
import AppleIcon from "@/assets/svgs/AppleIcon";
import GoogleIcon from "@/assets/svgs/GoogleIcon";
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";
-import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
-import {useFetchAndSaveOutlookEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
-import {useFetchAndSaveAppleEvents} from "@/hooks/useFetchAndSaveAppleEvents";
-import * as AppleAuthentication from 'expo-apple-authentication';
import ExpoLocalization from "expo-localization/src/ExpoLocalization";
import {colorMap} from "@/constants/colorMap";
+import {useSetAtom} from "jotai";
+import {settingsPageIndex} from "../calendar/atoms";
+import CalendarSettingsDialog from "./calendar_components/CalendarSettingsDialog";
+import {useClearTokens} from "@/hooks/firebase/useClearTokens";
+import {useCalSync} from "@/hooks/useCalSync";
+import Feather from "@expo/vector-icons/Feather";
-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",
- ],
-};
-const microsoftConfig = {
- 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;
-}) => {
+const CalendarSettingsPage = () => {
const {profileData} = useAuthContext();
- const [firstDayOfWeek, setFirstDayOfWeek] = useState(profileData?.firstDayOfWeek ?? ExpoLocalization.getCalendars()[0].firstWeekday === 1 ? "Mondays" : "Sundays");
+ const setPageIndex = useSetAtom(settingsPageIndex);
+
+ const [firstDayOfWeek, setFirstDayOfWeek] = useState(
+ profileData?.firstDayOfWeek ??
+ ExpoLocalization.getCalendars()[0].firstWeekday === 1
+ ? "Mondays"
+ : "Sundays"
+ );
+ const [isModalVisible, setModalVisible] = useState(false);
+ const [selectedService, setSelectedService] = useState<
+ "google" | "outlook" | "apple"
+ >("google");
+ const [selectedEmail, setSelectedEmail] = useState("");
+
+ const showConfirmationDialog = (
+ serviceName: "google" | "outlook" | "apple",
+ email: string
+ ) => {
+ setSelectedService(serviceName);
+ setSelectedEmail(email);
+ setModalVisible(true);
+ };
+
+ const handleConfirm = async () => {
+ await clearToken({email: selectedEmail, provider: selectedService});
+ setModalVisible(false);
+ };
+
+ const handleCancel = () => {
+ setModalVisible(false);
+ };
const [selectedColor, setSelectedColor] = useState(
profileData?.eventColor ?? colorMap.pink
@@ -63,169 +61,22 @@ const CalendarSettingsPage = (props: {
);
const {mutateAsync: updateUserData} = useUpdateUserData();
- const {mutateAsync: fetchAndSaveGoogleEvents, isLoading: isSyncingGoogle} = useFetchAndSaveGoogleEvents();
- const {mutateAsync: fetchAndSaveOutlookEvents, isLoading: isSyncingOutlook} = useFetchAndSaveOutlookEvents();
- const {mutateAsync: fetchAndSaveAppleEvents, isLoading: isSyncingApple} = useFetchAndSaveAppleEvents();
+ const {mutateAsync: clearToken} = useClearTokens();
-
- WebBrowser.maybeCompleteAuthSession();
- const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
-
- useEffect(() => {
- signInWithGoogle();
- }, [response]);
-
- 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;
-
- let googleAccounts = profileData?.googleAccounts;
- const updatedGoogleAccounts = googleAccounts ? {...googleAccounts, [googleMail]: accessToken} : {[googleMail]: accessToken};
-
- await updateUserData({
- newUserData: {googleAccounts: updatedGoogleAccounts},
- });
-
- await fetchAndSaveGoogleEvents({token: accessToken, email: 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;
-
- let microsoftAccounts = profileData?.microsoftAccounts;
- const updatedMicrosoftAccounts = microsoftAccounts ? {...microsoftAccounts, [outlookMail]: tokenData.access_token} : {[outlookMail]: tokenData.access_token};
-
- // Update user data with Microsoft token and email
- await updateUserData({
- newUserData: {microsoftAccounts: updatedMicrosoftAccounts},
- });
-
- await fetchAndSaveOutlookEvents(tokenData.access_token, 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 handleAppleSignIn = async () => {
- try {
- console.log("Starting Apple Sign-in...");
-
- const credential = await AppleAuthentication.signInAsync({
- requestedScopes: [
- AppleAuthentication.AppleAuthenticationScope.EMAIL,
- AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
- ],
- });
-
- console.log("Apple sign-in result:", credential);
-
- const appleToken = credential.identityToken;
- const appleMail = credential.email;
-
- if (appleToken) {
- console.log("Apple ID token received. Fetch user info if needed...");
-
- await updateUserData({
- newUserData: {appleToken, appleMail},
- });
-
- console.log("User data updated with Apple ID token.");
- await fetchAndSaveAppleEvents({token: appleToken, email: appleMail!});
- } else {
- console.warn("Apple authentication was not successful or email was hidden.");
- }
- } catch (error) {
- console.error("Error during Apple Sign-in:", error);
- }
- };
+ const {
+ isSyncingGoogle,
+ isSyncingOutlook,
+ isConnectedToGoogle,
+ isConnectedToMicrosoft,
+ isConnectedToApple,
+ handleAppleSignIn,
+ isSyncingApple,
+ handleMicrosoftSignIn,
+ fetchAndSaveOutlookEvents,
+ fetchAndSaveGoogleEvents,
+ handleStartGoogleSignIn,
+ fetchAndSaveAppleEvents
+ } = useCalSync()
const debouncedUpdateUserData = useCallback(
debounce(async (color: string) => {
@@ -259,9 +110,9 @@ const CalendarSettingsPage = (props: {
);
const handleChangeFirstDayOfWeek = (firstDayOfWeek: string) => {
- setFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
- debouncedUpdateFirstDayOfWeek(firstDayOfWeek === "Sundays" ? "Mondays" : "Sundays");
- }
+ setFirstDayOfWeek(firstDayOfWeek);
+ debouncedUpdateFirstDayOfWeek(firstDayOfWeek);
+ };
const handleChangeColor = (color: string) => {
setPreviousSelectedColor(selectedColor);
@@ -269,80 +120,25 @@ const CalendarSettingsPage = (props: {
debouncedUpdateUserData(color);
};
- const clearToken = async (provider: "google" | "outlook" | "apple", email: string) => {
- const newUserData: Partial = {};
- if (provider === "google") {
- let googleAccounts = profileData?.googleAccounts;
- if (googleAccounts) {
- googleAccounts[email] = null;
- newUserData.googleAccounts = googleAccounts;
- }
- } else if (provider === "outlook") {
- let microsoftAccounts = profileData?.microsoftAccounts;
- if (microsoftAccounts) {
- microsoftAccounts[email] = null;
- newUserData.microsoftAccounts = microsoftAccounts;
- }
- } else if (provider === "apple") {
- let appleAccounts = profileData?.appleAccounts;
- if (appleAccounts) {
- appleAccounts[email] = null;
- newUserData.appleAccounts = appleAccounts;
- }
- }
- await updateUserData({newUserData});
- };
-
- let isConnectedToGoogle = false;
- if (profileData?.googleAccounts) {
- Object.values(profileData?.googleAccounts).forEach((item) => {
- if (item !== null) {
- isConnectedToGoogle = true;
- return;
- }
- });
- }
-
- let isConnectedToMicrosoft = false;
- const microsoftAccounts = profileData?.microsoftAccounts;
- if (microsoftAccounts) {
- Object.values(profileData?.microsoftAccounts).forEach((item) => {
- if (item !== null) {
- isConnectedToMicrosoft = true;
- return;
- }
- });
- }
-
- let isConnectedToApple = false;
- if (profileData?.appleAccounts) {
- Object.values(profileData?.appleAccounts).forEach((item) => {
- if (item !== null) {
- isConnectedToApple = true;
- return;
- }
- });
- }
-
return (
+ setPageIndex(0)}>
+
+
+
+ Return to main settings
+
+
+
- props.setSelectedPage(0)}>
-
-
-
- Return to main settings
-
-
-
Calendar settings
@@ -424,11 +220,11 @@ const CalendarSettingsPage = (props: {
promptAsync()}
- label={"Connect Google"}
+ onPress={() => handleStartGoogleSignIn()}
+ label={profileData?.googleAccounts ? "Connect another Google account" : "Connect Google account"}
labelStyle={styles.addCalLbl}
labelProps={{
- numberOfLines: 2
+ numberOfLines: 2,
}}
iconSource={() => (
@@ -439,52 +235,14 @@ const CalendarSettingsPage = (props: {
color="black"
text70BL
/>
- {profileData?.googleAccounts ? Object.keys(profileData?.googleAccounts)?.map((googleMail) => {
- const googleToken = profileData?.googleAccounts?.[googleMail];
- return googleToken && clearToken("google", googleMail)}
- label={`Disconnect ${googleMail}`}
- labelStyle={styles.addCalLbl}
- labelProps={{
- numberOfLines: 2
- }}
- iconSource={() => (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- }) : null}
- handleAppleSignIn()}
- label={"Connect Apple"}
- labelStyle={styles.addCalLbl}
- labelProps={{
- numberOfLines: 2
- }}
- iconSource={() => (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- {profileData?.appleAccounts ? Object.keys(profileData?.appleAccounts)?.map((appleEmail) => {
- const appleToken = profileData?.appleAccounts?.[appleEmail];
- return appleToken && clearToken("apple", appleEmail)}
- label={`Disconnect ${appleEmail}`}
+ {!profileData?.appleAccounts && (
+ handleAppleSignIn()}
+ label={"Connect Apple"}
labelStyle={styles.addCalLbl}
labelProps={{
- numberOfLines: 2
+ numberOfLines: 2,
}}
iconSource={() => (
@@ -495,14 +253,14 @@ const CalendarSettingsPage = (props: {
color="black"
text70BL
/>
- }) : null}
+ )}
handleMicrosoftSignIn()}
- label={"Connect Outlook"}
+ label={profileData?.microsoftAccounts ? "Connect another Outlook account" : "Connect Outlook"}
labelStyle={styles.addCalLbl}
labelProps={{
- numberOfLines: 2
+ numberOfLines: 2,
}}
iconSource={() => (
@@ -513,147 +271,220 @@ const CalendarSettingsPage = (props: {
color="black"
text70BL
/>
- {profileData?.microsoftAccounts ? Object.keys(profileData?.microsoftAccounts)?.map((microsoftEmail) => {
- const microsoftToken = profileData?.microsoftAccounts?.[microsoftEmail];
- return microsoftToken && clearToken("outlook", microsoftEmail)}
- label={`Disconnect ${microsoftEmail}`}
- labelStyle={styles.addCalLbl}
- labelProps={{
- numberOfLines: 2
- }}
- iconSource={() => (
-
-
-
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- }) : null}
- {(isConnectedToGoogle || isConnectedToMicrosoft || isConnectedToApple) && (
+ {(isConnectedToGoogle ||
+ isConnectedToMicrosoft ||
+ isConnectedToApple) && (
<>
Connected Calendars
-
+
- {profileData?.googleAccounts && Object.keys(profileData?.googleAccounts)?.map((googleEmail) => {
- const googleToken = profileData?.googleAccounts?.[googleEmail];
- return googleToken && (
- fetchAndSaveGoogleEvents({token: googleToken, email: googleEmail})}
- >
-
- fetchAndSaveGoogleEvents({token: googleToken, email: googleEmail})}
- label={`Sync ${googleEmail}`}
- labelStyle={styles.addCalLbl}
- labelProps={{numberOfLines: 3}}
- iconSource={() => (
-
-
+ {profileData?.googleAccounts &&
+ Object.keys(profileData?.googleAccounts)?.map(
+ (googleEmail) => {
+ const googleToken =
+ profileData?.googleAccounts?.[googleEmail]?.accessToken;
+ return (
+ googleToken && (
+
+
+ {isSyncingGoogle ? (
+
+
+
+ ) : (
+
+
+ fetchAndSaveGoogleEvents({
+ token: googleToken,
+ email: googleEmail,
+ })
+ }
+ iconSource={() => }
+ />
+
+ )}
+
+
+
+
+ {googleEmail}
+
+ showConfirmationDialog("google", googleEmail)
+ }
+ iconSource={() => }
+ />
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
+
+ )
+ );
+ }
+ )}
- {isSyncingGoogle ? (
-
- ) : (
-
- )}
-
-
- )
- })}
+ {profileData?.appleAccounts &&
+ Object.keys(profileData?.appleAccounts)?.map((appleEmail) => {
+ console.log(profileData?.appleAccounts)
- {profileData?.appleAccounts && Object.keys(profileData?.appleAccounts)?.map((appleEmail) => {
- const appleToken = profileData?.appleAccounts?.[appleEmail];
- return appleToken && (
- fetchAndSaveAppleEvents({
- email: appleEmail,
- token: appleToken
- })}>
-
- fetchAndSaveAppleEvents({
- email: appleEmail,
- token: appleToken
- })}
- label={`Sync ${appleEmail}`}
- labelStyle={styles.addCalLbl}
- labelProps={{numberOfLines: 3}}
- iconSource={() => (
-
+ const appleToken = profileData?.appleAccounts?.[appleEmail];
+ return (
+ appleToken && (
+
+
+
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- {isSyncingApple ? (
-
- ) : (
-
- )}
-
-
- )
- })}
+
+ {appleEmail}
+
+ {isSyncingApple ? (
+
+
+
+ ) : (
+
+
+ fetchAndSaveAppleEvents({
+ email: appleEmail,
+ token: appleToken,
+ })
+ }
+ iconSource={() => }
+ />
+
+ )}
+ showConfirmationDialog("apple", appleEmail)}
+ iconSource={() => }
+ />
+
+
+ )
+ );
+ })}
- {profileData?.microsoftAccounts && Object.keys(profileData?.microsoftAccounts)?.map((microsoftEmail) => {
- const microsoftToken = profileData?.microsoftAccounts?.[microsoftEmail];
- return microsoftToken && (
- fetchAndSaveOutlookEvents({
- token: microsoftToken,
- email: microsoftEmail
- })}
- >
-
- fetchAndSaveOutlookEvents({
- token: microsoftToken,
- email: microsoftEmail
- })}
- label={`Sync ${microsoftEmail}`}
- labelStyle={styles.addCalLbl}
- labelProps={{numberOfLines: 3}}
- iconSource={() => (
-
-
+ {profileData?.microsoftAccounts &&
+ Object.keys(profileData?.microsoftAccounts)?.map(
+ (microsoftEmail) => {
+ const microsoftToken =
+ profileData?.microsoftAccounts?.[microsoftEmail];
+ return (
+ microsoftToken && (
+
+
+ {isSyncingOutlook ? (
+
+
+
+ ) : (
+
+
+ fetchAndSaveOutlookEvents({
+ token: microsoftToken,
+ email: microsoftEmail,
+ })
+ }
+ iconSource={() => }
+ />
+
+ )}
+
+
+
+
+ {microsoftEmail}
+
+ showConfirmationDialog("outlook", microsoftEmail)
+ }
+ iconSource={() => }
+ />
- )}
- style={styles.addCalBtn}
- color="black"
- text70BL
- />
- {isSyncingOutlook ? (
-
- ) : (
-
- )}
-
-
- )
- })}
+
+ )
+ );
+ }
+ )}
>
)}
+
);
};
@@ -698,10 +529,10 @@ const styles = StyleSheet.create({
fontSize: 16,
fontFamily: "PlusJakartaSan_500Medium",
flexWrap: "wrap",
- width: "75%",
+ width: "70%",
textAlign: "left",
lineHeight: 20,
- overflow: "visible"
+ overflow: "hidden",
},
subTitle: {
fontFamily: "Manrope_600SemiBold",
diff --git a/components/pages/settings/ChoreRewardSettings.tsx b/components/pages/settings/ChoreRewardSettings.tsx
index 4330a87..0e0215e 100644
--- a/components/pages/settings/ChoreRewardSettings.tsx
+++ b/components/pages/settings/ChoreRewardSettings.tsx
@@ -4,30 +4,32 @@ import { Ionicons } from "@expo/vector-icons";
import { ToDosContextProvider } from "@/contexts/ToDosContext";
import ToDosList from "../todos/ToDosList";
import { ScrollView } from "react-native-gesture-handler";
+import { settingsPageIndex } from "../calendar/atoms";
+import { useAtom } from "jotai";
+
+const ChoreRewardSettings = () => {
+ const [pageIndex, setPageIndex] = useAtom(settingsPageIndex);
-const ChoreRewardSettings = (props: {
- setSelectedPage: (page: number) => void;
-}) => {
return (
-
+ setPageIndex(0)}>
+
+
+
+ Return to main settings
+
+
+
+
- props.setSelectedPage(0)}>
-
-
-
- Return to main settings
-
-
-
Chore Reward Settings
diff --git a/components/pages/settings/SettingsPage.tsx b/components/pages/settings/SettingsPage.tsx
index b90efe8..cbe0b6c 100644
--- a/components/pages/settings/SettingsPage.tsx
+++ b/components/pages/settings/SettingsPage.tsx
@@ -1,7 +1,7 @@
-import {Button, Text, View} from "react-native-ui-lib";
-import React, {useState} from "react";
-import {StyleSheet} from "react-native";
-import {Octicons} from "@expo/vector-icons";
+import { Button, Text, View } from "react-native-ui-lib";
+import React, { useState } from "react";
+import {Linking, StyleSheet} from "react-native";
+import { Octicons } from "@expo/vector-icons";
import CalendarSettingsPage from "./CalendarSettingsPage";
import ChoreRewardSettings from "./ChoreRewardSettings";
import UserSettings from "./UserSettings";
@@ -9,120 +9,138 @@ import ProfileIcon from "@/assets/svgs/ProfileIcon";
import CalendarIcon from "@/assets/svgs/CalendarIcon";
import PrivacyPolicyIcon from "@/assets/svgs/PrivacyPolicyIcon";
import ArrowRightIcon from "@/assets/svgs/ArrowRightIcon";
-import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
+import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
+import { settingsPageIndex } from "../calendar/atoms";
+import { useAtom } from "jotai";
const pageIndex = {
- main: 0,
- user: 1,
- calendar: 2,
- chore: 3,
- policy: 4,
+ main: 0,
+ user: 1,
+ calendar: 2,
+ chore: 3,
+ policy: 4,
};
-const SettingsPage = () => {
- const {profileData} = useAuthContext()
- const isntParent = profileData?.userType !== ProfileType.PARENT
+const PRIVACY_POLICY_URL = 'https://callyapp.com';
- const [selectedPage, setSelectedPage] = useState(0);
- return (
-
- {selectedPage == 0 && (
-
-
-
-
- Manage My Profile
-
-
-
- }
- onPress={() => setSelectedPage(pageIndex.user)}
- />
-
-
-
- Calendar Settings
-
-
-
- }
- onPress={() => {
- setSelectedPage(pageIndex.calendar);
- }}
- />
-
-
-
- To-Do Reward Settings
-
-
-
- }
- onPress={() => setSelectedPage(pageIndex.chore)}
- />
-
-
-
- Cally Privacy Policy
-
-
-
- }
- />
-
- )}
- {selectedPage == pageIndex.calendar && (
-
- )}
- {selectedPage == pageIndex.chore && (
-
- )}
- {selectedPage == pageIndex.user && (
-
- )}
+
+const SettingsPage = () => {
+ const { profileData } = useAuthContext();
+ const [pageIndex, setPageIndex] = useAtom(settingsPageIndex);
+ const isntParent = profileData?.userType !== ProfileType.PARENT;
+
+ const openPrivacyPolicy = async () => {
+ const supported = await Linking.canOpenURL(PRIVACY_POLICY_URL);
+ if (supported) {
+ await Linking.openURL(PRIVACY_POLICY_URL);
+ } else {
+ console.log("Don't know how to open this URL:", PRIVACY_POLICY_URL);
+ }
+ };
+
+ return (
+
+ {pageIndex == 0 && (
+
+
+
+
+ Manage My Profile
+
+
+
+ }
+ onPress={() => setPageIndex(1)}
+ />
+
+
+
+ Calendar Settings
+
+
+
+ }
+ onPress={() => {
+ setPageIndex(2);
+ }}
+ />
+
+
+
+ To-Do Reward Settings
+
+
+
+ }
+ onPress={() => setPageIndex(3)}
+ />
+
+
+ Cally Privacy Policy
+
+
+ }
+ />
- );
+ )}
+ {pageIndex == 2 && }
+ {pageIndex == 3 && }
+ {pageIndex == 1 && }
+
+ );
};
export default SettingsPage;
const styles = StyleSheet.create({
- mainBtn: {
- width: 311,
- justifyContent: "flex-start",
- marginBottom: 20,
- height: 57.61,
- },
- label: {
- fontFamily: "Poppins_400Regular",
- fontSize: 14.71,
- textAlignVertical: "center",
- },
- disabledText: {
- color: '#A9A9A9', // Example of a gray color for disabled text
- },
-});
\ No newline at end of file
+ mainBtn: {
+ width: 311,
+ justifyContent: "flex-start",
+ marginBottom: 20,
+ height: 57.61,
+ },
+ label: {
+ fontFamily: "Poppins_400Regular",
+ fontSize: 14.71,
+ textAlignVertical: "center",
+ },
+ disabledText: {
+ color: "#A9A9A9", // Example of a gray color for disabled text
+ },
+});
diff --git a/components/pages/settings/UserSettings.tsx b/components/pages/settings/UserSettings.tsx
index b1008d0..bb908dd 100644
--- a/components/pages/settings/UserSettings.tsx
+++ b/components/pages/settings/UserSettings.tsx
@@ -1,99 +1,131 @@
-import { Text, TouchableOpacity, View } from "react-native-ui-lib";
-import React, { useState } from "react";
-import { Ionicons } from "@expo/vector-icons";
-import { ScrollView, StyleSheet } from "react-native";
+import {FloatingButton, Text, TouchableOpacity, View,} from "react-native-ui-lib";
+import React, {useState} from "react";
+import {Ionicons} from "@expo/vector-icons";
+import {ScrollView, StyleSheet} from "react-native";
import MyProfile from "./user_settings_views/MyProfile";
import MyGroup from "./user_settings_views/MyGroup";
+import {useAtom, useSetAtom} from "jotai";
+import {settingsPageIndex, userSettingsView} from "../calendar/atoms";
+import PlusIcon from "@/assets/svgs/PlusIcon";
-const UserSettings = (props: { setSelectedPage: (page: number) => void }) => {
- const [selectedView, setSelectedView] = useState(true);
- return (
-
-
- props.setSelectedPage(0)}>
-
-
-
- Return to main settings
-
-
-
-
-
- User Management
-
-
- setSelectedView(true)}
- centerV
- centerH
- style={selectedView == true ? styles.btnSelected : styles.btnNot}
- >
-
-
- My Profile
-
-
-
- setSelectedView(false)}
- centerV
- centerH
- style={selectedView == false ? styles.btnSelected : styles.btnNot}
- >
-
-
- My Group
-
-
-
-
- {selectedView && }
- {!selectedView && }
-
-
+const UserSettings = () => {
+ const setPageIndex = useSetAtom(settingsPageIndex);
+ const [userView, setUserView] = useAtom(userSettingsView);
+ const [onNewUserClick, setOnNewUserClick] = useState<(boolean)>(false);
- {!selectedView && (
-
- selview
+ return (
+
+
+ {
+ setPageIndex(0);
+ setUserView(true);
+ }}
+ >
+
+
+
+ Return to main settings
+
+
+
+
+
+ User Management
+
+
+ setUserView(true)}
+ centerV
+ centerH
+ style={userView == true ? styles.btnSelected : styles.btnNot}
+ >
+
+
+ My Profile
+
+
+
+ setUserView(false)}
+ centerV
+ centerH
+ style={userView == false ? styles.btnSelected : styles.btnNot}
+ >
+
+
+ My Group
+
+
+
+
+ {userView && }
+ {!userView && }
+
+
+ {!userView && (
+ ,
+ onPress: () => setOnNewUserClick(true),
+ style: styles.bottomButton,
+ labelStyle: {fontFamily: "Manrope_600SemiBold", fontSize: 15},
+ }}
+ />
+ )}
- )}
-
- );
+ );
};
const styles = StyleSheet.create({
- buttonSwitch: {
- borderRadius: 50,
- width: "100%",
- backgroundColor: "#ebebeb",
- height: 45,
- },
- btnSelected: {
- backgroundColor: "#05a8b6",
- height: "100%",
- width: "50%",
- borderRadius: 50,
- },
- btnTxt: {
- fontFamily: "Manrope_500Medium",
- fontSize: 15,
- },
- btnNot: {
- height: "100%",
- width: "50%",
- borderRadius: 50,
- },
- title: { fontFamily: "Manrope_600SemiBold", fontSize: 18 },
+ bottomButton: {
+ position: "absolute",
+ bottom: 15,
+ marginHorizontal: 28,
+ width: 337,
+ backgroundColor: "#e8156c",
+ height: 53.26,
+ },
+ buttonSwitch: {
+ borderRadius: 50,
+ width: "100%",
+ backgroundColor: "#ebebeb",
+ height: 45,
+ },
+ btnSelected: {
+ backgroundColor: "#05a8b6",
+ height: "100%",
+ width: "50%",
+ borderRadius: 50,
+ },
+ btnTxt: {
+ fontFamily: "Manrope_500Medium",
+ fontSize: 15,
+ },
+ btnNot: {
+ height: "100%",
+ width: "50%",
+ borderRadius: 50,
+ },
+ title: {fontFamily: "Manrope_600SemiBold", fontSize: 18},
});
export default UserSettings;
diff --git a/components/pages/settings/calendar_components/CalendarSettingsDialog.tsx b/components/pages/settings/calendar_components/CalendarSettingsDialog.tsx
new file mode 100644
index 0000000..12eea93
--- /dev/null
+++ b/components/pages/settings/calendar_components/CalendarSettingsDialog.tsx
@@ -0,0 +1,86 @@
+import React from "react";
+import { Dialog, Button, Text, View } from "react-native-ui-lib";
+import { StyleSheet } from "react-native";
+
+interface ConfirmationDialogProps {
+ visible: boolean;
+ serviceName: "google" | "outlook" | "apple";
+ email: string;
+ onDismiss: () => void;
+ onConfirm: () => void;
+}
+
+const CalendarSettingsDialog: React.FC = ({
+ visible,
+ serviceName,
+ email,
+ onDismiss,
+ onConfirm,
+}) => {
+ return (
+
+ );
+};
+
+// Empty stylesheet for future styles
+const styles = StyleSheet.create({
+ confirmBtn: {
+ backgroundColor: "#ea156d",
+ },
+ cancelBtn: {
+ backgroundColor: "white",
+ },
+ dialog: {
+ backgroundColor: "white",
+ paddingHorizontal: 25,
+ paddingTop: 35,
+ paddingBottom: 17,
+ borderRadius: 20,
+ },
+ title: {
+ fontFamily: "Manrope_600SemiBold",
+ fontSize: 22,
+ marginBottom: 20,
+ },
+ text: {
+ fontFamily: "PlusJakartaSans_400Regular",
+ fontSize: 16,
+ marginBottom: 25,
+ },
+});
+
+export default CalendarSettingsDialog;
diff --git a/components/pages/settings/user_components/DeleteProfileDialogs.tsx b/components/pages/settings/user_components/DeleteProfileDialogs.tsx
new file mode 100644
index 0000000..64b8efa
--- /dev/null
+++ b/components/pages/settings/user_components/DeleteProfileDialogs.tsx
@@ -0,0 +1,128 @@
+import React, { useState } from "react";
+import { Dialog, Button, Text, View } from "react-native-ui-lib";
+import { StyleSheet } from "react-native";
+import { Feather } from "@expo/vector-icons";
+
+interface ConfirmationDialogProps {
+ visible: boolean;
+ onDismiss: () => void;
+ onFirstYes: () => void;
+ onConfirm: () => void;
+}
+
+const DeleteProfileDialogs: React.FC = ({
+ visible,
+ onDismiss,
+ onFirstYes,
+ onConfirm,
+}) => {
+ const [confirmationDialog, setConfirmationDialog] = useState(false);
+ return (
+ <>
+
+
+ >
+ );
+};
+
+// Empty stylesheet for future styles
+const styles = StyleSheet.create({
+ confirmBtn: {
+ backgroundColor: "#FF5449",
+ },
+ cancelBtn: {
+ backgroundColor: "white",
+ },
+ dialog: {
+ backgroundColor: "white",
+ paddingHorizontal: 25,
+ paddingTop: 25,
+ paddingBottom: 17,
+ borderRadius: 20,
+ },
+ title: {
+ fontFamily: "Manrope_600SemiBold",
+ fontSize: 22,
+ marginBottom: 5,
+ },
+ text: {
+ fontFamily: "PlusJakartaSans_400Regular",
+ fontSize: 16,
+ marginBottom: 0,
+ },
+});
+
+export default DeleteProfileDialogs;
diff --git a/components/pages/settings/user_settings_views/MyGroup.tsx b/components/pages/settings/user_settings_views/MyGroup.tsx
index e138682..c1d6867 100644
--- a/components/pages/settings/user_settings_views/MyGroup.tsx
+++ b/components/pages/settings/user_settings_views/MyGroup.tsx
@@ -3,8 +3,7 @@ import {
Button,
Card,
Colors,
- Dialog,
- FloatingButton,
+ Dialog, Image,
KeyboardAwareScrollView,
PanningProvider,
Picker,
@@ -15,10 +14,10 @@ import {
View,
} from "react-native-ui-lib";
import React, {useEffect, useRef, useState} from "react";
-import {ScrollView, StyleSheet} from "react-native";
+import {ImageBackground, Platform, StyleSheet} from "react-native";
import {PickerSingleValue} from "react-native-ui-lib/src/components/picker/types";
import {useCreateSubUser} from "@/hooks/firebase/useCreateSubUser";
-import {ProfileType} from "@/contexts/AuthContext";
+import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import UserMenu from "@/components/pages/settings/user_settings_views/UserMenu";
import {uuidv4} from "@firebase/util";
@@ -28,11 +27,18 @@ import CircledXIcon from "@/assets/svgs/CircledXIcon";
import ProfileIcon from "@/assets/svgs/ProfileIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import Ionicons from "@expo/vector-icons/Ionicons";
-import {PreviousNextView} from "react-native-keyboard-manager";
+import KeyboardManager, {PreviousNextView,} from "react-native-keyboard-manager";
+import {ScrollView} from "react-native-gesture-handler";
+import {useUploadProfilePicture} from "@/hooks/useUploadProfilePicture";
+import {ImagePickerAsset} from "expo-image-picker";
-const MyGroup = () => {
+type MyGroupProps = {
+ onNewUserClick: boolean;
+ setOnNewUserClick: React.Dispatch>;
+};
+
+const MyGroup: React.FC = ({onNewUserClick, setOnNewUserClick}) => {
const [showAddUserDialog, setShowAddUserDialog] = useState(false);
- const [showNewUserInfoDialog, setShowNewUserInfoDialog] = useState(false);
const [selectedStatus, setSelectedStatus] = useState<
string | PickerSingleValue
>(ProfileType.CHILD);
@@ -40,13 +46,19 @@ const MyGroup = () => {
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
+ const [newUserId, setNewUserId] = useState("")
+
const lNameRef = useRef(null);
const emailRef = useRef(null);
- const [showQRCodeDialog, setShowQRCodeDialog] = useState(false);
+ const [showQRCodeDialog, setShowQRCodeDialog] = useState(
+ false
+ );
const {mutateAsync: createSubUser, isLoading, isError} = useCreateSubUser();
const {data: familyMembers} = useGetFamilyMembers(true);
+ const {user} = useAuthContext();
+ const {pickImage, changeProfilePicture, handleClearImage, pfpUri, profileImageAsset} = useUploadProfilePicture(newUserId)
const parents =
familyMembers?.filter((x) => x.userType === ProfileType.PARENT) ?? [];
@@ -67,10 +79,10 @@ const MyGroup = () => {
return;
}
- if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
- console.error("Email is required for non-family device users");
- return;
- }
+ // if (selectedStatus !== ProfileType.FAMILY_DEVICE && !email) {
+ // console.error("Email is required for non-family device users");
+ // return;
+ // }
if (email && !email.includes("@")) {
console.error("Invalid email address");
@@ -87,28 +99,38 @@ const MyGroup = () => {
console.log(res);
if (!isError) {
- setShowNewUserInfoDialog(false);
+ setOnNewUserClick(false);
if (res?.data?.userId) {
- setTimeout(() => {
+ if (profileImageAsset) {
+ await changeProfilePicture(profileImageAsset)
setShowQRCodeDialog(res.data.userId);
- }, 500);
+ } else {
+ setTimeout(() => {
+ setShowQRCodeDialog(res.data.userId);
+ }, 500);
+ }
+
+ handleClearImage()
}
}
};
+ useEffect(() => {
+ if (Platform.OS === "ios") KeyboardManager.setEnableAutoToolbar(true);
+ }, []);
+
useEffect(() => {
setFirstName("");
setLastName("");
setEmail("");
- }, [])
-
+ }, []);
// @ts-ignore
return (
-
-
-
+
+
+
{!parents.length && !children.length && !caregivers.length && (
{isLoading ? "Loading...." : "No user devices added"}
@@ -116,8 +138,8 @@ const MyGroup = () => {
)}
{(!!parents.length || !!children.length) && (
- <>
-
+
+
Family
{[...parents, ...children]?.map((member, index) => (
@@ -128,39 +150,45 @@ const MyGroup = () => {
style={styles.familyCard}
row
centerV
- padding-10
+ paddingT-10
>
-
-
-
+ {member.pfp ? (
+
+ ) : (
+
+ )}
+
+
{member.firstName} {member.lastName}
-
+
+
+
+
{member.userType === ProfileType.PARENT
- ? "Admin (You)"
+ ? `Admin${member.uid === user?.uid ? " (You)" : ""}`
: "Child"}
+ setShowQRCodeDialog(val)}
+ showQRCodeDialog={showQRCodeDialog === member?.uid}
+ userId={member?.uid!}
+ />
-
-
-
- setShowQRCodeDialog(val)}
- showQRCodeDialog={showQRCodeDialog === member?.uid}
- userId={member?.uid!}
- />
))}
- >
+
)}
{!!caregivers.length && (
- <>
-
+
+
Caregivers
{caregivers?.map((member) => (
@@ -196,7 +224,7 @@ const MyGroup = () => {
/>
))}
- >
+
)}
{!!familyDevices.length && (
@@ -237,19 +265,8 @@ const MyGroup = () => {
))}
>
)}
-
-
-
- setShowNewUserInfoDialog(true),
- style: styles.bottomButton,
- }}
- />
+
+
-
- }
- backgroundColor={Colors.grey60}
- style={{borderRadius: 25}}
- center
- />
- {
- }}>
-
- Upload User Profile Photo
-
-
+ {pfpUri ? (
+
+ ) : (
+
+ }
+ backgroundColor={Colors.grey60}
+ style={{borderRadius: 25}}
+ center
+ />
+ )}
+
+ {pfpUri ? (
+
+
+ Clear user photo
+
+
+ ) : (
+
+
+ Upload User Profile Photo
+
+
+ )}
+
Member Status
@@ -349,21 +386,30 @@ const MyGroup = () => {
floatingPlaceholder
style={styles.inViewPicker}
trailingAccessory={
-
-
+
+
}
>
-
+
{
onChangeText={setFirstName}
style={styles.inputField}
onSubmitEditing={() => {
- lNameRef.current?.focus()
+ lNameRef.current?.focus();
}}
blurOnSubmit={false}
returnKeyType="next"
@@ -404,11 +450,10 @@ const MyGroup = () => {
onChangeText={setLastName}
style={styles.inputField}
onSubmitEditing={() => {
- emailRef.current?.focus()
+ emailRef.current?.focus();
}}
blurOnSubmit={false}
returnKeyType="next"
-
/>
>
)}
@@ -470,12 +515,14 @@ const styles = StyleSheet.create({
marginVertical: 15,
backgroundColor: "white",
width: "100%",
- borderRadius: 15,
- padding: 20,
+ borderRadius: 12,
+ paddingHorizontal: 21,
+ paddingVertical: 20,
},
bottomButton: {
position: "absolute",
- bottom: 80,
+ bottom: 50,
+ backgroundColor: "#e8156c",
width: "100%",
},
familyCard: {
@@ -556,6 +603,16 @@ const styles = StyleSheet.create({
fontFamily: "PlusJakartaSans_500Medium",
fontSize: 13,
},
+ pfp: {aspectRatio: 1, width: 37.03, borderRadius: 10.56},
+ userType: {
+ fontFamily: "Manrope_500Medium",
+ fontSize: 12,
+ color: "#858585",
+ },
+ name: {
+ fontFamily: "Manrope_600SemiBold",
+ fontSize: 16,
+ },
});
export default MyGroup;
diff --git a/components/pages/settings/user_settings_views/MyProfile.tsx b/components/pages/settings/user_settings_views/MyProfile.tsx
index 869c619..a183b3a 100644
--- a/components/pages/settings/user_settings_views/MyProfile.tsx
+++ b/components/pages/settings/user_settings_views/MyProfile.tsx
@@ -3,6 +3,7 @@ import { StyleSheet, TouchableOpacity } from "react-native";
import { ScrollView } from "react-native-gesture-handler";
import * as ImagePicker from "expo-image-picker";
import {
+ Button,
Colors,
Image,
Picker,
@@ -18,6 +19,7 @@ import { useAuthContext } from "@/contexts/AuthContext";
import { useUpdateUserData } from "@/hooks/firebase/useUpdateUserData";
import { useChangeProfilePicture } from "@/hooks/firebase/useChangeProfilePicture";
import { colorMap } from "@/constants/colorMap";
+import DeleteProfileDialogs from "../user_components/DeleteProfileDialogs";
const MyProfile = () => {
const { user, profileData } = useAuthContext();
@@ -32,6 +34,15 @@ const MyProfile = () => {
string | ImagePicker.ImagePickerAsset | null
>(profileData?.pfp || null);
+ const [showDeleteDialog, setShowDeleteDialog] = useState(false);
+
+ const handleHideDeleteDialog = () => {
+ setShowDeleteDialog(false);
+ };
+ const handleShowDeleteDialog = () => {
+ setShowDeleteDialog(true);
+ };
+
const { mutateAsync: updateUserData } = useUpdateUserData();
const { mutateAsync: changeProfilePicture } = useChangeProfilePicture();
const isFirstRender = useRef(true);
@@ -48,13 +59,12 @@ const MyProfile = () => {
return;
}
debouncedUserDataUpdate();
- }, [timeZone, lastName, firstName, profileImage]);
+ }, [timeZone, lastName, firstName]);
useEffect(() => {
if (profileData) {
setFirstName(profileData.firstName || "");
setLastName(profileData.lastName || "");
- // setProfileImage(profileData.pfp || null);
setTimeZone(
profileData.timeZone || Localization.getCalendars()[0].timeZone!
);
@@ -78,7 +88,7 @@ const MyProfile = () => {
if (!result.canceled) {
setProfileImage(result.assets[0].uri);
- changeProfilePicture(result.assets[0]);
+ await changeProfilePicture(result.assets[0]);
}
};
@@ -93,7 +103,7 @@ const MyProfile = () => {
: profileImage;
return (
-
+
Your Profile
@@ -205,6 +215,22 @@ const MyProfile = () => {
+
+ {
+ setShowDeleteDialog(false);
+ }}
+ visible={showDeleteDialog}
+ onDismiss={handleHideDeleteDialog}
+ onConfirm={() => {console.log('delete account here')}}
+ />
);
};
diff --git a/components/pages/settings/user_settings_views/UserMenu.tsx b/components/pages/settings/user_settings_views/UserMenu.tsx
index 2e001e6..6b6cfd5 100644
--- a/components/pages/settings/user_settings_views/UserMenu.tsx
+++ b/components/pages/settings/user_settings_views/UserMenu.tsx
@@ -32,7 +32,10 @@ const UserMenu = ({
panDirection={PanningDirectionsEnum.DOWN}
>
- Scan this QR Code to Login:
+ Ask your family to download the app
+ and then scan the
+ QR Code to join the family group:
+
{
const [isVisible, setIsVisible] = useState(false);
return (
-
setIsVisible(!isVisible)}
>
-
+
Create new to do
-
-
+ {isVisible && (
+
+ )}
+
);
};
@@ -53,8 +61,7 @@ const styles = StyleSheet.create({
},
button: {
backgroundColor: "rgb(253, 23, 117)",
- paddingVertical: 15,
- paddingHorizontal: 30,
+ height: 53.26,
borderRadius: 30,
width: 335,
},
diff --git a/components/pages/todos/AddChoreDialog.tsx b/components/pages/todos/AddChoreDialog.tsx
index d93b20f..099d690 100644
--- a/components/pages/todos/AddChoreDialog.tsx
+++ b/components/pages/todos/AddChoreDialog.tsx
@@ -1,317 +1,353 @@
-import { View, Text, Button, Switch, PickerModes } from "react-native-ui-lib";
-import React, { useRef, useState } from "react";
-import PointsSlider from "@/components/shared/PointsSlider";
-import { repeatOptions, useToDosContext } from "@/contexts/ToDosContext";
-import { Feather, AntDesign, Ionicons } from "@expo/vector-icons";
import {
- Dialog,
- TextField,
- DateTimePicker,
- Picker,
- ButtonSize,
+ Button,
+ ButtonSize,
+ DateTimePicker,
+ Dialog,
+ Picker,
+ PickerModes,
+ Switch,
+ Text,
+ TextField,
+ TextFieldRef,
+ View
} from "react-native-ui-lib";
-import { PanningDirectionsEnum } from "react-native-ui-lib/src/incubator/panView";
-import { Dimensions, StyleSheet } from "react-native";
+import React, {useEffect, useRef, useState} from "react";
+import PointsSlider from "@/components/shared/PointsSlider";
+import {repeatOptions, useToDosContext} from "@/contexts/ToDosContext";
+import {Ionicons} from "@expo/vector-icons";
+import {PanningDirectionsEnum} from "react-native-ui-lib/src/incubator/panView";
+import {Dimensions, KeyboardAvoidingView, StyleSheet} from "react-native";
import DropModalIcon from "@/assets/svgs/DropModalIcon";
-import { IToDo } from "@/hooks/firebase/types/todoData";
+import {IToDo} from "@/hooks/firebase/types/todoData";
import AssigneesDisplay from "@/components/shared/AssigneesDisplay";
-import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
+import {useGetFamilyMembers} from "@/hooks/firebase/useGetFamilyMembers";
import CalendarIcon from "@/assets/svgs/CalendarIcon";
-import ClockIcon from "@/assets/svgs/ClockIcon";
import ClockOIcon from "@/assets/svgs/ClockOIcon";
import ProfileIcon from "@/assets/svgs/ProfileIcon";
import RepeatFreq from "./RepeatFreq";
interface IAddChoreDialog {
- isVisible: boolean;
- setIsVisible: (value: boolean) => void;
- selectedTodo?: IToDo;
+ isVisible: boolean;
+ setIsVisible: (value: boolean) => void;
+ selectedTodo?: IToDo;
}
const defaultTodo = {
- id: "",
- title: "",
- points: 10,
- date: new Date(),
- rotate: false,
- repeatType: "Every week",
- assignees: [],
+ id: "",
+ title: "",
+ points: 10,
+ date: new Date(),
+ rotate: false,
+ repeatType: "Every week",
+ assignees: [],
+ repeatDays: []
};
const AddChoreDialog = (addChoreDialogProps: IAddChoreDialog) => {
- const { addToDo, updateToDo } = useToDosContext();
- const [todo, setTodo] = useState(
- addChoreDialogProps.selectedTodo ?? defaultTodo
- );
- const [selectedAssignees, setSelectedAssignees] = useState(
- addChoreDialogProps?.selectedTodo?.assignees ?? []
- );
- const { width, height } = Dimensions.get("screen");
- const [points, setPoints] = useState(todo.points);
+ const {addToDo, updateToDo} = useToDosContext();
+ const [todo, setTodo] = useState(
+ addChoreDialogProps.selectedTodo ?? defaultTodo
+ );
+ const [selectedAssignees, setSelectedAssignees] = useState(
+ addChoreDialogProps?.selectedTodo?.assignees ?? []
+ );
+ const {width} = Dimensions.get("screen");
+ const [points, setPoints] = useState(todo.points);
- const { data: members } = useGetFamilyMembers();
+ const titleRef = useRef(null)
- const handleClose = () => {
- setTodo(defaultTodo);
- setSelectedAssignees([]);
- addChoreDialogProps.setIsVisible(false);
- };
+ const {data: members} = useGetFamilyMembers();
- const handleChange = (text: string) => {
- const numericValue = parseInt(text, 10);
+ const handleClose = () => {
+ setTodo(defaultTodo);
+ setSelectedAssignees([]);
+ addChoreDialogProps.setIsVisible(false);
+ };
- if (!isNaN(numericValue) && numericValue >= 0 && numericValue <= 100) {
- setPoints(numericValue);
- } else if (text === "") {
- setPoints(0);
- }
- };
+ const handleChange = (text: string) => {
+ const numericValue = parseInt(text, 10);
- return (
- handleClose}
- containerStyle={{
- borderRadius: 10,
- backgroundColor: "white",
- alignSelf: "stretch",
- padding: 0,
- paddingTop: 4,
- margin: 0,
- }}
- visible={addChoreDialogProps.isVisible}
- >
-
- {
- handleClose();
- }}
- />
-
- {
- handleClose();
- }}
- />
-
- {
- try {
- if (addChoreDialogProps.selectedTodo) {
- updateToDo({
- ...todo,
- points: points,
- assignees: selectedAssignees,
- });
- } else {
- addToDo({
- ...todo,
- done: false,
- points: points,
- assignees: selectedAssignees,
- });
- }
- handleClose();
- } catch (error) {
- console.error(error);
+ if (!isNaN(numericValue) && numericValue >= 0 && numericValue <= 100) {
+ setPoints(numericValue);
+ } else if (text === "") {
+ setPoints(0);
+ }
+ };
+
+ const handleRepeatDaysChange = (day: string, set: boolean) => {
+ if (set) {
+ const updatedTodo = {
+ ...todo,
+ repeatDays: [...todo.repeatDays, day]
}
- }}
- />
-
- {
- setTodo((oldValue: IToDo) => ({ ...oldValue, title: text }));
- }}
- placeholderTextColor="#2d2d30"
- text60R
- marginT-15
- marginL-30
- />
-
-
-
- {todo?.date && (
-
-
- {
- setTodo((oldValue: IToDo) => ({ ...oldValue, date: date }));
- }}
- />
-
- )}
-
-
-
- {
- if (value) {
- if (value.toString() == "None") {
- setTodo((oldValue) => ({
- ...oldValue,
- date: null,
- repeatType: "None",
- }));
- } else {
- setTodo((oldValue) => ({
- ...oldValue,
- date: new Date(),
- repeatType: value.toString(),
- }));
- }
- }
- }}
- topBarProps={{ title: "Repeat" }}
- style={{
- marginVertical: 5,
- fontFamily: "PlusJakartaSans_500Medium",
- fontSize: 16,
- }}
- >
- {repeatOptions.map((option) => (
-
- ))}
-
-
- {todo.repeatType == "Every week" && }
-
-
+ setTodo(updatedTodo);
+ } else {
+ const array = todo.repeatDays ?? [];
+ let index = array.indexOf(day);
+ array.splice(index, 1);
+ const updatedTodo = {
+ ...todo,
+ repeatDays: array
+ }
+ setTodo(updatedTodo);
+ }
+ }
-
-
-
- Assignees
-
-
- {
- setSelectedAssignees([...selectedAssignees, ...value]);
- }}
- style={{ marginVertical: 5 }}
- mode={PickerModes.MULTI}
- renderInput={() => (
- (
-
- )}
- style={{
- marginLeft: "auto",
- borderRadius: 8,
- backgroundColor: "#ffe8f1",
- borderColor: "#ea156c",
- borderWidth: 1,
- }}
- color="#ea156c"
- label="Assign"
- labelStyle={{ fontFamily: "Manrope_600SemiBold", fontSize: 14 }}
- />
- )}
+ useEffect(() => {
+ setTimeout(() => {
+ titleRef?.current?.focus()
+ }, 500)
+ }, []);
+
+ return (
+ handleClose}
+ containerStyle={{
+ borderRadius: 10,
+ backgroundColor: "white",
+ alignSelf: "stretch",
+ padding: 0,
+ paddingTop: 4,
+ margin: 0,
+ }}
+ visible={addChoreDialogProps.isVisible}
>
- {members?.map((member) => (
-
- ))}
-
-
-
-
-
-
-
- Take Turns
-
-
- setTodo((oldValue) => ({ ...oldValue, rotate: value }))
- }
- />
-
-
-
-
-
- Reward Points
-
-
-
-
- );
+
+ {
+ handleClose();
+ }}
+ />
+
+ {
+ handleClose();
+ }}
+ />
+
+ {
+ try {
+ if (addChoreDialogProps.selectedTodo) {
+ updateToDo({
+ ...todo,
+ points: points,
+ assignees: selectedAssignees
+ });
+ } else {
+ addToDo({
+ ...todo,
+ done: false,
+ points: points,
+ assignees: selectedAssignees,
+ repeatDays: todo.repeatDays ?? []
+ });
+ }
+ handleClose();
+ } catch (error) {
+ console.error(error);
+ }
+ }}
+ />
+
+
+ {
+ setTodo((oldValue: IToDo) => ({...oldValue, title: text}));
+ }}
+ placeholderTextColor="#2d2d30"
+ text60R
+ marginT-15
+ marginL-30
+ ref={titleRef}
+ />
+
+
+
+ {todo?.date && (
+
+
+ {
+ setTodo((oldValue: IToDo) => ({...oldValue, date: date}));
+ }}
+ />
+
+ )}
+
+
+
+ {
+ if (value) {
+ if (value.toString() == "None") {
+ setTodo((oldValue) => ({
+ ...oldValue,
+ date: null,
+ repeatType: "None",
+ }));
+ } else {
+ setTodo((oldValue) => ({
+ ...oldValue,
+ date: new Date(),
+ repeatType: value.toString(),
+ }));
+ }
+ }
+ }}
+ topBarProps={{title: "Repeat"}}
+ style={{
+ marginVertical: 5,
+ fontFamily: "PlusJakartaSans_500Medium",
+ fontSize: 16,
+ }}
+ >
+ {repeatOptions.map((option) => (
+
+ ))}
+
+
+ {todo.repeatType == "Every week" && }
+
+
+
+
+
+
+ Assignees
+
+
+ {
+ setSelectedAssignees(value);
+ }}
+ style={{marginVertical: 5}}
+ mode={PickerModes.MULTI}
+ renderInput={() => (
+ (
+
+ )}
+ style={{
+ marginLeft: "auto",
+ borderRadius: 8,
+ backgroundColor: "#ffe8f1",
+ borderColor: "#ea156c",
+ borderWidth: 1,
+ }}
+ color="#ea156c"
+ label="Assign"
+ labelStyle={{fontFamily: "Manrope_600SemiBold", fontSize: 14}}
+ />
+ )}
+ >
+ {members?.map((member) => (
+
+ ))}
+
+
+
+
+
+
+
+ Take Turns
+
+
+ setTodo((oldValue) => ({...oldValue, rotate: value}))
+ }
+ />
+
+
+
+
+
+ Reward Points
+
+
+
+
+
+ );
};
export default AddChoreDialog;
const styles = StyleSheet.create({
- divider: { height: 1, backgroundColor: "#e4e4e4", marginVertical: 15 },
- gradient: {
- height: "25%",
- position: "absolute",
- bottom: 0,
- width: "100%",
- },
- buttonContainer: {
- position: "absolute",
- bottom: 25,
- width: "100%",
- },
- button: {
- backgroundColor: "rgb(253, 23, 117)",
- paddingVertical: 20,
- },
- topBtn: {
- backgroundColor: "white",
- color: "#05a8b6",
- },
- rotateSwitch: {
- marginLeft: 35,
- marginBottom: 10,
- marginTop: 25,
- },
- sub: {
- fontFamily: "Manrope_600SemiBold",
- fontSize: 18,
- },
+ divider: {height: 1, backgroundColor: "#e4e4e4", marginVertical: 15},
+ gradient: {
+ height: "25%",
+ position: "absolute",
+ bottom: 0,
+ width: "100%",
+ },
+ buttonContainer: {
+ position: "absolute",
+ bottom: 25,
+ width: "100%",
+ },
+ button: {
+ backgroundColor: "rgb(253, 23, 117)",
+ paddingVertical: 20,
+ },
+ topBtn: {
+ backgroundColor: "white",
+ color: "#05a8b6",
+ },
+ rotateSwitch: {
+ marginLeft: 35,
+ marginBottom: 10,
+ marginTop: 25,
+ },
+ sub: {
+ fontFamily: "Manrope_600SemiBold",
+ fontSize: 18,
+ },
});
diff --git a/components/pages/todos/RepeatFreq.tsx b/components/pages/todos/RepeatFreq.tsx
index fb2fea5..8399c8c 100644
--- a/components/pages/todos/RepeatFreq.tsx
+++ b/components/pages/todos/RepeatFreq.tsx
@@ -1,7 +1,8 @@
import { View, Text, TouchableOpacity, Picker } from "react-native-ui-lib";
import React, { useEffect, useState } from "react";
+import {DAYS_OF_WEEK_ENUM} from "@/hooks/firebase/types/todoData";
-const RepeatFreq = () => {
+const RepeatFreq = ({ repeatDays, handleRepeatDaysChange }: { repeatDays: string[], handleRepeatDaysChange: Function }) => {
const [weeks, setWeeks] = useState(1);
const weekOptions: number[] = Array.from({ length: 52 }, (_, i) => i + 1);
@@ -9,23 +10,30 @@ const RepeatFreq = () => {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
};
export default RepeatFreq;
-const RepeatOption = ({ value }: { value: string }) => {
- const [isSet, setisSet] = useState(false);
+const RepeatOption = ({ value, handleRepeatDaysChange, repeatDays }: { value: string, handleRepeatDaysChange: Function, repeatDays: string[] }) => {
+ const [isSet, setisSet] = useState(repeatDays.includes(value));
+
+
+ const handleDayChange = () => {
+ handleRepeatDaysChange(value, !isSet)
+ setisSet(!isSet);
+ }
+
return (
- setisSet(!isSet)}>
+
{
+const ToDoItem = (props: {
+ item: IToDo;
+ isSettings?: boolean;
+ is7Days?: boolean;
+}) => {
const { updateToDo } = useToDosContext();
const { data: members } = useGetFamilyMembers();
+ const { profileData } = useAuthContext();
+ const isParent = profileData?.userType === ProfileType.PARENT;
+
const [visible, setVisible] = useState(false);
const [points, setPoints] = useState(props.item.points);
const [pointsModalVisible, setPointsModalVisible] = useState(false);
@@ -41,16 +51,24 @@ const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => {
const selectedMembers = members?.filter((x) =>
props?.item?.assignees?.includes(x?.uid!)
);
+
+ let isTodoEditable;
+ if (isParent) {
+ isTodoEditable = true;
+ } else {
+ isTodoEditable = props.item.creatorId === profileData?.uid;
+ }
+
return (
{visible && (
@@ -66,23 +84,21 @@ const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => {
style={{
textDecorationLine: props.item.done ? "line-through" : "none",
fontFamily: "Manrope_500Medium",
+ color: props.item.done? "#a09f9f": "black",
fontSize: 15,
}}
onPress={() => {
- setVisible(true);
+ isTodoEditable ? setVisible(true) : null;
}}
>
{props.item.title}
{
updateToDo({ id: props.item.id, done: !props.item.done });
@@ -146,9 +162,12 @@ const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => {
return props.item.repeatType;
}
})()}
+ {props.item.date &&
+ props.is7Days &&
+ " / " + format(props.item.date, "EEEE")}
- )}
+ )}
{selectedMembers?.map((member) => {
@@ -246,3 +265,17 @@ const ToDoItem = (props: { item: IToDo; isSettings?: boolean }) => {
};
export default ToDoItem;
+
+const styles = StyleSheet.create({
+ checkbox: {
+ borderRadius: 50,
+ borderWidth: 0.7,
+ color: "#bfbfbf",
+ borderColor: "#bfbfbf",
+ width: 24.64,
+ aspectRatio: 1,
+ },
+ checked: {
+ borderRadius: 50,
+ },
+});
diff --git a/components/pages/todos/ToDosList.tsx b/components/pages/todos/ToDosList.tsx
index 6589427..431c293 100644
--- a/components/pages/todos/ToDosList.tsx
+++ b/components/pages/todos/ToDosList.tsx
@@ -14,40 +14,64 @@ import { IToDo } from "@/hooks/firebase/types/todoData";
const groupToDosByDate = (toDos: IToDo[]) => {
let sortedTodos = toDos.sort((a, b) => a.date - b.date);
- return sortedTodos.reduce((groups, toDo) => {
- let dateKey;
+ return sortedTodos.reduce(
+ (groups, toDo) => {
+ let dateKey;
+ let subDateKey;
- const isNext7Days = (date: Date) => {
- const today = new Date();
- return isWithinInterval(date, { start: today, end: addDays(today, 7) });
- };
+ const isNext7Days = (date: Date) => {
+ const today = new Date();
+ return isWithinInterval(date, { start: today, end: addDays(today, 7) });
+ };
- const isNext30Days = (date: Date) => {
- const today = new Date();
- return isWithinInterval(date, { start: today, end: addDays(today, 30) });
- };
+ const isNext30Days = (date: Date) => {
+ const today = new Date();
+ return isWithinInterval(date, {
+ start: today,
+ end: addDays(today, 30),
+ });
+ };
- if (toDo.date === null) {
- dateKey = "No Date";
- } else if (isToday(toDo.date)) {
- dateKey = "Today";
- } else if (isTomorrow(toDo.date)) {
- dateKey = "Tomorrow";
- } else if (isNext7Days(toDo.date)) {
- dateKey = "Next 7 Days";
- } else if (isNext30Days(toDo.date)) {
- dateKey = "Next 30 Days";
- } else {
- dateKey = format(toDo.date, "EEE MMM dd");
+ if (toDo.date === null) {
+ dateKey = "No Date";
+ } else if (isToday(toDo.date)) {
+ dateKey = "Today";
+ } else if (isTomorrow(toDo.date)) {
+ dateKey = "Tomorrow";
+ } else if (isNext7Days(toDo.date)) {
+ dateKey = "Next 7 Days";
+ } else if (isNext30Days(toDo.date)) {
+ dateKey = "Next 30 Days";
+ subDateKey = format(toDo.date, "MMM d");
+ } else {
+ return groups;
+ }
+
+ if (!groups[dateKey]) {
+ groups[dateKey] = {
+ items: [],
+ subgroups: {},
+ };
+ }
+
+ if (dateKey === "Next 30 Days" && subDateKey) {
+ if (!groups[dateKey].subgroups[subDateKey]) {
+ groups[dateKey].subgroups[subDateKey] = [];
+ }
+ groups[dateKey].subgroups[subDateKey].push(toDo);
+ } else {
+ groups[dateKey].items.push(toDo);
+ }
+
+ return groups;
+ },
+ {} as {
+ [key: string]: {
+ items: IToDo[];
+ subgroups: { [key: string]: IToDo[] };
+ };
}
-
- if (!groups[dateKey]) {
- groups[dateKey] = [];
- }
-
- groups[dateKey].push(toDo);
- return groups;
- }, {} as { [key: string]: IToDo[] });
+ );
};
const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
@@ -67,11 +91,139 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
}));
};
- const noDateToDos = groupedToDos["No Date"] || [];
+ const noDateToDos = groupedToDos["No Date"]?.items || [];
const datedToDos = Object.keys(groupedToDos).filter(
(key) => key !== "No Date"
);
+ const renderTodoGroup = (dateKey: string) => {
+ const isExpanded = expandedGroups[dateKey] || false;
+
+ if (dateKey === "Next 30 Days") {
+ const subgroups = Object.entries(groupedToDos[dateKey].subgroups).sort(
+ ([dateA], [dateB]) => {
+ const dateAObj = new Date(dateA);
+ const dateBObj = new Date(dateB);
+ return dateAObj.getTime() - dateBObj.getTime();
+ }
+ );
+
+ return (
+
+ toggleExpand(dateKey)}
+ style={{
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ paddingHorizontal: 0,
+ marginBottom: 4,
+ marginTop: 15,
+ }}
+ >
+
+ {dateKey}
+
+
+
+
+ {isExpanded &&
+ subgroups.map(([subDate, items]) => {
+ const sortedItems = [
+ ...items.filter((toDo) => !toDo.done),
+ ...items.filter((toDo) => toDo.done),
+ ];
+
+ return (
+
+
+
+ {subDate}
+
+
+
+ {sortedItems.map((item) => (
+
+ ))}
+
+ );
+ })}
+
+ );
+ }
+
+ const sortedToDos = [
+ ...groupedToDos[dateKey].items.filter((toDo) => !toDo.done),
+ ...groupedToDos[dateKey].items.filter((toDo) => toDo.done),
+ ];
+
+ return (
+
+ toggleExpand(dateKey)}
+ style={{
+ flexDirection: "row",
+ justifyContent: "space-between",
+ alignItems: "center",
+ paddingHorizontal: 0,
+ marginBottom: 4,
+ marginTop: 15,
+ }}
+ >
+
+ {dateKey}
+
+
+
+
+ {isExpanded &&
+ sortedToDos.map((item) => (
+
+ ))}
+
+ );
+ };
+
return (
{noDateToDos.length > 0 && (
@@ -85,26 +237,14 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
>
Unscheduled
- {!expandNoDate && (
- {
- setExpandNoDate(!expandNoDate);
- }}
- />
- )}
- {expandNoDate && (
- {
- setExpandNoDate(!expandNoDate);
- }}
- />
- )}
+ {
+ setExpandNoDate(!expandNoDate);
+ }}
+ />
{expandNoDate &&
noDateToDos
@@ -115,50 +255,7 @@ const ToDosList = ({ isSettings }: { isSettings?: boolean }) => {
)}
- {datedToDos.map((dateKey) => {
- const isExpanded = expandedGroups[dateKey] || false;
- const sortedToDos = [
- ...groupedToDos[dateKey].filter((toDo) => !toDo.done),
- ...groupedToDos[dateKey].filter((toDo) => toDo.done),
- ];
-
- return (
-
- toggleExpand(dateKey)}
- style={{
- flexDirection: "row",
- justifyContent: "space-between",
- alignItems: "center",
- paddingHorizontal: 0,
- marginBottom: 4,
- marginTop: 15,
- }}
- >
-
- {dateKey}
-
- {!isExpanded && (
-
- )}
- {isExpanded && (
-
- )}
-
-
- {isExpanded &&
- sortedToDos.map((item) => (
-
- ))}
-
- );
- })}
+ {datedToDos.map(renderTodoGroup)}
);
};
diff --git a/components/pages/todos/ToDosPage.tsx b/components/pages/todos/ToDosPage.tsx
index 8dddfc2..6c88672 100644
--- a/components/pages/todos/ToDosPage.tsx
+++ b/components/pages/todos/ToDosPage.tsx
@@ -1,83 +1,90 @@
-import {Button, Text, View} from "react-native-ui-lib";
-import React, {useState} from "react";
+import { Button, Text, View } from "react-native-ui-lib";
+import React, { useState } from "react";
import HeaderTemplate from "@/components/shared/HeaderTemplate";
import AddChore from "./AddChore";
import ProgressCard from "./ProgressCard";
import ToDosList from "./ToDosList";
-import {Dimensions, ScrollView, StyleSheet} from "react-native";
-import {TouchableOpacity} from "react-native-gesture-handler";
-import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
+import { Dimensions, ScrollView, StyleSheet } from "react-native";
+import { TouchableOpacity } from "react-native-gesture-handler";
+import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
import FamilyChoresProgress from "./family-chores/FamilyChoresProgress";
import UserChoresProgress from "./user-chores/UserChoresProgress";
+import { useAtom } from "jotai";
+import { toDosPageIndex } from "../calendar/atoms";
const ToDosPage = () => {
- const [pageIndex, setPageIndex] = useState(0);
- const {profileData} = useAuthContext();
- const {width, height} = Dimensions.get("screen");
- const pageLink = (
- setPageIndex(1)}>
-
- View family progress
-
-
- );
- return (
- <>
-
- {pageIndex == 0 && (
-
- setPageIndex(1)}>
+
+ View family progress
+
+
+ );
+ return (
+ <>
+
+
+ {pageIndex == 0 && (
+
+
+
+ {profileData?.userType == ProfileType.CHILD && (
+
+ setPageIndex(2)}
>
-
-
- {profileData?.userType == ProfileType.CHILD && (
-
- setPageIndex(2)}
- >
-
- View your full progress report here
-
-
- }
- />
-
- )}
-
-
-
-
+
+ View your full progress report here
+
+
+ }
+ />
+
)}
- {pageIndex == 1 && }
- {pageIndex == 2 && }
+
+
- {
- profileData?.userType == ProfileType.PARENT &&
- }
- >
- )
- ;
+ )}
+ {pageIndex == 1 && (
+
+ )}
+ {pageIndex == 2 && }
+
+
+
+ >
+ );
};
const styles = StyleSheet.create({
- linkBtn: {
- backgroundColor: "transparent",
- padding: 0,
- },
+ linkBtn: {
+ backgroundColor: "transparent",
+ padding: 0,
+ },
});
export default ToDosPage;
diff --git a/components/pages/todos/family-chores/FamilyChoresProgress.tsx b/components/pages/todos/family-chores/FamilyChoresProgress.tsx
index 8e2bcb1..66570fe 100644
--- a/components/pages/todos/family-chores/FamilyChoresProgress.tsx
+++ b/components/pages/todos/family-chores/FamilyChoresProgress.tsx
@@ -3,6 +3,7 @@ import React from "react";
import { ImageBackground, StyleSheet } from "react-native";
import FamilyChart from "./FamilyChart";
import { TouchableOpacity } from "react-native-ui-lib/src/incubator";
+import { Ionicons } from "@expo/vector-icons";
const FamilyChoresProgress = ({
setPageIndex,
@@ -12,7 +13,20 @@ const FamilyChoresProgress = ({
return (
setPageIndex(0)}>
- Back to ToDos
+
+
+
+ Return to To Do's
+
+
diff --git a/components/pages/todos/user-chores/UserChoresProgress.tsx b/components/pages/todos/user-chores/UserChoresProgress.tsx
index b4a4fbd..f4cec19 100644
--- a/components/pages/todos/user-chores/UserChoresProgress.tsx
+++ b/components/pages/todos/user-chores/UserChoresProgress.tsx
@@ -30,8 +30,21 @@ const UserChoresProgress = ({
showsHorizontalScrollIndicator={false}
>
setPageIndex(0)}>
- Back to ToDos
-
+
+
+
+ Return to To Do's
+
+
+
Your To Do's Progress Report
diff --git a/components/shared/HeaderTemplate.tsx b/components/shared/HeaderTemplate.tsx
index b55d97d..3d47487 100644
--- a/components/shared/HeaderTemplate.tsx
+++ b/components/shared/HeaderTemplate.tsx
@@ -1,18 +1,37 @@
import { Image, Text, View } from "react-native-ui-lib";
-import React from "react";
-import { useAuthContext } from "@/contexts/AuthContext";
+import React, { useEffect, useState } from "react";
+import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
import { StyleSheet } from "react-native";
import { colorMap } from "@/constants/colorMap";
+import { useAtom } from "jotai";
+import { isFamilyViewAtom } from "../pages/calendar/atoms";
+import { useGetChildrenByParentId } from "@/hooks/firebase/useGetChildrenByParentId";
+import { useGetFamilyMembers } from "@/hooks/firebase/useGetFamilyMembers";
+import { UserProfile } from "@/hooks/firebase/types/profileTypes";
+import { child } from "@react-native-firebase/storage";
+import CachedImage from 'expo-cached-image'
const HeaderTemplate = (props: {
message: string;
isWelcome: boolean;
children?: React.ReactNode;
link?: React.ReactNode;
+ isCalendar?: boolean;
+ isToDos?: boolean;
+ isBrainDump?: boolean;
+ isGroceries?: boolean;
}) => {
const { user, profileData } = useAuthContext();
- const headerHeight: number = 72;
+ const { data: members } = useGetFamilyMembers();
+ const [children, setChildren] = useState([]);
+ const [isFamilyView] = useAtom(isFamilyViewAtom);
+
+ const headerHeight: number =
+ (props.isCalendar && 65.54) ||
+ (props.isToDos && 84) ||
+ (props.isGroceries && 72.09) ||
+ 65.54;
const styles = StyleSheet.create({
pfp: {
@@ -26,14 +45,71 @@ const HeaderTemplate = (props: {
pfpTxt: {
fontFamily: "Manrope_500Medium",
fontSize: 30,
- color: 'white',
+ color: "white",
+ },
+ childrenPfpArr: {
+ width: 65.54,
+ position: "absolute",
+ bottom: -12.44,
+ left: (children.length > 3 && -9) || 0,
+ height: 27.32,
+ },
+ childrenPfp: {
+ aspectRatio: 1,
+ width: 27.32,
+ backgroundColor: "#fd1575",
+ borderRadius: 50,
+ position: "absolute",
+ borderWidth: 2,
+ borderColor: "#f2f2f2",
+ },
+ bottomMarg: {
+ marginBottom: isFamilyView ? 30 : 15,
},
});
+ useEffect(() => {
+ if (members) {
+ const childrenMembers = members.filter(
+ (member) => member.userType === ProfileType.CHILD
+ );
+ setChildren(childrenMembers);
+ }
+ }, []);
+
return (
-
+
{profileData?.pfp ? (
-
+
+
+ {isFamilyView && props.isCalendar && (
+
+ {children?.slice(0, 3).map((child, index) => {
+ return child.pfp ? (
+
+ ) : (
+
+
+ {child?.firstName?.at(0)}
+ {child?.firstName?.at(1)}
+
+
+ );
+ })}
+ {children?.length > 3 && (
+
+ +{children.length - 3}
+
+ )}
+
+ )}
+
) : (
diff --git a/components/shared/RemoveAssigneeBtn.tsx b/components/shared/RemoveAssigneeBtn.tsx
index 9416009..3384949 100644
--- a/components/shared/RemoveAssigneeBtn.tsx
+++ b/components/shared/RemoveAssigneeBtn.tsx
@@ -6,7 +6,7 @@ import { View } from "react-native-ui-lib";
const RemoveAssigneeBtn = () => {
return (
-
+
);
};
diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx
index e6f28cc..8483f67 100644
--- a/contexts/AuthContext.tsx
+++ b/contexts/AuthContext.tsx
@@ -8,7 +8,7 @@ import {UserProfile} from "@/hooks/firebase/types/profileTypes";
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
import Constants from 'expo-constants';
-import { Platform } from 'react-native';
+import {Platform} from 'react-native';
import {useQueryClient} from "react-query";
@@ -24,6 +24,7 @@ interface IAuthContext {
profileType?: ProfileType,
profileData?: UserProfile,
setProfileData: (profileData: UserProfile) => void,
+ setRedirectOverride: (val: boolean) => void,
refreshProfileData: () => Promise
}
@@ -50,16 +51,16 @@ async function registerForPushNotificationsAsync() {
}
if (Device.isDevice) {
- const { status: existingStatus } = await Notifications.getPermissionsAsync();
+ const {status: existingStatus} = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
- const { status } = await Notifications.requestPermissionsAsync();
+ const {status} = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
- alert('Failed to get push token for push notification!');
+ // alert('Failed to get push token for push notification!');
return;
}
@@ -72,15 +73,15 @@ async function registerForPushNotificationsAsync() {
}
try {
- const token = (await Notifications.getExpoPushTokenAsync({ projectId })).data;
+ const token = (await Notifications.getExpoPushTokenAsync({projectId})).data;
console.log('Push Token:', token);
return token;
} catch (error) {
- alert(`Error getting push token: ${error}`);
+ // alert(`Error getting push token: ${error}`);
throw error;
}
} else {
- alert('Must use a physical device for push notifications');
+ // alert('Must use a physical device for push notifications');
}
}
@@ -91,24 +92,28 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
const [initializing, setInitializing] = useState(true);
const [profileType, setProfileType] = useState(undefined);
const [profileData, setProfileData] = useState(undefined);
+ const [redirectOverride, setRedirectOverride] = useState(false);
+
const {replace} = useRouter();
const ready = !initializing;
const queryClient = useQueryClient();
const onAuthStateChangedHandler = async (authUser: FirebaseAuthTypes.User | null) => {
- setUser(authUser);
+ if (!redirectOverride) {
+ setUser(authUser);
- if (authUser) {
- await refreshProfileData(authUser);
- const pushToken = await registerForPushNotificationsAsync();
- if (pushToken) {
- await savePushTokenToFirestore(authUser.uid, pushToken);
+ if (authUser) {
+ await refreshProfileData(authUser);
+ const pushToken = await registerForPushNotificationsAsync();
+ if (pushToken) {
+ await savePushTokenToFirestore(authUser.uid, pushToken);
+ }
}
- }
- if (initializing) setInitializing(false);
+ if (initializing) setInitializing(false);
+ }
};
const refreshProfileData = async (user?: FirebaseAuthTypes.User) => {
@@ -152,12 +157,12 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
}, [initializing]);
useEffect(() => {
- if (ready && user) {
+ if (ready && user && !redirectOverride) {
replace({pathname: "/(auth)/calendar"});
- } else if (ready && !user) {
+ } else if (ready && !user && !redirectOverride) {
replace({pathname: "/(unauth)"});
}
- }, [user, ready]);
+ }, [user, ready, redirectOverride]);
useEffect(() => {
const sub = Notifications.addNotificationReceivedListener(notification => {
@@ -175,7 +180,8 @@ export const AuthContextProvider: FC<{ children: ReactNode }> = ({children}) =>
}
return (
-
+
{children}
);
diff --git a/contexts/DumpContext.tsx b/contexts/DumpContext.tsx
index f732eb9..8bcefc7 100644
--- a/contexts/DumpContext.tsx
+++ b/contexts/DumpContext.tsx
@@ -1,5 +1,10 @@
+import { useCreateNote } from "@/hooks/firebase/useCreateNote";
+import { useDeleteNote } from "@/hooks/firebase/useDeleteNote";
+import { useGetNotes } from "@/hooks/firebase/useGetNotes";
+import { useUpdateNote } from "@/hooks/firebase/useUpdateNote";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { createContext, useContext, useState } from "react";
+import { create } from "react-test-renderer";
export interface IBrainDump {
id: number;
@@ -8,7 +13,7 @@ export interface IBrainDump {
}
interface IBrainDumpContext {
- brainDumps: IBrainDump[];
+ brainDumps: IBrainDump[] | undefined;
updateBrainDumpItem: (id: number, changes: Partial) => void;
isAddingBrainDump: boolean;
setIsAddingBrainDump: (value: boolean) => void;
@@ -23,70 +28,43 @@ const BrainDumpContext = createContext(
export const BrainDumpProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
+ const { data: brainDumps } = useGetNotes();
+ const { mutate: deleteNote } = useDeleteNote();
+ const { mutateAsync: createBrainDump } = useCreateNote();
+ const { mutateAsync: updateNoteMutate } = useUpdateNote();
+
const [isAddingBrainDump, setIsAddingBrainDump] = useState(false);
- const [brainDumps, setBrainDumps] = useState([
- {
- id: 0,
- title: "Favorite Weekend Activities",
- description:
- "What's something fun we can do together this weekend? Maybe a new game, a picnic?",
- },
- {
- id: 1,
- title: "What’s For Dinner",
- description:
- "What’s one meal you’d love to have for dinner this week?",
- },
- {
- id: 2,
- title: "The Best Thing About Today",
- description:
- "What was the highlight of your day? Let’s each take a moment to share something!",
- },
- {
- id: 3,
- title: "A Dream Vacation Spot",
- description:
- "If we could go anywhere in the world right now, where would it be? Everyone pick one dream destination and tell us why.",
- },
- {
- id: 4,
- title: "Favorite Childhood Memory",
- description:
- "What’s a favorite memory from your childhood? Let’s take a trip down memory lane and share some of the moments that made us smile.",
- },
- {
- id: 5,
- title: "A New Family Tradition",
- description:
- "What’s one new tradition we could start as a family? Maybe a weekly movie night, a monthly game day, or a yearly family trip. Share your ideas!",
- },
- ]);
const addBrainDump = (BrainDump: IBrainDump) => {
- setBrainDumps((prevBrainDumps) => [
- ...prevBrainDumps,
- {
- ...BrainDump,
- id: prevBrainDumps.length
- ? prevBrainDumps[prevBrainDumps.length - 1].id + 1
- : 0,
- },
- ]);
+ createBrainDump(BrainDump);
};
const updateBrainDumpItem = (id: number, changes: Partial) => {
- setBrainDumps((prevBrainDumps) =>
- prevBrainDumps.map((BrainDump) =>
- BrainDump.id === id ? { ...BrainDump, ...changes } : BrainDump
- )
+ updateNoteMutate(
+ {
+ id: id,
+ changes: changes,
+ },
+ {
+ onSuccess: (data) => {
+ console.log("Note updated successfully", data);
+ },
+ onError: (error) => {
+ console.error("Failed to update note:", error);
+ },
+ }
);
};
const deleteBrainDump = (id: number) => {
- setBrainDumps((prevBrainDumps) =>
- prevBrainDumps.filter((BrainDump) => BrainDump.id !== id)
- );
+ deleteNote(id.toString(), {
+ onSuccess: () => {
+ console.log("Feedback deleted successfully");
+ },
+ onError: (error) => {
+ console.error("Failed to delete feedback:", error);
+ },
+ });
};
return (
@@ -97,7 +75,7 @@ export const BrainDumpProvider: React.FC<{ children: React.ReactNode }> = ({
isAddingBrainDump,
setIsAddingBrainDump,
addBrainDump,
- deleteBrainDump
+ deleteBrainDump,
}}
>
{children}
diff --git a/contexts/FeedbackContext.tsx b/contexts/FeedbackContext.tsx
new file mode 100644
index 0000000..4159ea3
--- /dev/null
+++ b/contexts/FeedbackContext.tsx
@@ -0,0 +1,87 @@
+import { useCreateFeedback } from "@/hooks/firebase/useCreateFeedback";
+import { useDeleteFeedback } from "@/hooks/firebase/useDeleteFeedback";
+import { useGetFeedbacks } from "@/hooks/firebase/useGetFeedbacks";
+import { useUpdateFeedback } from "@/hooks/firebase/useUpdateFeedback";
+import { MaterialCommunityIcons } from "@expo/vector-icons";
+import { createContext, useContext, useState } from "react";
+
+export interface IFeedback {
+ id: number;
+ title: string;
+ text: string;
+}
+
+interface IFeedbackContext {
+ feedbacks: IFeedback[] | undefined;
+ isAddingFeedback: boolean;
+ setIsAddingFeedback: (value: boolean) => void;
+ addFeedback: (BrainDump: IFeedback) => void;
+ updateFeedback: (id: number, changes: Partial) => void;
+ deleteFeedback: (id: number) => void;
+}
+
+const FeedbackContext = createContext(undefined);
+
+export const FeedbackProvider: React.FC<{ children: React.ReactNode }> = ({
+ children,
+}) => {
+ const {
+ mutateAsync: createFeedback,
+ isLoading: isAdding,
+ isError,
+ } = useCreateFeedback();
+ const { data: feedbacks } = useGetFeedbacks();
+ const { mutate: deleteFeedbackMutate } = useDeleteFeedback();
+ const { mutate: updateFeedbackMutate } = useUpdateFeedback();
+
+ const [isAddingFeedback, setIsAddingFeedback] = useState(false);
+
+ const addFeedback = (Feedback: IFeedback) => {
+ createFeedback({ title: Feedback.title, text: Feedback.text });
+ };
+
+ const updateFeedback = (id: number, changes: Partial) => {
+ updateFeedbackMutate(
+ {
+ id: id,
+ changes: changes,
+ },
+ {
+ onSuccess: (data) => {
+ console.log("Feedback updated successfully", data);
+ },
+ onError: (error) => {
+ console.error("Failed to update feedback:", error);
+ },
+ }
+ );
+ };
+
+ const deleteFeedback = (id: number) => {
+ deleteFeedbackMutate(id.toString(), {
+ onSuccess: () => {
+ console.log("Feedback deleted successfully");
+ },
+ onError: (error) => {
+ console.error("Failed to delete feedback:", error);
+ },
+ });
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFeedbackContext = () => useContext(FeedbackContext)!;
diff --git a/contexts/ToDosContext.tsx b/contexts/ToDosContext.tsx
index 6fadc5e..25ff49f 100644
--- a/contexts/ToDosContext.tsx
+++ b/contexts/ToDosContext.tsx
@@ -1,8 +1,6 @@
import { createContext, FC, ReactNode, useContext, useState } from "react";
import {IToDo} from "@/hooks/firebase/types/todoData";
-import {useGetGroceries} from "@/hooks/firebase/useGetGroceries";
import {useGetTodos} from "@/hooks/firebase/useGetTodos";
-import {useCreateGrocery} from "@/hooks/firebase/useCreateGrocery";
import {useCreateTodo} from "@/hooks/firebase/useCreateTodo";
import {useUpdateTodo} from "@/hooks/firebase/useUpdateTodo";
diff --git a/firebase/functions/index.js b/firebase/functions/index.js
index 475f6d8..704507b 100644
--- a/firebase/functions/index.js
+++ b/firebase/functions/index.js
@@ -18,49 +18,50 @@ exports.sendNotificationOnEventCreation = functions.firestore
.document('Events/{eventId}')
.onCreate(async (snapshot, context) => {
const eventData = snapshot.data();
- const {familyId, creatorId} = eventData;
+ const { familyId, creatorId, email } = eventData;
+
+ if (email) {
+ console.log('Event has an email field. Skipping notification.');
+ return;
+ }
if (!familyId || !creatorId) {
console.error('Missing familyId or creatorId in event data');
return;
}
+ let pushTokens = await getPushTokensForFamilyExcludingCreator(familyId, creatorId);
+
if (!pushTokens.length) {
- pushTokens = await getPushTokensForFamilyExcludingCreator(familyId, creatorId);
- if (!pushTokens.length) {
- console.log('No push tokens available for the event.');
- return;
- }
+ console.log('No push tokens available for the event.');
+ return;
}
- // Increment event count for debouncing
eventCount++;
if (notificationTimeout) {
- clearTimeout(notificationTimeout); // Reset the timer if events keep coming
+ clearTimeout(notificationTimeout);
}
- // Set a debounce time (e.g., 5 seconds)
notificationTimeout = setTimeout(async () => {
const eventMessage = eventCount === 1
? `An event "${eventData.title}" has been added. Check it out!`
: `${eventCount} new events have been added.`;
- let messages = [];
- for (let pushToken of pushTokens) {
+ let messages = pushTokens.map(pushToken => {
if (!Expo.isExpoPushToken(pushToken)) {
console.error(`Push token ${pushToken} is not a valid Expo push token`);
- continue;
+ return null;
}
- messages.push({
+ return {
to: pushToken,
sound: 'default',
title: 'New Events Added!',
body: eventMessage,
- data: {eventId: context.params.eventId},
- });
- }
+ data: { eventId: context.params.eventId },
+ };
+ }).filter(Boolean);
let chunks = expo.chunkPushNotifications(messages);
let tickets = [];
@@ -75,7 +76,7 @@ exports.sendNotificationOnEventCreation = functions.firestore
console.log('Notification successfully sent:', ticket.id);
} else if (ticket.status === 'error') {
console.error(`Notification error: ${ticket.message}`);
- if (ticket.details && ticket.details.error === 'DeviceNotRegistered') {
+ if (ticket.details?.error === 'DeviceNotRegistered') {
await removeInvalidPushToken(ticket.to);
}
}
@@ -85,10 +86,10 @@ exports.sendNotificationOnEventCreation = functions.firestore
}
}
- eventCount = 0; // Reset the event count after sending notification
- pushTokens = []; // Reset push tokens for the next round
+ eventCount = 0;
+ pushTokens = [];
- }, 5000); // Debounce time (5 seconds)
+ }, 5000);
});
@@ -180,7 +181,7 @@ exports.generateCustomToken = onRequest(async (request, response) => {
}
});
-exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async (context) => {
+exports.refreshTokens = functions.pubsub.schedule('every 1 hours').onRun(async (context) => {
console.log('Running token refresh job...');
const profilesSnapshot = await db.collection('Profiles').get();
@@ -191,7 +192,7 @@ exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async
if (profileData.googleAccounts) {
try {
for (const googleEmail of Object.keys(profileData?.googleAccounts)) {
- const googleToken = profileData?.googleAccounts?.[googleEmail];
+ const googleToken = profileData?.googleAccounts?.[googleEmail]?.refreshToken;
if (googleToken) {
const refreshedGoogleToken = await refreshGoogleToken(googleToken);
const updatedGoogleAccounts = {...profileData.googleAccounts, [googleEmail]: refreshedGoogleToken};
@@ -238,29 +239,35 @@ exports.refreshTokens = functions.pubsub.schedule('every 12 hours').onRun(async
return null;
});
-// Function to refresh Google token
-async function refreshGoogleToken(token) {
- // Assuming you use OAuth2 token refresh flow
- const response = await axios.post('https://oauth2.googleapis.com/token', {
- grant_type: 'refresh_token',
- refresh_token: token, // Add refresh token stored previously
- client_id: 'YOUR_GOOGLE_CLIENT_ID',
- client_secret: 'YOUR_GOOGLE_CLIENT_SECRET',
- });
+async function refreshGoogleToken(refreshToken) {
+ try {
+ const response = await axios.post('https://oauth2.googleapis.com/token', {
+ grant_type: 'refresh_token',
+ refresh_token: refreshToken,
+ client_id: "406146460310-2u67ab2nbhu23trp8auho1fq4om29fc0.apps.googleusercontent.com", // Web client ID from googleConfig
+ });
- return response.data.access_token; // Return new access token
+ return response.data.access_token; // Return the new access token
+ } catch (error) {
+ console.error("Error refreshing Google token:", error);
+ throw error;
+ }
}
-async function refreshMicrosoftToken(token) {
- const response = await axios.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
- grant_type: 'refresh_token',
- refresh_token: token, // Add refresh token stored previously
- client_id: 'YOUR_MICROSOFT_CLIENT_ID',
- client_secret: 'YOUR_MICROSOFT_CLIENT_SECRET',
- scope: 'https://graph.microsoft.com/Calendars.ReadWrite offline_access',
- });
+async function refreshMicrosoftToken(refreshToken) {
+ try {
+ const response = await axios.post('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
+ grant_type: 'refresh_token',
+ refresh_token: refreshToken,
+ client_id: "13c79071-1066-40a9-9f71-b8c4b138b4af", // Client ID from microsoftConfig
+ scope: "openid profile email offline_access Calendars.ReadWrite User.Read", // Scope from microsoftConfig
+ });
- return response.data.access_token; // Return new access token
+ return response.data.access_token; // Return the new access token
+ } catch (error) {
+ console.error("Error refreshing Microsoft token:", error);
+ throw error;
+ }
}
async function getPushTokensForEvent() {
diff --git a/firebase/functions/yarn.lock b/firebase/functions/yarn.lock
new file mode 100644
index 0000000..c102e08
--- /dev/null
+++ b/firebase/functions/yarn.lock
@@ -0,0 +1,2353 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
+ integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
+ dependencies:
+ eslint-visitor-keys "^3.3.0"
+
+"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
+ version "4.11.1"
+ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f"
+ integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==
+
+"@eslint/eslintrc@^2.1.4":
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
+ integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^9.6.0"
+ globals "^13.19.0"
+ ignore "^5.2.0"
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ minimatch "^3.1.2"
+ strip-json-comments "^3.1.1"
+
+"@eslint/js@8.57.1":
+ version "8.57.1"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
+ integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
+
+"@fastify/busboy@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-3.0.0.tgz#328a4639cdd9282c1d1f56aa84943f153df8839d"
+ integrity sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==
+
+"@firebase/app-check-interop-types@0.3.2":
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.2.tgz#455b6562c7a3de3ef75ea51f72dfec5829ad6997"
+ integrity sha512-LMs47Vinv2HBMZi49C09dJxp0QT5LwDzFaVGf/+ITHe3BlIhUiLNttkATSXplc89A2lAaeTqjgqVkiRfUGyQiQ==
+
+"@firebase/app-types@0.9.2":
+ version "0.9.2"
+ resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.2.tgz#8cbcceba784753a7c0066a4809bc22f93adee080"
+ integrity sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==
+
+"@firebase/auth-interop-types@0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.3.tgz#927f1f2139a680b55fef0bddbff2c982b08587e8"
+ integrity sha512-Fc9wuJGgxoxQeavybiuwgyi+0rssr76b+nHpj+eGhXFYAdudMWyfBHvFL/I5fEHniUM/UQdFzi9VXJK2iZF7FQ==
+
+"@firebase/component@0.6.9":
+ version "0.6.9"
+ resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.9.tgz#4248cfeab222245ada0d7f78ece95a87574532b4"
+ integrity sha512-gm8EUEJE/fEac86AvHn8Z/QW8BvR56TBw3hMW0O838J/1mThYQXAIQBgUv75EqlCZfdawpWLrKt1uXvp9ciK3Q==
+ dependencies:
+ "@firebase/util" "1.10.0"
+ tslib "^2.1.0"
+
+"@firebase/database-compat@^1.0.2":
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-1.0.8.tgz#69ab03d00e27a89f65486896ea219094aa38c27f"
+ integrity sha512-OpeWZoPE3sGIRPBKYnW9wLad25RaWbGyk7fFQe4xnJQKRzlynWeFBSRRAoLE2Old01WXwskUiucNqUUVlFsceg==
+ dependencies:
+ "@firebase/component" "0.6.9"
+ "@firebase/database" "1.0.8"
+ "@firebase/database-types" "1.0.5"
+ "@firebase/logger" "0.4.2"
+ "@firebase/util" "1.10.0"
+ tslib "^2.1.0"
+
+"@firebase/database-types@1.0.5", "@firebase/database-types@^1.0.0":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.5.tgz#2d923f42e3d9911b9eec537ed8b5ecaa0ce95c37"
+ integrity sha512-fTlqCNwFYyq/C6W7AJ5OCuq5CeZuBEsEwptnVxlNPkWCo5cTTyukzAHRSO/jaQcItz33FfYrrFk1SJofcu2AaQ==
+ dependencies:
+ "@firebase/app-types" "0.9.2"
+ "@firebase/util" "1.10.0"
+
+"@firebase/database@1.0.8":
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.8.tgz#01bb0d0cb5653ae6a6641523f6f085b4c1be9c2f"
+ integrity sha512-dzXALZeBI1U5TXt6619cv0+tgEhJiwlUtQ55WNZY7vGAjv7Q1QioV969iYwt1AQQ0ovHnEW0YW9TiBfefLvErg==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.2"
+ "@firebase/auth-interop-types" "0.2.3"
+ "@firebase/component" "0.6.9"
+ "@firebase/logger" "0.4.2"
+ "@firebase/util" "1.10.0"
+ faye-websocket "0.11.4"
+ tslib "^2.1.0"
+
+"@firebase/logger@0.4.2":
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.2.tgz#74dfcfeedee810deb8a7080d5b7eba56aa16ffa2"
+ integrity sha512-Q1VuA5M1Gjqrwom6I6NUU4lQXdo9IAQieXlujeHZWvRt1b7qQ0KwBaNAjgxG27jgF9/mUwsNmO8ptBCGVYhB0A==
+ dependencies:
+ tslib "^2.1.0"
+
+"@firebase/util@1.10.0":
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.10.0.tgz#9ec8ab54da82bfc31baff0c43cb281998cbeddab"
+ integrity sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==
+ dependencies:
+ tslib "^2.1.0"
+
+"@google-cloud/firestore@^7.7.0":
+ version "7.10.0"
+ resolved "https://registry.yarnpkg.com/@google-cloud/firestore/-/firestore-7.10.0.tgz#fc434f6da583aba48d5532ce322e8c03af1978f8"
+ integrity sha512-VFNhdHvfnmqcHHs6YhmSNHHxQqaaD64GwiL0c+e1qz85S8SWZPC2XFRf8p9yHRTF40Kow424s1KBU9f0fdQa+Q==
+ dependencies:
+ "@opentelemetry/api" "^1.3.0"
+ fast-deep-equal "^3.1.1"
+ functional-red-black-tree "^1.0.1"
+ google-gax "^4.3.3"
+ protobufjs "^7.2.6"
+
+"@google-cloud/paginator@^5.0.0":
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-5.0.2.tgz#86ad773266ce9f3b82955a8f75e22cd012ccc889"
+ integrity sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==
+ dependencies:
+ arrify "^2.0.0"
+ extend "^3.0.2"
+
+"@google-cloud/projectify@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@google-cloud/projectify/-/projectify-4.0.0.tgz#d600e0433daf51b88c1fa95ac7f02e38e80a07be"
+ integrity sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==
+
+"@google-cloud/promisify@^4.0.0":
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-4.0.0.tgz#a906e533ebdd0f754dca2509933334ce58b8c8b1"
+ integrity sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==
+
+"@google-cloud/storage@^7.7.0":
+ version "7.13.0"
+ resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-7.13.0.tgz#b59a495861fe7c48f78c1b482b9404f07aa60e66"
+ integrity sha512-Y0rYdwM5ZPW3jw/T26sMxxfPrVQTKm9vGrZG8PRyGuUmUJ8a2xNuQ9W/NNA1prxqv2i54DSydV8SJqxF2oCVgA==
+ dependencies:
+ "@google-cloud/paginator" "^5.0.0"
+ "@google-cloud/projectify" "^4.0.0"
+ "@google-cloud/promisify" "^4.0.0"
+ abort-controller "^3.0.0"
+ async-retry "^1.3.3"
+ duplexify "^4.1.3"
+ fast-xml-parser "^4.4.1"
+ gaxios "^6.0.2"
+ google-auth-library "^9.6.3"
+ html-entities "^2.5.2"
+ mime "^3.0.0"
+ p-limit "^3.0.1"
+ retry-request "^7.0.0"
+ teeny-request "^9.0.0"
+ uuid "^8.0.0"
+
+"@grpc/grpc-js@^1.10.9":
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.12.2.tgz#97eda82dd49bb9c24eaf6434ea8d7de446e95aac"
+ integrity sha512-bgxdZmgTrJZX50OjyVwz3+mNEnCTNkh3cIqGPWVNeW9jX6bn1ZkU80uPd+67/ZpIJIjRQ9qaHCjhavyoWYxumg==
+ dependencies:
+ "@grpc/proto-loader" "^0.7.13"
+ "@js-sdsl/ordered-map" "^4.4.2"
+
+"@grpc/proto-loader@^0.7.13":
+ version "0.7.13"
+ resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.13.tgz#f6a44b2b7c9f7b609f5748c6eac2d420e37670cf"
+ integrity sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==
+ dependencies:
+ lodash.camelcase "^4.3.0"
+ long "^5.0.0"
+ protobufjs "^7.2.5"
+ yargs "^17.7.2"
+
+"@humanwhocodes/config-array@^0.13.0":
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748"
+ integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
+ dependencies:
+ "@humanwhocodes/object-schema" "^2.0.3"
+ debug "^4.3.1"
+ minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
+ integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
+
+"@js-sdsl/ordered-map@^4.4.2":
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz#9299f82874bab9e4c7f9c48d865becbfe8d6907c"
+ integrity sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+"@opentelemetry/api@^1.3.0":
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe"
+ integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==
+
+"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
+ integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
+
+"@protobufjs/base64@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
+ integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
+
+"@protobufjs/codegen@^2.0.4":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
+ integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
+
+"@protobufjs/eventemitter@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
+ integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
+
+"@protobufjs/fetch@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
+ integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.1"
+ "@protobufjs/inquire" "^1.1.0"
+
+"@protobufjs/float@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
+ integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
+
+"@protobufjs/inquire@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
+ integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
+
+"@protobufjs/path@^1.1.2":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
+ integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
+
+"@protobufjs/pool@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
+ integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
+
+"@protobufjs/utf8@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
+ integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
+
+"@tootallnate/once@2":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+ integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+
+"@types/body-parser@*":
+ version "1.19.5"
+ resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4"
+ integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==
+ dependencies:
+ "@types/connect" "*"
+ "@types/node" "*"
+
+"@types/caseless@*":
+ version "0.12.5"
+ resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.5.tgz#db9468cb1b1b5a925b8f34822f1669df0c5472f5"
+ integrity sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==
+
+"@types/connect@*":
+ version "3.4.38"
+ resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858"
+ integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==
+ dependencies:
+ "@types/node" "*"
+
+"@types/cors@^2.8.5":
+ version "2.8.17"
+ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b"
+ integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==
+ dependencies:
+ "@types/node" "*"
+
+"@types/express-serve-static-core@*":
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.0.0.tgz#91f06cda1049e8f17eeab364798ed79c97488a1c"
+ integrity sha512-AbXMTZGt40T+KON9/Fdxx0B2WK5hsgxcfXJLr5bFpZ7b4JCex2WyQPTEKdXqfHiY5nKKBScZ7yCoO6Pvgxfvnw==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+ "@types/send" "*"
+
+"@types/express-serve-static-core@^4.17.33":
+ version "4.19.6"
+ resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267"
+ integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==
+ dependencies:
+ "@types/node" "*"
+ "@types/qs" "*"
+ "@types/range-parser" "*"
+ "@types/send" "*"
+
+"@types/express@4.17.3":
+ version "4.17.3"
+ resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.3.tgz#38e4458ce2067873b09a73908df488870c303bd9"
+ integrity sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "*"
+ "@types/serve-static" "*"
+
+"@types/express@^4.17.17":
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d"
+ integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==
+ dependencies:
+ "@types/body-parser" "*"
+ "@types/express-serve-static-core" "^4.17.33"
+ "@types/qs" "*"
+ "@types/serve-static" "*"
+
+"@types/http-errors@*":
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f"
+ integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==
+
+"@types/jsonwebtoken@^9.0.2":
+ version "9.0.7"
+ resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2"
+ integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==
+ dependencies:
+ "@types/node" "*"
+
+"@types/lodash@^4.14.104":
+ version "4.17.10"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.10.tgz#64f3edf656af2fe59e7278b73d3e62404144a6e6"
+ integrity sha512-YpS0zzoduEhuOWjAotS6A5AVCva7X4lVlYLF0FYHAY9sdraBfnatttHItlWeZdGhuEkf+OzMNg2ZYAx8t+52uQ==
+
+"@types/long@^4.0.0":
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
+ integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
+
+"@types/mime@^1":
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
+ integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==
+
+"@types/node@*", "@types/node@>=13.7.0", "@types/node@^22.0.1":
+ version "22.7.5"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b"
+ integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==
+ dependencies:
+ undici-types "~6.19.2"
+
+"@types/qs@*":
+ version "6.9.16"
+ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794"
+ integrity sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==
+
+"@types/range-parser@*":
+ version "1.2.7"
+ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb"
+ integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==
+
+"@types/request@^2.48.8":
+ version "2.48.12"
+ resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30"
+ integrity sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==
+ dependencies:
+ "@types/caseless" "*"
+ "@types/node" "*"
+ "@types/tough-cookie" "*"
+ form-data "^2.5.0"
+
+"@types/send@*":
+ version "0.17.4"
+ resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a"
+ integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==
+ dependencies:
+ "@types/mime" "^1"
+ "@types/node" "*"
+
+"@types/serve-static@*":
+ version "1.15.7"
+ resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714"
+ integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==
+ dependencies:
+ "@types/http-errors" "*"
+ "@types/node" "*"
+ "@types/send" "*"
+
+"@types/tough-cookie@*":
+ version "4.0.5"
+ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304"
+ integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==
+
+"@typescript-eslint/eslint-plugin@^8.7.0":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz#9364b756d4d78bcbdf6fd3e9345e6924c68ad371"
+ integrity sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==
+ dependencies:
+ "@eslint-community/regexpp" "^4.10.0"
+ "@typescript-eslint/scope-manager" "8.8.1"
+ "@typescript-eslint/type-utils" "8.8.1"
+ "@typescript-eslint/utils" "8.8.1"
+ "@typescript-eslint/visitor-keys" "8.8.1"
+ graphemer "^1.4.0"
+ ignore "^5.3.1"
+ natural-compare "^1.4.0"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/scope-manager@8.8.1":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz#b4bea1c0785aaebfe3c4ab059edaea1c4977e7ff"
+ integrity sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==
+ dependencies:
+ "@typescript-eslint/types" "8.8.1"
+ "@typescript-eslint/visitor-keys" "8.8.1"
+
+"@typescript-eslint/type-utils@8.8.1":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz#31f59ec46e93a02b409fb4d406a368a59fad306e"
+ integrity sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==
+ dependencies:
+ "@typescript-eslint/typescript-estree" "8.8.1"
+ "@typescript-eslint/utils" "8.8.1"
+ debug "^4.3.4"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/types@8.8.1":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.8.1.tgz#ebe85e0fa4a8e32a24a56adadf060103bef13bd1"
+ integrity sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==
+
+"@typescript-eslint/typescript-estree@8.8.1":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz#34649f4e28d32ee49152193bc7dedc0e78e5d1ec"
+ integrity sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==
+ dependencies:
+ "@typescript-eslint/types" "8.8.1"
+ "@typescript-eslint/visitor-keys" "8.8.1"
+ debug "^4.3.4"
+ fast-glob "^3.3.2"
+ is-glob "^4.0.3"
+ minimatch "^9.0.4"
+ semver "^7.6.0"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/utils@8.8.1":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.8.1.tgz#9e29480fbfa264c26946253daa72181f9f053c9d"
+ integrity sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.4.0"
+ "@typescript-eslint/scope-manager" "8.8.1"
+ "@typescript-eslint/types" "8.8.1"
+ "@typescript-eslint/typescript-estree" "8.8.1"
+
+"@typescript-eslint/visitor-keys@8.8.1":
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz#0fb1280f381149fc345dfde29f7542ff4e587fc5"
+ integrity sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==
+ dependencies:
+ "@typescript-eslint/types" "8.8.1"
+ eslint-visitor-keys "^3.4.3"
+
+"@ungap/structured-clone@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
+ integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
+
+abort-controller@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
+ integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==
+ dependencies:
+ event-target-shim "^5.0.0"
+
+accepts@~1.3.8:
+ version "1.3.8"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+ integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
+ dependencies:
+ mime-types "~2.1.34"
+ negotiator "0.6.3"
+
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn@^8.9.0:
+ version "8.12.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
+ integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
+
+agent-base@6:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+ integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+ dependencies:
+ debug "4"
+
+agent-base@^7.0.2:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317"
+ integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==
+ dependencies:
+ debug "^4.3.4"
+
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+ integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
+
+arrify@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
+ integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
+
+async-retry@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280"
+ integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==
+ dependencies:
+ retry "0.13.1"
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+base64-js@^1.3.0:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
+ integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+
+bignumber.js@^9.0.0:
+ version "9.1.2"
+ resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
+ integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
+
+body-parser@1.20.3:
+ version "1.20.3"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
+ integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
+ dependencies:
+ bytes "3.1.2"
+ content-type "~1.0.5"
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ on-finished "2.4.1"
+ qs "6.13.0"
+ raw-body "2.5.2"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
+ integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+ dependencies:
+ fill-range "^7.1.1"
+
+buffer-equal-constant-time@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
+ integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
+
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
+call-bind@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
+ integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ set-function-length "^1.2.1"
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+chalk@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+cliui@^8.0.1:
+ version "8.0.1"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
+ integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.1"
+ wrap-ansi "^7.0.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+combined-stream@^1.0.6:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+content-disposition@0.5.4:
+ version "0.5.4"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+ integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
+ dependencies:
+ safe-buffer "5.2.1"
+
+content-type@~1.0.4, content-type@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
+ integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+ integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
+
+cookie@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9"
+ integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==
+
+cors@^2.8.5:
+ version "2.8.5"
+ resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
+ integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+ dependencies:
+ object-assign "^4"
+ vary "^1"
+
+cross-spawn@^7.0.2:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
+deep-is@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
+ integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
+define-data-property@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e"
+ integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==
+ dependencies:
+ es-define-property "^1.0.0"
+ es-errors "^1.3.0"
+ gopd "^1.0.1"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+depd@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
+
+doctrine@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+ integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+ dependencies:
+ esutils "^2.0.2"
+
+duplexify@^4.0.0, duplexify@^4.1.3:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.3.tgz#a07e1c0d0a2c001158563d32592ba58bddb0236f"
+ integrity sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==
+ dependencies:
+ end-of-stream "^1.4.1"
+ inherits "^2.0.3"
+ readable-stream "^3.1.1"
+ stream-shift "^1.0.2"
+
+ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
+ integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
+ dependencies:
+ safe-buffer "^5.0.1"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
+
+encodeurl@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+ integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
+end-of-stream@^1.4.1:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
+ integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
+ dependencies:
+ once "^1.4.0"
+
+err-code@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9"
+ integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==
+
+es-define-property@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
+ integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==
+ dependencies:
+ get-intrinsic "^1.2.4"
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+escalade@^3.1.1:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+eslint-config-google@^0.14.0:
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz#4f5f8759ba6e11b424294a219dbfa18c508bcc1a"
+ integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
+
+eslint-scope@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+ integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+eslint@^8.57.1:
+ version "8.57.1"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9"
+ integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.6.1"
+ "@eslint/eslintrc" "^2.1.4"
+ "@eslint/js" "8.57.1"
+ "@humanwhocodes/config-array" "^0.13.0"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@nodelib/fs.walk" "^1.2.8"
+ "@ungap/structured-clone" "^1.2.0"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
+ doctrine "^3.0.0"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^7.2.2"
+ eslint-visitor-keys "^3.4.3"
+ espree "^9.6.1"
+ esquery "^1.4.2"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ globals "^13.19.0"
+ graphemer "^1.4.0"
+ ignore "^5.2.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ is-path-inside "^3.0.3"
+ js-yaml "^4.1.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+espree@^9.6.0, espree@^9.6.1:
+ version "9.6.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+ integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
+ dependencies:
+ acorn "^8.9.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.4.1"
+
+esquery@^1.4.2:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
+
+event-target-shim@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
+ integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
+
+expo-server-sdk@^3.11.0:
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/expo-server-sdk/-/expo-server-sdk-3.11.0.tgz#80bc16e2ec103a1235e856adf0d2ae358f7f5ddc"
+ integrity sha512-EGH82ZcdAFjKq+6daDE8Xj7BjaSeP1VDvZ3Hmtn/KzEQ3ffqHkauMsgXL2wLEPlvatLq3EsYNcejXRBV54WnFQ==
+ dependencies:
+ node-fetch "^2.6.0"
+ promise-limit "^2.7.0"
+ promise-retry "^2.0.1"
+
+express@^4.17.1:
+ version "4.21.1"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.21.1.tgz#9dae5dda832f16b4eec941a4e44aa89ec481b281"
+ integrity sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==
+ dependencies:
+ accepts "~1.3.8"
+ array-flatten "1.1.1"
+ body-parser "1.20.3"
+ content-disposition "0.5.4"
+ content-type "~1.0.4"
+ cookie "0.7.1"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "2.0.0"
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "1.3.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ merge-descriptors "1.0.3"
+ methods "~1.1.2"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.10"
+ proxy-addr "~2.0.7"
+ qs "6.13.0"
+ range-parser "~1.2.1"
+ safe-buffer "5.2.1"
+ send "0.19.0"
+ serve-static "1.16.2"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+extend@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
+ integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
+
+farmhash-modern@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/farmhash-modern/-/farmhash-modern-1.1.0.tgz#c36b34ad196290d57b0b482dc89e637d0b59835f"
+ integrity sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
+ integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.4"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fast-xml-parser@^4.4.1:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz#2882b7d01a6825dfdf909638f2de0256351def37"
+ integrity sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==
+ dependencies:
+ strnum "^1.0.5"
+
+fastq@^1.6.0:
+ version "1.17.1"
+ resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
+ integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==
+ dependencies:
+ reusify "^1.0.4"
+
+faye-websocket@0.11.4:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da"
+ integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==
+ dependencies:
+ websocket-driver ">=0.5.1"
+
+file-entry-cache@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
+ integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+ dependencies:
+ flat-cache "^3.0.4"
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+finalhandler@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019"
+ integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ on-finished "2.4.1"
+ parseurl "~1.3.3"
+ statuses "2.0.1"
+ unpipe "~1.0.0"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+firebase-admin@^12.1.0:
+ version "12.6.0"
+ resolved "https://registry.yarnpkg.com/firebase-admin/-/firebase-admin-12.6.0.tgz#495bfa30a4484d1af6390bb3481fdf911bca530e"
+ integrity sha512-gc0pDiUmxscxBhcjMcttmjvExJmnQdVRb+IIth95CvMm7F9rLdabrQZThW2mK02HR696P+rzd6NqkdUA3URu4w==
+ dependencies:
+ "@fastify/busboy" "^3.0.0"
+ "@firebase/database-compat" "^1.0.2"
+ "@firebase/database-types" "^1.0.0"
+ "@types/node" "^22.0.1"
+ farmhash-modern "^1.1.0"
+ jsonwebtoken "^9.0.0"
+ jwks-rsa "^3.1.0"
+ node-forge "^1.3.1"
+ uuid "^10.0.0"
+ optionalDependencies:
+ "@google-cloud/firestore" "^7.7.0"
+ "@google-cloud/storage" "^7.7.0"
+
+firebase-functions-test@^3.1.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/firebase-functions-test/-/firebase-functions-test-3.3.0.tgz#63566f0ea71d9fd0651c756b29a8b50ec5f408d8"
+ integrity sha512-X+OOA34MGrsTimFXTDnWT0psAqnmBkJ85bGCoLMwjgei5Prfkqh3bv5QASnXC/cmIVBSF2Qw9uW1+mF/t3kFlw==
+ dependencies:
+ "@types/lodash" "^4.14.104"
+ lodash "^4.17.5"
+ ts-deepmerge "^2.0.1"
+
+firebase-functions@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/firebase-functions/-/firebase-functions-5.1.1.tgz#15c668d072d4000d6a348aabb7ab4acdbd2442f8"
+ integrity sha512-KkyKZE98Leg/C73oRyuUYox04PQeeBThdygMfeX+7t1cmKWYKa/ZieYa89U8GHgED+0mF7m7wfNZOfbURYxIKg==
+ dependencies:
+ "@types/cors" "^2.8.5"
+ "@types/express" "4.17.3"
+ cors "^2.8.5"
+ express "^4.17.1"
+ protobufjs "^7.2.2"
+
+flat-cache@^3.0.4:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
+ integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.3"
+ rimraf "^3.0.2"
+
+flatted@^3.2.9:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
+ integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
+
+form-data@^2.5.0:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.2.tgz#dc653743d1de2fcc340ceea38079daf6e9069fd2"
+ integrity sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.6"
+ mime-types "^2.1.12"
+ safe-buffer "^5.2.1"
+
+forwarded@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
+ integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
+
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+functional-red-black-tree@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+ integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==
+
+gaxios@^6.0.0, gaxios@^6.0.2, gaxios@^6.1.1:
+ version "6.7.1"
+ resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.1.tgz#ebd9f7093ede3ba502685e73390248bb5b7f71fb"
+ integrity sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==
+ dependencies:
+ extend "^3.0.2"
+ https-proxy-agent "^7.0.1"
+ is-stream "^2.0.0"
+ node-fetch "^2.6.9"
+ uuid "^9.0.1"
+
+gcp-metadata@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.0.tgz#9b0dd2b2445258e7597f2024332d20611cbd6b8c"
+ integrity sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==
+ dependencies:
+ gaxios "^6.0.0"
+ json-bigint "^1.0.0"
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd"
+ integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ has-proto "^1.0.1"
+ has-symbols "^1.0.3"
+ hasown "^2.0.0"
+
+glob-parent@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob@^7.1.3:
+ version "7.2.3"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^13.19.0:
+ version "13.24.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
+ integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
+ dependencies:
+ type-fest "^0.20.2"
+
+google-auth-library@^9.3.0, google-auth-library@^9.6.3:
+ version "9.14.2"
+ resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.14.2.tgz#92a53ba32b3a9ff9ced8ed34129edb5a7fa7fb52"
+ integrity sha512-R+FRIfk1GBo3RdlRYWPdwk8nmtVUOn6+BkDomAC46KoU8kzXzE1HLmOasSCbWUByMMAGkknVF0G5kQ69Vj7dlA==
+ dependencies:
+ base64-js "^1.3.0"
+ ecdsa-sig-formatter "^1.0.11"
+ gaxios "^6.1.1"
+ gcp-metadata "^6.1.0"
+ gtoken "^7.0.0"
+ jws "^4.0.0"
+
+google-gax@^4.3.3:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-4.4.1.tgz#95a9cf7ee7777ac22d1926a45b5f886dd8beecae"
+ integrity sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==
+ dependencies:
+ "@grpc/grpc-js" "^1.10.9"
+ "@grpc/proto-loader" "^0.7.13"
+ "@types/long" "^4.0.0"
+ abort-controller "^3.0.0"
+ duplexify "^4.0.0"
+ google-auth-library "^9.3.0"
+ node-fetch "^2.7.0"
+ object-hash "^3.0.0"
+ proto3-json-serializer "^2.0.2"
+ protobufjs "^7.3.2"
+ retry-request "^7.0.0"
+ uuid "^9.0.1"
+
+gopd@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+ integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+ dependencies:
+ get-intrinsic "^1.1.3"
+
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
+gtoken@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26"
+ integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==
+ dependencies:
+ gaxios "^6.0.0"
+ jws "^4.0.0"
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-property-descriptors@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854"
+ integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==
+ dependencies:
+ es-define-property "^1.0.0"
+
+has-proto@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd"
+ integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==
+
+has-symbols@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+ integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+hasown@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
+html-entities@^2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f"
+ integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==
+
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
+http-parser-js@>=0.5.1:
+ version "0.5.8"
+ resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
+ integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
+
+http-proxy-agent@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+ integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
+ dependencies:
+ "@tootallnate/once" "2"
+ agent-base "6"
+ debug "4"
+
+https-proxy-agent@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
+ integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
+ dependencies:
+ agent-base "6"
+ debug "4"
+
+https-proxy-agent@^7.0.1:
+ version "7.0.5"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2"
+ integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==
+ dependencies:
+ agent-base "^7.0.2"
+ debug "4"
+
+iconv-lite@0.4.24:
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+ignore@^5.2.0, ignore@^5.3.1:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
+ integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
+
+import-fresh@^3.2.1:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+ integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, inherits@2.0.4, inherits@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ipaddr.js@1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-path-inside@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
+is-stream@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+ integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+jose@^4.14.6:
+ version "4.15.9"
+ resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.9.tgz#9b68eda29e9a0614c042fa29387196c7dd800100"
+ integrity sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+json-bigint@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
+ integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
+ dependencies:
+ bignumber.js "^9.0.0"
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+jsonwebtoken@^9.0.0:
+ version "9.0.2"
+ resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
+ integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
+ dependencies:
+ jws "^3.2.2"
+ lodash.includes "^4.3.0"
+ lodash.isboolean "^3.0.3"
+ lodash.isinteger "^4.0.4"
+ lodash.isnumber "^3.0.3"
+ lodash.isplainobject "^4.0.6"
+ lodash.isstring "^4.0.1"
+ lodash.once "^4.0.0"
+ ms "^2.1.1"
+ semver "^7.5.4"
+
+jwa@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
+ integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
+ dependencies:
+ buffer-equal-constant-time "1.0.1"
+ ecdsa-sig-formatter "1.0.11"
+ safe-buffer "^5.0.1"
+
+jwa@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
+ integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
+ dependencies:
+ buffer-equal-constant-time "1.0.1"
+ ecdsa-sig-formatter "1.0.11"
+ safe-buffer "^5.0.1"
+
+jwks-rsa@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/jwks-rsa/-/jwks-rsa-3.1.0.tgz#50406f23e38c9b2682cd437f824d7d61aa983171"
+ integrity sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==
+ dependencies:
+ "@types/express" "^4.17.17"
+ "@types/jsonwebtoken" "^9.0.2"
+ debug "^4.3.4"
+ jose "^4.14.6"
+ limiter "^1.1.5"
+ lru-memoizer "^2.2.0"
+
+jws@^3.2.2:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
+ integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
+ dependencies:
+ jwa "^1.4.1"
+ safe-buffer "^5.0.1"
+
+jws@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
+ integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
+ dependencies:
+ jwa "^2.0.0"
+ safe-buffer "^5.0.1"
+
+keyv@^4.5.3:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+limiter@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2"
+ integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash.camelcase@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
+ integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==
+
+lodash.clonedeep@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
+ integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
+
+lodash.includes@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
+ integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
+
+lodash.isboolean@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
+ integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
+
+lodash.isinteger@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
+ integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
+
+lodash.isnumber@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
+ integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
+
+lodash.isplainobject@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
+ integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
+
+lodash.isstring@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
+ integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lodash.once@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
+ integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
+
+lodash@^4.17.5:
+ version "4.17.21"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+long@^5.0.0:
+ version "5.2.3"
+ resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
+ integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
+
+lru-cache@6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+ dependencies:
+ yallist "^4.0.0"
+
+lru-memoizer@^2.2.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.3.0.tgz#ef0fbc021bceb666794b145eefac6be49dc47f31"
+ integrity sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==
+ dependencies:
+ lodash.clonedeep "^4.5.0"
+ lru-cache "6.0.0"
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
+
+merge-descriptors@1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
+ integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
+
+merge2@^1.3.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==
+
+micromatch@^4.0.4:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
+ integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
+ dependencies:
+ braces "^3.0.3"
+ picomatch "^2.3.1"
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34:
+ version "2.1.35"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+mime@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
+mime@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
+ integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
+
+minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
+
+ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
+negotiator@0.6.3:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+ integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+
+node-fetch@^2.6.0, node-fetch@^2.6.9, node-fetch@^2.7.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
+ integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
+ dependencies:
+ whatwg-url "^5.0.0"
+
+node-forge@^1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
+ integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
+
+object-assign@^4:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-hash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
+ integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
+
+object-inspect@^1.13.1:
+ version "1.13.2"
+ resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff"
+ integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==
+
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
+once@^1.3.0, once@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.5"
+
+p-limit@^3.0.1, p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-to-regexp@0.1.10:
+ version "0.1.10"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
+ integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
+
+picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+promise-limit@^2.7.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/promise-limit/-/promise-limit-2.7.0.tgz#eb5737c33342a030eaeaecea9b3d3a93cb592b26"
+ integrity sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==
+
+promise-retry@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22"
+ integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==
+ dependencies:
+ err-code "^2.0.2"
+ retry "^0.12.0"
+
+proto3-json-serializer@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz#5b705203b4d58f3880596c95fad64902617529dd"
+ integrity sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==
+ dependencies:
+ protobufjs "^7.2.5"
+
+protobufjs@^7.2.2, protobufjs@^7.2.5, protobufjs@^7.2.6, protobufjs@^7.3.2:
+ version "7.4.0"
+ resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a"
+ integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==
+ dependencies:
+ "@protobufjs/aspromise" "^1.1.2"
+ "@protobufjs/base64" "^1.1.2"
+ "@protobufjs/codegen" "^2.0.4"
+ "@protobufjs/eventemitter" "^1.1.0"
+ "@protobufjs/fetch" "^1.1.0"
+ "@protobufjs/float" "^1.0.2"
+ "@protobufjs/inquire" "^1.1.0"
+ "@protobufjs/path" "^1.1.2"
+ "@protobufjs/pool" "^1.1.0"
+ "@protobufjs/utf8" "^1.1.0"
+ "@types/node" ">=13.7.0"
+ long "^5.0.0"
+
+proxy-addr@~2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
+ integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
+ dependencies:
+ forwarded "0.2.0"
+ ipaddr.js "1.9.1"
+
+punycode@^2.1.0:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+qs@6.13.0:
+ version "6.13.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+ integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
+ dependencies:
+ side-channel "^1.0.6"
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+range-parser@~1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.5.2:
+ version "2.5.2"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
+ integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
+ dependencies:
+ bytes "3.1.2"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
+readable-stream@^3.1.1:
+ version "3.6.2"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
+ integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+retry-request@^7.0.0:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-7.0.2.tgz#60bf48cfb424ec01b03fca6665dee91d06dd95f3"
+ integrity sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==
+ dependencies:
+ "@types/request" "^2.48.8"
+ extend "^3.0.2"
+ teeny-request "^9.0.0"
+
+retry@0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
+ integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
+retry@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
+ integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==
+
+reusify@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+ integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.2.1, safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+"safer-buffer@>= 2.1.2 < 3":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+semver@^7.5.4, semver@^7.6.0:
+ version "7.6.3"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+send@0.19.0:
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
+ integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
+ dependencies:
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "2.0.0"
+ mime "1.6.0"
+ ms "2.1.3"
+ on-finished "2.4.1"
+ range-parser "~1.2.1"
+ statuses "2.0.1"
+
+serve-static@1.16.2:
+ version "1.16.2"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296"
+ integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==
+ dependencies:
+ encodeurl "~2.0.0"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.19.0"
+
+set-function-length@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449"
+ integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==
+ dependencies:
+ define-data-property "^1.1.4"
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-property-descriptors "^1.0.2"
+
+setprototypeof@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+side-channel@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
+ integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
+ dependencies:
+ call-bind "^1.0.7"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.4"
+ object-inspect "^1.13.1"
+
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+stream-events@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5"
+ integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==
+ dependencies:
+ stubs "^3.0.0"
+
+stream-shift@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b"
+ integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+strnum@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
+ integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
+
+stubs@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"
+ integrity sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+teeny-request@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-9.0.0.tgz#18140de2eb6595771b1b02203312dfad79a4716d"
+ integrity sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==
+ dependencies:
+ http-proxy-agent "^5.0.0"
+ https-proxy-agent "^5.0.0"
+ node-fetch "^2.6.9"
+ stream-events "^1.0.5"
+ uuid "^9.0.0"
+
+text-table@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+ integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
+ts-api-utils@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
+ integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
+
+ts-deepmerge@^2.0.1:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz#36786a9a10b5f3a6f5154007cf17bfba7251e0a7"
+ integrity sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==
+
+tslib@^2.1.0:
+ version "2.7.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
+ integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
+
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
+type-is@~1.6.18:
+ version "1.6.18"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+undici-types@~6.19.2:
+ version "6.19.8"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
+ integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+util-deprecate@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+utils-merge@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+ integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
+
+uuid@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
+ integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
+
+uuid@^8.0.0:
+ version "8.3.2"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
+uuid@^9.0.0, uuid@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
+ integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
+
+vary@^1, vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
+
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+websocket-driver@>=0.5.1:
+ version "0.7.4"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
+ integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
+ dependencies:
+ http-parser-js ">=0.5.1"
+ safe-buffer ">=5.1.0"
+ websocket-extensions ">=0.1.1"
+
+websocket-extensions@>=0.1.1:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
+ integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yargs-parser@^21.1.1:
+ version "21.1.1"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+ integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
+yargs@^17.7.2:
+ version "17.7.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
+ dependencies:
+ cliui "^8.0.1"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.3"
+ y18n "^5.0.5"
+ yargs-parser "^21.1.1"
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/hooks/firebase/types/todoData.ts b/hooks/firebase/types/todoData.ts
index e71d79f..32542c0 100644
--- a/hooks/firebase/types/todoData.ts
+++ b/hooks/firebase/types/todoData.ts
@@ -11,4 +11,22 @@ export interface IToDo {
creatorId?: string;
familyId?: string;
assignees?: string[]; // Optional list of assignees
+ connectedTodoId?: string;
}
+
+export const DAYS_OF_WEEK_ENUM = {
+ MONDAY: "Monday",
+ TUESDAY: "Tuesday",
+ WEDNESDAY: "Wednesday",
+ THURSDAY: "Thursday",
+ FRIDAY: "Friday",
+ SATURDAY: "Saturday",
+ SUNDAY: "Sunday"
+}
+
+export const REPEAT_TYPE = {
+ NONE: "None",
+ EVERY_WEEK: "Every week",
+ ONCE_A_MONTH: "Once a month",
+ ONCE_A_YEAR: "Once a year"
+}
\ No newline at end of file
diff --git a/hooks/firebase/useChangeProfilePicture.ts b/hooks/firebase/useChangeProfilePicture.ts
index 4f0b97e..6dff508 100644
--- a/hooks/firebase/useChangeProfilePicture.ts
+++ b/hooks/firebase/useChangeProfilePicture.ts
@@ -1,13 +1,13 @@
-import { useMutation, useQueryClient } from "react-query";
+import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
import storage from "@react-native-firebase/storage";
-import { useAuthContext } from "@/contexts/AuthContext";
+import {useAuthContext} from "@/contexts/AuthContext";
import * as ImagePicker from "expo-image-picker";
-import { Platform } from "react-native";
+import {Platform} from "react-native";
-export const useChangeProfilePicture = () => {
+export const useChangeProfilePicture = (customUserId?: string) => {
const queryClient = useQueryClient();
- const { user, refreshProfileData } = useAuthContext();
+ const {user, refreshProfileData} = useAuthContext();
return useMutation({
mutationKey: ["changeProfilePicture"],
@@ -38,20 +38,24 @@ export const useChangeProfilePicture = () => {
const downloadURL = await reference.getDownloadURL();
console.log("Download URL:", downloadURL);
+ if(!customUserId) {
await firestore()
.collection("Profiles")
.doc(user?.uid)
- .update({ pfp: downloadURL });
+ .update({pfp: downloadURL});
+ }
} catch (e) {
- console.error("Error uploading profile picture:", e.message);
+ console.error("Error uploading profile picture:", e);
throw e;
}
},
onSuccess: () => {
// Invalidate queries to refresh profile data
- queryClient.invalidateQueries("Profiles");
- refreshProfileData();
+ if (!customUserId) {
+ queryClient.invalidateQueries("Profiles");
+ refreshProfileData();
+ }
},
});
};
\ No newline at end of file
diff --git a/hooks/firebase/useClearTokens.ts b/hooks/firebase/useClearTokens.ts
new file mode 100644
index 0000000..35fd305
--- /dev/null
+++ b/hooks/firebase/useClearTokens.ts
@@ -0,0 +1,39 @@
+import {useMutation} from "react-query";
+import {UserProfile} from "@firebase/auth";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
+
+export const useClearTokens = () => {
+ const {profileData} = useAuthContext();
+ const {mutateAsync: updateUserData} = useUpdateUserData();
+
+ return useMutation({
+ mutationKey: ["clearTokens"],
+ mutationFn: async ({provider, email}: {
+ provider: "google" | "outlook" | "apple",
+ email: string
+ }) => {
+ const newUserData: Partial = {};
+ if (provider === "google") {
+ let googleAccounts = profileData?.googleAccounts;
+ if (googleAccounts) {
+ googleAccounts[email] = null;
+ newUserData.googleAccounts = googleAccounts;
+ }
+ } else if (provider === "outlook") {
+ let microsoftAccounts = profileData?.microsoftAccounts;
+ if (microsoftAccounts) {
+ microsoftAccounts[email] = null;
+ newUserData.microsoftAccounts = microsoftAccounts;
+ }
+ } else if (provider === "apple") {
+ let appleAccounts = profileData?.appleAccounts;
+ if (appleAccounts) {
+ appleAccounts[email] = null;
+ newUserData.appleAccounts = appleAccounts;
+ }
+ }
+ await updateUserData({newUserData});
+ },
+ })
+}
\ No newline at end of file
diff --git a/hooks/firebase/useCreateEvent.ts b/hooks/firebase/useCreateEvent.ts
index 4aecf1e..2b56346 100644
--- a/hooks/firebase/useCreateEvent.ts
+++ b/hooks/firebase/useCreateEvent.ts
@@ -30,9 +30,10 @@ export const useCreateEvent = () => {
return;
}
}
+ const newDoc = firestore().collection('Events').doc();
await firestore()
.collection("Events")
- .add({...eventData, creatorId: currentUser?.uid, familyId: profileData?.familyId});
+ .add({...eventData, id: newDoc.id, creatorId: currentUser?.uid, familyId: profileData?.familyId});
} catch (e) {
console.error(e);
}
@@ -44,34 +45,41 @@ export const useCreateEvent = () => {
}
export const useCreateEventsFromProvider = () => {
- const {user: currentUser} = useAuthContext();
+ const { user: currentUser } = useAuthContext();
const queryClient = useQueryClient();
return useMutation({
mutationKey: ["createEventsFromProvider"],
mutationFn: async (eventDataArray: Partial[]) => {
try {
- for (const eventData of eventDataArray) {
+ // Create an array of promises for each event's Firestore read/write operation
+ const promises = eventDataArray.map(async (eventData) => {
console.log("Processing EventData: ", eventData);
+ // Check if the event already exists
const snapshot = await firestore()
.collection("Events")
.where("id", "==", eventData.id)
.get();
if (snapshot.empty) {
- await firestore()
+ // Event doesn't exist, so add it
+ return firestore()
.collection("Events")
- .add({...eventData, creatorId: currentUser?.uid});
+ .add({ ...eventData, creatorId: currentUser?.uid });
} else {
- console.log("Event already exists, updating...");
+ // Event exists, update it
const docId = snapshot.docs[0].id;
- await firestore()
+ return firestore()
.collection("Events")
.doc(docId)
- .set({...eventData, creatorId: currentUser?.uid}, {merge: true});
+ .set({ ...eventData, creatorId: currentUser?.uid }, { merge: true });
}
- }
+ });
+
+ // Execute all promises in parallel
+ await Promise.all(promises);
+
} catch (e) {
console.error("Error creating/updating events: ", e);
}
diff --git a/hooks/firebase/useCreateFeedback.ts b/hooks/firebase/useCreateFeedback.ts
new file mode 100644
index 0000000..849afe1
--- /dev/null
+++ b/hooks/firebase/useCreateFeedback.ts
@@ -0,0 +1,85 @@
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useMutation, useQueryClient} from "react-query";
+import firestore from "@react-native-firebase/firestore";
+import { IFeedback } from "@/contexts/FeedbackContext";
+
+export const useCreateFeedback = () => {
+ const {user: currentUser, profileData} = useAuthContext()
+ const queryClients = useQueryClient()
+
+ return useMutation({
+ mutationKey: ["createFeedback"],
+ mutationFn: async (feedback: Partial) => {
+ try {
+ if (feedback.id) {
+ const snapshot = await firestore()
+ .collection("Feedbacks")
+ .where("id", "==", feedback.id)
+ .get();
+
+ if (!snapshot.empty) {
+ const docId = snapshot.docs[0].id;
+ await firestore()
+ .collection("Feedbacks")
+ .doc(docId)
+ .set({
+ ...feedback,
+ creatorId: currentUser?.uid,
+ }, {merge: true});
+ return;
+ }
+ }
+ const newDoc = firestore().collection('Feedbacks').doc();
+ await firestore()
+ .collection("Feedbacks")
+ .add({...feedback, id: newDoc.id, creatorId: currentUser?.uid});
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ onSuccess: () => {
+ queryClients.invalidateQueries("feedbacks")
+ }
+ })
+}
+
+export const useCreateFeedbacksFromProvider = () => {
+ const { user: currentUser } = useAuthContext();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["createFeedbacksFromProvider"],
+ mutationFn: async (feedbackDataArray: Partial[]) => {
+ try {
+ const promises = feedbackDataArray.map(async (feedbackData) => {
+ console.log("Processing FeedbackData: ", feedbackData);
+
+ const snapshot = await firestore()
+ .collection("Feedbacks")
+ .where("id", "==", feedbackData.id)
+ .get();
+
+ if (snapshot.empty) {
+ return firestore()
+ .collection("Feedbacks")
+ .add({ ...feedbackData, creatorId: currentUser?.uid });
+ } else {
+ const docId = snapshot.docs[0].id;
+ return firestore()
+ .collection("Feedbacks")
+ .doc(docId)
+ .set({ ...feedbackData, creatorId: currentUser?.uid }, { merge: true });
+ }
+ });
+
+ await Promise.all(promises);
+
+ } catch (e) {
+ console.error("Error creating/updating feedbacks: ", e);
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries("feedbacks");
+ }
+ });
+};
\ No newline at end of file
diff --git a/hooks/firebase/useCreateNote.ts b/hooks/firebase/useCreateNote.ts
new file mode 100644
index 0000000..417e959
--- /dev/null
+++ b/hooks/firebase/useCreateNote.ts
@@ -0,0 +1,91 @@
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useMutation, useQueryClient } from "react-query";
+import firestore from "@react-native-firebase/firestore";
+import { IFeedback } from "@/contexts/FeedbackContext";
+import { IBrainDump } from "@/contexts/DumpContext";
+
+export const useCreateNote = () => {
+ const { user: currentUser, profileData } = useAuthContext();
+ const queryClients = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["createNote"],
+ mutationFn: async (note: Partial) => {
+ try {
+ if (note.id) {
+ const snapshot = await firestore()
+ .collection("BrainDumps")
+ .where("id", "==", note.id)
+ .get();
+
+ if (!snapshot.empty) {
+ const docId = snapshot.docs[0].id;
+ await firestore()
+ .collection("BrainDumps")
+ .doc(docId)
+ .set(
+ {
+ ...note,
+ creatorId: currentUser?.uid,
+ },
+ { merge: true }
+ );
+ return;
+ }
+ }
+ const newDoc = firestore().collection("BrainDumps").doc();
+ await firestore()
+ .collection("BrainDumps")
+ .add({ ...note, id: newDoc.id, creatorId: currentUser?.uid });
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ onSuccess: () => {
+ queryClients.invalidateQueries("braindumps");
+ },
+ });
+};
+
+export const useCreateNotesFromProvider = () => {
+ const { user: currentUser } = useAuthContext();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["createNotesFromProvider"],
+ mutationFn: async (noteDataArray: Partial[]) => {
+ try {
+ const promises = noteDataArray.map(async (noteData) => {
+ console.log("Processing NoteData: ", noteData);
+
+ const snapshot = await firestore()
+ .collection("BrainDumps")
+ .where("id", "==", noteData.id)
+ .get();
+
+ if (snapshot.empty) {
+ return firestore()
+ .collection("BrainDumps")
+ .add({ ...noteData, creatorId: currentUser?.uid });
+ } else {
+ const docId = snapshot.docs[0].id;
+ return firestore()
+ .collection("BrainDumps")
+ .doc(docId)
+ .set(
+ { ...noteData, creatorId: currentUser?.uid },
+ { merge: true }
+ );
+ }
+ });
+
+ await Promise.all(promises);
+ } catch (e) {
+ console.error("Error creating/updating braindumps: ", e);
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries("braindumps");
+ },
+ });
+};
diff --git a/hooks/firebase/useCreateSubUser.ts b/hooks/firebase/useCreateSubUser.ts
index 84c5408..3418bb8 100644
--- a/hooks/firebase/useCreateSubUser.ts
+++ b/hooks/firebase/useCreateSubUser.ts
@@ -15,7 +15,7 @@ export const useCreateSubUser = () => {
return await functions().httpsCallable("createSubUser")({
...userProfile,
email,
- familyId: profileData?.familyId
+ familyId: profileData?.familyId!
}) as HttpsCallableResult<{ userId: string }>
} else {
throw Error("Can't create sub-users as a non-parent.")
diff --git a/hooks/firebase/useCreateTodo.ts b/hooks/firebase/useCreateTodo.ts
index d065fc5..06e4979 100644
--- a/hooks/firebase/useCreateTodo.ts
+++ b/hooks/firebase/useCreateTodo.ts
@@ -1,7 +1,17 @@
-import { useMutation, useQueryClient } from "react-query";
+import {useMutation, useQueryClient} from "react-query";
import firestore from "@react-native-firebase/firestore";
-import { useAuthContext } from "@/contexts/AuthContext";
-import { IToDo } from "@/hooks/firebase/types/todoData";
+import {useAuthContext} from "@/contexts/AuthContext";
+import {DAYS_OF_WEEK_ENUM, IToDo, REPEAT_TYPE} from "@/hooks/firebase/types/todoData";
+import {addDays, addMonths, addWeeks, addYears, compareAsc, format, subDays} from "date-fns";
+
+export const daysOfWeek = [
+ DAYS_OF_WEEK_ENUM.MONDAY,
+ DAYS_OF_WEEK_ENUM.TUESDAY,
+ DAYS_OF_WEEK_ENUM.WEDNESDAY,
+ DAYS_OF_WEEK_ENUM.THURSDAY,
+ DAYS_OF_WEEK_ENUM.FRIDAY,
+ DAYS_OF_WEEK_ENUM.SATURDAY,
+ DAYS_OF_WEEK_ENUM.SUNDAY];
export const useCreateTodo = () => {
const { user: currentUser, profileData } = useAuthContext();
@@ -11,10 +21,92 @@ export const useCreateTodo = () => {
mutationKey: ["createTodo"],
mutationFn: async (todoData: Partial) => {
try {
- const newDoc = firestore().collection('Todos').doc();
- await firestore()
- .collection("Todos")
- .add({...todoData, id: newDoc.id, familyId: profileData?.familyId, creatorId: currentUser?.uid})
+ if (todoData.repeatType === REPEAT_TYPE.NONE) {
+ const newDoc = firestore().collection('Todos').doc();
+ let originalTodo = {...todoData, id: newDoc.id, familyId: profileData?.familyId, creatorId: currentUser?.uid}
+ await firestore()
+ .collection("Todos")
+ .add(originalTodo);
+ } else {
+ // Create the one original to do
+ const newDoc = firestore().collection('Todos').doc();
+ let originalTodo = {...todoData, id: newDoc.id, familyId: profileData?.familyId, creatorId: currentUser?.uid, connectedTodoId: newDoc.id};
+
+ originalTodo = resolveTodoAlternatingAssignees(todoData, originalTodo, 0);
+
+ await firestore()
+ .collection("Todos")
+ .add(originalTodo);
+
+ const batch = firestore().batch();
+
+ if (todoData.repeatType === REPEAT_TYPE.EVERY_WEEK) {
+
+ let date = originalTodo.date;
+ let repeatDays = originalTodo.repeatDays;
+ const dates = [];
+
+ const originalDateDay = format(date, 'EEEE');
+ const originalNumber = daysOfWeek.indexOf(originalDateDay);
+ repeatDays?.forEach((day) => {
+ let number = daysOfWeek.indexOf(day);
+ let newDate;
+ if (originalNumber > number) {
+ let diff = originalNumber - number;
+ newDate = subDays(date, diff);
+ } else {
+ let diff = number - originalNumber;
+ newDate = addDays(date, diff);
+ }
+ dates.push(newDate);
+ });
+
+ // TODO: for the next 52 weeks
+ let index = 1;
+ for (let i = 0; i < 4; i++) {
+ dates?.forEach((dateToAdd) => {
+ index ++;
+ let newTodoDate = addWeeks(dateToAdd, i);
+ if (compareAsc(newTodoDate, originalTodo.date) !== 0) {
+
+ let docRef = firestore().collection("Todos").doc();
+ let newTodo = { ...originalTodo, id: docRef.id, date: newTodoDate, connectedTodoId: newDoc.id };
+ newTodo = resolveTodoAlternatingAssignees(todoData, newTodo, index);
+
+ batch.set(docRef, newTodo);
+ }
+ })
+ }
+ } else if (todoData.repeatType === REPEAT_TYPE.ONCE_A_MONTH) {
+
+ // for the next 12 months
+ for (let i = 1; i < 12; i++) {
+ let date = originalTodo.date;
+ const nextMonth = addMonths(date, i);
+
+ let docRef = firestore().collection("Todos").doc();
+ let newTodo = { ...originalTodo, id: docRef.id, date: nextMonth, connectedTodoId: newDoc.id };
+ newTodo = resolveTodoAlternatingAssignees(todoData, newTodo, i);
+
+ batch.set(docRef, newTodo);
+ }
+ } else if (todoData.repeatType === REPEAT_TYPE.ONCE_A_YEAR) {
+
+ // for the next 5 years
+ for (let i = 1; i < 5; i++) {
+ let date = originalTodo.date;
+ const nextMonth = addYears(date, i);
+
+ let docRef = firestore().collection("Todos").doc();
+ let newTodo = { ...originalTodo, id: docRef.id, date: nextMonth, connectedTodoId: newDoc.id };
+ newTodo = resolveTodoAlternatingAssignees(todoData, newTodo, i);
+
+ batch.set(docRef, newTodo);
+ }
+ }
+
+ await batch.commit();
+ }
} catch (e) {
console.error(e)
}
@@ -23,4 +115,15 @@ export const useCreateTodo = () => {
queryClients.invalidateQueries("todos")
}
})
+}
+
+export const resolveTodoAlternatingAssignees = (todoData, newTodo, i) => {
+ if (todoData.assignees && todoData.rotate && todoData?.assignees?.length !== 0) {
+ const assignees = todoData.assignees;
+ const assignee = assignees[i % assignees.length];
+
+ newTodo = {...newTodo, assignees: [assignee]};
+ }
+
+ return newTodo;
}
\ No newline at end of file
diff --git a/hooks/firebase/useDeleteEvent.ts b/hooks/firebase/useDeleteEvent.ts
new file mode 100644
index 0000000..ac24168
--- /dev/null
+++ b/hooks/firebase/useDeleteEvent.ts
@@ -0,0 +1,39 @@
+import {useMutation, useQueryClient} from "react-query";
+import firestore from "@react-native-firebase/firestore";
+
+export const useDeleteEvent = () => {
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["deleteEvent"],
+ mutationFn: async ({eventId, docId}: { eventId?: string; docId?: string }) => {
+ try {
+ if (docId) {
+ await firestore()
+ .collection("Events")
+ .doc(docId)
+ .delete();
+ } else if (eventId) {
+ const snapshot = await firestore()
+ .collection("Events")
+ .where("id", "==", eventId)
+ .get();
+
+ const doc = snapshot.docs[0];
+ if (doc) {
+ await doc.ref.delete();
+ } else {
+ console.warn("Event not found");
+ }
+ } else {
+ console.warn("No identifier provided");
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries("events");
+ }
+ });
+};
\ No newline at end of file
diff --git a/hooks/firebase/useDeleteFeedback.ts b/hooks/firebase/useDeleteFeedback.ts
new file mode 100644
index 0000000..2490332
--- /dev/null
+++ b/hooks/firebase/useDeleteFeedback.ts
@@ -0,0 +1,45 @@
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useMutation, useQueryClient} from "react-query";
+import firestore from "@react-native-firebase/firestore";
+
+export const useDeleteFeedback = () => {
+ const { user: currentUser } = useAuthContext();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["deleteFeedback"],
+ mutationFn: async (feedbackId: string) => {
+ try {
+ // Find the document with matching id field
+ const snapshot = await firestore()
+ .collection("Feedbacks")
+ .where("id", "==", feedbackId)
+ .get();
+
+ if (snapshot.empty) {
+ throw new Error("Feedback not found");
+ }
+
+ // Get the first matching document
+ const docId = snapshot.docs[0].id;
+
+ // Optional: Check if the current user is the creator
+ const feedbackData = snapshot.docs[0].data();
+ if (feedbackData.creatorId !== currentUser?.uid) {
+ throw new Error(
+ "Unauthorized: You can only delete your own feedback"
+ );
+ }
+
+ // Delete the document
+ await firestore().collection("Feedbacks").doc(docId).delete();
+ } catch (e) {
+ console.error("Error deleting feedback: ", e);
+ throw e; // Re-throw the error to be handled by the mutation
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries("feedbacks");
+ },
+ });
+};
diff --git a/hooks/firebase/useDeleteNote.ts b/hooks/firebase/useDeleteNote.ts
new file mode 100644
index 0000000..cc78709
--- /dev/null
+++ b/hooks/firebase/useDeleteNote.ts
@@ -0,0 +1,39 @@
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useMutation, useQueryClient } from "react-query";
+import firestore from "@react-native-firebase/firestore";
+
+export const useDeleteNote = () => {
+ const { user: currentUser } = useAuthContext();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["deleteNote"],
+ mutationFn: async (noteId: string) => {
+ try {
+ const snapshot = await firestore()
+ .collection("BrainDumps")
+ .where("id", "==", noteId)
+ .get();
+
+ if (snapshot.empty) {
+ throw new Error("Note not found");
+ }
+
+ const docId = snapshot.docs[0].id;
+
+ const noteData = snapshot.docs[0].data();
+ if (noteData.creatorId !== currentUser?.uid) {
+ throw new Error("Unauthorized: You can only delete your own Note");
+ }
+
+ await firestore().collection("BrainDumps").doc(docId).delete();
+ } catch (e) {
+ console.error("Error deleting note: ", e);
+ throw e;
+ }
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries("braindumps");
+ },
+ });
+};
diff --git a/hooks/firebase/useGetEvents.ts b/hooks/firebase/useGetEvents.ts
index c8c64d2..3b7254a 100644
--- a/hooks/firebase/useGetEvents.ts
+++ b/hooks/firebase/useGetEvents.ts
@@ -1,23 +1,24 @@
-import {useQuery} from "react-query";
+import { useQuery } from "react-query";
import firestore from "@react-native-firebase/firestore";
-import {useAuthContext} from "@/contexts/AuthContext";
-import {useAtomValue} from "jotai";
-import {isFamilyViewAtom} from "@/components/pages/calendar/atoms";
-import {colorMap} from "@/constants/colorMap";
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useAtomValue } from "jotai";
+import { isFamilyViewAtom } from "@/components/pages/calendar/atoms";
+import { colorMap } from "@/constants/colorMap";
export const useGetEvents = () => {
- const {user, profileData} = useAuthContext();
+ const { user, profileData } = useAuthContext();
const isFamilyView = useAtomValue(isFamilyViewAtom);
return useQuery({
queryKey: ["events", user?.uid, isFamilyView],
queryFn: async () => {
const db = firestore();
-
const userId = user?.uid;
const familyId = profileData?.familyId;
+
let allEvents = [];
+ // If family view is active, include family, creator, and attendee events
if (isFamilyView) {
const familyQuery = db.collection("Events").where("familyID", "==", familyId);
const creatorQuery = db.collection("Events").where("creatorId", "==", userId);
@@ -29,12 +30,14 @@ export const useGetEvents = () => {
attendeeQuery.get(),
]);
+ // Collect all events
const familyEvents = familySnapshot.docs.map(doc => doc.data());
const creatorEvents = creatorSnapshot.docs.map(doc => doc.data());
const attendeeEvents = attendeeSnapshot.docs.map(doc => doc.data());
allEvents = [...familyEvents, ...creatorEvents, ...attendeeEvents];
} else {
+ // Only include creator and attendee events when family view is off
const creatorQuery = db.collection("Events").where("creatorId", "==", userId);
const attendeeQuery = db.collection("Events").where("attendees", "array-contains", userId);
@@ -49,19 +52,28 @@ export const useGetEvents = () => {
allEvents = [...creatorEvents, ...attendeeEvents];
}
- allEvents = allEvents.filter((event, index, self) =>
- index === self.findIndex(e => e.id === event.id)
- );
+ // Use a Map to ensure uniqueness only for events with IDs
+ const uniqueEventsMap = new Map();
+ allEvents.forEach(event => {
+ if (event.id) {
+ uniqueEventsMap.set(event.id, event); // Ensure uniqueness for events with IDs
+ } else {
+ uniqueEventsMap.set(Math.random().toString(36), event); // Generate a temp key for events without ID
+ }
+ });
+ const uniqueEvents = Array.from(uniqueEventsMap.values());
- allEvents = allEvents.filter(event => {
+ // Filter out private events unless the user is the creator
+ const filteredEvents = uniqueEvents.filter(event => {
if (event.private) {
return event.creatorId === userId;
}
return true;
});
+ // Attach event colors and return the final list of events
return await Promise.all(
- allEvents.map(async (event) => {
+ filteredEvents.map(async (event) => {
const profileSnapshot = await db
.collection("Profiles")
.doc(event.creatorId)
@@ -71,13 +83,13 @@ export const useGetEvents = () => {
const eventColor = profileData?.eventColor || colorMap.pink;
return {
- id: event.id,
+ id: event.id || Math.random().toString(36).substr(2, 9), // Generate temp ID if missing
title: event.title,
start: new Date(event.startDate.seconds * 1000),
end: new Date(event.endDate.seconds * 1000),
hideHours: event.allDay,
- eventColor: eventColor,
- notes: event.notes
+ eventColor,
+ notes: event.notes,
};
})
);
diff --git a/hooks/firebase/useGetFeedbacks.ts b/hooks/firebase/useGetFeedbacks.ts
new file mode 100644
index 0000000..da0b699
--- /dev/null
+++ b/hooks/firebase/useGetFeedbacks.ts
@@ -0,0 +1,28 @@
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useQuery } from "react-query";
+import firestore from "@react-native-firebase/firestore";
+import { IFeedback } from "@/contexts/FeedbackContext";
+
+export const useGetFeedbacks = () => {
+ const { user: currentUser } = useAuthContext();
+
+ return useQuery({
+ queryKey: ["feedbacks", currentUser?.uid],
+ queryFn: async () => {
+ try {
+ const snapshot = await firestore()
+ .collection("Feedbacks")
+ .where("creatorId", "==", currentUser?.uid)
+ .get();
+
+ return snapshot.docs.map((doc) => ({
+ ...doc.data(),
+ })) as IFeedback[];
+ } catch (error) {
+ console.error("Error fetching feedbacks:", error);
+ throw error;
+ }
+ },
+ enabled: !!currentUser?.uid, // Only run query if we have a user ID
+ });
+};
diff --git a/hooks/firebase/useGetNotes.ts b/hooks/firebase/useGetNotes.ts
new file mode 100644
index 0000000..62b590a
--- /dev/null
+++ b/hooks/firebase/useGetNotes.ts
@@ -0,0 +1,28 @@
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useQuery } from "react-query";
+import firestore from "@react-native-firebase/firestore";
+import { IBrainDump } from "@/contexts/DumpContext";
+
+export const useGetNotes = () => {
+ const { user: currentUser } = useAuthContext();
+
+ return useQuery({
+ queryKey: ["braindumps", currentUser?.uid],
+ queryFn: async () => {
+ try {
+ const snapshot = await firestore()
+ .collection("BrainDumps")
+ .where("creatorId", "==", currentUser?.uid)
+ .get();
+
+ return snapshot.docs.map((doc) => ({
+ ...doc.data(),
+ })) as IBrainDump[];
+ } catch (error) {
+ console.error("Error fetching braindumps:", error);
+ throw error;
+ }
+ },
+ enabled: !!currentUser?.uid,
+ });
+};
diff --git a/hooks/firebase/useGetTodos.ts b/hooks/firebase/useGetTodos.ts
index b1d6b77..de4b853 100644
--- a/hooks/firebase/useGetTodos.ts
+++ b/hooks/firebase/useGetTodos.ts
@@ -1,20 +1,25 @@
import { useQuery } from "react-query";
-import firestore from "@react-native-firebase/firestore";
-import { useAuthContext } from "@/contexts/AuthContext";
-import {UserProfile} from "@/hooks/firebase/types/profileTypes";
-import {IToDo} from "@/hooks/firebase/types/todoData";
+import firestore, {or, query, where} from "@react-native-firebase/firestore";
+import { ProfileType, useAuthContext } from "@/contexts/AuthContext";
+import { IToDo } from "@/hooks/firebase/types/todoData";
export const useGetTodos = () => {
const { user, profileData } = useAuthContext();
- //TODO: Add role based filtering for todos
return useQuery({
queryKey: ["todos", user?.uid],
queryFn: async () => {
- const snapshot = await firestore()
- .collection("Todos")
- .where("familyId", "==", profileData?.familyId)
- .get();
+
+ let snapshot;
+ if (profileData?.userType === ProfileType.PARENT) {
+ snapshot = await firestore()
+ .collection("Todos")
+ .where("familyId", "==", profileData?.familyId)
+ .get();
+ } else {
+ let todosQuery = query(firestore().collection("Todos"), or(where("assignees", "array-contains", user?.uid), where("creatorId", "==", user?.uid)));
+ snapshot = await todosQuery.get();
+ }
return snapshot.docs.map((doc) => {
const data = doc.data();
@@ -23,6 +28,7 @@ export const useGetTodos = () => {
...data,
id: doc.id,
date: data.date ? new Date(data.date.seconds * 1000) : null,
+ repeatDays: data.repeatDays ?? []
};
}) as IToDo[];
}
diff --git a/hooks/firebase/useLoginWithQrCode.ts b/hooks/firebase/useLoginWithQrCode.ts
index 13cc44b..d28c456 100644
--- a/hooks/firebase/useLoginWithQrCode.ts
+++ b/hooks/firebase/useLoginWithQrCode.ts
@@ -1,12 +1,16 @@
import {useMutation} from "react-query";
import functions, {FirebaseFunctionsTypes} from '@react-native-firebase/functions';
import auth from "@react-native-firebase/auth";
+import {useAuthContext} from "@/contexts/AuthContext";
export const useLoginWithQrCode = () => {
+ const {setRedirectOverride} = useAuthContext()
+
return useMutation({
mutationKey: ["loginWithQrCode"],
mutationFn: async ({userId}: { userId: string }) => {
try {
+ setRedirectOverride(true)
const res = await functions().httpsCallable("generateCustomToken")({userId}) as FirebaseFunctionsTypes.HttpsCallableResult<{
token: string
}>
diff --git a/hooks/firebase/useSignUp.ts b/hooks/firebase/useSignUp.ts
index 2464cd0..4326297 100644
--- a/hooks/firebase/useSignUp.ts
+++ b/hooks/firebase/useSignUp.ts
@@ -1,44 +1,47 @@
-import { useMutation } from "react-query";
+import {useMutation} from "react-query";
import auth from "@react-native-firebase/auth";
-import { ProfileType } from "@/contexts/AuthContext";
-import { useSetUserData } from "./useSetUserData";
+import {ProfileType, useAuthContext} from "@/contexts/AuthContext";
+import {useSetUserData} from "./useSetUserData";
import {uuidv4} from "@firebase/util";
import * as Localization from "expo-localization";
export const useSignUp = () => {
- const { mutateAsync: setUserData } = useSetUserData();
+ const {setRedirectOverride} = useAuthContext()
+ const {mutateAsync: setUserData} = useSetUserData();
- return useMutation({
- mutationKey: ["signUp"],
- mutationFn: async ({
- email,
- password,
- firstName,
- lastName,
- }: {
- email: string;
- password: string;
- firstName: string;
- lastName: string;
- }) => {
- await auth()
- .createUserWithEmailAndPassword(email, password)
- .then(async (res) => {
- try {
- await setUserData({
- newUserData: {
- userType: ProfileType.PARENT,
- firstName: firstName,
- lastName: lastName,
- familyId: uuidv4(),
- timeZone: Localization.getCalendars()[0].timeZone,
- },
- customUser: res.user,
- });
- } catch (error) {
- console.error(error);
- }
- });
- },
- });
+ return useMutation({
+ mutationKey: ["signUp"],
+ mutationFn: async ({
+ email,
+ password,
+ firstName,
+ lastName,
+ }: {
+ email: string;
+ password: string;
+ firstName: string;
+ lastName: string;
+ }) => {
+ setRedirectOverride(true)
+
+ await auth()
+ .createUserWithEmailAndPassword(email, password)
+ .then(async (res) => {
+ try {
+ await setUserData({
+ newUserData: {
+ userType: ProfileType.PARENT,
+ firstName: firstName,
+ lastName: lastName,
+ familyId: uuidv4(),
+ timeZone: Localization.getCalendars()[0].timeZone,
+ },
+ customUser: res.user,
+ });
+ } catch (error) {
+ console.error(error);
+ }
+ });
+ },
+ });
};
diff --git a/hooks/firebase/useUpdateFeedback.ts b/hooks/firebase/useUpdateFeedback.ts
new file mode 100644
index 0000000..aa7260c
--- /dev/null
+++ b/hooks/firebase/useUpdateFeedback.ts
@@ -0,0 +1,65 @@
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useMutation, useQueryClient } from "react-query";
+import firestore from "@react-native-firebase/firestore";
+import { IFeedback } from "@/contexts/FeedbackContext";
+
+interface UpdateFeedbackParams {
+ id: number;
+ changes: Partial;
+}
+
+export const useUpdateFeedback = () => {
+ const { user: currentUser } = useAuthContext();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["updateFeedback"],
+ mutationFn: async ({ id, changes }: UpdateFeedbackParams) => {
+ try {
+ const snapshot = await firestore()
+ .collection("Feedbacks")
+ .where("id", "==", id)
+ .get();
+
+ if (snapshot.empty) {
+ throw new Error("Feedback not found");
+ }
+
+ const docId = snapshot.docs[0].id;
+ const feedbackData = snapshot.docs[0].data();
+
+ if (feedbackData.creatorId !== currentUser?.uid) {
+ throw new Error(
+ "Unauthorized: You can only update your own feedback"
+ );
+ }
+
+ await firestore()
+ .collection("Feedbacks")
+ .doc(docId)
+ .update({
+ ...changes,
+ updatedAt: firestore.FieldValue.serverTimestamp(),
+ lastModifiedBy: currentUser?.uid,
+ });
+
+ return {
+ id,
+ ...feedbackData,
+ ...changes,
+ };
+ } catch (e) {
+ console.error("Error updating feedback: ", e);
+ throw e;
+ }
+ },
+ onSuccess: (updatedFeedback) => {
+ queryClient.invalidateQueries("feedbacks");
+
+ queryClient.setQueryData(
+ ["feedback", updatedFeedback.id],
+ updatedFeedback
+ );
+ },
+ });
+};
diff --git a/hooks/firebase/useUpdateNote.ts b/hooks/firebase/useUpdateNote.ts
new file mode 100644
index 0000000..fa1035f
--- /dev/null
+++ b/hooks/firebase/useUpdateNote.ts
@@ -0,0 +1,65 @@
+import { useAuthContext } from "@/contexts/AuthContext";
+import { useMutation, useQueryClient } from "react-query";
+import firestore from "@react-native-firebase/firestore";
+import { IBrainDump } from "@/contexts/DumpContext";
+
+interface UpdateNoteParams {
+ id: number;
+ changes: Partial;
+}
+
+export const useUpdateNote = () => {
+ const { user: currentUser } = useAuthContext();
+ const queryClient = useQueryClient();
+
+ return useMutation({
+ mutationKey: ["updateNote"],
+ mutationFn: async ({ id, changes }: UpdateNoteParams) => {
+ try {
+ const snapshot = await firestore()
+ .collection("BrainDumps")
+ .where("id", "==", id)
+ .get();
+
+ if (snapshot.empty) {
+ throw new Error("Note not found");
+ }
+
+ const docId = snapshot.docs[0].id;
+ const noteData = snapshot.docs[0].data();
+
+ if (noteData.creatorId !== currentUser?.uid) {
+ throw new Error(
+ "Unauthorized: You can only update your own note"
+ );
+ }
+
+ await firestore()
+ .collection("BrainDumps")
+ .doc(docId)
+ .update({
+ ...changes,
+ updatedAt: firestore.FieldValue.serverTimestamp(),
+ lastModifiedBy: currentUser?.uid,
+ });
+
+ return {
+ id,
+ ...noteData,
+ ...changes,
+ };
+ } catch (e) {
+ console.error("Error updating note: ", e);
+ throw e;
+ }
+ },
+ onSuccess: (updatedNote) => {
+ queryClient.invalidateQueries("braindumps");
+
+ queryClient.setQueryData(
+ ["feedback", updatedNote.id],
+ updatedNote
+ );
+ },
+ });
+};
diff --git a/hooks/firebase/useUpdateTodo.ts b/hooks/firebase/useUpdateTodo.ts
index b1b985f..35b254c 100644
--- a/hooks/firebase/useUpdateTodo.ts
+++ b/hooks/firebase/useUpdateTodo.ts
@@ -1,18 +1,153 @@
import { useMutation, useQueryClient } from "react-query";
import firestore from "@react-native-firebase/firestore";
-import { IToDo } from "@/hooks/firebase/types/todoData";
+import {IToDo, REPEAT_TYPE} from "@/hooks/firebase/types/todoData";
+import {addDays, addMonths, addWeeks, addYears, compareAsc, format, subDays} from "date-fns";
+import {daysOfWeek, resolveTodoAlternatingAssignees} from "@/hooks/firebase/useCreateTodo";
+import {useAuthContext} from "@/contexts/AuthContext";
export const useUpdateTodo = () => {
+ const { user: currentUser, profileData } = useAuthContext();
const queryClients = useQueryClient()
return useMutation({
mutationKey: ["updateTodo"],
mutationFn: async (todoData: Partial) => {
try {
- await firestore()
- .collection("Todos")
- .doc(todoData.id)
- .update(todoData);
+ if (todoData.connectedTodoId) {
+ console.log("CONNECTED")
+ const snapshot = await firestore()
+ .collection("Todos")
+ .where("connectedTodoId", "==", todoData.connectedTodoId)
+ .get();
+ const connectedTodos = snapshot.docs.map((doc) => {
+ const data = doc.data();
+
+ return {
+ ...data,
+ id: doc.id,
+ date: data.date ? new Date(data.date.seconds * 1000) : null,
+ ref: doc.ref
+ };
+ }) as IToDo[];
+
+ console.log("CONNECTED TODO");
+ let filteredTodos = connectedTodos?.filter((item) => compareAsc(format(item.date, 'yyyy-MM-dd'), format(todoData.date, 'yyyy-MM-dd')) === 1 ||
+ compareAsc(format(item.date, 'yyyy-MM-dd'), format(todoData.date, 'yyyy-MM-dd')) === 0).sort((a,b) =>{
+ return b.date?.getSeconds() - a.date?.getSeconds();
+ });
+
+ let firstTodo = filteredTodos?.[0];
+ const batch = firestore().batch();
+ if (compareAsc(format(firstTodo?.date, 'yyyy-MM-dd'), format(todoData.date, 'yyyy-MM-dd')) !== 0 || firstTodo?.repeatType !== todoData.repeatType) {
+
+ console.log("DELETE");
+ filteredTodos?.forEach((item) => {
+ batch.delete(item.ref);
+ });
+
+ if (todoData.repeatType === REPEAT_TYPE.NONE) {
+
+ console.log("NONE");
+ const newDoc = firestore().collection('Todos').doc();
+ let originalTodo = {...todoData, id: newDoc.id, familyId: profileData?.familyId, creatorId: currentUser?.uid}
+ batch.set(newDoc, originalTodo);
+ } else if (todoData.repeatType === REPEAT_TYPE.EVERY_WEEK) {
+
+ console.log("EVERY WEEK");
+ let date = todoData?.date;
+ let repeatDays = todoData?.repeatDays;
+ const dates = [];
+
+ const originalDateDay = format(date, 'EEEE');
+ const originalNumber = daysOfWeek.indexOf(originalDateDay);
+ repeatDays?.forEach((day) => {
+ let number = daysOfWeek.indexOf(day);
+ let newDate;
+ if (originalNumber > number) {
+ let diff = originalNumber - number;
+ newDate = subDays(date, diff);
+ } else {
+ let diff = number - originalNumber;
+ newDate = addDays(date, diff);
+ }
+ dates.push(newDate);
+ });
+
+ let todosToAddCycles = 4;
+ if (firstTodo?.repeatType === REPEAT_TYPE.EVERY_WEEK) {
+ todosToAddCycles = filteredTodos?.length / firstTodo?.repeatDays?.length;
+ }
+ console.log(todosToAddCycles);
+ let newDoc = firestore().collection("Todos").doc();
+ let originalTodo = { ...todoData, id: newDoc.id, date: todoData.date, connectedTodoId: newDoc?.id };
+ originalTodo = resolveTodoAlternatingAssignees(todoData, originalTodo, 0);
+ batch.set(newDoc, originalTodo);
+
+ console.log(dates);
+ let index = 1;
+ for (let i = 0; i <= todosToAddCycles; i++) {
+ dates?.forEach((dateToAdd) => {
+ index++;
+ let newTodoDate = addWeeks(dateToAdd, i);
+ if (compareAsc(newTodoDate, originalTodo.date) !== 0) {
+
+ let docRef = firestore().collection("Todos").doc();
+ let newTodo = { ...todoData, id: docRef.id, date: newTodoDate, connectedTodoId: newDoc?.id };
+ newTodo = resolveTodoAlternatingAssignees(todoData, newTodo, index);
+
+ batch.set(docRef, newTodo);
+ }
+ })
+ }
+ } else if (todoData.repeatType === REPEAT_TYPE.ONCE_A_MONTH) {
+
+ console.log("ONCE A MONTH");
+ // for the next 12 months
+ for (let i = 0; i < 12; i++) {
+ let date = todoData?.date;
+ const nextMonth = addMonths(date, i);
+
+ let docRef = firestore().collection("Todos").doc();
+ let newTodo = { ...todoData, id: docRef.id, date: nextMonth, connectedTodoId: firstTodo?.connectedTodoId };
+ newTodo = resolveTodoAlternatingAssignees(todoData, newTodo, i);
+ batch.set(docRef, newTodo);
+ }
+ } else if (todoData.repeatType === REPEAT_TYPE.ONCE_A_YEAR) {
+
+ console.log("ONCE A YEAR");
+ // for the next 5 years
+ for (let i = 0; i < 5; i++) {
+ let date = todoData?.date;
+ const nextMonth = addYears(date, i);
+
+ let docRef = firestore().collection("Todos").doc();
+ let newTodo = { ...todoData, id: docRef.id, date: nextMonth, connectedTodoId: firstTodo?.connectedTodoId };
+ newTodo = resolveTodoAlternatingAssignees(todoData, newTodo, i);
+ batch.set(docRef, newTodo);
+ }
+ }
+
+ await batch.commit();
+ } else if (firstTodo?.repeatDays !== todoData.repeatDays) {
+
+ console.log("UPDATE REPEAT DAYS");
+ await updateRepeatDaysTodos(batch, todoData, firstTodo, filteredTodos)
+ } else {
+ filteredTodos?.forEach((item) => {
+
+ console.log("UPDATE");
+ batch.update(item.ref, {...todoData, date: item.date});
+ })
+ await batch.commit();
+ }
+ } else {
+ console.log("REGULAR UPDATE");
+ console.log(todoData);
+ await firestore()
+ .collection("Todos")
+ .doc(todoData.id)
+ .update(todoData);
+ }
} catch (e) {
console.error(e)
}
@@ -21,4 +156,62 @@ export const useUpdateTodo = () => {
queryClients.invalidateQueries("todos")
}
})
+}
+
+const updateRepeatDaysTodos = async (batch: any, todoData: IToDo, firstTodo: IToDo, filteredTodos: IToDo[]) => {
+ const todosToAddCycles = filteredTodos?.length / firstTodo?.repeatDays?.length;
+ console.log(todosToAddCycles);
+
+ filteredTodos?.forEach((item) => {
+
+ batch.update(item.ref, {...todoData, date: item.date});
+ })
+
+ let newRepeatDays = todoData.repeatDays?.filter((element) => firstTodo?.repeatDays?.indexOf(element) === -1);
+ let removeRepeatDays = firstTodo?.repeatDays?.filter((element) => todoData?.repeatDays?.indexOf(element) === -1);
+ const dates = [];
+
+ let date = firstTodo?.date;
+ const originalDateDay = format(date, 'EEEE');
+ const originalNumber = daysOfWeek.indexOf(originalDateDay);
+ newRepeatDays?.forEach((day) => {
+ let number = daysOfWeek.indexOf(day);
+ let newDate;
+ if (originalNumber > number) {
+ let diff = originalNumber - number;
+ newDate = subDays(date, diff);
+ } else {
+ let diff = number - originalNumber;
+ newDate = addDays(date, diff);
+ }
+ dates.push(newDate);
+ });
+
+ let index = 0;
+ for (let i = 0; i < todosToAddCycles; i++) {
+ dates?.forEach((dateToAdd) => {
+ index ++;
+ let newTodoDate = addWeeks(dateToAdd, i);
+ if (compareAsc(newTodoDate, firstTodo?.date) !== 0) {
+ let newTodo = {...todoData, date: newTodoDate};
+ newTodo = resolveTodoAlternatingAssignees(todoData, newTodo, index);
+
+ let docRef = firestore().collection("Todos").doc();
+ batch.set(docRef, newTodo);
+ }
+ })
+ }
+
+ removeRepeatDays?.forEach((removeDay) => {
+ filteredTodos?.forEach((item) => {
+
+ let todoDate = item.date;
+ const todoDateDay = format(todoDate, 'EEEE');
+
+ if (todoDateDay === removeDay) {
+ batch.delete(item.ref);
+ }
+ })
+ })
+ await batch.commit();
}
\ No newline at end of file
diff --git a/hooks/useCalSync.ts b/hooks/useCalSync.ts
new file mode 100644
index 0000000..76c2dff
--- /dev/null
+++ b/hooks/useCalSync.ts
@@ -0,0 +1,288 @@
+import {useAuthContext} from "@/contexts/AuthContext";
+import {useEffect} from "react";
+import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
+import {useFetchAndSaveGoogleEvents} from "@/hooks/useFetchAndSaveGoogleEvents";
+import {useFetchAndSaveOutlookEvents} from "@/hooks/useFetchAndSaveOutlookEvents";
+import {useFetchAndSaveAppleEvents} from "@/hooks/useFetchAndSaveAppleEvents";
+import * as WebBrowser from "expo-web-browser";
+import * as Google from "expo-auth-session/providers/google";
+import * as AuthSession from "expo-auth-session";
+import * as AppleAuthentication from "expo-apple-authentication";
+
+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",
+ ],
+ extraParams: {
+ access_type: "offline",
+ },
+};
+
+const microsoftConfig = {
+ 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",
+};
+
+export const useCalSync = () => {
+ const {profileData} = useAuthContext();
+
+ const {mutateAsync: updateUserData} = useUpdateUserData();
+ const {mutateAsync: fetchAndSaveGoogleEvents, isLoading: isSyncingGoogle} =
+ useFetchAndSaveGoogleEvents();
+ const {
+ mutateAsync: fetchAndSaveOutlookEvents,
+ isLoading: isSyncingOutlook,
+ } = useFetchAndSaveOutlookEvents();
+ const {mutateAsync: fetchAndSaveAppleEvents, isLoading: isSyncingApple} =
+ useFetchAndSaveAppleEvents();
+
+ WebBrowser.maybeCompleteAuthSession();
+ const [_, response, promptAsync] = Google.useAuthRequest(googleConfig);
+
+ useEffect(() => {
+ signInWithGoogle();
+ }, [response]);
+
+ const signInWithGoogle = async () => {
+ try {
+ if (response?.type === "success") {
+ const {accessToken, refreshToken} = response?.authentication!;
+
+ const userInfoResponse = await fetch(
+ "https://www.googleapis.com/oauth2/v3/userinfo",
+ {
+ headers: {Authorization: `Bearer ${accessToken}`},
+ }
+ );
+
+ const userInfo = await userInfoResponse.json();
+ const googleMail = userInfo.email;
+
+ let googleAccounts = profileData?.googleAccounts || {};
+ const updatedGoogleAccounts = {
+ ...googleAccounts,
+ [googleMail]: {accessToken, refreshToken},
+ };
+
+ await updateUserData({
+ newUserData: {googleAccounts: updatedGoogleAccounts},
+ });
+
+ await fetchAndSaveGoogleEvents({
+ token: accessToken,
+ email: 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;
+
+ let microsoftAccounts = profileData?.microsoftAccounts;
+ const updatedMicrosoftAccounts = microsoftAccounts
+ ? {...microsoftAccounts, [outlookMail]: tokenData.access_token}
+ : {[outlookMail]: tokenData.access_token};
+
+ await updateUserData({
+ newUserData: {microsoftAccounts: updatedMicrosoftAccounts},
+ });
+
+ await fetchAndSaveOutlookEvents(
+ tokenData.access_token,
+ 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 handleAppleSignIn = async () => {
+ try {
+ console.log("Starting Apple Sign-in...");
+
+ const credential = await AppleAuthentication.signInAsync({
+ requestedScopes: [
+ AppleAuthentication.AppleAuthenticationScope.EMAIL,
+ AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
+ ],
+ });
+
+ console.log("Apple sign-in result:", credential);
+
+ alert(JSON.stringify(credential))
+
+ const appleToken = credential.identityToken;
+ const appleMail = credential.email!;
+
+
+ if (appleToken) {
+ console.log("Apple ID token received. Fetch user info if needed...");
+
+ let appleAcounts = profileData?.appleAccounts;
+ const updatedAppleAccounts = appleAcounts
+ ? {...appleAcounts, [appleMail]: appleToken}
+ : {[appleMail]: appleToken};
+
+ await updateUserData({
+ newUserData: {appleAccounts: updatedAppleAccounts},
+ });
+
+ console.log("User data updated with Apple ID token.");
+ await fetchAndSaveAppleEvents({token: appleToken, email: appleMail!});
+ } else {
+ console.warn(
+ "Apple authentication was not successful or email was hidden."
+ );
+ }
+ } catch (error) {
+ console.error("Error during Apple Sign-in:", error);
+ }
+ };
+
+
+ let isConnectedToGoogle = false;
+ if (profileData?.googleAccounts) {
+ Object.values(profileData?.googleAccounts).forEach((item) => {
+ if (item !== null) {
+ isConnectedToGoogle = true;
+ return;
+ }
+ });
+ }
+
+ let isConnectedToMicrosoft = false;
+ const microsoftAccounts = profileData?.microsoftAccounts;
+ if (microsoftAccounts) {
+ Object.values(profileData?.microsoftAccounts).forEach((item) => {
+ if (item !== null) {
+ isConnectedToMicrosoft = true;
+ return;
+ }
+ });
+ }
+
+ let isConnectedToApple = false;
+ if (profileData?.appleAccounts) {
+ Object.values(profileData?.appleAccounts).forEach((item) => {
+ if (item !== null) {
+ isConnectedToApple = true;
+ return;
+ }
+ });
+ }
+
+
+ return {
+ handleAppleSignIn,
+ handleMicrosoftSignIn,
+ handleGoogleSignIn: signInWithGoogle,
+ handleStartGoogleSignIn: promptAsync,
+ fetchAndSaveOutlookEvents,
+ fetchAndSaveAppleEvents,
+ fetchAndSaveGoogleEvents,
+ isConnectedToApple,
+ isConnectedToMicrosoft,
+ isConnectedToGoogle,
+ isSyncingOutlook,
+ isSyncingGoogle,
+ isSyncingApple
+ }
+}
\ No newline at end of file
diff --git a/hooks/useFetchAndSaveAppleEvents.ts b/hooks/useFetchAndSaveAppleEvents.ts
index b0f1783..85f1cf7 100644
--- a/hooks/useFetchAndSaveAppleEvents.ts
+++ b/hooks/useFetchAndSaveAppleEvents.ts
@@ -1,9 +1,10 @@
-import {useMutation} from "react-query";
+import {useMutation, useQueryClient} from "react-query";
import {useAuthContext} from "@/contexts/AuthContext";
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
import {fetchiPhoneCalendarEvents} from "@/calendar-integration/apple-calendar-utils";
export const useFetchAndSaveAppleEvents = () => {
+ const queryClient = useQueryClient()
const {profileData} = useAuthContext();
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
@@ -29,5 +30,8 @@ export const useFetchAndSaveAppleEvents = () => {
throw error;
}
},
+ onSuccess: () => {
+ queryClient.invalidateQueries(["events"])
+ },
});
};
\ No newline at end of file
diff --git a/hooks/useFetchAndSaveGoogleEvents.ts b/hooks/useFetchAndSaveGoogleEvents.ts
index 7f823a0..b96c0f9 100644
--- a/hooks/useFetchAndSaveGoogleEvents.ts
+++ b/hooks/useFetchAndSaveGoogleEvents.ts
@@ -1,11 +1,14 @@
-import {useMutation} from "react-query";
+import {useMutation, useQueryClient} from "react-query";
import {fetchGoogleCalendarEvents} from "@/calendar-integration/google-calendar-utils";
import {useAuthContext} from "@/contexts/AuthContext";
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
+import {useClearTokens} from "@/hooks/firebase/useClearTokens";
export const useFetchAndSaveGoogleEvents = () => {
+ const queryClient = useQueryClient()
const {profileData} = useAuthContext();
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
+ const {mutateAsync: clearToken} = useClearTokens();
return useMutation({
mutationKey: ["fetchAndSaveGoogleEvents"],
@@ -25,9 +28,14 @@ export const useFetchAndSaveGoogleEvents = () => {
timeMax.toISOString().slice(0, -5) + "Z"
);
+ if(!response.success) {
+ await clearToken({email: email!, provider: "google"})
+ return
+ }
+
console.log("Google Calendar events fetched:", response);
- const items = response?.map((item) => {
+ const items = response?.googleEvents?.map((item) => {
if (item.allDay) {
item.startDate = new Date(new Date(item.startDate).setHours(0, 0, 0, 0));
item.endDate = item.startDate;
@@ -41,5 +49,8 @@ export const useFetchAndSaveGoogleEvents = () => {
throw error; // Ensure errors are propagated to the mutation
}
},
+ onSuccess: () => {
+ queryClient.invalidateQueries(["events"])
+ },
});
};
\ No newline at end of file
diff --git a/hooks/useFetchAndSaveOutlookEvents.ts b/hooks/useFetchAndSaveOutlookEvents.ts
index 36540bb..5d5f6a3 100644
--- a/hooks/useFetchAndSaveOutlookEvents.ts
+++ b/hooks/useFetchAndSaveOutlookEvents.ts
@@ -1,9 +1,10 @@
-import {useMutation} from "react-query";
+import {useMutation, useQueryClient} from "react-query";
import {useAuthContext} from "@/contexts/AuthContext";
import {useCreateEventsFromProvider} from "@/hooks/firebase/useCreateEvent";
import {fetchMicrosoftCalendarEvents} from "@/calendar-integration/microsoft-calendar-utils";
export const useFetchAndSaveOutlookEvents = () => {
+ const queryClient = useQueryClient()
const {profileData} = useAuthContext();
const {mutateAsync: createEventsFromProvider} = useCreateEventsFromProvider();
@@ -32,5 +33,8 @@ export const useFetchAndSaveOutlookEvents = () => {
throw error;
}
},
+ onSuccess: () => {
+ queryClient.invalidateQueries(["events"])
+ },
});
};
\ No newline at end of file
diff --git a/hooks/useUploadProfilePicture.ts b/hooks/useUploadProfilePicture.ts
new file mode 100644
index 0000000..5c452b6
--- /dev/null
+++ b/hooks/useUploadProfilePicture.ts
@@ -0,0 +1,62 @@
+import {useState} from "react";
+import * as ImagePicker from "expo-image-picker";
+import {useChangeProfilePicture} from "@/hooks/firebase/useChangeProfilePicture";
+import {useUpdateUserData} from "@/hooks/firebase/useUpdateUserData";
+
+export const useUploadProfilePicture = (customUserId?: string, existingPfp?: string) => {
+ const [profileImage, setProfileImage] = useState<
+ string | ImagePicker.ImagePickerAsset | null
+ >(existingPfp || null);
+
+ const [profileImageAsset, setProfileImageAsset] = useState<
+ ImagePicker.ImagePickerAsset | null
+ >(null);
+
+ const {mutateAsync: updateUserData} = useUpdateUserData();
+ const {mutateAsync: changeProfilePicture} = useChangeProfilePicture(customUserId);
+
+ const pickImage = async () => {
+ const permissionResult =
+ await ImagePicker.requestMediaLibraryPermissionsAsync();
+ if (!permissionResult.granted) {
+ alert("Permission to access camera roll is required!");
+ return;
+ }
+
+ const result = await ImagePicker.launchImageLibraryAsync({
+ mediaTypes: ImagePicker.MediaTypeOptions.Images,
+ allowsEditing: true,
+ aspect: [1, 1],
+ quality: 1,
+ });
+
+ if (!result.canceled) {
+ setProfileImage(result.assets[0].uri);
+ setProfileImageAsset(result.assets[0]);
+ if (!customUserId) {
+ await changeProfilePicture(result.assets[0]);
+ }
+ }
+ };
+
+ const handleClearImage = async () => {
+ if (!customUserId) {
+ await updateUserData({newUserData: {pfp: null}});
+ }
+ setProfileImage(null);
+ };
+
+ const pfpUri =
+ profileImage && typeof profileImage === "object" && "uri" in profileImage
+ ? profileImage.uri
+ : profileImage;
+
+ return {
+ pfpUri,
+ profileImage,
+ profileImageAsset,
+ handleClearImage,
+ pickImage,
+ changeProfilePicture
+ }
+}
\ No newline at end of file
diff --git a/ios/cally.xcodeproj/project.pbxproj b/ios/cally.xcodeproj/project.pbxproj
index ef5c283..ca73519 100644
--- a/ios/cally.xcodeproj/project.pbxproj
+++ b/ios/cally.xcodeproj/project.pbxproj
@@ -450,7 +450,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = "Cally";
+ PRODUCT_NAME = Cally;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -484,7 +484,7 @@
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
PRODUCT_BUNDLE_IDENTIFIER = com.cally.app;
- PRODUCT_NAME = "Cally";
+ PRODUCT_NAME = Cally;
SWIFT_OBJC_BRIDGING_HEADER = "cally/cally-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/ios/cally/Info.plist b/ios/cally/Info.plist
index 468c1d5..046ab16 100644
--- a/ios/cally/Info.plist
+++ b/ios/cally/Info.plist
@@ -47,7 +47,7 @@
CFBundleVersion
- 40
+ 60
LSRequiresIPhoneOS
NSAppTransportSecurity
@@ -118,6 +118,24 @@
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
$(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
+ $(PRODUCT_BUNDLE_IDENTIFIER).expo.index_route
UILaunchStoryboardName
SplashScreen
diff --git a/package-lock.json b/package-lock.json
index 150ac14..2df2850 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,7 @@
"": {
"name": "cally",
"version": "1.0.0",
+ "hasInstallScript": true,
"dependencies": {
"@expo-google-fonts/manrope": "^0.2.3",
"@expo-google-fonts/plus-jakarta-sans": "^0.2.3",
@@ -50,12 +51,14 @@
"firebase-functions": "^5.1.0",
"fuzzysort": "^3.0.2",
"jotai": "^2.9.1",
+ "patch-package": "^8.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.74.3",
"react-native-app-auth": "^8.0.0",
- "react-native-big-calendar": "^4.14.0",
+ "react-native-big-calendar": "^4.15.1",
"react-native-calendars": "^1.1306.0",
+ "react-native-element-dropdown": "^2.12.2",
"react-native-gesture-handler": "~2.16.1",
"react-native-gifted-charts": "^1.4.41",
"react-native-keyboard-manager": "^6.5.16-0",
@@ -82,6 +85,7 @@
"@types/react-test-renderer": "^18.0.7",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
+ "postinstall-postinstall": "^2.1.0",
"react-test-renderer": "18.2.0",
"typescript": "~5.3.3"
}
@@ -6013,6 +6017,12 @@
"node": ">=10.0.0"
}
},
+ "node_modules/@yarnpkg/lockfile": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
+ "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
@@ -13185,6 +13195,30 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
+ "node_modules/json-stable-stringify": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz",
+ "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.5",
+ "isarray": "^2.0.5",
+ "jsonify": "^0.0.1",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/json-stable-stringify/node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "license": "MIT"
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -13209,6 +13243,15 @@
"graceful-fs": "^4.1.6"
}
},
+ "node_modules/jsonify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
+ "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
+ "license": "Public Domain",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
@@ -13313,6 +13356,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/klaw-sync": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+ "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.1.11"
+ }
+ },
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -15060,6 +15112,74 @@
"cross-spawn": "^7.0.3"
}
},
+ "node_modules/patch-package": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
+ "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@yarnpkg/lockfile": "^1.1.0",
+ "chalk": "^4.1.2",
+ "ci-info": "^3.7.0",
+ "cross-spawn": "^7.0.3",
+ "find-yarn-workspace-root": "^2.0.0",
+ "fs-extra": "^9.0.0",
+ "json-stable-stringify": "^1.0.2",
+ "klaw-sync": "^6.0.0",
+ "minimist": "^1.2.6",
+ "open": "^7.4.2",
+ "rimraf": "^2.6.3",
+ "semver": "^7.5.3",
+ "slash": "^2.0.0",
+ "tmp": "^0.0.33",
+ "yaml": "^2.2.2"
+ },
+ "bin": {
+ "patch-package": "index.js"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">5"
+ }
+ },
+ "node_modules/patch-package/node_modules/open": {
+ "version": "7.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
+ "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0",
+ "is-wsl": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/patch-package/node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/patch-package/node_modules/slash": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+ "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -15295,6 +15415,14 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
+ "node_modules/postinstall-postinstall": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz",
+ "integrity": "sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT"
+ },
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@@ -15908,13 +16036,13 @@
"license": "MIT"
},
"node_modules/react-native-big-calendar": {
- "version": "4.14.0",
- "resolved": "https://registry.npmjs.org/react-native-big-calendar/-/react-native-big-calendar-4.14.0.tgz",
- "integrity": "sha512-EYCxqXnRAg8QWsW3Npq3JI/9+lXlo9o6Gri7WttQQSqE/cGkVrVeKXObpvN6Cc4qrIUvnc4cgLAeM/j4+bOb6g==",
+ "version": "4.15.1",
+ "resolved": "https://registry.npmjs.org/react-native-big-calendar/-/react-native-big-calendar-4.15.1.tgz",
+ "integrity": "sha512-hNrzkM+9Kb2T0J/1fW9AMaeN+AuhakCfNtQPaQL29l3JXgOO14ikJ3iPqQkmNVbuiWYiMrpI25hrmXffiOVIgQ==",
"license": "MIT",
"dependencies": {
"calendarize": "^1.1.1",
- "dayjs": "^1.11.10"
+ "dayjs": "^1.11.13"
},
"peerDependencies": {
"react": "*",
@@ -15939,6 +16067,22 @@
"moment": "^2.29.4"
}
},
+ "node_modules/react-native-element-dropdown": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.2.tgz",
+ "integrity": "sha512-Tf8hfRuniYEXo+LGoVgIMoItKWuPLX6jbqlwAFgMbBhmWGTuV+g1OVOAx/ny16kgnwp+NhgJoWpxhVvr7HSmXA==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.21"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ }
+ },
"node_modules/react-native-gesture-handler": {
"version": "2.16.2",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.16.2.tgz",
diff --git a/package.json b/package.json
index f05e3e3..f05d490 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
"prebuild": "npx expo prebuild -p ios",
"prebuild-build-submit-ios": "yarn run prebuild && yarn run build-ios && yarn run submit",
"prebuild-build-submit-ios-cicd": "yarn build-ios-cicd",
- "prebuild-build-submit-cicd": "yarn build-cicd"
+ "prebuild-build-submit-cicd": "yarn build-cicd",
+ "postinstall": "patch-package"
},
"jest": {
"preset": "jest-expo"
@@ -49,6 +50,7 @@
"expo-auth-session": "^5.5.2",
"expo-barcode-scanner": "~13.0.1",
"expo-build-properties": "~0.12.4",
+ "expo-cached-image": "^51.0.19",
"expo-calendar": "~13.0.5",
"expo-camera": "~15.0.16",
"expo-constants": "~16.0.2",
@@ -70,11 +72,12 @@
"firebase-functions": "^5.1.0",
"fuzzysort": "^3.0.2",
"jotai": "^2.9.1",
+ "patch-package": "^8.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.74.3",
"react-native-app-auth": "^8.0.0",
- "react-native-big-calendar": "^4.14.0",
+ "react-native-big-calendar": "^4.15.1",
"react-native-calendars": "^1.1306.0",
"react-native-element-dropdown": "^2.12.2",
"react-native-gesture-handler": "~2.16.1",
@@ -103,6 +106,7 @@
"@types/react-test-renderer": "^18.0.7",
"jest": "^29.2.1",
"jest-expo": "~51.0.3",
+ "postinstall-postinstall": "^2.1.0",
"react-test-renderer": "18.2.0",
"typescript": "~5.3.3"
},
diff --git a/patches/react-native-big-calendar+4.15.1.patch b/patches/react-native-big-calendar+4.15.1.patch
new file mode 100644
index 0000000..9173bde
--- /dev/null
+++ b/patches/react-native-big-calendar+4.15.1.patch
@@ -0,0 +1,186 @@
+diff --git a/node_modules/react-native-big-calendar/build/index.js b/node_modules/react-native-big-calendar/build/index.js
+index 848ceba..57fbaed 100644
+--- a/node_modules/react-native-big-calendar/build/index.js
++++ b/node_modules/react-native-big-calendar/build/index.js
+@@ -9,6 +9,17 @@ var isoWeek = require('dayjs/plugin/isoWeek');
+ var React = require('react');
+ var reactNative = require('react-native');
+ var calendarize = require('calendarize');
++var {
++ startOfDay,
++ endOfDay,
++ startOfWeek,
++ isAfter,
++ isBefore,
++ isSameDay,
++ differenceInDays,
++ add,
++ getTime
++} = require('date-fns');
+
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
+
+@@ -1000,87 +1011,91 @@ function _CalendarBodyForMonthView(_a) {
+ var start = _a.start, end = _a.end;
+ return day.isBetween(dayjs__default["default"](start).startOf('day'), dayjs__default["default"](end).endOf('day'), null, '[)');
+ });
+- }
+- else {
+- /**
+- * Better way to sort overlapping events that spans accross multiple days
+- * For example, if you want following events
+- * Event 1, start = 01/01 12:00, end = 02/01 12:00
+- * Event 2, start = 02/01 12:00, end = 03/01 12:00
+- * Event 3, start = 03/01 12:00, end = 04/01 12:00
+- *
+- * When drawing calendar in month view, event 3 should be placed at 3rd index for 03/01, because Event 2 are placed at 2nd index for 02/01 and 03/01
+- *
+- */
+- var min_1 = day.startOf('day'), max_1 = day.endOf('day');
+- //filter all events that starts from the current week until the current day, and sort them by reverse starting time
+- var filteredEvents_1 = events
+- .filter(function (_a) {
+- var start = _a.start, end = _a.end;
+- return dayjs__default["default"](end).isAfter(day.startOf('week')) && dayjs__default["default"](start).isBefore(max_1);
+- })
+- .sort(function (a, b) {
+- if (dayjs__default["default"](a.start).isSame(b.start, 'day')) {
+- var aDuration = dayjs__default["default"].duration(dayjs__default["default"](a.end).diff(dayjs__default["default"](a.start))).days();
+- var bDuration = dayjs__default["default"].duration(dayjs__default["default"](b.end).diff(dayjs__default["default"](b.start))).days();
+- return aDuration - bDuration;
+- }
+- return b.start.getTime() - a.start.getTime();
++ } else {
++ // Convert day once and cache all the commonly used dates/times
++ const jsDay = day?.toDate?.() || day;
++ const weekStart = startOfWeek(jsDay);
++ var min_1 = startOfDay(jsDay);
++ var max_1 = endOfDay(jsDay);
++ const max1Time = getTime(max_1);
++ const min1Time = getTime(min_1);
++
++ // Pre-process events with dates and cache timestamps
++ const processedEvents = events.map(event => {
++ const startDay = event.start?.toDate?.() || new Date(event.start);
++ const endDay = event.end?.toDate?.() || new Date(event.end);
++ return {
++ ...event,
++ startDay,
++ endDay,
++ startTime: getTime(startDay),
++ endTime: getTime(endDay),
++ startDayStart: startOfDay(startDay)
++ };
+ });
+- /**
+- * find the most relevant min date to filter the events
+- * in the example:
+- * 1. when rendering for 01/01, min date will be 01/01 (start of day for event 1)
+- * 2. when rendering for 02/01, min date will be 01/01 (start of day for event 1)
+- * 3. when rendering for 03/01, min date will be 01/01 (start of day for event 1)
+- * 4. when rendering for 04/01, min date will be 01/01 (start of day for event 1)
+- * 5. when rendering for 05/01, min date will be 05/01 (no event overlaps with 05/01)
+- */
+- filteredEvents_1.forEach(function (_a) {
+- var start = _a.start, end = _a.end;
+- if (dayjs__default["default"](end).isAfter(min_1) && dayjs__default["default"](start).isBefore(min_1)) {
+- min_1 = dayjs__default["default"](start).startOf('day');
++
++ // Filter events within the weekly range and sort by reverse start time
++ let filteredEvents_1 = processedEvents
++ .filter(({ startTime, endTime }) =>
++ endTime > getTime(weekStart) && startTime < max1Time
++ )
++ .sort((a, b) => {
++ if (isSameDay(a.startDay, b.startDay)) {
++ // Pre-calculate durations since they're used in sorting
++ const aDuration = differenceInDays(a.endDay, a.startDay);
++ const bDuration = differenceInDays(b.endDay, b.startDay);
++ return bDuration - aDuration;
++ }
++ return b.startTime - a.startTime;
++ });
++
++ // Update min_1 to the earliest startDay for overlapping events
++ for (const event of filteredEvents_1) {
++ if (event.endTime > min1Time && event.startTime < min1Time) {
++ min_1 = event.startDayStart;
++ break; // We only need the first one due to the sort order
+ }
+- });
++ }
++
++ // Filter to keep only events that overlap the min to max range, then reverse
++ const min1TimeUpdated = getTime(min_1);
+ filteredEvents_1 = filteredEvents_1
+- .filter(function (_a) {
+- var start = _a.start, end = _a.end;
+- return dayjs__default["default"](end).endOf('day').isAfter(min_1) && dayjs__default["default"](start).isBefore(max_1);
+- })
++ .filter(({ startTime, endDay }) =>
++ getTime(endOfDay(endDay)) > min1TimeUpdated && startTime < max1Time
++ )
+ .reverse();
+- /**
+- * We move eligible event to the top
+- * For example, when rendering for 03/01, Event 3 should be moved to the top, since there is a gap left by Event 1
+- */
+- var finalEvents_1 = [];
+- var tmpDay_1 = day.startOf('week');
+- //re-sort events from the start of week until the calendar cell date
+- //optimize sorting of event nodes and make sure that no empty gaps are left on top of calendar cell
+- while (!tmpDay_1.isAfter(day)) {
+- filteredEvents_1.forEach(function (event) {
+- if (dayjs__default["default"](event.end).isBefore(tmpDay_1.startOf('day'))) {
+- var eventToMoveUp = filteredEvents_1.find(function (e) {
+- return dayjs__default["default"](e.start).startOf('day').isSame(tmpDay_1.startOf('day'));
+- });
+- if (eventToMoveUp != undefined) {
+- //remove eventToMoveUp from finalEvents first
+- if (finalEvents_1.indexOf(eventToMoveUp) > -1) {
+- finalEvents_1.splice(finalEvents_1.indexOf(eventToMoveUp), 1);
+- }
+- if (finalEvents_1.indexOf(event) > -1) {
+- finalEvents_1.splice(finalEvents_1.indexOf(event), 1, eventToMoveUp);
+- }
+- else {
++
++ // Move eligible events to the top, preventing duplicate entries
++ const finalEvents_1 = [];
++ const seenEvents = new Set(); // Use Set for faster lookups
++ let tmpDay_1 = weekStart;
++
++ while (!isAfter(tmpDay_1, jsDay)) {
++ const tmpDayTime = getTime(tmpDay_1);
++
++ for (const event of filteredEvents_1) {
++ const eventEndDayTime = getTime(startOfDay(event.endDay));
++
++ if (!seenEvents.has(event)) {
++ if (eventEndDayTime < tmpDayTime) {
++ // Find event starting on tmpDay
++ const eventToMoveUp = filteredEvents_1.find(e =>
++ isSameDay(e.startDayStart, tmpDay_1)
++ );
++ if (eventToMoveUp && !seenEvents.has(eventToMoveUp)) {
+ finalEvents_1.push(eventToMoveUp);
++ seenEvents.add(eventToMoveUp);
+ }
++ } else {
++ finalEvents_1.push(event);
++ seenEvents.add(event);
+ }
+ }
+- else if (finalEvents_1.indexOf(event) == -1) {
+- finalEvents_1.push(event);
+- }
+- });
+- tmpDay_1 = tmpDay_1.add(1, 'day');
++ }
++ tmpDay_1 = add(tmpDay_1, { days: 1 });
+ }
++
++ console.log(finalEvents_1);
+ return finalEvents_1;
+ }
+ }, [events, sortedMonthView]);
diff --git a/yarn.lock b/yarn.lock
index 4c0db75..031ff68 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -953,6 +953,89 @@
wrap-ansi "^7.0.0"
ws "^8.12.1"
+"@expo/cli@0.18.30":
+ version "0.18.30"
+ resolved "https://registry.yarnpkg.com/@expo/cli/-/cli-0.18.30.tgz#0cb4829aa11e98ae350a5c15958b9816e9a1d2f0"
+ integrity sha512-V90TUJh9Ly8stYo8nwqIqNWCsYjE28GlVFWEhAFCUOp99foiQr8HSTpiiX5GIrprcPoWmlGoY+J5fQA29R4lFg==
+ dependencies:
+ "@babel/runtime" "^7.20.0"
+ "@expo/code-signing-certificates" "0.0.5"
+ "@expo/config" "~9.0.0-beta.0"
+ "@expo/config-plugins" "~8.0.8"
+ "@expo/devcert" "^1.0.0"
+ "@expo/env" "~0.3.0"
+ "@expo/image-utils" "^0.5.0"
+ "@expo/json-file" "^8.3.0"
+ "@expo/metro-config" "0.18.11"
+ "@expo/osascript" "^2.0.31"
+ "@expo/package-manager" "^1.5.0"
+ "@expo/plist" "^0.1.0"
+ "@expo/prebuild-config" "7.0.9"
+ "@expo/rudder-sdk-node" "1.1.1"
+ "@expo/spawn-async" "^1.7.2"
+ "@expo/xcpretty" "^4.3.0"
+ "@react-native/dev-middleware" "0.74.85"
+ "@urql/core" "2.3.6"
+ "@urql/exchange-retry" "0.3.0"
+ accepts "^1.3.8"
+ arg "5.0.2"
+ better-opn "~3.0.2"
+ bplist-creator "0.0.7"
+ bplist-parser "^0.3.1"
+ cacache "^18.0.2"
+ chalk "^4.0.0"
+ ci-info "^3.3.0"
+ connect "^3.7.0"
+ debug "^4.3.4"
+ env-editor "^0.4.1"
+ fast-glob "^3.3.2"
+ find-yarn-workspace-root "~2.0.0"
+ form-data "^3.0.1"
+ freeport-async "2.0.0"
+ fs-extra "~8.1.0"
+ getenv "^1.0.0"
+ glob "^7.1.7"
+ graphql "15.8.0"
+ graphql-tag "^2.10.1"
+ https-proxy-agent "^5.0.1"
+ internal-ip "4.3.0"
+ is-docker "^2.0.0"
+ is-wsl "^2.1.1"
+ js-yaml "^3.13.1"
+ json-schema-deref-sync "^0.13.0"
+ lodash.debounce "^4.0.8"
+ md5hex "^1.0.0"
+ minimatch "^3.0.4"
+ node-fetch "^2.6.7"
+ node-forge "^1.3.1"
+ npm-package-arg "^7.0.0"
+ open "^8.3.0"
+ ora "3.4.0"
+ picomatch "^3.0.1"
+ pretty-bytes "5.6.0"
+ progress "2.0.3"
+ prompts "^2.3.2"
+ qrcode-terminal "0.11.0"
+ require-from-string "^2.0.2"
+ requireg "^0.2.2"
+ resolve "^1.22.2"
+ resolve-from "^5.0.0"
+ resolve.exports "^2.0.2"
+ semver "^7.6.0"
+ send "^0.18.0"
+ slugify "^1.3.4"
+ source-map-support "~0.5.21"
+ stacktrace-parser "^0.1.10"
+ structured-headers "^0.4.1"
+ tar "^6.0.5"
+ temp-dir "^2.0.0"
+ tempy "^0.7.1"
+ terminal-link "^2.1.1"
+ text-table "^0.2.0"
+ url-join "4.0.0"
+ wrap-ansi "^7.0.0"
+ ws "^8.12.1"
+
"@expo/code-signing-certificates@0.0.5":
version "0.0.5"
resolved "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz"
@@ -961,6 +1044,27 @@
node-forge "^1.2.1"
nullthrows "^1.1.1"
+"@expo/config-plugins@8.0.10":
+ version "8.0.10"
+ resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-8.0.10.tgz#5cda076f38bc04675cb42d8acdd23d6e460a62de"
+ integrity sha512-KG1fnSKRmsudPU9BWkl59PyE0byrE2HTnqbOrgwr2FAhqh7tfr9nRs6A9oLS/ntpGzmFxccTEcsV0L4apsuxxg==
+ dependencies:
+ "@expo/config-types" "^51.0.3"
+ "@expo/json-file" "~8.3.0"
+ "@expo/plist" "^0.1.0"
+ "@expo/sdk-runtime-versions" "^1.0.0"
+ chalk "^4.1.2"
+ debug "^4.3.1"
+ find-up "~5.0.0"
+ getenv "^1.0.0"
+ glob "7.1.6"
+ resolve-from "^5.0.0"
+ semver "^7.5.4"
+ slash "^3.0.0"
+ slugify "^1.6.6"
+ xcode "^3.0.1"
+ xml2js "0.6.0"
+
"@expo/config-plugins@8.0.9", "@expo/config-plugins@~8.0.0-beta.0", "@expo/config-plugins@~8.0.8":
version "8.0.9"
resolved "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-8.0.9.tgz"
@@ -984,7 +1088,7 @@
"@expo/config-plugins@~5.0.3":
version "5.0.4"
- resolved "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-5.0.4.tgz"
+ resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-5.0.4.tgz#216fea6558fe66615af1370de55193f4181cb23e"
integrity sha512-vzUcVpqOMs3h+hyRdhGwk+eGIOhXa5xYdd92yO17RMNHav3v/+ekMbs7XA2c3lepMO8Yd4/5hqmRw9ZTL6jGzg==
dependencies:
"@expo/config-types" "^47.0.0"
@@ -1013,6 +1117,11 @@
resolved "https://registry.npmjs.org/@expo/config-types/-/config-types-51.0.2.tgz"
integrity sha512-IglkIoiDwJMY01lYkF/ZSBoe/5cR+O3+Gx6fpLFjLfgZGBTdyPkKa1g8NWoWQCk+D3cKL2MDbszT2DyRRB0YqQ==
+"@expo/config-types@^51.0.3":
+ version "51.0.3"
+ resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-51.0.3.tgz#520bdce5fd75f9d234fd81bd0347443086419450"
+ integrity sha512-hMfuq++b8VySb+m9uNNrlpbvGxYc8OcFCUX9yTmi9tlx6A4k8SDabWFBgmnr4ao3wEArvWrtUQIfQCVtPRdpKA==
+
"@expo/config@9.0.3", "@expo/config@~9.0.0", "@expo/config@~9.0.0-beta.0":
version "9.0.3"
resolved "https://registry.npmjs.org/@expo/config/-/config-9.0.3.tgz"
@@ -1030,6 +1139,23 @@
slugify "^1.3.4"
sucrase "3.34.0"
+"@expo/config@9.0.4":
+ version "9.0.4"
+ resolved "https://registry.yarnpkg.com/@expo/config/-/config-9.0.4.tgz#52f0a94edd0e2c36dfb5e284cc1a6d99d9d2af97"
+ integrity sha512-g5ns5u1JSKudHYhjo1zaSfkJ/iZIcWmUmIQptMJZ6ag1C0ShL2sj8qdfU8MmAMuKLOgcIfSaiWlQnm4X3VJVkg==
+ dependencies:
+ "@babel/code-frame" "~7.10.4"
+ "@expo/config-plugins" "~8.0.8"
+ "@expo/config-types" "^51.0.3"
+ "@expo/json-file" "^8.3.0"
+ getenv "^1.0.0"
+ glob "7.1.6"
+ require-from-string "^2.0.2"
+ resolve-from "^5.0.0"
+ semver "^7.6.0"
+ slugify "^1.3.4"
+ sucrase "3.34.0"
+
"@expo/config@~7.0.2":
version "7.0.3"
resolved "https://registry.npmjs.org/@expo/config/-/config-7.0.3.tgz"
@@ -1278,6 +1404,23 @@
semver "^7.6.0"
xml2js "0.6.0"
+"@expo/prebuild-config@7.0.9":
+ version "7.0.9"
+ resolved "https://registry.yarnpkg.com/@expo/prebuild-config/-/prebuild-config-7.0.9.tgz#7abd489e18ed6514a0c9cd214eb34c0d5efda799"
+ integrity sha512-9i6Cg7jInpnGEHN0jxnW0P+0BexnePiBzmbUvzSbRXpdXihYUX2AKMu73jgzxn5P1hXOSkzNS7umaY+BZ+aBag==
+ dependencies:
+ "@expo/config" "~9.0.0-beta.0"
+ "@expo/config-plugins" "~8.0.8"
+ "@expo/config-types" "^51.0.3"
+ "@expo/image-utils" "^0.5.0"
+ "@expo/json-file" "^8.3.0"
+ "@react-native/normalize-colors" "0.74.85"
+ debug "^4.3.1"
+ fs-extra "^9.0.0"
+ resolve-from "^5.0.0"
+ semver "^7.6.0"
+ xml2js "0.6.0"
+
"@expo/rudder-sdk-node@1.1.1":
version "1.1.1"
resolved "https://registry.npmjs.org/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz"
@@ -3183,6 +3326,11 @@
resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz"
integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==
+"@yarnpkg/lockfile@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz"
+ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
"@zxing/text-encoding@0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz"
@@ -3563,6 +3711,19 @@ babel-plugin-polyfill-regenerator@^0.6.1:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.6.2"
+babel-plugin-react-compiler@0.0.0-experimental-592953e-20240517:
+ version "0.0.0-experimental-592953e-20240517"
+ resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-592953e-20240517.tgz#e800fa1550d03573cd5637218dc711f12f642249"
+ integrity sha512-OjG1SVaeQZaJrqkMFJatg8W/MTow8Ak5rx2SI0ETQBO1XvOk/XZGMbltNCPdFJLKghBYoBjC+Y3Ap/Xr7B01mA==
+ dependencies:
+ "@babel/generator" "7.2.0"
+ "@babel/types" "^7.19.0"
+ chalk "4"
+ invariant "^2.2.4"
+ pretty-format "^24"
+ zod "^3.22.4"
+ zod-validation-error "^2.1.0"
+
babel-plugin-react-compiler@^0.0.0-experimental-592953e-20240517:
version "0.0.0-experimental-7d62301-20240821"
resolved "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-7d62301-20240821.tgz"
@@ -3630,6 +3791,22 @@ babel-preset-expo@~11.0.14:
babel-plugin-react-native-web "~0.19.10"
react-refresh "^0.14.2"
+babel-preset-expo@~11.0.15:
+ version "11.0.15"
+ resolved "https://registry.yarnpkg.com/babel-preset-expo/-/babel-preset-expo-11.0.15.tgz#f29b1ac1f59f8739f63c80515906186586c24d3c"
+ integrity sha512-rgiMTYwqIPULaO7iZdqyL7aAff9QLOX6OWUtLZBlOrOTreGY1yHah/5+l8MvI6NVc/8Zj5LY4Y5uMSnJIuzTLw==
+ dependencies:
+ "@babel/plugin-proposal-decorators" "^7.12.9"
+ "@babel/plugin-transform-export-namespace-from" "^7.22.11"
+ "@babel/plugin-transform-object-rest-spread" "^7.12.13"
+ "@babel/plugin-transform-parameters" "^7.22.15"
+ "@babel/preset-react" "^7.22.15"
+ "@babel/preset-typescript" "^7.23.0"
+ "@react-native/babel-preset" "0.74.87"
+ babel-plugin-react-compiler "0.0.0-experimental-592953e-20240517"
+ babel-plugin-react-native-web "~0.19.10"
+ react-refresh "^0.14.2"
+
babel-preset-jest@^29.6.3:
version "29.6.3"
resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz"
@@ -3982,7 +4159,7 @@ ci-info@^2.0.0:
resolved "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
-ci-info@^3.2.0, ci-info@^3.3.0:
+ci-info@^3.2.0, ci-info@^3.3.0, ci-info@^3.7.0:
version "3.9.0"
resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz"
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
@@ -4456,7 +4633,7 @@ date-fns@^3.6.0:
resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz"
integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
-dayjs@^1.11.10, dayjs@^1.8.15:
+dayjs@^1.11.13, dayjs@^1.8.15:
version "1.11.13"
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
@@ -5052,6 +5229,13 @@ expo-build-properties@~0.12.4:
ajv "^8.11.0"
semver "^7.6.0"
+expo-cached-image@^51.0.19:
+ version "51.0.19"
+ resolved "https://registry.yarnpkg.com/expo-cached-image/-/expo-cached-image-51.0.19.tgz#27447d761a4b7414a2e5fee2e25c9436dd6f073e"
+ integrity sha512-HcIKolCKyrYcfimWp64S25Tv8YneUsKV47yJ93L4l4NVA7GJulqSS/fr2jf6B3mzw5rZNDU+eDAf1nzcxavfkg==
+ dependencies:
+ expo "51"
+
expo-calendar@~13.0.5:
version "13.0.5"
resolved "https://registry.npmjs.org/expo-calendar/-/expo-calendar-13.0.5.tgz"
@@ -5196,6 +5380,19 @@ expo-modules-autolinking@1.11.2:
require-from-string "^2.0.2"
resolve-from "^5.0.0"
+expo-modules-autolinking@1.11.3:
+ version "1.11.3"
+ resolved "https://registry.yarnpkg.com/expo-modules-autolinking/-/expo-modules-autolinking-1.11.3.tgz#bc64d278c04015014bb5802e3cfcd942d7c07168"
+ integrity sha512-oYh8EZEvYF5TYppxEKUTTJmbr8j7eRRnrIxzZtMvxLTXoujThVPMFS/cbnSnf2bFm1lq50TdDNABhmEi7z0ngQ==
+ dependencies:
+ chalk "^4.1.0"
+ commander "^7.2.0"
+ fast-glob "^3.2.5"
+ find-up "^5.0.0"
+ fs-extra "^9.1.0"
+ require-from-string "^2.0.2"
+ resolve-from "^5.0.0"
+
expo-modules-core@1.12.24:
version "1.12.24"
resolved "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-1.12.24.tgz"
@@ -5203,6 +5400,13 @@ expo-modules-core@1.12.24:
dependencies:
invariant "^2.2.4"
+expo-modules-core@1.12.26:
+ version "1.12.26"
+ resolved "https://registry.yarnpkg.com/expo-modules-core/-/expo-modules-core-1.12.26.tgz#86c4087dc6246abfc4d7f5e61097dc8cc4b22262"
+ integrity sha512-y8yDWjOi+rQRdO+HY+LnUlz8qzHerUaw/LUjKPU/mX8PRXP4UUPEEp5fjAwBU44xjNmYSHWZDwet4IBBE+yQUA==
+ dependencies:
+ invariant "^2.2.4"
+
expo-notifications@~0.28.18:
version "0.28.18"
resolved "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.28.18.tgz"
@@ -5301,6 +5505,27 @@ expo-web-browser@~13.0.0, expo-web-browser@~13.0.3:
resolved "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-13.0.3.tgz"
integrity sha512-HXb7y82ApVJtqk8tManyudtTrCtx8xcUnVzmJECeHCB0SsWSQ+penVLZxJkcyATWoJOsFMnfVSVdrTcpKKGszQ==
+expo@51:
+ version "51.0.38"
+ resolved "https://registry.yarnpkg.com/expo/-/expo-51.0.38.tgz#e4127b230454a34a507cfb9f1a2e4b3855cb0579"
+ integrity sha512-/B9npFkOPmv6WMIhdjQXEY0Z9k/67UZIVkodW8JxGIXwKUZAGHL+z1R5hTtWimpIrvVhyHUFU3f8uhfEKYhHNQ==
+ dependencies:
+ "@babel/runtime" "^7.20.0"
+ "@expo/cli" "0.18.30"
+ "@expo/config" "9.0.4"
+ "@expo/config-plugins" "8.0.10"
+ "@expo/metro-config" "0.18.11"
+ "@expo/vector-icons" "^14.0.3"
+ babel-preset-expo "~11.0.15"
+ expo-asset "~10.0.10"
+ expo-file-system "~17.0.1"
+ expo-font "~12.0.10"
+ expo-keep-awake "~13.0.2"
+ expo-modules-autolinking "1.11.3"
+ expo-modules-core "1.12.26"
+ fbemitter "^3.0.0"
+ whatwg-url-without-unicode "8.0.0-3"
+
expo@~51.0.24:
version "51.0.34"
resolved "https://registry.npmjs.org/expo/-/expo-51.0.34.tgz"
@@ -5548,7 +5773,7 @@ find-up@^5.0.0, find-up@~5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
-find-yarn-workspace-root@~2.0.0:
+find-yarn-workspace-root@^2.0.0, find-yarn-workspace-root@~2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz"
integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
@@ -7220,6 +7445,16 @@ json-schema-traverse@^1.0.0:
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+json-stable-stringify@^1.0.2:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz"
+ integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==
+ dependencies:
+ call-bind "^1.0.5"
+ isarray "^2.0.5"
+ jsonify "^0.0.1"
+ object-keys "^1.1.1"
+
json5@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz"
@@ -7248,6 +7483,11 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+jsonify@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz"
+ integrity sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==
+
jsonwebtoken@^9.0.0:
version "9.0.2"
resolved "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz"
@@ -7320,6 +7560,13 @@ kind-of@^6.0.0, kind-of@^6.0.1, kind-of@^6.0.2:
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+klaw-sync@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz"
+ integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+
kleur@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz"
@@ -8265,7 +8512,7 @@ open@^6.2.0:
dependencies:
is-wsl "^1.1.0"
-open@^7.0.3:
+open@^7.0.3, open@^7.4.2:
version "7.4.2"
resolved "https://registry.npmjs.org/open/-/open-7.4.2.tgz"
integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
@@ -8439,6 +8686,27 @@ password-prompt@^1.0.4:
ansi-escapes "^4.3.2"
cross-spawn "^7.0.3"
+patch-package@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz"
+ integrity sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==
+ dependencies:
+ "@yarnpkg/lockfile" "^1.1.0"
+ chalk "^4.1.2"
+ ci-info "^3.7.0"
+ cross-spawn "^7.0.3"
+ find-yarn-workspace-root "^2.0.0"
+ fs-extra "^9.0.0"
+ json-stable-stringify "^1.0.2"
+ klaw-sync "^6.0.0"
+ minimist "^1.2.6"
+ open "^7.4.2"
+ rimraf "^2.6.3"
+ semver "^7.5.3"
+ slash "^2.0.0"
+ tmp "^0.0.33"
+ yaml "^2.2.2"
+
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz"
@@ -8564,6 +8832,11 @@ postcss@~8.4.32:
picocolors "^1.0.1"
source-map-js "^1.2.0"
+postinstall-postinstall@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz"
+ integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ==
+
pretty-bytes@5.6.0:
version "5.6.0"
resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz"
@@ -8832,13 +9105,13 @@ react-native-base64@0.0.2:
resolved "https://registry.npmjs.org/react-native-base64/-/react-native-base64-0.0.2.tgz"
integrity sha512-Fu/J1a2y0X22EJDWqJR2oEa1fpP4gTFjYxk8ElJdt1Yak3HOXmFJ7EohLVHU2DaQkgmKfw8qb7u/48gpzveRbg==
-react-native-big-calendar@^4.14.0:
- version "4.14.0"
- resolved "https://registry.npmjs.org/react-native-big-calendar/-/react-native-big-calendar-4.14.0.tgz"
- integrity sha512-EYCxqXnRAg8QWsW3Npq3JI/9+lXlo9o6Gri7WttQQSqE/cGkVrVeKXObpvN6Cc4qrIUvnc4cgLAeM/j4+bOb6g==
+react-native-big-calendar@^4.15.1:
+ version "4.15.1"
+ resolved "https://registry.npmjs.org/react-native-big-calendar/-/react-native-big-calendar-4.15.1.tgz"
+ integrity sha512-hNrzkM+9Kb2T0J/1fW9AMaeN+AuhakCfNtQPaQL29l3JXgOO14ikJ3iPqQkmNVbuiWYiMrpI25hrmXffiOVIgQ==
dependencies:
calendarize "^1.1.1"
- dayjs "^1.11.10"
+ dayjs "^1.11.13"
react-native-calendars@^1.1306.0:
version "1.1306.0"
@@ -8857,7 +9130,7 @@ react-native-calendars@^1.1306.0:
react-native-element-dropdown@^2.12.2:
version "2.12.2"
- resolved "https://registry.yarnpkg.com/react-native-element-dropdown/-/react-native-element-dropdown-2.12.2.tgz#48d0c12b87591e2498c73bbde80e18374a4c262e"
+ resolved "https://registry.npmjs.org/react-native-element-dropdown/-/react-native-element-dropdown-2.12.2.tgz"
integrity sha512-Tf8hfRuniYEXo+LGoVgIMoItKWuPLX6jbqlwAFgMbBhmWGTuV+g1OVOAx/ny16kgnwp+NhgJoWpxhVvr7HSmXA==
dependencies:
lodash "^4.17.21"
@@ -9308,6 +9581,13 @@ rimraf@3.0.2, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
+rimraf@^2.6.3:
+ version "2.7.1"
+ resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
+ integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+ dependencies:
+ glob "^7.1.3"
+
rimraf@~2.6.2:
version "2.6.3"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz"
@@ -9611,6 +9891,11 @@ sisteransi@^1.0.5:
resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz"
integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+slash@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz"
+ integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
@@ -10931,7 +11216,7 @@ yallist@^4.0.0:
resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
-yaml@^2.2.1:
+yaml@^2.2.1, yaml@^2.2.2:
version "2.5.0"
resolved "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz"
integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==