diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 00000000..e6c400b3 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "osu-golf-drill-challenge-app" + } +} diff --git a/Contexts.js b/Contexts.js deleted file mode 100644 index 6f1bdb92..00000000 --- a/Contexts.js +++ /dev/null @@ -1,3 +0,0 @@ -import { createContext } from "react"; - -export const CurrentUserContext = createContext("c0nEyjaOMhItMQTLMY0X"); diff --git a/Utility.js b/Utility.js index bc68759a..1b7e1f6b 100644 --- a/Utility.js +++ b/Utility.js @@ -33,6 +33,17 @@ export function refToID(ref) { return ref["_key"] ? ref["_key"]["path"]["segments"].at(-1) : "bad ref"; } +export function getUnique(array, field) { + const uniqueMap = new Map(); + array.forEach((element) => { + const keyValue = element[field]; + if (!uniqueMap.has(keyValue)) { + uniqueMap.set(keyValue, element); + } + }); + return Array.from(uniqueMap.values()); +} + export function calculateAverageProxToHole(drillSubmissions) { const userAverages = []; drillSubmissions.forEach((submission) => { @@ -106,5 +117,5 @@ export function createOutputData(inputValues, attemptData) { return { ...object, sid: shotNum }; }); - console.log("Output Data: ", outputData); + //console.log("Output Data: ", outputData); } diff --git a/app/_layout.js b/app/_layout.js index 594f8b05..ba491859 100644 --- a/app/_layout.js +++ b/app/_layout.js @@ -1,25 +1,30 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Stack } from "expo-router"; import { useState } from "react"; -import { CurrentUserContext } from "~/Contexts"; +import { CurrentUserContext } from "~/contexts/CurrentUserContext"; + +// Create a client +const queryClient = new QueryClient(); export default function RootLayoutNav() { const [currentUser, setCurrentUser] = useState("c0nEyjaOMhItMQTLMY0X"); + const [currentTeam, setCurrentTeam] = useState("1"); - // Function to update the context value - const updateCurrentUser = (newValue) => { - setCurrentUser(newValue); - }; return ( - - + - - - - - + + + + + + + ); } diff --git a/app/content/drill/[id]/attempts/[attempt].js b/app/content/drill/[id]/attempts/[attempt].js index 8a056f2c..4be67d8f 100644 --- a/app/content/drill/[id]/attempts/[attempt].js +++ b/app/content/drill/[id]/attempts/[attempt].js @@ -1,6 +1,4 @@ import { useLocalSearchParams } from "expo-router"; -import { doc, getDoc } from "firebase/firestore"; -import { useEffect, useState } from "react"; import { ScrollView, StyleSheet, @@ -11,19 +9,39 @@ import { import { SafeAreaView } from "react-native-safe-area-context"; import ScatterChart from "react-native-scatter-chart"; import { numTrunc } from "~/Utility"; +import ErrorComponent from "~/components/errorComponent"; +import Loading from "~/components/loading"; import ShotAccordion from "~/components/shotAccordion"; -import db from "~/firebaseConfig"; +import { useAttempts } from "~/hooks/useAttempts"; +import { useDrillInfo } from "~/hooks/useDrillInfo"; function Result() { const drillId = useLocalSearchParams()["id"]; const attemptId = useLocalSearchParams()["attempt"]; - const [drillInfo, setDrillInfo] = useState({}); - const [attempt, setAttempt] = useState({}); + const { width } = useWindowDimensions(); + + const { + data: drillInfo, + isLoading: drillInfoIsLoading, + error: drillInfoError, + } = useDrillInfo(drillId); + + const { + data: attempt, + isLoading: attemptIsLoading, + error: attemptError, + } = useAttempts({ attemptId }); + + if (drillInfoIsLoading || attemptIsLoading) { + return ; + } + + if (drillInfoError || attemptError) { + return ; + } let dots = []; if ( - Object.keys(drillInfo).length > 0 && - Object.keys(attempt).length > 0 && drillInfo["outputs"].includes("sideLanding") && drillInfo["outputs"].includes("carryDiff") ) { @@ -32,29 +50,6 @@ function Result() { value["carryDiff"], ]); } - const { width } = useWindowDimensions(); - useEffect(() => { - // massive data fetching on refresh. May or may not get its data from cache - let mainOutputAttempt = ""; - getDoc(doc(db, "teams", "1", "drills", drillId)).then((doc) => { - // get drill data - if (doc.exists()) { - setDrillInfo(doc.data()); - console.log("got drill data", mainOutputAttempt); - } else { - // doc.data() will be undefined in this case - console.log("No such document!"); - } - }); - getDoc(doc(db, "teams", "1", "attempts", attemptId)) - .then((doc) => { - setAttempt(doc.data()); - }) - .catch((error) => { - console.log("Error getting documents: ", error); - }); - return () => {}; - }, []); return ( <> diff --git a/app/content/drill/[id]/description.js b/app/content/drill/[id]/description.js index 88379466..88b5c4c1 100644 --- a/app/content/drill/[id]/description.js +++ b/app/content/drill/[id]/description.js @@ -1,9 +1,23 @@ import { Link, useLocalSearchParams } from "expo-router"; import { Image, ScrollView } from "react-native"; import { Button, Text } from "react-native-paper"; +import Loading from "~/components/loading"; +import ErrorComponent from "../../../../components/errorComponent"; +import { useDrillInfo } from "../../../../hooks/useDrillInfo"; -export default function Description({ descData }) { +export default function Description() { const drillId = useLocalSearchParams()["id"]; + + const { + data: drillInfo, + error: drillInfoError, + isLoading: drillInfoIsLoading, + } = useDrillInfo(drillId); + + if (drillInfoIsLoading) return ; + + if (drillInfoError) return ; + return ( Description - {descData.description} + {drillInfo["description"]} ; - useEffect(() => { - getDoc(drillsRef).then((document) => { - setDrillData(document.data()); - }); - }, []); + if (drillInfoError) return ; const tabComponent = () => { switch (value) { case "leaderboard": return ; case "description": - return ; + return ; case "stats": return ; } @@ -46,7 +47,7 @@ export default function Index() { }} color={"#F24E1E"} /> - + {/* Tab system */} diff --git a/app/content/drill/[id]/leaderboard.js b/app/content/drill/[id]/leaderboard.js index 4eaad76a..dbe4f8be 100644 --- a/app/content/drill/[id]/leaderboard.js +++ b/app/content/drill/[id]/leaderboard.js @@ -1,119 +1,92 @@ import { Link, useLocalSearchParams, usePathname } from "expo-router"; -import { - collection, - doc, - getDoc, - getDocs, - query, - where, -} from "firebase/firestore"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { ScrollView, View } from "react-native"; import { Avatar, Icon, List, Text } from "react-native-paper"; import { numTrunc } from "~/Utility"; -import db from "~/firebaseConfig"; +import ErrorComponent from "~/components/errorComponent"; +import Loading from "~/components/loading"; +import { useAttempts } from "~/hooks/useAttempts"; +import { useDrillInfo } from "~/hooks/useDrillInfo"; +import { useUserInfo } from "~/hooks/useUserInfo"; export default function Leaderboard() { const drillId = useLocalSearchParams()["id"]; - const [userInfo, setUserInfo] = useState({}); const currentPath = usePathname(); + const [defaultMainOutputAttempt, setDefaultMainOutputAttempt] = + useState(true); //whether mainOutputAttempt is the default set on drills or has been changed by user + const [customMainOutputAttempt, setCustomMainOutputAttempt] = useState("did"); //What is the custom mainOutputAttempt in case defaultMainOutputAttempt is false - const [drillLeaderboardAttempts, setDrillLeaderboardAttempts] = useState([]); - const [drillInfo, setDrillInfo] = useState({}); + const { + data: userInfo, + userIsLoading: userIsLoading, + userError: userError, + } = useUserInfo(); - useEffect(() => { - // massive data fetching on refresh. May or may not get its data from cache - let mainOutputAttempt = ""; - getDocs(collection(db, "teams", "1", "users")).then((querySnapshot) => { - // get all attempts - const newUserInfo = {}; - querySnapshot.forEach((doc) => { - newUserInfo[doc.id] = doc.data(); - }); - setUserInfo(newUserInfo); - console.log("got attempts", userInfo); - }); - getDoc(doc(db, "teams", "1", "drills", drillId)).then((doc) => { - // get drill data - if (doc.exists()) { - mainOutputAttempt = doc.data()["mainOutputAttempt"]; - setDrillInfo(doc.data()); - console.log("got drill data", mainOutputAttempt); - } else { - // doc.data() will be undefined in this case - console.log("No such document!"); - } - }); - getDocs( - query( - collection(db, "teams", "1", "attempts"), - where("did", "==", drillId), - ), - ) - .then((querySnapshot) => { - // get all attempts in drill and filter only the highest score for a user - let newDrillAttempts = {}; - querySnapshot.forEach((doc) => { - const data = doc.data(); - // console.log("data: ", data); - if (!newDrillAttempts[data["uid"]]) - newDrillAttempts[data["uid"]] = { - score: doc.data()[mainOutputAttempt], - id: doc.id, - }; - else if ( - newDrillAttempts[data["uid"]][mainOutputAttempt] < - doc.data()[mainOutputAttempt] - ) - newDrillAttempts[data["uid"]] = { - score: doc.data()[mainOutputAttempt], - id: doc.id, - }; - }); - setDrillLeaderboardAttempts( - Object.keys(newDrillAttempts).map((key) => { - return { - user: key, - score: newDrillAttempts[key]["score"], - id: newDrillAttempts[key]["id"], - }; - }), - ); - console.log("finished processing attempts", drillLeaderboardAttempts); - }) - .catch((error) => { - console.log("Error getting documents: ", error); - }); - return () => {}; - }, []); + const { + data: drillInfo, + isLoading: drillIsLoading, + error: drillError, + } = useDrillInfo(drillId); - const orderedLeaderboard = drillLeaderboardAttempts.sort( - (a, b) => a["score"] - b["score"], - ); + const { + data: attempts, + isLoading: attemptIsLoading, + error: attemptError, + } = useAttempts({ drillId }); + + //console.log("userInfo: ", userInfo); + //console.log("drillInfo: ", drillInfo); + + if (userIsLoading || drillIsLoading || attemptIsLoading) { + return ; + } + + if (userError || drillError || attemptError) { + return ; + } - const getUserInfo = (userId) => { - return userInfo[userId] || { name: "Unknown", uid: "unknown" }; - }; + // console.log("attempts: ", attempts); + + const mainOutputAttempt = defaultMainOutputAttempt + ? drillInfo["mainOutputAttempt"] + : customMainOutputAttempt; + + const drillLeaderboardAttempts = {}; + for (const id in attempts) { + const entry = attempts[id]; + // If this uid has not been seen before or the current score is higher, store it + if ( + !drillLeaderboardAttempts[entry.uid] || + drillLeaderboardAttempts[entry.uid][mainOutputAttempt] < + entry[mainOutputAttempt] + ) { + drillLeaderboardAttempts[entry.uid] = entry; + } + } + + const orderedLeaderboard = Object.values(drillLeaderboardAttempts).sort( + (a, b) => a[mainOutputAttempt] - b[mainOutputAttempt], + ); - console.log(drillLeaderboardAttempts); + // console.log(orderedLeaderboard[0]); return ( {orderedLeaderboard.map((attempt) => ( } right={() => ( - {numTrunc(attempt.score)} ft + {numTrunc(attempt[mainOutputAttempt])} ft )} diff --git a/app/content/drill/[id]/statistics.js b/app/content/drill/[id]/statistics.js index 9fc7ce1a..f4731427 100644 --- a/app/content/drill/[id]/statistics.js +++ b/app/content/drill/[id]/statistics.js @@ -1,51 +1,37 @@ import { useLocalSearchParams } from "expo-router"; -import { useContext, useEffect, useState } from "react"; -import { - collection, - doc, - getDoc, - getDocs, - query, - where, -} from "firebase/firestore"; -import { CurrentUserContext } from "~/Contexts"; +import { useContext } from "react"; import BarChartScreen from "~/components/barChart"; -import db from "~/firebaseConfig"; +import ErrorComponent from "~/components/errorComponent"; +import Loading from "~/components/loading"; +import { CurrentUserContext } from "~/contexts/CurrentUserContext"; +import { useAttempts } from "~/hooks/useAttempts"; +import { useDrillInfo } from "~/hooks/useDrillInfo"; export default function Stat() { const drillId = useLocalSearchParams()["id"]; - const [drillInfo, setDrillInfo] = useState(""); - const [drillAttempts, setDrillAttempts] = useState([]); const userId = useContext(CurrentUserContext)["currentUser"]; - useEffect(() => { - // massive data fetching on refresh. May or may not get its data from cache - getDoc(doc(db, "teams", "1", "drills", drillId)).then((doc) => { - // get drill data - if (doc.exists()) { - setDrillInfo(doc.data()); - } else { - // doc.data() will be undefined in this case - console.log("No such document!"); - } - }); - getDocs( - query( - collection(db, "teams", "1", "attempts"), - where("did", "==", drillId), - where("uid", "==", userId), - ), - ) - .then((querySnapshot) => { - // get all attempts in drill and filter only the highest score for a user - let newDrillAttempts = []; - querySnapshot.forEach((doc) => newDrillAttempts.push(doc.data())); - setDrillAttempts(newDrillAttempts); - }) - .catch((error) => { - console.log("Error getting documents: ", error); - }); - return () => {}; - }, []); + + const { + data: drillInfo, + isLoading: drillInfoIsLoading, + drillInfoError, + } = useDrillInfo(drillId); + + const { + data: drillAttempts, + isLoading: drillAttemptsIsLoading, + error: drillAttemptsError, + } = useAttempts({ drillId, userId }); + + if (drillInfoIsLoading || drillAttemptsIsLoading) { + return ; + } + + if (drillInfoError || drillAttemptsError) { + return ; + } + // console.log(drillAttempts); + return ; } diff --git a/app/content/drill/index.js b/app/content/drill/index.js index 70e2bdd3..ee694946 100644 --- a/app/content/drill/index.js +++ b/app/content/drill/index.js @@ -1,33 +1,28 @@ import { Link, useNavigation } from "expo-router"; -import React, { useEffect } from "react"; -import { RefreshControl, ScrollView, StyleSheet } from "react-native"; +import { ScrollView, StyleSheet } from "react-native"; import { Appbar, List, PaperProvider } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; -import { collection, getDocs } from "firebase/firestore"; -import db from "~/firebaseConfig"; +import ErrorComponent from "~/components/errorComponent"; +import Loading from "~/components/loading"; +import { useDrillInfo } from "~/hooks/useDrillInfo"; export default function Index() { - const [drills, setDrills] = React.useState([]); // [{} - const drillsRef = collection(db, "teams", "1", "drills"); const navigation = useNavigation(); - const [refreshing, setRefreshing] = React.useState(false); + const { + data: drillInfo, + error: drillInfoError, + isLoading: drillInfoIsLoading, + } = useDrillInfo(); - useEffect(() => { - getDocs(drillsRef).then((querySnapshot) => { - let newDrills = []; - querySnapshot.forEach((doc) => { - newDrills.push(doc.data()); - }); - setDrills(newDrills); - setRefreshing(false); - }); - }, [refreshing]); + if (drillInfoIsLoading) { + return ; + } - const onRefresh = React.useCallback(() => { - setRefreshing(true); - }, []); + if (drillInfoError) { + return ; + } return ( @@ -37,9 +32,8 @@ export default function Index() { - - {drills.map((drill) => ( + {Object.values(drillInfo).map((drill) => ( { - // massive data fetching on refresh. May or may not get its data from cache - getDoc(doc(db, "teams", "1", "drills", drillId)).then((doc) => { - // get drill data - if (doc.exists()) { - setDrillInfo(doc.data()); - console.log("got drill data", drillInfo); - } else { - // doc.data() will be undefined in this case - console.log("No such document!"); - } - }); - getDocs( - query( - collection(db, "teams", "1", "attempts"), - where("did", "==", drillId), - where("uid", "==", userId), - ), - ) - .then((querySnapshot) => { - // get all attempts in drill and filter only the highest score for a user - let newDrillAttempts = []; - querySnapshot.forEach((doc) => newDrillAttempts.push(doc.data())); - setDrillAttempts(newDrillAttempts); - }) - .catch((error) => { - console.log("Error getting documents: ", error); - }); - return () => {}; - }, []); + + const { + data: drillInfo, + isLoading: drillInfoIsLoading, + drillInfoError, + } = useDrillInfo(drillId); + + const { + data: drillAttempts, + isLoading: drillAttemptsIsLoading, + error: drillAttemptsError, + } = useAttempts({ drillId, userId }); + + if (drillInfoIsLoading || drillAttemptsIsLoading) { + return ; + } + + if (drillInfoError || drillAttemptsError) { + return ; + } + return ( diff --git a/app/content/profile/index.js b/app/content/profile/index.js index 72241fd9..f558c94f 100644 --- a/app/content/profile/index.js +++ b/app/content/profile/index.js @@ -1,51 +1,49 @@ import { useNavigation } from "expo-router"; -import { - collection, - doc, - getDoc, - getDocs, - query, - where, -} from "firebase/firestore"; -import { useContext, useEffect, useState } from "react"; +import { useContext } from "react"; import { ScrollView, StyleSheet, Text, View } from "react-native"; import { Appbar, PaperProvider } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; -import { CurrentUserContext } from "~/Contexts"; +import { getUnique } from "~/Utility"; import DrillCard from "~/components/drillCard"; +import ErrorComponent from "~/components/errorComponent"; +import Loading from "~/components/loading"; import ProfileCard from "~/components/profileCard"; -import db from "~/firebaseConfig"; +import { CurrentUserContext } from "~/contexts/CurrentUserContext"; +import { useAttempts } from "~/hooks/useAttempts"; +import { useDrillInfo } from "~/hooks/useDrillInfo"; +import { useUserInfo } from "~/hooks/useUserInfo"; function Index(props) { const navigation = useNavigation(); - const [userData, setUserData] = useState({}); const userId = useContext(CurrentUserContext)["currentUser"]; - const [drills, setDrills] = useState({}); + const { + data: userData, + userError: userError, + userIsLoading: userIsLoading, + } = useUserInfo(userId); + const { + data: attempts, + error: attemptsError, + isLoading: attemptsIsLoading, + } = useAttempts({ userId }); - useEffect(() => { - let uniqueDrills = new Set(); - getDoc(doc(db, "teams", "1", "users", userId)).then((doc) => { - setUserData(doc.data()); - }); - getDocs( - query(collection(db, "teams", "1", "attempts")), - where("uid", "==", userId), - ).then((querySnapshot) => { - querySnapshot.forEach((doc) => { - uniqueDrills.add(doc.data()["did"]); - }); - }); - getDocs( - query(collection(db, "teams", "1", "drills")), - where("did", "in", uniqueDrills), - ).then((querySnapshot) => { - let newDrills = {}; - querySnapshot.forEach((doc) => { - newDrills[doc.id] = doc.data(); - }); - setDrills(newDrills); - }); - }, []); + const { + data: drillInfo, + error: drillInfoError, + isLoading: drillInfoIsLoading, + } = useDrillInfo(); + + if (userIsLoading || drillInfoIsLoading || attemptsIsLoading) { + return ; + } + + if (userError || drillInfoError || attemptsError) { + return ( + + ); + } + + const uniqueDrills = getUnique(attempts, "did"); return ( @@ -69,11 +67,12 @@ function Index(props) { Drill History - {Object.keys(drills).length > 0 ? ( - Object.keys(drills).map((drillId) => { + {uniqueDrills.length > 0 ? ( + uniqueDrills.map((drill) => { + const drillId = drill["did"]; return ( diff --git a/app/content/profile/statistics.js b/app/content/profile/statistics.js index dd4512a0..21b557b4 100644 --- a/app/content/profile/statistics.js +++ b/app/content/profile/statistics.js @@ -2,11 +2,6 @@ import { PaperProvider, Text } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; //This is for the list of drills export default function Statistics() { - /*React.useEffect(() => { - navigation.getParent()?.setOptions({ - tabBarStyle: { display: 'none' }, - }); - })*/ return ( { - getDocs(usersRef).then((querySnapshot) => { - let newUsers = []; - querySnapshot.forEach((doc) => { - newUsers.push(doc.data()); - }); - setUsers(newUsers); - }); - }, []); + const { data: userInfo, userIsLoading, userError } = useUserInfo(); const [searchQuery, setSearchQuery] = React.useState(""); const onChangeSearch = (query) => setSearchQuery(query); - const foundUsers = users.filter((user) => - user.name.toLowerCase().includes(searchQuery.toLowerCase()), - ); - - console.log("Found: ", foundUsers); + //console.log("Found: ", foundUsers); // ref const bottomSheetModalRef = useRef(null); @@ -65,9 +48,17 @@ function Index() { bottomSheetModalRef.current?.present(); }, []); const handleSheetChanges = useCallback((index) => { - console.log("handleSheetChanges", index); + //console.log("handleSheetChanges", index); }, []); + if (userIsLoading) return ; + + if (userError) return ; + + const foundUsers = Object.values(userInfo).filter((user) => + user.name.toLowerCase().includes(searchQuery.toLowerCase()), + ); + return ( - {Object.keys(users).length} members + {Object.keys(userInfo).length} members ; + } + + if (drillInfoError || drillAttemptsError) { + return ; + } - useEffect(() => { - // massive data fetching on refresh. May or may not get its data from cache - getDoc(doc(db, "teams", "1", "drills", drillId)).then((doc) => { - // get drill data - if (doc.exists()) { - setDrillInfo(doc.data()); - } else { - // doc.data() will be undefined in this case - console.log("No such document!"); - } - }); - getDocs( - query( - collection(db, "teams", "1", "attempts"), - where("did", "==", drillId), - where("uid", "==", userId), - ), - ) - .then((querySnapshot) => { - // get all attempts in drill and filter only the highest score for a user - let newDrillAttempts = []; - querySnapshot.forEach((doc) => newDrillAttempts.push(doc.data())); - setDrillAttempts(newDrillAttempts); - }) - .catch((error) => { - console.log("Error getting documents: ", error); - }); - return () => {}; - }, []); return ( diff --git a/app/content/team/users/[user]/index.js b/app/content/team/users/[user]/index.js index f89ccacd..55c9c211 100644 --- a/app/content/team/users/[user]/index.js +++ b/app/content/team/users/[user]/index.js @@ -1,52 +1,49 @@ import { useLocalSearchParams, useNavigation } from "expo-router"; -import { - collection, - doc, - getDoc, - getDocs, - query, - where, -} from "firebase/firestore"; -import { useEffect, useState } from "react"; import { ScrollView, StyleSheet, Text, View } from "react-native"; import { Appbar, PaperProvider } from "react-native-paper"; import { SafeAreaView } from "react-native-safe-area-context"; +import { refToID } from "~/Utility"; import DrillCard from "~/components/drillCard"; +import ErrorComponent from "~/components/errorComponent"; +import Loading from "~/components/loading"; import ProfileCard from "~/components/profileCard"; -import db from "~/firebaseConfig"; -import { refToID } from "~/Utility"; +import { useAttempts } from "~/hooks/useAttempts"; +import { useDrillInfo } from "~/hooks/useDrillInfo"; +import { useUserInfo } from "~/hooks/useUserInfo"; +import { getUnique } from "../../../../../Utility"; function Index(props) { - const user_id = useLocalSearchParams()["user"]; + const userId = useLocalSearchParams()["user"]; const navigation = useNavigation(); - const [drills, setDrills] = useState({}); - const [userData, setUserData] = useState({}); - console.log("userData", userData); - useEffect(() => { - let uniqueDrills = new Set(); - getDoc(doc(db, "teams", "1", "users", user_id)).then((doc) => { - setUserData(doc.data()); - }); - getDocs( - query(collection(db, "teams", "1", "attempts")), - where("uid", "==", user_id), - ).then((querySnapshot) => { - querySnapshot.forEach((doc) => { - uniqueDrills.add(doc.data()["did"]); - }); + const { + data: userData, + userError: userError, + userIsLoading: userIsLoading, + } = useUserInfo(userId); + + const { + data: attempts, + error: attemptsError, + isLoading: attemptsIsLoading, + } = useAttempts({ userId }); + + const { + data: drillInfo, + error: drillInfoError, + isLoading: drillInfoIsLoading, + } = useDrillInfo(); + + if (userIsLoading || drillInfoIsLoading || attemptsIsLoading) { + return ; + } + + if (userError || drillInfoError || attemptsError) { + return ( + + ); + } - getDocs( - query(collection(db, "teams", "1", "drills")), - where("did", "in", uniqueDrills), - ).then((querySnapshot) => { - let newDrills = {}; - querySnapshot.forEach((doc) => { - newDrills[doc.id] = doc.data(); - }); - setDrills(newDrills); - }); - }); - }, []); + const uniqueDrills = getUnique(attempts, "did"); return ( @@ -68,19 +65,22 @@ function Index(props) { Drills - {Object.keys(drills).length > 0 ? ( - Object.keys(drills).map((drillId) => ( - - )) + {uniqueDrills.length > 0 ? ( + uniqueDrills.map((drill) => { + const drillId = drill["did"]; + return ( + + ); + }) ) : ( No drills attempted yet )} diff --git a/app/segments/drill/[id]/submission/input.js b/app/segments/drill/[id]/submission/input.js index 992cb08a..f98ad0e9 100644 --- a/app/segments/drill/[id]/submission/input.js +++ b/app/segments/drill/[id]/submission/input.js @@ -65,7 +65,7 @@ export default function Input({ inputValues, setInputValues }) { labelStyle={styles.buttonText} mode="contained-tonal" onPress={() => { - console.log("Pressed Next Shot"); + //console.log("Pressed Next Shot"); handleNextShotButtonClick(); }} > @@ -79,7 +79,7 @@ export default function Input({ inputValues, setInputValues }) { labelStyle={styles.buttonText} mode="contained-tonal" onPress={() => { - console.log("Pressed Back to Latest"); + //console.log("Pressed Back to Latest"); setShotIndex(currentShot); }} > @@ -109,7 +109,7 @@ export default function Input({ inputValues, setInputValues }) { setShotIndex(shotIndex + 1); setCurrentShot(currentShot + 1); } else { - console.log("Not all input fields entered!"); + //console.log("Not all input fields entered!"); setEmptyInputBannerVisable(true); } }; @@ -130,7 +130,7 @@ export default function Input({ inputValues, setInputValues }) { navigationBottomSheetModalRef.current?.present(); }, []); const handleNavigationSheetChanges = useCallback((index) => { - console.log("handleSheetChanges", index); + //console.log("handleSheetChanges", index); }, []); /***** Description Bottom Sheet Stuff *****/ @@ -142,7 +142,7 @@ export default function Input({ inputValues, setInputValues }) { descriptionBottomSheetModalRef.current?.present(); }, []); const handleDesciptionSheetChanges = useCallback((index) => { - console.log("handleDesciptionSheetChanges", index); + //console.log("handleDesciptionSheetChanges", index); }, []); /***** Leave drill Dialog Stuff *****/ @@ -244,9 +244,9 @@ export default function Input({ inputValues, setInputValues }) { onPress={() => { //this loop is a test to see if inputs are maintained in state for (let i = 0; i < AttemptData.shots.length; i++) { - console.log("InputValue[", i, "]: ", inputValues[i]); + //console.log("InputValue[", i, "]: ", inputValues[i]); } - console.log(inputValues); + //console.log(inputValues); }} > Log Input State Status @@ -275,7 +275,7 @@ export default function Input({ inputValues, setInputValues }) { { - console.log("Clicked on ", id); + //console.log("Clicked on ", id); setShotIndex(id); }} inputs={item.inputs} @@ -341,7 +341,7 @@ export default function Input({ inputValues, setInputValues }) { { - console.log("Pressed View All Shots"); + //console.log("Pressed View All Shots"); handlePresentNavigationModalPress(); }} > diff --git a/components/barChart.js b/components/barChart.js index f0aff71c..3625ee06 100644 --- a/components/barChart.js +++ b/components/barChart.js @@ -203,7 +203,7 @@ export default function BarChartScreen({ drillData, drillInfo }) { }, bottomTextContainer: { flexDirection: "row", - justifyContent: "space-between", + justifyContent: "flex-start", marginBottom: 13, marginLeft: 8, marginRight: 8, @@ -276,11 +276,19 @@ export default function BarChartScreen({ drillData, drillInfo }) { - {dateString} - + + {dateString} + + MA: {numTrunc(movingAvgData[selected])} - SG: {numTrunc(data[selected])} + + SG: {numTrunc(data[selected])} + {drillDataSorted[selected]["shots"].map((shot) => ( diff --git a/components/drillCard.js b/components/drillCard.js index b61382ac..c1d8c167 100644 --- a/components/drillCard.js +++ b/components/drillCard.js @@ -2,6 +2,7 @@ import { Link } from "expo-router"; import { StyleSheet, Text, TouchableOpacity, View } from "react-native"; function DrillCard(props) { + console.log(props); return ( diff --git a/components/errorComponent.js b/components/errorComponent.js new file mode 100644 index 00000000..888996f0 --- /dev/null +++ b/components/errorComponent.js @@ -0,0 +1,16 @@ +import { PaperProvider, Text } from "react-native-paper"; +import { SafeAreaView } from "react-native-safe-area-context"; + +function ErrorComponent({ message }) { + const displayMessage = + message.constructor === Array ? message.join(", ") : message; + return ( + + + An error has occurred: {displayMessage} + + + ); +} + +export default ErrorComponent; diff --git a/components/loading.js b/components/loading.js new file mode 100644 index 00000000..7e6ce652 --- /dev/null +++ b/components/loading.js @@ -0,0 +1,14 @@ +import { PaperProvider, Text } from "react-native-paper"; +import { SafeAreaView } from "react-native-safe-area-context"; + +function Loading() { + return ( + + + Loading... + + + ); +} + +export default Loading; diff --git a/components/shotAccordion.js b/components/shotAccordion.js index 36c21ed9..3e2187ef 100644 --- a/components/shotAccordion.js +++ b/components/shotAccordion.js @@ -76,22 +76,34 @@ function ShotAccordion(props) { }, }} title={ - - + + Shot: {props.shot["sid"]}/ {props.total} - + Target:{" "} {props.shot["target"]} yd - + SG:{" "} {numTrunc(props.shot[props.drillInfo["mainOutputShot"]])} } - style={styles.container} + style={{ + backgroundColor: "#f5f5f5", + borderWidth: 1, + borderColor: "#ddd", + borderRadius: 8, + }} > {}, + updateCurrentTeam: () => {}, +}); diff --git a/firebaseConfig.js b/firebaseConfig.js index 55226fd4..876f5686 100644 --- a/firebaseConfig.js +++ b/firebaseConfig.js @@ -22,4 +22,6 @@ const firebaseConfig = { const app = initializeApp(firebaseConfig); const db = getFirestore(app); +// connectFirestoreEmulator(db, "localhost", 8080); + module.exports = db; diff --git a/generate_drill_data.py b/generate_drill_data.py index 50ef4304..e9ecba56 100644 --- a/generate_drill_data.py +++ b/generate_drill_data.py @@ -3,8 +3,11 @@ import math import firebase_admin from firebase_admin import firestore +import os # Application Default credentials are automatically created. +# os.environ["FIRESTORE_EMULATOR_HOST"] = "localhost:8080" + app = firebase_admin.initialize_app() db = firestore.client() @@ -26,9 +29,12 @@ def lookup_round_down(value, lookup_keys, lookup_values): line_test = ["8", "9", "PW", "9", "8", "7", "8", "9", "PW", "9", "8", "7", "8", "9", "PW", "9", "8", "7", "8", "9"] +users = ["rcnS0atnVgt4svjVK0ZS","dkjydFrmyi9dRK9Jj2Su","TaSveOyBkVaK012r6meC","c0nEyjaOMhItMQTLMY0X","8mTnNFsMQYlTDeaQmluZ","8j6vfO5xpIdZF9dUAAV8oizr60v1","8aUSErrZSHWEsgRYIedq","6r2BOnaLTaiDgPMd7RWa"] + +collection_ref = db.collection("teams").document("1").collection("attempts") # Function to generate random data for one submission -def generate_submission(submission_id): +def generate_submission(user_id): # Unix timestamp for the submission time_stamp = random.randint(1600000000, 1800000000) @@ -68,11 +74,19 @@ def generate_submission(submission_id): strokes_gained_total += strokes_gained shots.append(shot) + + # Generate a new document reference with an auto-generated ID + doc_ref = collection_ref.document() + + # Get the auto-generated ID + doc_id = doc_ref.id + # One submission submission = { "time": time_stamp, - "uid": "c0nEyjaOMhItMQTLMY0X", + "uid": user_id, "did": "SpvYyY94HaulVH2zmVyM", + "id": doc_id, "strokesGained": strokes_gained_total, "strokesGainedAverage": strokes_gained_total / num_shots, "carryDiffAverage": carry_diff_total / num_shots, @@ -80,10 +94,11 @@ def generate_submission(submission_id): "proxHoleAverage": prox_hole_total / num_shots, "shots": shots } - db.collection("teams").document("1").collection("attempts").document().set(submission) + + doc_ref.set(submission) return submission -def generate_submission_line(submission_id): +def generate_submission_line(user_id): # Unix timestamp for the submission time_stamp = random.randint(1600000000, 1800000000) @@ -103,21 +118,31 @@ def generate_submission_line(submission_id): side_landing_total += side_landing shots.append(shot) + + # Generate a new document reference with an auto-generated ID + doc_ref = collection_ref.document() + + # Get the auto-generated ID + doc_id = doc_ref.id + # One submission submission = { "time": time_stamp, - "uid": "c0nEyjaOMhItMQTLMY0X", + "uid": user_id, "did": "YtCsaxzscFScnpZYmnKI", + "id": doc_id, "sideLandingTotal": side_landing_total, "sideLandingAverage": side_landing_total / len(shots), "shots": shots } - db.collection("teams").document("1").collection("attempts").document().set(submission) + + doc_ref.set(submission) return submission # Generate 100 submissions -submissions = [generate_submission(i) for i in range(100)] - -# Print the submissions without indentation or new lines -print(submissions) +for user_id in users: + for i in range(random.randint(50, 150)): + submission = generate_submission(user_id) + for i in range(random.randint(50, 150)): + submission = generate_submission_line(user_id) diff --git a/hooks/useAttempts.js b/hooks/useAttempts.js new file mode 100644 index 00000000..7251a1be --- /dev/null +++ b/hooks/useAttempts.js @@ -0,0 +1,49 @@ +import { useQuery } from "@tanstack/react-query"; +import { + collection, + doc, + getDoc, + getDocs, + query, + where, +} from "firebase/firestore"; +import { useContext } from "react"; +import { CurrentUserContext } from "~/contexts/CurrentUserContext"; +import db from "~/firebaseConfig"; + +//this code scares me +export const useAttempts = ({ + attemptId = null, + userId = null, + drillId = null, +}) => { + const teamId = useContext(CurrentUserContext).currentTeam; + const { data, error, isLoading } = useQuery({ + queryKey: ["attempts", teamId, { attemptId, userId, drillId }], + queryFn: async () => { + if (attemptId) { + const querySnapshot = await getDoc( + doc(db, "teams", teamId, "attempts", attemptId), + ); + return querySnapshot.data(); + } else { + let q = query(collection(db, "teams", teamId, "attempts")); + if (drillId) { + q = query(q, where("did", "==", drillId)); + } + if (userId) { + q = query(q, where("uid", "==", userId)); + } + const querySnapshot = await getDocs(q); + + return querySnapshot.docs.map((doc) => doc.data()); + } + }, + }); + + return { + data, + error, + isLoading, + }; +}; diff --git a/hooks/useDrillInfo.js b/hooks/useDrillInfo.js new file mode 100644 index 00000000..d05b2e05 --- /dev/null +++ b/hooks/useDrillInfo.js @@ -0,0 +1,37 @@ +import { useQuery } from "@tanstack/react-query"; +import { collection, doc, getDoc, getDocs } from "firebase/firestore"; +import { useContext } from "react"; +import { CurrentUserContext } from "~/contexts/CurrentUserContext"; +import db from "~/firebaseConfig"; + +export const useDrillInfo = (drillId = null) => { + const teamId = useContext(CurrentUserContext).currentTeam; + const { data, error, isLoading } = useQuery({ + queryKey: drillId ? ["drillInfo", teamId, drillId] : ["drillInfo", teamId], + queryFn: async () => { + if (drillId) { + // Fetch specific drill info + const docSnapshot = await getDoc( + doc(db, "teams", teamId, "drills", drillId), + ); + return docSnapshot.data(); + } else { + // Fetch all drills info + const newDrillInfo = {}; + const querySnapshot = await getDocs( + collection(db, "teams", teamId, "drills"), + ); + querySnapshot.forEach((doc) => { + newDrillInfo[doc.id] = doc.data(); + }); + return newDrillInfo; + } + }, + }); + + return { + data, + error, + isLoading, + }; +}; diff --git a/hooks/useUserInfo.js b/hooks/useUserInfo.js new file mode 100644 index 00000000..b88f494a --- /dev/null +++ b/hooks/useUserInfo.js @@ -0,0 +1,36 @@ +import { useQuery } from "@tanstack/react-query"; +import { collection, doc, getDoc, getDocs } from "firebase/firestore"; +import { useContext } from "react"; +import { CurrentUserContext } from "~/contexts/CurrentUserContext"; +import db from "~/firebaseConfig"; + +export const useUserInfo = (userId = null) => { + const teamId = useContext(CurrentUserContext).currentTeam; + + const { data, error, isLoading } = useQuery({ + queryKey: userId ? ["user", teamId, userId] : ["users", teamId], + queryFn: async () => { + if (userId) { + const querySnapshot = await getDoc( + doc(db, "teams", teamId, "users", userId), + ); + return querySnapshot.data(); + } else { + const newUserInfo = {}; + const querySnapshot = await getDocs( + collection(db, "teams", teamId, "users"), + ); + querySnapshot.forEach((doc) => { + newUserInfo[doc.id] = doc.data(); + }); + return newUserInfo; + } + }, + }); + + return { + data, + userError: error, + userIsLoading: isLoading, + }; +}; diff --git a/package.json b/package.json index e6994cab..bfe0e101 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,14 @@ "ios": "expo start --ios", "web": "expo start --web", "pretty": "prettier --write .", - "python": "./venv/bin/python" + "python": "./venv/bin/python", + "firestore": "firebase emulators:start --only firestore" }, "dependencies": { "@expo/ngrok": "^4.1.0", "@expo/webpack-config": "^19.0.0", "@gorhom/bottom-sheet": "^4", + "@tanstack/react-query": "^5.24.1", "babel-plugin-root-import": "^6.6.0", "d3-scale": "^4.0.2", "d3-shape": "^3.2.0", @@ -42,6 +44,7 @@ }, "devDependencies": { "@babel/core": "^7.20.0", + "@tanstack/eslint-plugin-query": "^5.20.1", "prettier": "3.2.5", "prettier-plugin-organize-imports": "^3.2.4" }, diff --git a/yarn.lock b/yarn.lock index 40ba96e0..cc389796 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1191,6 +1191,13 @@ dependencies: "@types/hammerjs" "^2.0.36" +"@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" + "@expo/bunyan@4.0.0", "@expo/bunyan@^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.0.tgz" @@ -2628,6 +2635,25 @@ dependencies: defer-to-connect "^2.0.0" +"@tanstack/eslint-plugin-query@^5.20.1": + version "5.20.1" + resolved "https://registry.yarnpkg.com/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.20.1.tgz#861afedd7cde6b98c88cf86a5923bb659122e7af" + integrity sha512-oIp7Wh90KHOm1FKCvcv87fiD2H96xo/crFrlhbvqBzR2f0tMEGOK/ANKMGNFQprd6BT6lyZhQPlOEkFdezsjIg== + dependencies: + "@typescript-eslint/utils" "^6.20.0" + +"@tanstack/query-core@5.24.1": + version "5.24.1" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.24.1.tgz#d40928dec22b47df97fb2648e8c499772e8d7eb2" + integrity sha512-DZ6Nx9p7BhjkG50ayJ+MKPgff+lMeol7QYXkvuU5jr2ryW/4ok5eanaS9W5eooA4xN0A/GPHdLGOZGzArgf5Cg== + +"@tanstack/react-query@^5.24.1": + version "5.24.1" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.24.1.tgz#bcb913febe0d813cec1fda7783298d07aa998b20" + integrity sha512-4+09JEdO4d6+Gc8Y/g2M/MuxDK5IY0QV8+2wL2304wPKJgJ54cBbULd3nciJ5uvh/as8rrxx6s0mtIwpRuGd1g== + dependencies: + "@tanstack/query-core" "5.24.1" + "@trysound/sax@0.2.0": version "0.2.0" resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" @@ -2768,7 +2794,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -2826,6 +2852,11 @@ resolved "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== +"@types/semver@^7.5.0": + version "7.5.8" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" + integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== + "@types/send@*": version "0.17.4" resolved "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz" @@ -2895,6 +2926,54 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== + +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== + dependencies: + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@^6.20.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" + +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== + dependencies: + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" + "@urql/core@2.3.6", "@urql/core@>=2.3.1": version "2.3.6" resolved "https://registry.npmjs.org/@urql/core/-/core-2.3.6.tgz" @@ -4788,6 +4867,11 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: + 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== + esprima@^4.0.0, esprima@~4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" @@ -5499,7 +5583,7 @@ globals@^11.1.0: resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globby@^11.0.1: +globby@^11.0.1, globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -7008,6 +7092,13 @@ minimalistic-assert@^1.0.0: dependencies: brace-expansion "^1.1.7" +minimatch@9.0.3, minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimatch@^5.0.1: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" @@ -7015,13 +7106,6 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1: - version "9.0.3" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" @@ -9356,6 +9440,11 @@ traverse@~0.6.6: resolved "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz" integrity sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA== +ts-api-utils@^1.0.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b" + integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"