From de35e1a8b7e3498ece42be387b042bc897e8b3c3 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Fri, 6 Sep 2024 14:57:23 +0100 Subject: [PATCH] Updated the MasterCorporate with the improved queries --- package.json | 5 +- src/dashboards/MasterCorporate.tsx | 116 +++++++++------------------ src/hooks/useUsers.tsx | 20 +++-- src/pages/(admin)/Lists/UserList.tsx | 2 +- src/pages/api/users/list.ts | 11 ++- src/pages/settings.tsx | 9 +-- src/utils/users.be.ts | 51 +++++++++--- yarn.lock | 85 ++++++++++---------- 8 files changed, 153 insertions(+), 146 deletions(-) diff --git a/package.json b/package.json index 90f3d697..99b697be 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@use-gesture/react": "^10.3.1", - "axios": "^1.3.5", + "axios": "^1", + "axios-cache-interceptor": "^1", "bcrypt": "^5.1.1", "chart.js": "^4.2.1", "class-variance-authority": "^0.7.0", @@ -80,7 +81,7 @@ "read-excel-file": "^5.7.1", "short-unique-id": "5.0.2", "stripe": "^13.10.0", - "swr": "^2.1.3", + "swr": "^2.2.5", "tailwind-merge": "^2.5.2", "tailwind-scrollbar-hide": "^1.1.7", "tailwindcss-animate": "^1.0.7", diff --git a/src/dashboards/MasterCorporate.tsx b/src/dashboards/MasterCorporate.tsx index 97fa2d5b..a6d4b97a 100644 --- a/src/dashboards/MasterCorporate.tsx +++ b/src/dashboards/MasterCorporate.tsx @@ -302,33 +302,30 @@ export default function MasterCorporateDashboard({user}: Props) { const [corporateAssignments, setCorporateAssignments] = useState<(Assignment & {corporate?: CorporateUser})[]>([]); const {data: stats} = useFilterRecordsByUser(); - const {users, reload} = useUsers(); + + const {users: students, reload: reloadStudents} = useUsers({type: "student"}); + const {users: teachers, reload: reloadTeachers} = useUsers({type: "teacher"}); + const {users: corporates, reload: reloadCorporates} = useUsers({type: "corporate"}); + const {groups} = useGroups({admin: user.id, userType: user.type}); const {balance} = useUserBalance(); - const masterCorporateUserGroups = useMemo( - () => [...new Set(groups.filter((u) => u.admin === user.id).flatMap((g) => g.participants))], - [groups, user.id], - ); - - const corporateUserGroups = useMemo(() => [...new Set(groups.flatMap((g) => g.participants))], [groups]); + const users = useMemo(() => [...students, ...teachers, ...corporates], [corporates, students, teachers]); const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); const assignmentsUsers = useMemo( () => - users.filter( - (x) => - (x.type === "student" || x.type === "teacher") && - (!!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) || false - : groups.flatMap((g) => g.participants).includes(x.id)), + [...students, ...teachers].filter((x) => + !!selectedUser + ? groups + .filter((g) => g.admin === selectedUser.id) + .flatMap((g) => g.participants) + .includes(x.id) || false + : groups.flatMap((g) => g.participants).includes(x.id), ), - [groups, users, selectedUser], + [groups, selectedUser, teachers, students], ); const appendUserFilters = useFilterStore((state) => state.appendUserFilter); @@ -340,17 +337,16 @@ export default function MasterCorporateDashboard({user}: Props) { useEffect(() => { setCorporateAssignments( - assignments.filter(activeAssignmentFilter).map((a) => ({ - ...a, - corporate: !!users.find((x) => x.id === a.assigner) - ? getCorporateUser(users.find((x) => x.id === a.assigner)!, users, groups) - : undefined, - })), - ); - }, [assignments, groups, users]); + assignments.filter(activeAssignmentFilter).map((a) => { + const assigner = [...teachers, ...corporates].find((x) => x.id === a.assigner); - const studentFilter = (user: User) => user.type === "student"; - const teacherFilter = (user: User) => user.type === "teacher"; + return { + ...a, + corporate: assigner ? getCorporateUser(assigner, [...teachers, ...corporates], groups) : undefined, + }; + }), + ); + }, [assignments, groups, teachers, corporates]); const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); const UserDisplay = (displayUser: User) => ( @@ -386,14 +382,6 @@ export default function MasterCorporateDashboard({user}: Props) { }; const StudentPerformancePage = () => { - const students = users - .filter((x) => x.type === "student" && groups.flatMap((g) => g.participants).includes(x.id)) - .map((u) => ({ - ...u, - group: groups.find((x) => x.participants.includes(u.id)), - corporate: getCorporateUser(u, users, groups), - })); - return ( <>
@@ -410,21 +398,11 @@ export default function MasterCorporateDashboard({user}: Props) {
- + ); }; - const masterCorporateUsers = useMemo( - () => - masterCorporateUserGroups.reduce((accm: CorporateUser[], id) => { - const user = users.find((u) => u.id === id) as CorporateUser; - if (user) return [...accm, user]; - return accm; - }, []), - [masterCorporateUserGroups, users], - ); - const MasterStatisticalPage = () => { return ( <> @@ -437,7 +415,7 @@ export default function MasterCorporateDashboard({user}: Props) {

Master Statistical

- + ); }; @@ -445,20 +423,8 @@ export default function MasterCorporateDashboard({user}: Props) { const DefaultDashboard = () => ( <>
- router.push("/#students")} - Icon={BsPersonFill} - label="Students" - value={users.filter(studentFilter).length} - color="purple" - /> - router.push("/#teachers")} - Icon={BsPencilSquare} - label="Teachers" - value={users.filter(teacherFilter).length} - color="purple" - /> + router.push("/#students")} Icon={BsPersonFill} label="Students" value={students.length} color="purple" /> + router.push("/#teachers")} Icon={BsPencilSquare} label="Teachers" value={teachers.length} color="purple" /> groups.flatMap((g) => g.participants).includes(s.user)), ).toFixed(1)} color="purple" @@ -487,17 +453,11 @@ export default function MasterCorporateDashboard({user}: Props) { value={user.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).format("DD/MM/yyyy") : "Unlimited"} color="rose" /> - router.push("/#corporate")} - /> + router.push("/#corporate")} /> router.push("/#studentsPerformance")} /> @@ -526,8 +486,7 @@ export default function MasterCorporateDashboard({user}: Props) {
Latest students
- {users - .filter(studentFilter) + {students .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) .map((x) => ( @@ -537,8 +496,7 @@ export default function MasterCorporateDashboard({user}: Props) {
Latest teachers
- {users - .filter(teacherFilter) + {teachers .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) .map((x) => ( @@ -548,8 +506,7 @@ export default function MasterCorporateDashboard({user}: Props) {
Highest level students
- {users - .filter(studentFilter) + {students .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) .map((x) => ( @@ -559,8 +516,7 @@ export default function MasterCorporateDashboard({user}: Props) {
Highest exam count students
- {users - .filter(studentFilter) + {students .sort( (a, b) => Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length, @@ -589,7 +545,9 @@ export default function MasterCorporateDashboard({user}: Props) { loggedInUser={user} onClose={(shouldReload) => { setSelectedUser(undefined); - if (shouldReload) reload(); + if (shouldReload && selectedUser!.type === "student") reloadStudents(); + if (shouldReload && selectedUser!.type === "teacher") reloadTeachers(); + if (shouldReload && selectedUser!.type === "corporate") reloadCorporates(); }} onViewStudents={ selectedUser.type === "corporate" || selectedUser.type === "teacher" diff --git a/src/hooks/useUsers.tsx b/src/hooks/useUsers.tsx index 48b41b64..6a0a5429 100644 --- a/src/hooks/useUsers.tsx +++ b/src/hooks/useUsers.tsx @@ -1,21 +1,31 @@ -import {User} from "@/interfaces/user"; -import axios from "axios"; +import {Type, User} from "@/interfaces/user"; +import Axios from "axios"; import {useEffect, useState} from "react"; +import {setupCache} from "axios-cache-interceptor"; -export default function useUsers() { +const instance = Axios.create(); +const axios = setupCache(instance); + +export default function useUsers(props?: {type?: Type; page?: number; size?: number}) { const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const getData = () => { + const params = new URLSearchParams(); + if (!!props) + Object.keys(props).forEach((key) => { + if (!!props[key as keyof typeof props]) params.append(key, props[key as keyof typeof props]!.toString()); + }); + setIsLoading(true); axios - .get("/api/users/list", {headers: {page: "register"}}) + .get(`/api/users/list?${params.toString()}`, {headers: {page: "register"}}) .then((response) => setUsers(response.data)) .finally(() => setIsLoading(false)); }; - useEffect(getData, []); + useEffect(getData, [props]); return {users, isLoading, isError, reload: getData}; } diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index d9bfbe8c..e79be11c 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -80,7 +80,7 @@ export default function UserList({ useEffect(() => { (async () => { - if (users) { + if (users && users.length > 0) { const filteredUsers = filters.reduce((d, f) => d.filter(f), users); const sortedUsers = await asyncSorter(filteredUsers, sortFunction); diff --git a/src/pages/api/users/list.ts b/src/pages/api/users/list.ts index 3140c9bf..c33e5ecc 100644 --- a/src/pages/api/users/list.ts +++ b/src/pages/api/users/list.ts @@ -3,6 +3,7 @@ import type {NextApiRequest, NextApiResponse} from "next"; import {withIronSessionApiRoute} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; import {getLinkedUsers} from "@/utils/users.be"; +import {Type} from "@/interfaces/user"; export default withIronSessionApiRoute(handler, sessionOptions); @@ -12,6 +13,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { return; } - const users = await getLinkedUsers(req.session.user?.id, req.session.user?.type); + const {page, size, type} = req.query as {page?: string; size?: string; type?: Type}; + const users = await getLinkedUsers( + req.session.user?.id, + req.session.user?.type, + type, + page !== undefined ? parseInt(page) : undefined, + size !== undefined ? parseInt(size) : undefined, + ); + res.status(200).json(users); } diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index 0e75039d..b26a6a5e 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -2,7 +2,6 @@ import Head from "next/head"; import {withIronSessionSsr} from "iron-session/next"; import {sessionOptions} from "@/lib/session"; -import useUser from "@/hooks/useUser"; import {ToastContainer} from "react-toastify"; import Layout from "@/components/High/Layout"; import CodeGenerator from "./(admin)/CodeGenerator"; @@ -28,6 +27,7 @@ import {User} from "@/interfaces/user"; import {getUserPermissions} from "@/utils/permissions.be"; import {Permission, PermissionType} from "@/interfaces/permissions"; import {getUsers} from "@/utils/users.be"; +import useUsers from "@/hooks/useUsers"; export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { const user = req.session.user; @@ -50,21 +50,20 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { } const permissions = await getUserPermissions(user.id); - const users = await getUsers(); return { - props: {user, permissions, users}, + props: {user, permissions}, }; }, sessionOptions); interface Props { user: User; - users: User[]; permissions: PermissionType[]; } -export default function Admin({user, users, permissions}: Props) { +export default function Admin({user, permissions}: Props) { const {gradingSystem, mutate} = useGradingSystem(); + const {users} = useUsers(); const [modalOpen, setModalOpen] = useState(); diff --git a/src/utils/users.be.ts b/src/utils/users.be.ts index 24c74138..7a72a2d0 100644 --- a/src/utils/users.be.ts +++ b/src/utils/users.be.ts @@ -1,6 +1,20 @@ import {app} from "@/firebase"; -import {collection, doc, getDoc, getDocs, getFirestore, query, where} from "firebase/firestore"; +import { + collection, + doc, + documentId, + endAt, + getDoc, + getDocs, + getFirestore, + limit, + orderBy, + query, + startAfter, + startAt, + where, +} from "firebase/firestore"; import {CorporateUser, Group, Type, User} from "@/interfaces/user"; import {getGroupsForUser} from "./groups.be"; import {uniq, uniqBy} from "lodash"; @@ -38,15 +52,16 @@ export async function getSpecificUsers(ids: string[]) { return groups; } -export async function getLinkedUsers(userID?: string, type?: Type) { - const snapshot = await getDocs(collection(db, "users")); - const users = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as User[]; +export async function getLinkedUsers(userID?: string, userType?: Type, type?: Type, page?: number, size?: number) { + if (!userID || userType === "admin" || userType === "developer") { + const snapshot = await getDocs(collection(db, "users")); + const users = snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) as User[]; - if (!userID) return users; - if (type === "admin" || type === "developer") return users; + return users; + } const adminGroups = await getGroupsForUser(userID); const groups = await Promise.all(adminGroups.flatMap((x) => x.participants).map(async (x) => await getGroupsForUser(x))); @@ -55,10 +70,24 @@ export async function getLinkedUsers(userID?: string, type?: Type) { const participants = uniq([ ...adminGroups.flatMap((x) => x.participants), ...groups.flat().flatMap((x) => x.participants), - ...(type === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []), + ...(userType === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []), ]); - return users.filter((x) => participants.includes(x.id) && x.id !== userID); + const q = [ + where(documentId(), "in", participants), + ...(!!type ? [where("type", "==", type)] : []), + orderBy(documentId()), + ...(page !== undefined && !!size ? [startAt(page * size)] : []), + ...(page !== undefined && !!size ? [limit(page + 1 * size)] : []), + ]; + + const snapshot = await getDocs(query(collection(db, "users"), ...q)); + const users = snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) as User[]; + + return users; } export async function getUserBalance(user: User) { diff --git a/yarn.lock b/yarn.lock index ebda6715..407edc16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2307,12 +2307,21 @@ axe-core@^4.6.2: resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz" integrity sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg== -axios@^1.3.5: - version "1.3.5" - resolved "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz" - integrity sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw== +axios-cache-interceptor@^1: + version "1.5.3" + resolved "https://registry.yarnpkg.com/axios-cache-interceptor/-/axios-cache-interceptor-1.5.3.tgz#2083fc68aacb915240e37edcb792b4fed63540be" + integrity sha512-kPgGId9XW7tR+VF7hgSkqF4f6FrV4ecCyKxjkD9v1hNJ4sXSAskocr7SMKaVHVvrbzVeruwB6yL6Y9/lY1ApKg== dependencies: - follow-redirects "^1.15.0" + cache-parser "1.2.5" + fast-defer "1.1.8" + object-code "1.3.3" + +axios@^1: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== + dependencies: + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -2526,6 +2535,11 @@ busboy@1.6.0: dependencies: streamsearch "^1.1.0" +cache-parser@1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/cache-parser/-/cache-parser-1.2.5.tgz#f19102a788b03055389730eb0493e463e1b379ac" + integrity sha512-Md/4VhAHByQ9frQ15WD6LrMNiVw9AEl/J7vWIXw+sxT6fSOpbtt6LHTp76vy8+bOESPBO94117Hm2bIjlI7XjA== + call-bind@^1.0.2, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -2627,7 +2641,7 @@ classnames@^2.2.6, classnames@^2.3.0, classnames@^2.5.1: resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== -client-only@0.0.1: +client-only@0.0.1, client-only@^0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== @@ -3582,6 +3596,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-defer@1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/fast-defer/-/fast-defer-1.1.8.tgz#940ef9597b2ea51c4cd08e99d0f2a8978fa49ba2" + integrity sha512-lEJeOH5VL5R09j6AA0D4Uvq7AgsHw0dAImQQ+F3iSyHZuAxyQfWobsagGpTcOPvJr3urmKRHrs+Gs9hV+/Qm/Q== + fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9: version "3.2.12" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" @@ -3750,10 +3769,10 @@ flatted@^3.1.0: resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== -follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== fontkit@^2.0.2: version "2.0.2" @@ -5335,6 +5354,11 @@ object-assign@^4.0.1, object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-code@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/object-code/-/object-code-1.3.3.tgz#cf21843ddfecce3ec73fd141f66a7f16ba0cb93e" + integrity sha512-/Ds4Xd5xzrtUOJ+xJQ57iAy0BZsZltOHssnDgcZ8DOhgh41q1YJCnTPnWdWSLkNGNnxYzhYChjc5dgC9mEERCA== + object-hash@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz" @@ -6426,16 +6450,7 @@ streamsearch@^1.1.0: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - 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-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6499,14 +6514,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6597,11 +6605,12 @@ svg-arc-to-cubic-bezier@^3.0.0, svg-arc-to-cubic-bezier@^3.2.0: resolved "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz" integrity sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g== -swr@^2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/swr/-/swr-2.1.3.tgz" - integrity sha512-g3ApxIM4Fjbd6vvEAlW60hJlKcYxHb+wtehogTygrh6Jsw7wNagv9m4Oj5Gq6zvvZw0tcyhVGL9L0oISvl3sUw== +swr@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.5.tgz#063eea0e9939f947227d5ca760cc53696f46446b" + integrity sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg== dependencies: + client-only "^0.0.1" use-sync-external-store "^1.2.0" synckit@^0.8.4: @@ -7093,7 +7102,8 @@ wordwrap@^1.0.0: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: + name wrap-ansi-cjs version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -7111,15 +7121,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"