Merge remote-tracking branch 'origin/develop' into feature/training-content

This commit is contained in:
Carlos Mesquita
2024-08-30 09:25:38 +01:00
34 changed files with 2667 additions and 3306 deletions

View File

@@ -66,7 +66,7 @@
"react-diff-viewer": "^3.1.1", "react-diff-viewer": "^3.1.1",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-firebase-hooks": "^5.1.1", "react-firebase-hooks": "^5.1.1",
"react-icons": "^4.8.0", "react-icons": "^5.3.0",
"react-lineto": "^3.3.0", "react-lineto": "^3.3.0",
"react-media-recorder": "1.6.5", "react-media-recorder": "1.6.5",
"react-phone-number-input": "^3.3.6", "react-phone-number-input": "^3.3.6",

View File

@@ -106,7 +106,7 @@ export default function DemographicInformationInput({user, mutateUser}: Props) {
<GenderInput value={gender} onChange={setGender} /> <GenderInput value={gender} onChange={setGender} />
{user.type === "corporate" && ( {user.type === "corporate" && (
<Input name="position" onChange={setPosition} type="text" label="Position" placeholder="CEO, Head of Marketing..." required /> <Input name="position" onChange={setPosition} type="text" label="Department" placeholder="CEO, Head of Marketing..." required />
)} )}
{user.type !== "corporate" && <EmploymentStatusInput value={employment} onChange={setEmployment} />} {user.type !== "corporate" && <EmploymentStatusInput value={employment} onChange={setEmployment} />}
</form> </form>

View File

@@ -1,80 +1,80 @@
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import { useEffect, useState } from "react"; import {useEffect, useState} from "react";
import { motion } from "framer-motion"; import {motion} from "framer-motion";
import TimerEndedModal from "../TimerEndedModal"; import TimerEndedModal from "../TimerEndedModal";
import clsx from "clsx"; import clsx from "clsx";
import { BsStopwatch } from "react-icons/bs"; import {BsStopwatch} from "react-icons/bs";
interface Props { interface Props {
minTimer: number; minTimer: number;
disableTimer?: boolean; disableTimer?: boolean;
standalone?: boolean; standalone?: boolean;
} }
const Timer: React.FC<Props> = ({minTimer, disableTimer, standalone = false}) => { const Timer: React.FC<Props> = ({minTimer, disableTimer, standalone = false}) => {
const [timer, setTimer] = useState(minTimer * 60); const [timer, setTimer] = useState(minTimer * 60);
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [warningMode, setWarningMode] = useState(false); const [warningMode, setWarningMode] = useState(false);
const setHasExamEnded = useExamStore((state) => state.setHasExamEnded); const setHasExamEnded = useExamStore((state) => state.setHasExamEnded);
const { timeSpent } = useExamStore((state) => state); const {timeSpent} = useExamStore((state) => state);
useEffect(() => setTimer((prev) => prev - timeSpent), [timeSpent]); useEffect(() => setTimer((prev) => prev - timeSpent), [timeSpent]);
useEffect(() => { useEffect(() => {
if (!disableTimer) { if (!disableTimer) {
const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000); const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000);
return () => { return () => {
clearInterval(timerInterval); clearInterval(timerInterval);
}; };
} }
}, [disableTimer, minTimer]); }, [disableTimer, minTimer]);
useEffect(() => { useEffect(() => {
if (timer <= 0) setShowModal(true); if (timer <= 0) setShowModal(true);
}, [timer]); }, [timer]);
useEffect(() => { useEffect(() => {
if (timer < 300 && !warningMode) setWarningMode(true); if (timer < 300 && !warningMode) setWarningMode(true);
}, [timer, warningMode]); }, [timer, warningMode]);
return ( return (
<> <>
<TimerEndedModal <TimerEndedModal
isOpen={showModal} isOpen={showModal}
onClose={() => { onClose={() => {
setHasExamEnded(true); setHasExamEnded(true);
setShowModal(false); setShowModal(false);
}} }}
/> />
<motion.div <motion.div
className={clsx( className={clsx(
"absolute right-6 bg-mti-gray-seasalt px-4 py-3 flex items-center gap-2 rounded-full text-mti-gray-davy", "absolute right-6 bg-mti-gray-seasalt px-4 py-3 flex items-center gap-2 rounded-full text-mti-gray-davy",
standalone ? "top-10" : "top-4", standalone ? "top-10" : "top-4",
warningMode && !disableTimer && "bg-mti-red-light text-mti-gray-seasalt", warningMode && !disableTimer && "bg-mti-red-light text-mti-gray-seasalt",
)} )}
initial={{ scale: warningMode && !disableTimer ? 0.8 : 1 }} initial={{scale: warningMode && !disableTimer ? 0.8 : 1}}
animate={{ scale: warningMode && !disableTimer ? 1.1 : 1 }} animate={{scale: warningMode && !disableTimer ? 1.1 : 1}}
transition={{ repeat: Infinity, repeatType: "reverse", duration: 0.5, ease: "easeInOut" }}> transition={{repeat: Infinity, repeatType: "reverse", duration: 0.5, ease: "easeInOut"}}>
<BsStopwatch className="w-6 h-6" /> <BsStopwatch className="w-6 h-6" />
<span className="text-base font-semibold w-12"> <span className="text-lg font-bold w-12">
{timer > 0 && ( {timer > 0 && (
<> <>
{Math.floor(timer / 60) {Math.floor(timer / 60)
.toString(10) .toString(10)
.padStart(2, "0")} .padStart(2, "0")}
: :
{Math.floor(timer % 60) {Math.floor(timer % 60)
.toString(10) .toString(10)
.padStart(2, "0")} .padStart(2, "0")}
</> </>
)} )}
{timer <= 0 && <>00:00</>} {timer <= 0 && <>00:00</>}
</span> </span>
</motion.div> </motion.div>
</> </>
); );
} };
export default Timer; export default Timer;

View File

@@ -1,219 +1,163 @@
import { User } from "@/interfaces/user"; import {User} from "@/interfaces/user";
import Link from "next/link"; import Link from "next/link";
import FocusLayer from "@/components/FocusLayer"; import FocusLayer from "@/components/FocusLayer";
import { preventNavigation } from "@/utils/navigation.disabled"; import {preventNavigation} from "@/utils/navigation.disabled";
import { useRouter } from "next/router"; import {useRouter} from "next/router";
import { BsList, BsQuestionCircle, BsQuestionCircleFill } from "react-icons/bs"; import {BsList, BsQuestionCircle, BsQuestionCircleFill} from "react-icons/bs";
import clsx from "clsx"; import clsx from "clsx";
import moment from "moment"; import moment from "moment";
import MobileMenu from "./MobileMenu"; import MobileMenu from "./MobileMenu";
import { useEffect, useState } from "react"; import {useEffect, useState} from "react";
import { Type } from "@/interfaces/user"; import {Type} from "@/interfaces/user";
import { USER_TYPE_LABELS } from "@/resources/user"; import {USER_TYPE_LABELS} from "@/resources/user";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
import { isUserFromCorporate } from "@/utils/groups"; import {isUserFromCorporate} from "@/utils/groups";
import Button from "./Low/Button"; import Button from "./Low/Button";
import Modal from "./Modal"; import Modal from "./Modal";
import Input from "./Low/Input"; import Input from "./Low/Input";
import TicketSubmission from "./High/TicketSubmission"; import TicketSubmission from "./High/TicketSubmission";
import { Module } from "@/interfaces"; import {Module} from "@/interfaces";
import Badge from "./Low/Badge"; import Badge from "./Low/Badge";
import { import {BsArrowRepeat, BsBook, BsCheck, BsCheckCircle, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle} from "react-icons/bs";
BsArrowRepeat,
BsBook,
BsCheck,
BsCheckCircle,
BsClipboard,
BsHeadphones,
BsMegaphone,
BsPen,
BsXCircle,
} from "react-icons/bs";
interface Props { interface Props {
user: User; user: User;
navDisabled?: boolean; navDisabled?: boolean;
focusMode?: boolean; focusMode?: boolean;
onFocusLayerMouseEnter?: () => void; onFocusLayerMouseEnter?: () => void;
path: string; path: string;
} }
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
export default function Navbar({ export default function Navbar({user, path, navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) {
user, const [isMenuOpen, setIsMenuOpen] = useState(false);
path, const [disablePaymentPage, setDisablePaymentPage] = useState(true);
navDisabled = false, const [isTicketOpen, setIsTicketOpen] = useState(false);
focusMode = false,
onFocusLayerMouseEnter,
}: Props) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [disablePaymentPage, setDisablePaymentPage] = useState(true);
const [isTicketOpen, setIsTicketOpen] = useState(false);
const router = useRouter(); const router = useRouter();
const disableNavigation = preventNavigation(navDisabled, focusMode); const disableNavigation = preventNavigation(navDisabled, focusMode);
const expirationDateColor = (date: Date) => { const expirationDateColor = (date: Date) => {
const momentDate = moment(date); const momentDate = moment(date);
const today = moment(new Date()); const today = moment(new Date());
if (today.add(1, "days").isAfter(momentDate)) if (today.add(1, "days").isAfter(momentDate)) return "!bg-mti-red-ultralight border-mti-red-light";
return "!bg-mti-red-ultralight border-mti-red-light"; if (today.add(3, "days").isAfter(momentDate)) return "!bg-mti-rose-ultralight border-mti-rose-light";
if (today.add(3, "days").isAfter(momentDate)) if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light";
return "!bg-mti-rose-ultralight border-mti-rose-light"; };
if (today.add(7, "days").isAfter(momentDate))
return "!bg-mti-orange-ultralight border-mti-orange-light";
};
const showExpirationDate = () => { const showExpirationDate = () => {
if (!user.subscriptionExpirationDate) return false; if (!user.subscriptionExpirationDate) return false;
const momentDate = moment(user.subscriptionExpirationDate); const momentDate = moment(user.subscriptionExpirationDate);
const today = moment(new Date()); const today = moment(new Date());
return today.add(7, "days").isAfter(momentDate); return today.add(7, "days").isAfter(momentDate);
}; };
useEffect(() => { useEffect(() => {
if (user.type !== "student" && user.type !== "teacher") if (user.type !== "student" && user.type !== "teacher") return setDisablePaymentPage(false);
return setDisablePaymentPage(false); isUserFromCorporate(user.id).then((result) => setDisablePaymentPage(result));
isUserFromCorporate(user.id).then((result) => }, [user]);
setDisablePaymentPage(result)
);
}, [user]);
const badges = [ const badges = [
{ {
module: "reading", module: "reading",
icon: () => <BsBook className="h-4 w-4 text-white" />, icon: () => <BsBook className="h-4 w-4 text-white" />,
achieved: user.levels.reading >= user.desiredLevels.reading, achieved: user.levels.reading >= user.desiredLevels.reading,
}, },
{ {
module: "listening", module: "listening",
icon: () => <BsHeadphones className="h-4 w-4 text-white" />, icon: () => <BsHeadphones className="h-4 w-4 text-white" />,
achieved: user.levels.listening >= user.desiredLevels.listening, achieved: user.levels.listening >= user.desiredLevels.listening,
}, },
{ {
module: "writing", module: "writing",
icon: () => <BsPen className="h-4 w-4 text-white" />, icon: () => <BsPen className="h-4 w-4 text-white" />,
achieved: user.levels.writing >= user.desiredLevels.writing, achieved: user.levels.writing >= user.desiredLevels.writing,
}, },
{ {
module: "speaking", module: "speaking",
icon: () => <BsMegaphone className="h-4 w-4 text-white" />, icon: () => <BsMegaphone className="h-4 w-4 text-white" />,
achieved: user.levels.speaking >= user.desiredLevels.speaking, achieved: user.levels.speaking >= user.desiredLevels.speaking,
}, },
{ {
module: "level", module: "level",
icon: () => <BsClipboard className="h-4 w-4 text-white" />, icon: () => <BsClipboard className="h-4 w-4 text-white" />,
achieved: user.levels.level >= user.desiredLevels.level, achieved: user.levels.level >= user.desiredLevels.level,
}, },
]; ];
return ( return (
<> <>
<Modal <Modal isOpen={isTicketOpen} onClose={() => setIsTicketOpen(false)} title="Submit a ticket">
isOpen={isTicketOpen} <TicketSubmission user={user} page={router.asPath} onClose={() => setIsTicketOpen(false)} />
onClose={() => setIsTicketOpen(false)} </Modal>
title="Submit a ticket"
>
<TicketSubmission
user={user}
page={router.asPath}
onClose={() => setIsTicketOpen(false)}
/>
</Modal>
{user && ( {user && (
<MobileMenu <MobileMenu disableNavigation={disableNavigation} path={path} isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)} user={user} />
disableNavigation={disableNavigation} )}
path={path} <header className="-md:justify-between -md:px-4 relative flex w-full items-center bg-transparent py-2 md:gap-12 md:py-4">
isOpen={isMenuOpen} <Link href={disableNavigation ? "" : "/"} className=" flex items-center gap-8 md:px-8">
onClose={() => setIsMenuOpen(false)} <img src="/logo.png" alt="EnCoach's Logo" className="w-8 md:w-12" />
user={user} <h1 className="-md:hidden w-1/6 text-2xl font-bold">EnCoach</h1>
/> </Link>
)} <div className="flex items-center justify-end gap-4 md:mr-8 md:w-5/6">
<header className="-md:justify-between -md:px-4 relative flex w-full items-center bg-transparent py-2 md:gap-12 md:py-4"> {user.type === "student" &&
<Link badges.map((badge) => (
href={disableNavigation ? "" : "/"} <div
className=" flex items-center gap-8 md:px-8" key={badge.module}
> className={`${
<img src="/logo.png" alt="EnCoach's Logo" className="w-8 md:w-12" /> badge.achieved ? `bg-ielts-${badge.module}` : "bg-mti-gray-anti-flash"
<h1 className="-md:hidden w-1/6 text-2xl font-bold">EnCoach</h1> } flex h-8 w-8 items-center justify-center rounded-full`}>
</Link> {badge.icon()}
<div className="flex items-center justify-end gap-4 md:mr-8 md:w-5/6"> </div>
{user.type === "student" && ))}
badges.map((badge) => ( {/* OPEN TICKET SYSTEM */}
<div <button
key={badge.module} className={clsx(
className={`${badge.achieved ? `bg-ielts-${badge.module}`: 'bg-mti-gray-anti-flash'} flex h-8 w-8 items-center justify-center rounded-full`} "border-mti-purple-light tooltip tooltip-bottom flex h-8 w-8 flex-col items-center justify-center rounded-full border p-1",
> "hover:bg-mti-purple-light transition duration-300 ease-in-out hover:text-white z-20",
{badge.icon()} )}
</div> data-tip="Submit a help/feedback ticket"
))} onClick={() => setIsTicketOpen(true)}>
{/* OPEN TICKET SYSTEM */} <BsQuestionCircleFill />
<button </button>
className={clsx(
"border-mti-purple-light tooltip tooltip-bottom flex h-8 w-8 flex-col items-center justify-center rounded-full border p-1",
"hover:bg-mti-purple-light transition duration-300 ease-in-out hover:text-white z-20"
)}
data-tip="Submit a help/feedback ticket"
onClick={() => setIsTicketOpen(true)}
>
<BsQuestionCircleFill />
</button>
{showExpirationDate() && ( {showExpirationDate() && (
<Link <Link
href={ href={!!user.subscriptionExpirationDate && !disablePaymentPage ? "/payment" : ""}
!!user.subscriptionExpirationDate && !disablePaymentPage data-tip="Expiry date"
? "/payment" className={clsx(
: "" "flex w-fit cursor-pointer justify-center rounded-full border px-6 py-2 text-sm font-normal focus:outline-none",
} "tooltip tooltip-bottom transition duration-300 ease-in-out",
data-tip="Expiry date" !user.subscriptionExpirationDate
className={clsx( ? "bg-mti-green-ultralight border-mti-green-light"
"flex w-fit cursor-pointer justify-center rounded-full border px-6 py-2 text-sm font-normal focus:outline-none", : expirationDateColor(user.subscriptionExpirationDate),
"tooltip tooltip-bottom transition duration-300 ease-in-out", "border-mti-gray-platinum bg-white",
!user.subscriptionExpirationDate )}>
? "bg-mti-green-ultralight border-mti-green-light" {!user.subscriptionExpirationDate && "Unlimited"}
: expirationDateColor(user.subscriptionExpirationDate), {user.subscriptionExpirationDate && moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")}
"border-mti-gray-platinum bg-white" </Link>
)} )}
> <Link href={disableNavigation ? "" : "/profile"} className="-md:hidden flex items-center justify-end gap-6">
{!user.subscriptionExpirationDate && "Unlimited"} <img src={user.profilePicture} alt={user.name} className="h-10 w-10 rounded-full object-cover" />
{user.subscriptionExpirationDate && <span className="-md:hidden text-right">
moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")} {user.type === "corporate" ? `${user.corporateInformation?.companyInformation.name} |` : ""} {user.name} |{" "}
</Link> {USER_TYPE_LABELS[user.type]}
)} {user.type === "corporate" &&
<Link !!user.demographicInformation?.position &&
href={disableNavigation ? "" : "/profile"} ` | ${user.demographicInformation?.position || "N/A"}`}
className="-md:hidden flex items-center justify-end gap-6" </span>
> </Link>
<img <div className="cursor-pointer md:hidden" onClick={() => setIsMenuOpen(true)}>
src={user.profilePicture} <BsList className="text-mti-purple-light h-8 w-8" />
alt={user.name} </div>
className="h-10 w-10 rounded-full object-cover" </div>
/> {focusMode && <FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />}
<span className="-md:hidden text-right"> </header>
{user.type === "corporate" </>
? `${user.corporateInformation?.companyInformation.name} |` );
: ""}{" "}
{user.name} | {USER_TYPE_LABELS[user.type]}
</span>
</Link>
<div
className="cursor-pointer md:hidden"
onClick={() => setIsMenuOpen(true)}
>
<BsList className="text-mti-purple-light h-8 w-8" />
</div>
</div>
{focusMode && (
<FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />
)}
</header>
</>
);
} }

View File

@@ -212,7 +212,7 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u
)} )}
</div> </div>
<div className="fixed bottom-12 flex flex-col gap-0"> <div className="2xl:fixed bottom-12 flex flex-col gap-0 -2xl:mt-8">
<div <div
role="button" role="button"
tabIndex={1} tabIndex={1}

View File

@@ -140,8 +140,9 @@ const UserCard = ({
}, [users, referralAgent]); }, [users, referralAgent]);
const updateUser = () => { const updateUser = () => {
if (user.type === "corporate" || (user.type === "mastercorporate" && (!paymentValue || paymentValue < 0))) if ((user.type === "corporate" || user.type === "mastercorporate") && (!paymentValue || paymentValue < 0))
return toast.error("Please set a price for the user's package before updating!"); return toast.error("Please set a price for the user's package before updating!");
if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) return; if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) return;
axios axios
@@ -213,7 +214,7 @@ const UserCard = ({
}, },
{ {
icon: <BsPersonAdd className="w-6 h-6 md:w-8 md:h-8 text-mti-red-light" />, icon: <BsPersonAdd className="w-6 h-6 md:w-8 md:h-8 text-mti-red-light" />,
value: user.corporateInformation.companyInformation.userAmount, value: user.corporateInformation?.companyInformation?.userAmount,
label: "Number of Users", label: "Number of Users",
}, },
] ]
@@ -277,7 +278,15 @@ const UserCard = ({
onChange={setCompanyName} onChange={setCompanyName}
placeholder="Enter corporate name" placeholder="Enter corporate name"
defaultValue={companyName} defaultValue={companyName}
disabled={disabled || checkAccess(loggedInUser, getTypesOfUser(["developer", "admin"]))} disabled={
disabled ||
checkAccess(
loggedInUser,
getTypesOfUser(
user.type === "mastercorporate" ? ["developer", "admin"] : ["developer", "admin", "mastercorporate"],
),
)
}
/> />
<Input <Input
label="Number of Users" label="Number of Users"
@@ -508,7 +517,7 @@ const UserCard = ({
name="position" name="position"
onChange={setPosition} onChange={setPosition}
type="text" type="text"
label="Position" label="Department"
defaultValue={position} defaultValue={position}
placeholder="CEO, Head of Marketing..." placeholder="CEO, Head of Marketing..."
disabled disabled

View File

@@ -24,14 +24,13 @@ import useExams from "@/hooks/useExams";
interface Props { interface Props {
isCreating: boolean; isCreating: boolean;
assigner: string;
users: User[]; users: User[];
groups: Group[]; groups: Group[];
assignment?: Assignment; assignment?: Assignment;
cancelCreation: () => void; cancelCreation: () => void;
} }
export default function AssignmentCreator({isCreating, assignment, assigner, groups, users, cancelCreation}: Props) { export default function AssignmentCreator({isCreating, assignment, groups, users, cancelCreation}: Props) {
const [selectedModules, setSelectedModules] = useState<Module[]>(assignment?.exams.map((e) => e.module) || []); const [selectedModules, setSelectedModules] = useState<Module[]>(assignment?.exams.map((e) => e.module) || []);
const [assignees, setAssignees] = useState<string[]>(assignment?.assignees || []); const [assignees, setAssignees] = useState<string[]>(assignment?.assignees || []);
const [name, setName] = useState( const [name, setName] = useState(

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {Invite} from "@/interfaces/invite"; import {Invite} from "@/interfaces/invite";
import {Assignment} from "@/interfaces/results"; import {Assignment} from "@/interfaces/results";
import {CorporateUser, Stat, User} from "@/interfaces/user"; import {CorporateUser, MasterCorporateUser, Stat, User} from "@/interfaces/user";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import {getExamById} from "@/utils/exams"; import {getExamById} from "@/utils/exams";
import {getUserCorporate} from "@/utils/groups"; import {getUserCorporate} from "@/utils/groups";
@@ -30,11 +30,10 @@ import {toast} from "react-toastify";
interface Props { interface Props {
user: User; user: User;
linkedCorporate?: CorporateUser | MasterCorporateUser;
} }
export default function StudentDashboard({user}: Props) { export default function StudentDashboard({user, linkedCorporate}: Props) {
const [corporateUserToShow, setCorporateUserToShow] = useState<CorporateUser>();
const {users} = useUsers(); const {users} = useUsers();
const {gradingSystem} = useGradingSystem(); const {gradingSystem} = useGradingSystem();
const {data: stats} = useFilterRecordsByUser<Stat[]>(user.id, !user?.id); const {data: stats} = useFilterRecordsByUser<Stat[]>(user.id, !user?.id);
@@ -49,10 +48,6 @@ export default function StudentDashboard({user}: Props) {
const setSelectedModules = useExamStore((state) => state.setSelectedModules); const setSelectedModules = useExamStore((state) => state.setSelectedModules);
const setAssignment = useExamStore((state) => state.setAssignment); const setAssignment = useExamStore((state) => state.setAssignment);
useEffect(() => {
getUserCorporate(user.id).then(setCorporateUserToShow);
}, [user]);
const startAssignment = (assignment: Assignment) => { const startAssignment = (assignment: Assignment) => {
const examPromises = assignment.exams.filter((e) => e.assignee === user.id).map((e) => getExamById(e.module, e.id)); const examPromises = assignment.exams.filter((e) => e.assignee === user.id).map((e) => getExamById(e.module, e.id));
@@ -76,9 +71,9 @@ export default function StudentDashboard({user}: Props) {
return ( return (
<> <>
{corporateUserToShow && ( {linkedCorporate && (
<div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1"> <div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1">
Linked to: <b>{corporateUserToShow?.corporateInformation?.companyInformation.name || corporateUserToShow.name}</b> Linked to: <b>{linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name}</b>
</div> </div>
)} )}
<ProfileSummary <ProfileSummary

View File

@@ -2,486 +2,406 @@
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import { CorporateUser, Group, Stat, User } from "@/interfaces/user"; import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user";
import UserList from "@/pages/(admin)/Lists/UserList"; import UserList from "@/pages/(admin)/Lists/UserList";
import { dateSorter } from "@/utils"; import {dateSorter} from "@/utils";
import moment from "moment"; import moment from "moment";
import { useEffect, useState } from "react"; import {useEffect, useMemo, useState} from "react";
import { import {
BsArrowLeft, BsArrowLeft,
BsArrowRepeat, BsArrowRepeat,
BsClipboard2Data, BsClipboard2Data,
BsClipboard2DataFill, BsClipboard2DataFill,
BsClipboard2Heart, BsClipboard2Heart,
BsClipboard2X, BsClipboard2X,
BsClipboardPulse, BsClipboardPulse,
BsClock, BsClock,
BsEnvelopePaper, BsEnvelopePaper,
BsGlobeCentralSouthAsia, BsGlobeCentralSouthAsia,
BsPaperclip, BsPaperclip,
BsPeople, BsPeople,
BsPerson, BsPerson,
BsPersonAdd, BsPersonAdd,
BsPersonFill, BsPersonFill,
BsPersonFillGear, BsPersonFillGear,
BsPersonGear, BsPersonGear,
BsPlus, BsPlus,
BsRepeat, BsRepeat,
BsRepeat1, BsRepeat1,
} from "react-icons/bs"; } from "react-icons/bs";
import UserCard from "@/components/UserCard"; import UserCard from "@/components/UserCard";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
import { calculateAverageLevel, calculateBandScore } from "@/utils/score"; import {calculateAverageLevel, calculateBandScore} from "@/utils/score";
import { MODULE_ARRAY } from "@/utils/moduleUtils"; import {MODULE_ARRAY} from "@/utils/moduleUtils";
import { Module } from "@/interfaces"; import {Module} from "@/interfaces";
import { groupByExam } from "@/utils/stats"; import {groupByExam} from "@/utils/stats";
import IconCard from "./IconCard"; import IconCard from "./IconCard";
import GroupList from "@/pages/(admin)/Lists/GroupList"; import GroupList from "@/pages/(admin)/Lists/GroupList";
import useAssignments from "@/hooks/useAssignments"; import useAssignments from "@/hooks/useAssignments";
import { Assignment } from "@/interfaces/results"; import {Assignment} from "@/interfaces/results";
import AssignmentCard from "./AssignmentCard"; import AssignmentCard from "./AssignmentCard";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import clsx from "clsx"; import clsx from "clsx";
import ProgressBar from "@/components/Low/ProgressBar"; import ProgressBar from "@/components/Low/ProgressBar";
import AssignmentCreator from "./AssignmentCreator"; import AssignmentCreator from "./AssignmentCreator";
import AssignmentView from "./AssignmentView"; import AssignmentView from "./AssignmentView";
import { getUserCorporate } from "@/utils/groups"; import {getUserCorporate} from "@/utils/groups";
import { checkAccess } from "@/utils/permissions"; import {checkAccess} from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import { import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments";
futureAssignmentFilter,
pastAssignmentFilter,
archivedAssignmentFilter,
activeAssignmentFilter
} from '@/utils/assignments';
interface Props { interface Props {
user: User; user: User;
linkedCorporate?: CorporateUser | MasterCorporateUser;
} }
export default function TeacherDashboard({ user }: Props) { export default function TeacherDashboard({user, linkedCorporate}: Props) {
const [page, setPage] = useState(""); const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>(); const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [selectedAssignment, setSelectedAssignment] = useState<Assignment>(); const [selectedAssignment, setSelectedAssignment] = useState<Assignment>();
const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); const [isCreatingAssignment, setIsCreatingAssignment] = useState(false);
const [corporateUserToShow, setCorporateUserToShow] =
useState<CorporateUser>();
const { data: stats } = useFilterRecordsByUser<Stat[]>(); const {data: stats} = useFilterRecordsByUser<Stat[]>();
const { users, reload } = useUsers(); const {users, reload} = useUsers();
const { groups } = useGroups({ adminAdmins: user.id }); const {groups} = useGroups({adminAdmins: user.id});
const { permissions } = usePermissions(user.id); const {permissions} = usePermissions(user.id);
const { const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id});
assignments,
isLoading: isAssignmentsLoading,
reload: reloadAssignments,
} = useAssignments({ assigner: user.id });
useEffect(() => { const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]);
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
useEffect(() => { const assignmentsUsers = useMemo(
getUserCorporate(user.id).then(setCorporateUserToShow); () =>
}, [user]); users.filter(
(x) =>
x.type === "student" &&
(!!selectedUser
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id)
: groups.flatMap((g) => g.participants).includes(x.id)),
),
[groups, users, selectedUser],
);
const studentFilter = (user: User) => useEffect(() => {
user.type === "student" && setShowModal(!!selectedUser && page === "");
groups.flatMap((g) => g.participants).includes(user.id); }, [selectedUser, page]);
const getStatsByStudent = (user: User) => const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id);
stats.filter((s) => s.user === user.id);
const UserDisplay = (displayUser: User) => ( const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id);
<div
onClick={() => setSelectedUser(displayUser)}
className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300"
>
<img
src={displayUser.profilePicture}
alt={displayUser.name}
className="rounded-full w-10 h-10"
/>
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
const StudentsList = () => { const UserDisplay = (displayUser: User) => (
const filter = (x: User) => <div
x.type === "student" && onClick={() => setSelectedUser(displayUser)}
(!!selectedUser className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300">
? groups <img src={displayUser.profilePicture} alt={displayUser.name} className="rounded-full w-10 h-10" />
.filter((g) => g.admin === selectedUser.id) <div className="flex flex-col gap-1 items-start">
.flatMap((g) => g.participants) <span>{displayUser.name}</span>
.includes(x.id) || false <span className="text-sm opacity-75">{displayUser.email}</span>
: groups.flatMap((g) => g.participants).includes(x.id)); </div>
</div>
);
return ( const StudentsList = () => {
<UserList const filter = (x: User) =>
user={user} x.type === "student" &&
filters={[filter]} (!!selectedUser
renderHeader={(total) => ( ? groups
<div className="flex flex-col gap-4"> .filter((g) => g.admin === selectedUser.id)
<div .flatMap((g) => g.participants)
onClick={() => setPage("")} .includes(x.id) || false
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300" : groups.flatMap((g) => g.participants).includes(x.id));
>
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
);
};
const GroupsList = () => { return (
const filter = (x: Group) => x.admin === user.id; <UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
);
};
return ( const GroupsList = () => {
<> const filter = (x: Group) => x.admin === user.id;
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
>
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">
Groups ({groups.filter(filter).length})
</h2>
</div>
<GroupList user={user} /> return (
</> <>
); <div className="flex flex-col gap-4">
}; <div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Groups ({groups.filter(filter).length})</h2>
</div>
const averageLevelCalculator = (studentStats: Stat[]) => { <GroupList user={user} />
const formattedStats = studentStats </>
.map((s) => ({ );
focus: users.find((u) => u.id === s.user)?.focus, };
score: s.score,
module: s.module,
}))
.filter((f) => !!f.focus);
const bandScores = formattedStats.map((s) => ({
module: s.module,
level: calculateBandScore(
s.score.correct,
s.score.total,
s.module,
s.focus!
),
}));
const levels: { [key in Module]: number } = { const averageLevelCalculator = (studentStats: Stat[]) => {
reading: 0, const formattedStats = studentStats
listening: 0, .map((s) => ({
writing: 0, focus: users.find((u) => u.id === s.user)?.focus,
speaking: 0, score: s.score,
level: 0, module: s.module,
}; }))
bandScores.forEach((b) => (levels[b.module] += b.level)); .filter((f) => !!f.focus);
const bandScores = formattedStats.map((s) => ({
module: s.module,
level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!),
}));
return calculateAverageLevel(levels); const levels: {[key in Module]: number} = {
}; reading: 0,
listening: 0,
writing: 0,
speaking: 0,
level: 0,
};
bandScores.forEach((b) => (levels[b.module] += b.level));
const AssignmentsPage = () => { return calculateAverageLevel(levels);
return ( };
<>
<AssignmentView
isOpen={!!selectedAssignment && !isCreatingAssignment}
onClose={() => {
setSelectedAssignment(undefined);
setIsCreatingAssignment(false);
reloadAssignments();
}}
assignment={selectedAssignment}
/>
<AssignmentCreator
assignment={selectedAssignment}
groups={groups.filter(
(x) => x.admin === user.id || x.participants.includes(user.id)
)}
users={users.filter(
(x) =>
x.type === "student" &&
(!!selectedUser
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id)
: groups.flatMap((g) => g.participants).includes(x.id))
)}
assigner={user.id}
isCreating={isCreatingAssignment}
cancelCreation={() => {
setIsCreatingAssignment(false);
setSelectedAssignment(undefined);
reloadAssignments();
}}
/>
<div className="w-full flex justify-between items-center">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
>
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<div
onClick={reloadAssignments}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
>
<span>Reload</span>
<BsArrowRepeat
className={clsx(
"text-xl",
isAssignmentsLoading && "animate-spin"
)}
/>
</div>
</div>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Active Assignments ({assignments.filter(activeAssignmentFilter).length})
</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(activeAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Planned Assignments ({assignments.filter(futureAssignmentFilter).length})
</h2>
<div className="flex flex-wrap gap-2">
<div
onClick={() => setIsCreatingAssignment(true)}
className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300"
>
<BsPlus className="text-6xl" />
<span className="text-lg">New Assignment</span>
</div>
{assignments.filter(futureAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => {
setSelectedAssignment(a);
setIsCreatingAssignment(true);
}}
key={a.id}
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Past Assignments ({assignments.filter(pastAssignmentFilter).length})
</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(pastAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowArchive
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">
Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})
</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(archivedAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
);
};
const DefaultDashboard = () => ( const AssignmentsPage = () => {
<> return (
{corporateUserToShow && ( <>
<div className="absolute top-4 right-4 bg-neutral-200 px-2 rounded-lg py-1"> <AssignmentView
Linked to:{" "} isOpen={!!selectedAssignment && !isCreatingAssignment}
<b> onClose={() => {
{corporateUserToShow?.corporateInformation?.companyInformation setSelectedAssignment(undefined);
.name || corporateUserToShow.name} setIsCreatingAssignment(false);
</b> reloadAssignments();
</div> }}
)} assignment={selectedAssignment}
<section />
className={clsx( <AssignmentCreator
"flex -lg:flex-wrap gap-4 items-center -lg:justify-center lg:justify-start text-center", assignment={selectedAssignment}
!!corporateUserToShow && "mt-12 xl:mt-6" groups={assignmentsGroups}
)} users={assignmentsUsers}
> isCreating={isCreatingAssignment}
<IconCard cancelCreation={() => {
onClick={() => setPage("students")} setIsCreatingAssignment(false);
Icon={BsPersonFill} setSelectedAssignment(undefined);
label="Students" reloadAssignments();
value={users.filter(studentFilter).length} }}
color="purple" />
/> <div className="w-full flex justify-between items-center">
<IconCard <div
Icon={BsClipboard2Data} onClick={() => setPage("")}
label="Exams Performed" className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
value={ <BsArrowLeft className="text-xl" />
stats.filter((s) => <span>Back</span>
groups.flatMap((g) => g.participants).includes(s.user) </div>
).length <div
} onClick={reloadAssignments}
color="purple" className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300">
/> <span>Reload</span>
<IconCard <BsArrowRepeat className={clsx("text-xl", isAssignmentsLoading && "animate-spin")} />
Icon={BsPaperclip} </div>
label="Average Level" </div>
value={averageLevelCalculator( <section className="flex flex-col gap-4">
stats.filter((s) => <h2 className="text-2xl font-semibold">Active Assignments ({assignments.filter(activeAssignmentFilter).length})</h2>
groups.flatMap((g) => g.participants).includes(s.user) <div className="flex flex-wrap gap-2">
) {assignments.filter(activeAssignmentFilter).map((a) => (
).toFixed(1)} <AssignmentCard {...a} users={users} onClick={() => setSelectedAssignment(a)} key={a.id} />
color="purple" ))}
/> </div>
{checkAccess( </section>
user, <section className="flex flex-col gap-4">
["teacher", "developer"], <h2 className="text-2xl font-semibold">Planned Assignments ({assignments.filter(futureAssignmentFilter).length})</h2>
permissions, <div className="flex flex-wrap gap-2">
"viewGroup" <div
) && ( onClick={() => setIsCreatingAssignment(true)}
<IconCard className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300">
Icon={BsPeople} <BsPlus className="text-6xl" />
label="Groups" <span className="text-lg">New Assignment</span>
value={groups.filter((x) => x.admin === user.id).length} </div>
color="purple" {assignments.filter(futureAssignmentFilter).map((a) => (
onClick={() => setPage("groups")} <AssignmentCard
/> {...a}
)} users={users}
<div onClick={() => {
onClick={() => setPage("assignments")} setSelectedAssignment(a);
className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300" setIsCreatingAssignment(true);
> }}
<BsEnvelopePaper className="text-6xl text-mti-purple-light" /> key={a.id}
<span className="flex flex-col gap-1 items-center text-xl"> />
<span className="text-lg">Assignments</span> ))}
<span className="font-semibold text-mti-purple-light"> </div>
{assignments.filter((a) => !a.archived).length} </section>
</span> <section className="flex flex-col gap-4">
</span> <h2 className="text-2xl font-semibold">Past Assignments ({assignments.filter(pastAssignmentFilter).length})</h2>
</div> <div className="flex flex-wrap gap-2">
</section> {assignments.filter(pastAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowArchive
allowExcelDownload
/>
))}
</div>
</section>
<section className="flex flex-col gap-4">
<h2 className="text-2xl font-semibold">Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})</h2>
<div className="flex flex-wrap gap-2">
{assignments.filter(archivedAssignmentFilter).map((a) => (
<AssignmentCard
{...a}
users={users}
onClick={() => setSelectedAssignment(a)}
key={a.id}
allowDownload
reload={reloadAssignments}
allowUnarchive
allowExcelDownload
/>
))}
</div>
</section>
</>
);
};
<section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 w-full justify-between"> const DefaultDashboard = () => (
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <>
<span className="p-4">Latest students</span> {linkedCorporate && (
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="absolute top-4 right-4 bg-neutral-200 px-2 rounded-lg py-1">
{users Linked to: <b>{linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name}</b>
.filter(studentFilter) </div>
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) )}
.map((x) => ( <section
<UserDisplay key={x.id} {...x} /> className={clsx(
))} "flex -lg:flex-wrap gap-4 items-center -lg:justify-center lg:justify-start text-center",
</div> !!linkedCorporate && "mt-12 xl:mt-6",
</div> )}>
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <IconCard
<span className="p-4">Highest level students</span> onClick={() => setPage("students")}
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> Icon={BsPersonFill}
{users label="Students"
.filter(studentFilter) value={users.filter(studentFilter).length}
.sort( color="purple"
(a, b) => />
calculateAverageLevel(b.levels) - <IconCard
calculateAverageLevel(a.levels) Icon={BsClipboard2Data}
) label="Exams Performed"
.map((x) => ( value={stats.filter((s) => groups.flatMap((g) => g.participants).includes(s.user)).length}
<UserDisplay key={x.id} {...x} /> color="purple"
))} />
</div> <IconCard
</div> Icon={BsPaperclip}
<div className="bg-white shadow flex flex-col rounded-xl w-full"> label="Average Level"
<span className="p-4">Highest exam count students</span> value={averageLevelCalculator(stats.filter((s) => groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)}
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> color="purple"
{users />
.filter(studentFilter) {checkAccess(user, ["teacher", "developer"], permissions, "viewGroup") && (
.sort( <IconCard
(a, b) => Icon={BsPeople}
Object.keys(groupByExam(getStatsByStudent(b))).length - label="Groups"
Object.keys(groupByExam(getStatsByStudent(a))).length value={groups.filter((x) => x.admin === user.id).length}
) color="purple"
.map((x) => ( onClick={() => setPage("groups")}
<UserDisplay key={x.id} {...x} /> />
))} )}
</div> <div
</div> onClick={() => setPage("assignments")}
</section> className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300">
</> <BsEnvelopePaper className="text-6xl text-mti-purple-light" />
); <span className="flex flex-col gap-1 items-center text-xl">
<span className="text-lg">Assignments</span>
<span className="font-semibold text-mti-purple-light">{assignments.filter((a) => !a.archived).length}</span>
</span>
</div>
</section>
return ( <section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 w-full justify-between">
<> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<Modal isOpen={showModal} onClose={() => setSelectedUser(undefined)}> <span className="p-4">Latest students</span>
<> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{selectedUser && ( {users
<div className="w-full flex flex-col gap-8"> .filter(studentFilter)
<UserCard .sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
loggedInUser={user} .map((x) => (
onClose={(shouldReload) => { <UserDisplay key={x.id} {...x} />
setSelectedUser(undefined); ))}
if (shouldReload) reload(); </div>
}} </div>
onViewStudents={ <div className="bg-white shadow flex flex-col rounded-xl w-full">
selectedUser.type === "corporate" || <span className="p-4">Highest level students</span>
selectedUser.type === "teacher" <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
? () => setPage("students") {users
: undefined .filter(studentFilter)
} .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels))
onViewTeachers={ .map((x) => (
selectedUser.type === "corporate" <UserDisplay key={x.id} {...x} />
? () => setPage("teachers") ))}
: undefined </div>
} </div>
user={selectedUser} <div className="bg-white shadow flex flex-col rounded-xl w-full">
/> <span className="p-4">Highest exam count students</span>
</div> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
)} {users
</> .filter(studentFilter)
</Modal> .sort(
{page === "students" && <StudentsList />} (a, b) =>
{page === "groups" && <GroupsList />} Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length,
{page === "assignments" && <AssignmentsPage />} )
{page === "" && <DefaultDashboard />} .map((x) => (
</> <UserDisplay key={x.id} {...x} />
); ))}
</div>
</div>
</section>
</>
);
return (
<>
<Modal isOpen={showModal} onClose={() => setSelectedUser(undefined)}>
<>
{selectedUser && (
<div className="w-full flex flex-col gap-8">
<UserCard
loggedInUser={user}
onClose={(shouldReload) => {
setSelectedUser(undefined);
if (shouldReload) reload();
}}
onViewStudents={
selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => setPage("students") : undefined
}
onViewTeachers={selectedUser.type === "corporate" ? () => setPage("teachers") : undefined}
user={selectedUser}
/>
</div>
)}
</>
</Modal>
{page === "students" && <StudentsList />}
{page === "groups" && <GroupsList />}
{page === "assignments" && <AssignmentsPage />}
{page === "" && <DefaultDashboard />}
</>
);
} }

View File

@@ -55,7 +55,14 @@ const USER_TYPE_PERMISSIONS: {
}, },
}; };
export default function BatchCodeGenerator({user, onFinish}: {user: User; onFinish: () => void}) { interface Props {
user: User;
users: User[];
permissions: PermissionType[];
onFinish: () => void;
}
export default function BatchCodeGenerator({user, users, permissions, onFinish}: Props) {
const [infos, setInfos] = useState<{email: string; name: string; passport_id: string}[]>([]); const [infos, setInfos] = useState<{email: string; name: string; passport_id: string}[]>([]);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [expiryDate, setExpiryDate] = useState<Date | null>( const [expiryDate, setExpiryDate] = useState<Date | null>(
@@ -65,9 +72,6 @@ export default function BatchCodeGenerator({user, onFinish}: {user: User; onFini
const [type, setType] = useState<Type>("student"); const [type, setType] = useState<Type>("student");
const [showHelp, setShowHelp] = useState(false); const [showHelp, setShowHelp] = useState(false);
const {users} = useUsers();
const {permissions} = usePermissions(user?.id || "");
const {openFilePicker, filesContent, clear} = useFilePicker({ const {openFilePicker, filesContent, clear} = useFilePicker({
accept: ".xlsx", accept: ".xlsx",
multiple: false, multiple: false,

View File

@@ -61,7 +61,14 @@ const USER_TYPE_PERMISSIONS: {
}, },
}; };
export default function BatchCreateUser({user, onFinish}: {user: User; onFinish: () => void}) { interface Props {
user: User;
users: User[];
permissions: PermissionType[];
onFinish: () => void;
}
export default function BatchCreateUser({user, users, permissions, onFinish}: Props) {
const [infos, setInfos] = useState< const [infos, setInfos] = useState<
{ {
email: string; email: string;
@@ -83,9 +90,6 @@ export default function BatchCreateUser({user, onFinish}: {user: User; onFinish:
const [type, setType] = useState<Type>("student"); const [type, setType] = useState<Type>("student");
const [showHelp, setShowHelp] = useState(false); const [showHelp, setShowHelp] = useState(false);
const {users} = useUsers();
const {permissions} = usePermissions(user?.id || "");
const {openFilePicker, filesContent, clear} = useFilePicker({ const {openFilePicker, filesContent, clear} = useFilePicker({
accept: ".xlsx", accept: ".xlsx",
multiple: false, multiple: false,

View File

@@ -48,14 +48,19 @@ const USER_TYPE_PERMISSIONS: {
}, },
}; };
export default function CodeGenerator({user, onFinish}: {user: User; onFinish: () => void}) { interface Props {
user: User;
permissions: PermissionType[];
onFinish: () => void;
}
export default function CodeGenerator({user, permissions, onFinish}: Props) {
const [generatedCode, setGeneratedCode] = useState<string>(); const [generatedCode, setGeneratedCode] = useState<string>();
const [expiryDate, setExpiryDate] = useState<Date | null>( const [expiryDate, setExpiryDate] = useState<Date | null>(
user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null,
); );
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [type, setType] = useState<Type>("student"); const [type, setType] = useState<Type>("student");
const {permissions} = usePermissions(user?.id || "");
useEffect(() => { useEffect(() => {
if (!isExpiryDateEnabled) setExpiryDate(null); if (!isExpiryDateEnabled) setExpiryDate(null);

View File

@@ -7,7 +7,7 @@ import {CorporateUser, Group, User} from "@/interfaces/user";
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
import axios from "axios"; import axios from "axios";
import {capitalize, uniq} from "lodash"; import {capitalize, uniq} from "lodash";
import {useEffect, useState} from "react"; import {useEffect, useMemo, useState} from "react";
import {BsPencil, BsQuestionCircleFill, BsTrash} from "react-icons/bs"; import {BsPencil, BsQuestionCircleFill, BsTrash} from "react-icons/bs";
import Select from "react-select"; import Select from "react-select";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
@@ -65,6 +65,14 @@ const CreatePanel = ({user, users, group, onClose}: CreateDialogProps) => {
readAs: "ArrayBuffer", readAs: "ArrayBuffer",
}); });
const availableUsers = useMemo(() => {
if (user.type === "teacher") return users.filter((x) => ["student"].includes(x.type));
if (user.type === "corporate") return users.filter((x) => ["teacher", "student"].includes(x.type));
if (user.type === "mastercorporate") return users.filter((x) => ["corporate", "teacher", "student"].includes(x.type));
return users;
}, [user, users]);
useEffect(() => { useEffect(() => {
if (filesContent.length > 0) { if (filesContent.length > 0) {
setIsLoading(true); setIsLoading(true);
@@ -155,15 +163,7 @@ const CreatePanel = ({user, users, group, onClose}: CreateDialogProps) => {
value: x, value: x,
label: `${users.find((y) => y.id === x)?.email} - ${users.find((y) => y.id === x)?.name}`, label: `${users.find((y) => y.id === x)?.email} - ${users.find((y) => y.id === x)?.name}`,
}))} }))}
options={users options={availableUsers.map((x) => ({value: x.id, label: `${x.email} - ${x.name}`}))}
.filter((x) =>
user.type === "teacher"
? x.type === "student"
: user.type === "corporate"
? x.type === "student" || x.type === "teacher"
: x.type === "student" || x.type === "teacher" || x.type === "corporate",
)
.map((x) => ({value: x.id, label: `${x.email} - ${x.name}`}))}
onChange={(value) => setParticipants(value.map((x) => x.value))} onChange={(value) => setParticipants(value.map((x) => x.value))}
isMulti isMulti
isSearchable isSearchable

View File

@@ -9,10 +9,15 @@ import PackageList from "./PackageList";
import UserList from "./UserList"; import UserList from "./UserList";
import {checkAccess} from "@/utils/permissions"; import {checkAccess} from "@/utils/permissions";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import {PermissionType} from "@/interfaces/permissions";
export default function Lists({user}: {user: User}) { interface Props {
const {permissions} = usePermissions(user?.id || ""); user: User;
users: User[];
permissions: PermissionType[];
}
export default function Lists({user, users, permissions}: Props) {
return ( return (
<TabGroup> <TabGroup>
<TabList className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1"> <TabList className="flex space-x-1 rounded-xl bg-mti-purple-ultralight/40 p-1">

View File

@@ -54,7 +54,14 @@ const USER_TYPE_PERMISSIONS: {
}, },
}; };
export default function UserCreator({user, onFinish}: {user: User; onFinish: () => void}) { interface Props {
user: User;
users: User[];
permissions: PermissionType[];
onFinish: () => void;
}
export default function UserCreator({user, users, permissions, onFinish}: Props) {
const [name, setName] = useState<string>(); const [name, setName] = useState<string>();
const [email, setEmail] = useState<string>(); const [email, setEmail] = useState<string>();
const [phone, setPhone] = useState<string>(); const [phone, setPhone] = useState<string>();
@@ -72,10 +79,9 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: ()
const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [type, setType] = useState<Type>("student"); const [type, setType] = useState<Type>("student");
const [position, setPosition] = useState<string>();
const {permissions} = usePermissions(user?.id || "");
const {groups} = useGroups({admin: ["developer", "admin"].includes(user?.type) ? undefined : user?.id, userType: user?.type}); const {groups} = useGroups({admin: ["developer", "admin"].includes(user?.type) ? undefined : user?.id, userType: user?.type});
const {users} = useUsers();
useEffect(() => { useEffect(() => {
if (!isExpiryDateEnabled) setExpiryDate(null); if (!isExpiryDateEnabled) setExpiryDate(null);
@@ -104,6 +110,7 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: ()
email, email,
password, password,
groupID: group, groupID: group,
corporate: selectedCorporate || user.id,
type, type,
studentID: type === "student" ? studentID : undefined, studentID: type === "student" ? studentID : undefined,
expiryDate, expiryDate,
@@ -111,6 +118,7 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: ()
passport_id: type === "student" ? passportID : undefined, passport_id: type === "student" ? passportID : undefined,
phone, phone,
country, country,
position,
}, },
}; };
@@ -131,6 +139,7 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: ()
setExpiryDate(user?.subscriptionExpirationDate ? moment(user?.subscriptionExpirationDate).toDate() : null); setExpiryDate(user?.subscriptionExpirationDate ? moment(user?.subscriptionExpirationDate).toDate() : null);
setIsExpiryDateEnabled(true); setIsExpiryDateEnabled(true);
setType("student"); setType("student");
setPosition(undefined);
}) })
.catch(() => toast.error("Something went wrong! Please try again later!")) .catch(() => toast.error("Something went wrong! Please try again later!"))
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
@@ -186,10 +195,16 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: ()
</div> </div>
)} )}
{["corporate", "mastercorporate"].includes(type) && (
<Input type="text" name="department" label="Department" onChange={setPosition} value={position} placeholder="Department" />
)}
<div <div
className={clsx( className={clsx(
"flex flex-col gap-4", "flex flex-col gap-4",
(!["student", "teacher"].includes(type) || ["corporate", "teacher"].includes(user?.type)) && "col-span-2", (!["student", "teacher"].includes(type) || ["corporate", "teacher"].includes(user?.type)) &&
!["corporate", "mastercorporate"].includes(type) &&
"col-span-2",
)}> )}>
<label className="font-normal text-base text-mti-gray-dim">Group</label> <label className="font-normal text-base text-mti-gray-dim">Group</label>
<Select <Select

View File

@@ -13,7 +13,7 @@ import Speaking from "@/exams/Speaking";
import Writing from "@/exams/Writing"; import Writing from "@/exams/Writing";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import {Exam, LevelExam, UserSolution, Variant} from "@/interfaces/exam"; import {Exam, LevelExam, UserSolution, Variant} from "@/interfaces/exam";
import {Stat} from "@/interfaces/user"; import {Stat, User} from "@/interfaces/user";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import {evaluateSpeakingAnswer, evaluateWritingAnswer} from "@/utils/evaluation"; import {evaluateSpeakingAnswer, evaluateWritingAnswer} from "@/utils/evaluation";
import {defaultExamUserSolutions, getExam} from "@/utils/exams"; import {defaultExamUserSolutions, getExam} from "@/utils/exams";
@@ -28,9 +28,10 @@ import useGradingSystem from "@/hooks/useGrading";
interface Props { interface Props {
page: "exams" | "exercises"; page: "exams" | "exercises";
user: User;
} }
export default function ExamPage({page}: Props) { export default function ExamPage({page, user}: Props) {
const [variant, setVariant] = useState<Variant>("full"); const [variant, setVariant] = useState<Variant>("full");
const [avoidRepeated, setAvoidRepeated] = useState(false); const [avoidRepeated, setAvoidRepeated] = useState(false);
const [hasBeenUploaded, setHasBeenUploaded] = useState(false); const [hasBeenUploaded, setHasBeenUploaded] = useState(false);
@@ -59,7 +60,6 @@ export default function ExamPage({page}: Props) {
const {bgColor, setBgColor} = useExamStore((state) => state); const {bgColor, setBgColor} = useExamStore((state) => state);
const setShuffleMaps = useExamStore((state) => state.setShuffles); const setShuffleMaps = useExamStore((state) => state.setShuffles);
const {user} = useUser({redirectTo: "/login"});
const router = useRouter(); const router = useRouter();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -67,6 +67,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
focus: "academic", focus: "academic",
status: "active", status: "active",
desiredLevels: DEFAULT_DESIRED_LEVELS, desiredLevels: DEFAULT_DESIRED_LEVELS,
profilePicture: "/defaultAvatar.png",
levels: DEFAULT_LEVELS, levels: DEFAULT_LEVELS,
isFirstLogin: false, isFirstLogin: false,
isVerified: true, isVerified: true,

View File

@@ -75,6 +75,7 @@ async function registerIndividual(req: NextApiRequest, res: NextApiResponse) {
levels: DEFAULT_LEVELS, levels: DEFAULT_LEVELS,
bio: "", bio: "",
isFirstLogin: codeData ? codeData.type === "student" : true, isFirstLogin: codeData ? codeData.type === "student" : true,
profilePicture: "/defaultAvatar.png",
focus: "academic", focus: "academic",
type: email.endsWith("@ecrop.dev") ? "developer" : codeData ? codeData.type : "student", type: email.endsWith("@ecrop.dev") ? "developer" : codeData ? codeData.type : "student",
subscriptionExpirationDate: codeData ? codeData.expiryDate : moment().subtract(1, "days").toISOString(), subscriptionExpirationDate: codeData ? codeData.expiryDate : moment().subtract(1, "days").toISOString(),

View File

@@ -5,6 +5,7 @@ import {sessionOptions} from "@/lib/session";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import ExamPage from "./(exam)/ExamPage"; import ExamPage from "./(exam)/ExamPage";
import Head from "next/head"; import Head from "next/head";
import {User} from "@/interfaces/user";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -14,7 +15,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
redirect: { redirect: {
destination: "/login", destination: "/login",
permanent: false, permanent: false,
} },
}; };
} }
@@ -23,7 +24,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
redirect: { redirect: {
destination: "/", destination: "/",
permanent: false, permanent: false,
} },
}; };
} }
@@ -32,7 +33,11 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}; };
}, sessionOptions); }, sessionOptions);
export default function Page() { interface Props {
user: User;
}
export default function Page({user}: Props) {
return ( return (
<> <>
<Head> <Head>
@@ -44,7 +49,7 @@ export default function Page() {
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<ExamPage page="exams" /> <ExamPage page="exams" user={user} />
</> </>
); );
} }

View File

@@ -5,6 +5,7 @@ import {sessionOptions} from "@/lib/session";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import ExamPage from "./(exam)/ExamPage"; import ExamPage from "./(exam)/ExamPage";
import Head from "next/head"; import Head from "next/head";
import {User} from "@/interfaces/user";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -14,7 +15,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
redirect: { redirect: {
destination: "/login", destination: "/login",
permanent: false, permanent: false,
} },
}; };
} }
@@ -23,7 +24,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
redirect: { redirect: {
destination: "/", destination: "/",
permanent: false, permanent: false,
} },
}; };
} }
@@ -32,7 +33,11 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}; };
}, sessionOptions); }, sessionOptions);
export default function Page() { interface Props {
user: User;
}
export default function Page({user}: Props) {
return ( return (
<> <>
<Head> <Head>
@@ -44,7 +49,7 @@ export default function Page() {
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<ExamPage page="exercises" /> <ExamPage page="exercises" user={user} />
</> </>
); );
} }

View File

@@ -22,6 +22,7 @@ import WritingGeneration from "./(generation)/WritingGeneration";
import LevelGeneration from "./(generation)/LevelGeneration"; import LevelGeneration from "./(generation)/LevelGeneration";
import SpeakingGeneration from "./(generation)/SpeakingGeneration"; import SpeakingGeneration from "./(generation)/SpeakingGeneration";
import {checkAccess} from "@/utils/permissions"; import {checkAccess} from "@/utils/permissions";
import {User} from "@/interfaces/user";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
@@ -49,10 +50,12 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}; };
}, sessionOptions); }, sessionOptions);
export default function Generation() { interface Props {
const [module, setModule] = useState<Module>("reading"); user: User;
}
const {user} = useUser({redirectTo: "/login"}); export default function Generation({user}: Props) {
const [module, setModule] = useState<Module>("reading");
const [title, setTitle] = useState<string>(""); const [title, setTitle] = useState<string>("");
return ( return (

View File

@@ -29,7 +29,7 @@ import MasterCorporateDashboard from "@/dashboards/MasterCorporate";
import PaymentDue from "./(status)/PaymentDue"; import PaymentDue from "./(status)/PaymentDue";
import {useRouter} from "next/router"; import {useRouter} from "next/router";
import {PayPalScriptProvider} from "@paypal/react-paypal-js"; import {PayPalScriptProvider} from "@paypal/react-paypal-js";
import {CorporateUser, MasterCorporateUser, Type, User, userTypes} from "@/interfaces/user"; import {CorporateUser, Group, MasterCorporateUser, Type, User, userTypes} from "@/interfaces/user";
import Select from "react-select"; import Select from "react-select";
import {USER_TYPE_LABELS} from "@/resources/user"; import {USER_TYPE_LABELS} from "@/resources/user";
import {checkAccess, getTypesOfUser} from "@/utils/permissions"; import {checkAccess, getTypesOfUser} from "@/utils/permissions";
@@ -37,8 +37,10 @@ import {shouldRedirectHome} from "@/utils/navigation.disabled";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import {getUserName} from "@/utils/users"; import {getUserName} from "@/utils/users";
import {getParticipantGroups, getUserGroups} from "@/utils/groups.be";
import {getUsers} from "@/utils/users.be";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
@@ -59,26 +61,20 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}; };
} }
const groups = await getParticipantGroups(user.id);
const users = await getUsers();
return { return {
props: {user: req.session.user}, props: {user, groups, users},
}; };
}, sessionOptions); }, sessionOptions);
interface Props { interface Props {
user: User; user: User;
envVariables: {[key: string]: string}; groups: Group[];
users: User[];
} }
export default function Home(props: Props) { export default function Home({user, groups, users}: Props) {
const {user, mutateUser} = useUser({redirectTo: "/login"});
const {groups} = useGroups({});
const {users} = useUsers();
const router = useRouter();
useEffect(() => {
console.log(groups);
}, [groups]);
return ( return (
<> <>
<Head> <Head>

View File

@@ -1,32 +1,24 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import Head from "next/head"; import Head from "next/head";
import Navbar from "@/components/Navbar"; import Navbar from "@/components/Navbar";
import { import {BsFileEarmarkText, BsPencil, BsStar, BsBook, BsHeadphones, BsPen, BsMegaphone} from "react-icons/bs";
BsFileEarmarkText, import {withIronSessionSsr} from "iron-session/next";
BsPencil, import {sessionOptions} from "@/lib/session";
BsStar, import {useEffect, useState} from "react";
BsBook, import {averageScore, groupBySession, totalExams} from "@/utils/stats";
BsHeadphones,
BsPen,
BsMegaphone,
} from "react-icons/bs";
import { withIronSessionSsr } from "iron-session/next";
import { sessionOptions } from "@/lib/session";
import { useEffect, useState } from "react";
import { averageScore, groupBySession, totalExams } from "@/utils/stats";
import useUser from "@/hooks/useUser"; import useUser from "@/hooks/useUser";
import Diagnostic from "@/components/Diagnostic"; import Diagnostic from "@/components/Diagnostic";
import { ToastContainer } from "react-toastify"; import {ToastContainer} from "react-toastify";
import { capitalize } from "lodash"; import {capitalize} from "lodash";
import { Module } from "@/interfaces"; import {Module} from "@/interfaces";
import ProgressBar from "@/components/Low/ProgressBar"; import ProgressBar from "@/components/Low/ProgressBar";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import { calculateAverageLevel } from "@/utils/score"; import {calculateAverageLevel} from "@/utils/score";
import axios from "axios"; import axios from "axios";
import DemographicInformationInput from "@/components/DemographicInformationInput"; import DemographicInformationInput from "@/components/DemographicInformationInput";
import moment from "moment"; import moment from "moment";
import Link from "next/link"; import Link from "next/link";
import { MODULE_ARRAY } from "@/utils/moduleUtils"; import {MODULE_ARRAY} from "@/utils/moduleUtils";
import ProfileSummary from "@/components/ProfileSummary"; import ProfileSummary from "@/components/ProfileSummary";
import StudentDashboard from "@/dashboards/Student"; import StudentDashboard from "@/dashboards/Student";
import AdminDashboard from "@/dashboards/Admin"; import AdminDashboard from "@/dashboards/Admin";
@@ -35,209 +27,184 @@ import TeacherDashboard from "@/dashboards/Teacher";
import AgentDashboard from "@/dashboards/Agent"; import AgentDashboard from "@/dashboards/Agent";
import MasterCorporateDashboard from "@/dashboards/MasterCorporate"; import MasterCorporateDashboard from "@/dashboards/MasterCorporate";
import PaymentDue from "./(status)/PaymentDue"; import PaymentDue from "./(status)/PaymentDue";
import { useRouter } from "next/router"; import {useRouter} from "next/router";
import { PayPalScriptProvider } from "@paypal/react-paypal-js"; import {PayPalScriptProvider} from "@paypal/react-paypal-js";
import { import {CorporateUser, MasterCorporateUser, Type, userTypes} from "@/interfaces/user";
CorporateUser,
MasterCorporateUser,
Type,
userTypes,
} from "@/interfaces/user";
import Select from "react-select"; import Select from "react-select";
import { USER_TYPE_LABELS } from "@/resources/user"; import {USER_TYPE_LABELS} from "@/resources/user";
import { checkAccess, getTypesOfUser } from "@/utils/permissions"; import {checkAccess, getTypesOfUser} from "@/utils/permissions";
import {getUserCorporate} from "@/utils/groups.be";
export const getServerSideProps = withIronSessionSsr(({ req, res }) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
const envVariables: { [key: string]: string } = {}; const envVariables: {[key: string]: string} = {};
Object.keys(process.env) Object.keys(process.env)
.filter((x) => x.startsWith("NEXT_PUBLIC")) .filter((x) => x.startsWith("NEXT_PUBLIC"))
.forEach((x: string) => { .forEach((x: string) => {
envVariables[x] = process.env[x]!; envVariables[x] = process.env[x]!;
}); });
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",
permanent: false, permanent: false,
}, },
}; };
} }
return { const linkedCorporate = (await getUserCorporate(user.id)) || null;
props: { user: req.session.user, envVariables },
}; return {
props: {user, envVariables, linkedCorporate},
};
}, sessionOptions); }, sessionOptions);
interface Props { interface Props {
user: any; user: any;
envVariables: { [key: string]: string }; envVariables: {[key: string]: string};
linkedCorporate?: CorporateUser | MasterCorporateUser;
} }
export default function Home(props: Props) {
const { envVariables } = props;
const [showDiagnostics, setShowDiagnostics] = useState(false);
const [showDemographicInput, setShowDemographicInput] = useState(false);
const [selectedScreen, setSelectedScreen] = useState<Type>("admin");
const { user, mutateUser } = useUser({ redirectTo: "/login" }); export default function Home({linkedCorporate}: Props) {
const router = useRouter(); const [showDiagnostics, setShowDiagnostics] = useState(false);
const [showDemographicInput, setShowDemographicInput] = useState(false);
const [selectedScreen, setSelectedScreen] = useState<Type>("admin");
useEffect(() => { const {user, mutateUser} = useUser({redirectTo: "/login"});
if (user) { const router = useRouter();
setShowDemographicInput(
!user.demographicInformation ||
!user.demographicInformation.country ||
!user.demographicInformation.gender ||
!user.demographicInformation.phone
);
setShowDiagnostics(user.isFirstLogin && user.type === "student");
}
}, [user]);
const checkIfUserExpired = () => { useEffect(() => {
const expirationDate = user!.subscriptionExpirationDate; if (user) {
setShowDemographicInput(
!user.demographicInformation ||
!user.demographicInformation.country ||
!user.demographicInformation.gender ||
!user.demographicInformation.phone,
);
setShowDiagnostics(user.isFirstLogin && user.type === "student");
}
}, [user]);
if (expirationDate === null || expirationDate === undefined) return false; const checkIfUserExpired = () => {
if (moment(expirationDate).isAfter(moment(new Date()))) return false; const expirationDate = user!.subscriptionExpirationDate;
return true; if (expirationDate === null || expirationDate === undefined) return false;
}; if (moment(expirationDate).isAfter(moment(new Date()))) return false;
if ( return true;
user && };
(user.status === "paymentDue" ||
user.status === "disabled" ||
checkIfUserExpired())
) {
return (
<>
<Head>
<title>EnCoach</title>
<meta
name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
{user.status === "disabled" && (
<Layout user={user} navDisabled>
<div className="flex flex-col items-center justify-center text-center w-full gap-4">
<span className="font-bold text-lg">
Your account has been disabled!
</span>
<span>
Please contact an administrator if you believe this to be a
mistake.
</span>
</div>
</Layout>
)}
{(user.status === "paymentDue" || checkIfUserExpired()) && (
<PaymentDue hasExpired user={user} reload={router.reload} />
)}
</>
);
}
if (user && showDemographicInput) { if (user && (user.status === "paymentDue" || user.status === "disabled" || checkIfUserExpired())) {
return ( return (
<> <>
<Head> <Head>
<title>EnCoach</title> <title>EnCoach</title>
<meta <meta
name="description" name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/> />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Layout user={user} navDisabled> {user.status === "disabled" && (
<DemographicInformationInput mutateUser={mutateUser} user={user} /> <Layout user={user} navDisabled>
</Layout> <div className="flex flex-col items-center justify-center text-center w-full gap-4">
</> <span className="font-bold text-lg">Your account has been disabled!</span>
); <span>Please contact an administrator if you believe this to be a mistake.</span>
} </div>
</Layout>
)}
{(user.status === "paymentDue" || checkIfUserExpired()) && <PaymentDue hasExpired user={user} reload={router.reload} />}
</>
);
}
if (user && showDiagnostics) { if (user && showDemographicInput) {
return ( return (
<> <>
<Head> <Head>
<title>EnCoach</title> <title>EnCoach</title>
<meta <meta
name="description" name="description"
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
/> />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Layout user={user} navDisabled> <Layout user={user} navDisabled>
<Diagnostic user={user} onFinish={() => setShowDiagnostics(false)} /> <DemographicInformationInput mutateUser={mutateUser} user={user} />
</Layout> </Layout>
</> </>
); );
} }
return ( if (user && showDiagnostics) {
<> return (
<Head> <>
<title>EnCoach</title> <Head>
<meta <title>EnCoach</title>
name="description" <meta
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop." name="description"
/> content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
<meta name="viewport" content="width=device-width, initial-scale=1" /> />
<link rel="icon" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
</Head> <link rel="icon" href="/favicon.ico" />
<ToastContainer /> </Head>
{user && ( <Layout user={user} navDisabled>
<Layout user={user}> <Diagnostic user={user} onFinish={() => setShowDiagnostics(false)} />
{checkAccess(user, ["student"]) && <StudentDashboard user={user} />} </Layout>
{checkAccess(user, ["teacher"]) && <TeacherDashboard user={user} />} </>
{checkAccess(user, ["corporate"]) && ( );
<CorporateDashboard user={user as CorporateUser} /> }
)}
{checkAccess(user, ["mastercorporate"]) && (
<MasterCorporateDashboard user={user as MasterCorporateUser} />
)}
{checkAccess(user, ["agent"]) && <AgentDashboard user={user} />}
{checkAccess(user, ["admin"]) && <AdminDashboard user={user} />}
{checkAccess(user, ["developer"]) && (
<>
<Select
options={userTypes.map((u) => ({
value: u,
label: USER_TYPE_LABELS[u],
}))}
value={{
value: selectedScreen,
label: USER_TYPE_LABELS[selectedScreen],
}}
onChange={(value) =>
value
? setSelectedScreen(value.value)
: setSelectedScreen("admin")
}
/>
{selectedScreen === "student" && <StudentDashboard user={user} />} return (
{selectedScreen === "teacher" && <TeacherDashboard user={user} />} <>
{selectedScreen === "corporate" && ( <Head>
<CorporateDashboard user={user as unknown as CorporateUser} /> <title>EnCoach</title>
)} <meta
{selectedScreen === "mastercorporate" && ( name="description"
<MasterCorporateDashboard content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
user={user as unknown as MasterCorporateUser} />
/> <meta name="viewport" content="width=device-width, initial-scale=1" />
)} <link rel="icon" href="/favicon.ico" />
{selectedScreen === "agent" && <AgentDashboard user={user} />} </Head>
{selectedScreen === "admin" && <AdminDashboard user={user} />} <ToastContainer />
</> {user && (
)} <Layout user={user}>
</Layout> {checkAccess(user, ["student"]) && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
)} {checkAccess(user, ["teacher"]) && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
</> {checkAccess(user, ["corporate"]) && <CorporateDashboard linkedCorporate={linkedCorporate} user={user as CorporateUser} />}
); {checkAccess(user, ["mastercorporate"]) && <MasterCorporateDashboard user={user as MasterCorporateUser} />}
{checkAccess(user, ["agent"]) && <AgentDashboard user={user} />}
{checkAccess(user, ["admin"]) && <AdminDashboard user={user} />}
{checkAccess(user, ["developer"]) && (
<>
<Select
options={userTypes.map((u) => ({
value: u,
label: USER_TYPE_LABELS[u],
}))}
value={{
value: selectedScreen,
label: USER_TYPE_LABELS[selectedScreen],
}}
onChange={(value) => (value ? setSelectedScreen(value.value) : setSelectedScreen("admin"))}
/>
{selectedScreen === "student" && <StudentDashboard linkedCorporate={linkedCorporate} user={user} />}
{selectedScreen === "teacher" && <TeacherDashboard linkedCorporate={linkedCorporate} user={user} />}
{selectedScreen === "corporate" && (
<CorporateDashboard linkedCorporate={linkedCorporate} user={user as unknown as CorporateUser} />
)}
{selectedScreen === "mastercorporate" && <MasterCorporateDashboard user={user as unknown as MasterCorporateUser} />}
{selectedScreen === "agent" && <AgentDashboard user={user} />}
{selectedScreen === "admin" && <AdminDashboard user={user} />}
</>
)}
</Layout>
)}
</>
);
} }

View File

@@ -12,7 +12,16 @@ import Link from "next/link";
import axios from "axios"; import axios from "axios";
import {ErrorMessage} from "@/constants/errors"; import {ErrorMessage} from "@/constants/errors";
import clsx from "clsx"; import clsx from "clsx";
import {CorporateUser, EmploymentStatus, EMPLOYMENT_STATUS, Gender, User, DemographicInformation} from "@/interfaces/user"; import {
CorporateUser,
EmploymentStatus,
EMPLOYMENT_STATUS,
Gender,
User,
DemographicInformation,
MasterCorporateUser,
Group,
} from "@/interfaces/user";
import CountrySelect from "@/components/Low/CountrySelect"; import CountrySelect from "@/components/Low/CountrySelect";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import moment from "moment"; import moment from "moment";
@@ -34,8 +43,11 @@ import {capitalize} from "lodash";
import TopicModal from "@/components/Medium/TopicModal"; import TopicModal from "@/components/Medium/TopicModal";
import {v4} from "uuid"; import {v4} from "uuid";
import {checkAccess, getTypesOfUser} from "@/utils/permissions"; import {checkAccess, getTypesOfUser} from "@/utils/permissions";
import {getParticipantGroups, getUserCorporate} from "@/utils/groups.be";
import {InferGetServerSidePropsType} from "next";
import {getUsers} from "@/utils/users.be";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
@@ -57,18 +69,26 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
} }
return { return {
props: {user: req.session.user}, props: {
user,
linkedCorporate: (await getUserCorporate(user.id)) || null,
groups: await getParticipantGroups(user.id),
users: await getUsers(),
},
}; };
}, sessionOptions); }, sessionOptions);
interface Props { interface Props {
user: User; user: User;
groups: Group[];
users: User[];
mutateUser: Function; mutateUser: Function;
linkedCorporate?: CorporateUser | MasterCorporateUser;
} }
const DoubleColumnRow = ({children}: {children: ReactNode}) => <div className="flex flex-col lg:flex-row gap-8 w-full">{children}</div>; const DoubleColumnRow = ({children}: {children: ReactNode}) => <div className="flex flex-col lg:flex-row gap-8 w-full">{children}</div>;
function UserProfile({user, mutateUser}: Props) { function UserProfile({user, mutateUser, linkedCorporate, groups, users}: Props) {
const [bio, setBio] = useState(user.bio || ""); const [bio, setBio] = useState(user.bio || "");
const [name, setName] = useState(user.name || ""); const [name, setName] = useState(user.name || "");
const [email, setEmail] = useState(user.email || ""); const [email, setEmail] = useState(user.email || "");
@@ -111,9 +131,6 @@ function UserProfile({user, mutateUser}: Props) {
const [isPreferredTopicsOpen, setIsPreferredTopicsOpen] = useState(false); const [isPreferredTopicsOpen, setIsPreferredTopicsOpen] = useState(false);
const {groups} = useGroups({});
const {users} = useUsers();
const profilePictureInput = useRef(null); const profilePictureInput = useRef(null);
const expirationDateColor = (date: Date) => { const expirationDateColor = (date: Date) => {
const momentDate = moment(date); const momentDate = moment(date);
@@ -245,7 +262,7 @@ function UserProfile({user, mutateUser}: Props) {
<h1 className="text-4xl font-bold mb-6 -md:hidden">Edit Profile</h1> <h1 className="text-4xl font-bold mb-6 -md:hidden">Edit Profile</h1>
<form className="flex flex-col items-center gap-6 w-full" onSubmit={(e) => e.preventDefault()}> <form className="flex flex-col items-center gap-6 w-full" onSubmit={(e) => e.preventDefault()}>
<DoubleColumnRow> <DoubleColumnRow>
{user.type !== "corporate" ? ( {user.type !== "corporate" && user.type !== "mastercorporate" ? (
<Input <Input
label={user.type === "agent" ? "English name" : "Name"} label={user.type === "agent" ? "English name" : "Name"}
type="text" type="text"
@@ -260,6 +277,7 @@ function UserProfile({user, mutateUser}: Props) {
label="Company name" label="Company name"
type="text" type="text"
name="name" name="name"
disabled={!!linkedCorporate}
onChange={(e) => onChange={(e) =>
setCorporateInformation((prev) => ({ setCorporateInformation((prev) => ({
...prev!, ...prev!,
@@ -499,9 +517,10 @@ function UserProfile({user, mutateUser}: Props) {
onChange={setPosition} onChange={setPosition}
defaultValue={position} defaultValue={position}
type="text" type="text"
label="Position" label="Department"
placeholder="CEO, Head of Marketing..." placeholder="CEO, Head of Marketing..."
required required
disabled={!!linkedCorporate}
/> />
</DoubleColumnRow> </DoubleColumnRow>
</> </>
@@ -638,7 +657,7 @@ function UserProfile({user, mutateUser}: Props) {
); );
} }
export default function Home() { export default function Home(props: {linkedCorporate?: CorporateUser | MasterCorporateUser; groups: Group[]; users: User[]}) {
const {user, mutateUser} = useUser({redirectTo: "/login"}); const {user, mutateUser} = useUser({redirectTo: "/login"});
return ( return (
@@ -653,7 +672,7 @@ export default function Home() {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<ToastContainer /> <ToastContainer />
{user && <UserProfile user={user} mutateUser={mutateUser} />} {user && <UserProfile user={user} mutateUser={mutateUser} {...props} />}
</> </>
); );
} }

View File

@@ -1,28 +1,31 @@
/* eslint-disable @next/next/no-img-element */ /* eslint-disable @next/next/no-img-element */
import Head from "next/head"; import Head from "next/head";
import { withIronSessionSsr } from "iron-session/next"; import {withIronSessionSsr} from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import { Stat, User } from "@/interfaces/user"; import {Stat, User} from "@/interfaces/user";
import { useEffect, useRef, useState } from "react"; import {useEffect, useRef, useState} from "react";
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
import { groupByDate } from "@/utils/stats"; import {groupByDate} from "@/utils/stats";
import moment from "moment"; import moment from "moment";
import useUsers from "@/hooks/useUsers"; import useUsers from "@/hooks/useUsers";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import { ToastContainer } from "react-toastify"; import {ToastContainer} from "react-toastify";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import clsx from "clsx"; import clsx from "clsx";
import { shouldRedirectHome } from "@/utils/navigation.disabled"; import {shouldRedirectHome} from "@/utils/navigation.disabled";
import useAssignments from "@/hooks/useAssignments"; import useAssignments from "@/hooks/useAssignments";
import { uuidv4 } from "@firebase/util"; import {uuidv4} from "@firebase/util";
import { usePDFDownload } from "@/hooks/usePDFDownload"; import {usePDFDownload} from "@/hooks/usePDFDownload";
import useRecordStore from "@/stores/recordStore"; import useRecordStore from "@/stores/recordStore";
import StatsGridItem from "@/components/Medium/StatGridItem"; import StatsGridItem from "@/components/Medium/StatGridItem";
import RecordFilter from "@/components/Medium/RecordFilter"; import RecordFilter from "@/components/Medium/RecordFilter";
import { useRouter } from "next/router"; import {useRouter} from "next/router";
import useTrainingContentStore from "@/stores/trainingContentStore"; import useTrainingContentStore from "@/stores/trainingContentStore";
import {Assignment} from "@/interfaces/results";
import {getUsers} from "@/utils/users.be";
import {getAssignments, getAssignmentsByAssigner} from "@/utils/assignments.be";
export const getServerSideProps = withIronSessionSsr(({ req, res }) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
@@ -43,14 +46,23 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
}; };
} }
const users = await getUsers();
const assignments = await getAssignments();
return { return {
props: { user: req.session.user }, props: {user, users, assignments},
}; };
}, sessionOptions); }, sessionOptions);
type Filter = "months" | "weeks" | "days" | "assignments" | undefined; type Filter = "months" | "weeks" | "days" | "assignments" | undefined;
export default function History({ user }: { user: User }) { interface Props {
user: User;
users: User[];
assignments: Assignment[];
}
export default function History({user, users, assignments}: Props) {
const router = useRouter(); const router = useRouter();
const [statsUserId, setStatsUserId, training, setTraining] = useRecordStore((state) => [ const [statsUserId, setStatsUserId, training, setTraining] = useRecordStore((state) => [
state.selectedUser, state.selectedUser,
@@ -60,12 +72,10 @@ export default function History({ user }: { user: User }) {
]); ]);
// const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id); // const [statsUserId, setStatsUserId] = useState<string | undefined>(user.id);
const [groupedStats, setGroupedStats] = useState<{ [key: string]: Stat[] }>(); const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>();
const [filter, setFilter] = useState<Filter>(); const [filter, setFilter] = useState<Filter>();
const { assignments } = useAssignments({});
const { users } = useUsers(); const {data: stats, isLoading: isStatsLoading} = useFilterRecordsByUser<Stat[]>(statsUserId || user?.id);
const { data: stats, isLoading: isStatsLoading } = useFilterRecordsByUser<Stat[]>(statsUserId || user?.id);
const setExams = useExamStore((state) => state.setExams); const setExams = useExamStore((state) => state.setExams);
const setShowSolutions = useExamStore((state) => state.setShowSolutions); const setShowSolutions = useExamStore((state) => state.setShowSolutions);
@@ -100,12 +110,12 @@ export default function History({ user }: { user: User }) {
// if (!statsUserId) setStatsUserId(user.id); // if (!statsUserId) setStatsUserId(user.id);
// }, []); // }, []);
const filterStatsByDate = (stats: { [key: string]: Stat[] }) => { const filterStatsByDate = (stats: {[key: string]: Stat[]}) => {
if (filter && filter !== "assignments") { if (filter && filter !== "assignments") {
const filterDate = moment() const filterDate = moment()
.subtract({ [filter as string]: 1 }) .subtract({[filter as string]: 1})
.format("x"); .format("x");
const filteredStats: { [key: string]: Stat[] } = {}; const filteredStats: {[key: string]: Stat[]} = {};
Object.keys(stats).forEach((timestamp) => { Object.keys(stats).forEach((timestamp) => {
if (timestamp >= filterDate) filteredStats[timestamp] = stats[timestamp]; if (timestamp >= filterDate) filteredStats[timestamp] = stats[timestamp];
@@ -114,7 +124,7 @@ export default function History({ user }: { user: User }) {
} }
if (filter && filter === "assignments") { if (filter && filter === "assignments") {
const filteredStats: { [key: string]: Stat[] } = {}; const filteredStats: {[key: string]: Stat[]} = {};
Object.keys(stats).forEach((timestamp) => { Object.keys(stats).forEach((timestamp) => {
if (stats[timestamp].map((s) => s.assignment === undefined).includes(false)) if (stats[timestamp].map((s) => s.assignment === undefined).includes(false))
@@ -127,7 +137,6 @@ export default function History({ user }: { user: User }) {
return stats; return stats;
}; };
const MAX_TRAINING_EXAMS = 10; const MAX_TRAINING_EXAMS = 10;
const [selectedTrainingExams, setSelectedTrainingExams] = useState<string[]>([]); const [selectedTrainingExams, setSelectedTrainingExams] = useState<string[]>([]);
const setTrainingStats = useTrainingContentStore((state) => state.setStats); const setTrainingStats = useTrainingContentStore((state) => state.setStats);
@@ -200,7 +209,7 @@ export default function History({ user }: { user: User }) {
<ToastContainer /> <ToastContainer />
{user && ( {user && (
<Layout user={user}> <Layout user={user}>
<RecordFilter user={user} filterState={{ filter: filter, setFilter: setFilter }} > <RecordFilter user={user} filterState={{filter: filter, setFilter: setFilter}}>
{training && ( {training && (
<div className="flex flex-row"> <div className="flex flex-row">
<div className="font-semibold text-2xl mr-4"> <div className="font-semibold text-2xl mr-4">

View File

@@ -24,8 +24,12 @@ import UserCreator from "./(admin)/UserCreator";
import CorporateGradingSystem from "./(admin)/CorporateGradingSystem"; import CorporateGradingSystem from "./(admin)/CorporateGradingSystem";
import useGradingSystem from "@/hooks/useGrading"; import useGradingSystem from "@/hooks/useGrading";
import {CEFR_STEPS} from "@/resources/grading"; import {CEFR_STEPS} from "@/resources/grading";
import {User} from "@/interfaces/user";
import {getUserPermissions} from "@/utils/permissions.be";
import {Permission, PermissionType} from "@/interfaces/permissions";
import {getUsers} from "@/utils/users.be";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user || !user.isVerified) {
return { return {
@@ -45,14 +49,21 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
}; };
} }
const permissions = await getUserPermissions(user.id);
const users = await getUsers();
return { return {
props: {user: req.session.user}, props: {user, permissions, users},
}; };
}, sessionOptions); }, sessionOptions);
export default function Admin() { interface Props {
const {user} = useUser({redirectTo: "/login"}); user: User;
const {permissions} = usePermissions(user?.id || ""); users: User[];
permissions: PermissionType[];
}
export default function Admin({user, users, permissions}: Props) {
const {gradingSystem, mutate} = useGradingSystem(); const {gradingSystem, mutate} = useGradingSystem();
const [modalOpen, setModalOpen] = useState<string>(); const [modalOpen, setModalOpen] = useState<string>();
@@ -69,80 +80,78 @@ export default function Admin() {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<ToastContainer /> <ToastContainer />
{user && ( <Layout user={user} className="gap-6">
<Layout user={user} className="gap-6"> <Modal isOpen={modalOpen === "batchCreateUser"} onClose={() => setModalOpen(undefined)}>
<Modal isOpen={modalOpen === "batchCreateUser"} onClose={() => setModalOpen(undefined)}> <BatchCreateUser user={user} users={users} permissions={permissions} onFinish={() => setModalOpen(undefined)} />
<BatchCreateUser user={user} onFinish={() => setModalOpen(undefined)} /> </Modal>
</Modal> <Modal isOpen={modalOpen === "batchCreateCode"} onClose={() => setModalOpen(undefined)}>
<Modal isOpen={modalOpen === "batchCreateCode"} onClose={() => setModalOpen(undefined)}> <BatchCodeGenerator user={user} users={users} permissions={permissions} onFinish={() => setModalOpen(undefined)} />
<BatchCodeGenerator user={user} onFinish={() => setModalOpen(undefined)} /> </Modal>
</Modal> <Modal isOpen={modalOpen === "createCode"} onClose={() => setModalOpen(undefined)}>
<Modal isOpen={modalOpen === "createCode"} onClose={() => setModalOpen(undefined)}> <CodeGenerator user={user} permissions={permissions} onFinish={() => setModalOpen(undefined)} />
<CodeGenerator user={user} onFinish={() => setModalOpen(undefined)} /> </Modal>
</Modal> <Modal isOpen={modalOpen === "createUser"} onClose={() => setModalOpen(undefined)}>
<Modal isOpen={modalOpen === "createUser"} onClose={() => setModalOpen(undefined)}> <UserCreator user={user} users={users} permissions={permissions} onFinish={() => setModalOpen(undefined)} />
<UserCreator user={user} onFinish={() => setModalOpen(undefined)} /> </Modal>
</Modal> <Modal isOpen={modalOpen === "gradingSystem"} onClose={() => setModalOpen(undefined)}>
<Modal isOpen={modalOpen === "gradingSystem"} onClose={() => setModalOpen(undefined)}> <CorporateGradingSystem
<CorporateGradingSystem user={user}
user={user} defaultSteps={gradingSystem?.steps || CEFR_STEPS}
defaultSteps={gradingSystem?.steps || CEFR_STEPS} mutate={(steps) => {
mutate={(steps) => { mutate({user: user.id, steps});
mutate({user: user.id, steps}); setModalOpen(undefined);
setModalOpen(undefined); }}
}} />
/> </Modal>
</Modal>
<section className="w-full grid grid-cols-2 -md:grid-cols-1 gap-8"> <section className="w-full grid grid-cols-2 -md:grid-cols-1 gap-8">
<ExamLoader /> <ExamLoader />
{checkAccess(user, getTypesOfUser(["teacher"]), permissions, "viewCodes") && ( {checkAccess(user, getTypesOfUser(["teacher"]), permissions, "viewCodes") && (
<div className="w-full grid grid-cols-2 gap-4"> <div className="w-full grid grid-cols-2 gap-4">
<IconCard
Icon={BsCode}
label="Generate Single Code"
color="purple"
className="w-full h-full"
onClick={() => setModalOpen("createCode")}
/>
<IconCard
Icon={BsCodeSquare}
label="Generate Codes in Batch"
color="purple"
className="w-full h-full"
onClick={() => setModalOpen("batchCreateCode")}
/>
<IconCard
Icon={BsPersonFill}
label="Create Single User"
color="purple"
className="w-full h-full"
onClick={() => setModalOpen("createUser")}
/>
<IconCard
Icon={BsPeopleFill}
label="Create Users in Batch"
color="purple"
className="w-full h-full"
onClick={() => setModalOpen("batchCreateUser")}
/>
{checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) && (
<IconCard <IconCard
Icon={BsCode} Icon={BsGearFill}
label="Generate Single Code" label="Grading System"
color="purple" color="purple"
className="w-full h-full" className="w-full h-full col-span-2"
onClick={() => setModalOpen("createCode")} onClick={() => setModalOpen("gradingSystem")}
/> />
<IconCard )}
Icon={BsCodeSquare} </div>
label="Generate Codes in Batch" )}
color="purple" </section>
className="w-full h-full" <section className="w-full">
onClick={() => setModalOpen("batchCreateCode")} <Lists user={user} users={users} permissions={permissions} />
/> </section>
<IconCard </Layout>
Icon={BsPersonFill}
label="Create Single User"
color="purple"
className="w-full h-full"
onClick={() => setModalOpen("createUser")}
/>
<IconCard
Icon={BsPeopleFill}
label="Create Users in Batch"
color="purple"
className="w-full h-full"
onClick={() => setModalOpen("batchCreateUser")}
/>
{checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) && (
<IconCard
Icon={BsGearFill}
label="Grading System"
color="purple"
className="w-full h-full col-span-2"
onClick={() => setModalOpen("gradingSystem")}
/>
)}
</div>
)}
</section>
<section className="w-full">
<Lists user={user} />
</section>
</Layout>
)}
</> </>
); );
} }

View File

@@ -1,58 +1,36 @@
import { app } from "@/firebase"; import {app} from "@/firebase";
import { Assignment } from "@/interfaces/results"; import {Assignment} from "@/interfaces/results";
import { import {collection, getDocs, getFirestore, query, where} from "firebase/firestore";
collection,
getDocs,
getFirestore,
query,
where,
} from "firebase/firestore";
const db = getFirestore(app); const db = getFirestore(app);
export const getAssignmentsByAssigner = async ( export const getAssignmentsByAssigner = async (id: string, startDate?: Date, endDate?: Date) => {
id: string, const {docs} = await getDocs(
startDate?: Date, query(
endDate?: Date collection(db, "assignments"),
) => { ...[
const { docs } = await getDocs( where("assigner", "==", id),
query( ...(startDate ? [where("startDate", ">=", startDate.toISOString())] : []),
collection(db, "assignments"), // firebase doesnt accept compound queries so we have to filter on the server
...[ // ...endDate ? [where("endDate", "<=", endDate)] : [],
where("assigner", "==", id), ],
...(startDate ? [where("startDate", ">=", startDate.toISOString())] : []), ),
// firebase doesnt accept compound queries so we have to filter on the server );
// ...endDate ? [where("endDate", "<=", endDate)] : [], if (endDate) {
] return docs.map((x) => ({...(x.data() as Assignment), id: x.id})).filter((x) => new Date(x.endDate) <= endDate) as Assignment[];
) }
); return docs.map((x) => ({...x.data(), id: x.id})) as Assignment[];
if (endDate) { };
return docs export const getAssignments = async () => {
.map((x) => ({ ...(x.data() as Assignment), id: x.id })) const {docs} = await getDocs(collection(db, "assignments"));
.filter((x) => new Date(x.endDate) <= endDate) as Assignment[]; return docs.map((x) => ({...x.data(), id: x.id})) as Assignment[];
}
return docs.map((x) => ({ ...x.data(), id: x.id })) as Assignment[];
}; };
export const getAssignmentsByAssignerBetweenDates = async ( export const getAssignmentsByAssignerBetweenDates = async (id: string, startDate: Date, endDate: Date) => {
id: string, const {docs} = await getDocs(query(collection(db, "assignments"), where("assigner", "==", id)));
startDate: Date, return docs.map((x) => ({...x.data(), id: x.id})) as Assignment[];
endDate: Date
) => {
const { docs } = await getDocs(
query(collection(db, "assignments"), where("assigner", "==", id))
);
return docs.map((x) => ({ ...x.data(), id: x.id })) as Assignment[];
}; };
export const getAssignmentsByAssigners = async ( export const getAssignmentsByAssigners = async (ids: string[], startDate?: Date, endDate?: Date) => {
ids: string[], return (await Promise.all(ids.map((id) => getAssignmentsByAssigner(id, startDate, endDate)))).flat();
startDate?: Date,
endDate?: Date
) => {
return (
await Promise.all(
ids.map((id) => getAssignmentsByAssigner(id, startDate, endDate))
)
).flat();
}; };

View File

@@ -1,5 +1,5 @@
import {app} from "@/firebase"; import {app} from "@/firebase";
import {CorporateUser, Group, StudentUser, TeacherUser} from "@/interfaces/user"; import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser} from "@/interfaces/user";
import {collection, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore"; import {collection, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore";
import moment from "moment"; import moment from "moment";
import {getUser} from "./users.be"; import {getUser} from "./users.be";
@@ -35,14 +35,14 @@ export const updateExpiryDateOnGroup = async (participantID: string, corporateID
export const getUserCorporate = async (id: string) => { export const getUserCorporate = async (id: string) => {
const user = await getUser(id); const user = await getUser(id);
if (user.type === "corporate" || user.type === "mastercorporate") return user; if (user.type === "mastercorporate") return user;
const groups = await getParticipantGroups(id); const groups = await getParticipantGroups(id);
const admins = await Promise.all(groups.map((x) => x.admin).map(getUser)); const admins = await Promise.all(groups.map((x) => x.admin).map(getUser));
const corporates = admins.filter((x) => x.type === "corporate"); const corporates = admins.filter((x) => x.type === "corporate" || x.type === "mastercorporate");
if (corporates.length === 0) return undefined; if (corporates.length === 0) return undefined;
return corporates.shift() as CorporateUser; return corporates.shift() as CorporateUser | MasterCorporateUser;
}; };
export const getGroups = async () => { export const getGroups = async () => {

View File

@@ -1,99 +1,89 @@
import { app, adminApp } from "@/firebase"; import {app, adminApp} from "@/firebase";
import { getAuth } from "firebase-admin/auth"; import {getAuth} from "firebase-admin/auth";
import { import {collection, deleteDoc, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore";
collection, import {Permission, PermissionType, permissions, PermissionTopic} from "@/interfaces/permissions";
deleteDoc, import {v4} from "uuid";
doc,
getDoc,
getDocs,
getFirestore,
query,
setDoc,
where,
} from "firebase/firestore";
import {
Permission,
PermissionType,
permissions,
PermissionTopic,
} from "@/interfaces/permissions";
import { v4 } from "uuid";
const db = getFirestore(app); const db = getFirestore(app);
async function createPermission(topic: string, type: string) { async function createPermission(topic: string, type: string) {
const permData = doc(db, "permissions", v4()); const permData = doc(db, "permissions", v4());
const permDoc = await getDoc(permData); const permDoc = await getDoc(permData);
if (permDoc.exists()) { if (permDoc.exists()) {
return true; return true;
} }
await setDoc(permData, { await setDoc(permData, {
type, type,
topic, topic,
users: [], users: [],
}); });
} }
interface PermissionsHelperList { interface PermissionsHelperList {
topic: string; topic: string;
type: string; type: string;
} }
export function getPermissions(userId: string | undefined, docs: Permission[]) { export function getPermissions(userId: string | undefined, docs: Permission[]) {
if (!userId) { if (!userId) {
return []; return [];
} }
// the concept is like a blacklist // the concept is like a blacklist
// if the user exists in the list, he can't access this permission // if the user exists in the list, he can't access this permission
// even if his profile allows // even if his profile allows
const permissions = docs.reduce((acc: PermissionType[], doc: Permission) => { const permissions = docs.reduce((acc: PermissionType[], doc: Permission) => {
// typescript was complaining even with the validation on the top // typescript was complaining even with the validation on the top
if (doc.users.includes(userId)) { if (doc.users.includes(userId)) {
return acc; return acc;
} }
return [...acc, doc.type]; return [...acc, doc.type];
}, []) as PermissionType[]; }, []) as PermissionType[];
return permissions; return permissions;
}
export async function getUserPermissions(id: string) {
const permissions = await getPermissionDocs();
return getPermissions(id, permissions);
} }
export async function bootstrap() { export async function bootstrap() {
await permissions await permissions
.reduce((accm: PermissionsHelperList[], permissionData) => { .reduce((accm: PermissionsHelperList[], permissionData) => {
return [ return [
...accm, ...accm,
...permissionData.list.map((type) => ({ ...permissionData.list.map((type) => ({
topic: permissionData.topic, topic: permissionData.topic,
type, type,
})), })),
]; ];
}, []) }, [])
.forEach(async ({ topic, type }) => { .forEach(async ({topic, type}) => {
await createPermission(topic, type); await createPermission(topic, type);
}); });
} }
export async function getPermissionDoc(id: string) { export async function getPermissionDoc(id: string) {
const docRef = doc(db, "permissions", id); const docRef = doc(db, "permissions", id);
const docSnap = await getDoc(docRef); const docSnap = await getDoc(docRef);
if (docSnap.exists()) { if (docSnap.exists()) {
return docSnap.data() as Permission; return docSnap.data() as Permission;
} }
throw new Error("Permission not found"); throw new Error("Permission not found");
} }
export async function getPermissionDocs() { export async function getPermissionDocs() {
const q = query(collection(db, "permissions")); const q = query(collection(db, "permissions"));
// firebase is missing something like array-not-contains // firebase is missing something like array-not-contains
const snapshot = await getDocs(q); const snapshot = await getDocs(q);
const docs = snapshot.docs.map((doc) => ({ const docs = snapshot.docs.map((doc) => ({
id: doc.id, id: doc.id,
...doc.data(), ...doc.data(),
})) as Permission[]; })) as Permission[];
return docs; return docs;
} }

View File

@@ -5,21 +5,23 @@ import {CorporateUser, Group, User} from "@/interfaces/user";
import {getGroupsForUser} from "./groups.be"; import {getGroupsForUser} from "./groups.be";
import {uniq, uniqBy} from "lodash"; import {uniq, uniqBy} from "lodash";
import {getUserCodes} from "./codes.be"; import {getUserCodes} from "./codes.be";
import moment from "moment";
const db = getFirestore(app); const db = getFirestore(app);
export async function getUsers() { export async function getUsers() {
const snapshot = await getDocs(collection(db, "users")); const snapshot = await getDocs(collection(db, "users"));
return snapshot.docs.map((doc) => ({ return snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(), ...doc.data(),
})) as User[]; id: doc.id,
registrationDate: moment(doc.data().registrationDate).toISOString(),
})) as unknown as User[];
} }
export async function getUser(id: string) { export async function getUser(id: string) {
const userDoc = await getDoc(doc(db, "users", id)); const userDoc = await getDoc(doc(db, "users", id));
return {...userDoc.data(), id} as User; return {...userDoc.data(), id, registrationDate: moment(userDoc.data()?.registrationDate).toISOString()} as unknown as User;
} }
export async function getSpecificUsers(ids: string[]) { export async function getSpecificUsers(ids: string[]) {
@@ -28,9 +30,10 @@ export async function getSpecificUsers(ids: string[]) {
const snapshot = await getDocs(query(collection(db, "users"), where("id", "in", ids))); const snapshot = await getDocs(query(collection(db, "users"), where("id", "in", ids)));
const groups = snapshot.docs.map((doc) => ({ const groups = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(), ...doc.data(),
})) as User[]; id: doc.id,
registrationDate: moment(doc.data().registrationDate).toISOString(),
})) as unknown as User[];
return groups; return groups;
} }

View File

@@ -32,7 +32,7 @@ export const exportListToExcel = (rowUsers: User[], users: User[], groups: Group
gender: user.demographicInformation?.gender ? capitalize(user.demographicInformation.gender) : "N/A", gender: user.demographicInformation?.gender ? capitalize(user.demographicInformation.gender) : "N/A",
verified: user.isVerified?.toString() || "FALSE", verified: user.isVerified?.toString() || "FALSE",
})); }));
const header = "Name,Email,Type,Company Name,Expiry Date,Country,Phone,Employment/Position,Gender,Verification"; const header = "Name,Email,Type,Company Name,Expiry Date,Country,Phone,Employment/Department,Gender,Verification";
const rowsString = rows.map((x) => Object.values(x).join(",")).join("\n"); const rowsString = rows.map((x) => Object.values(x).join(",")).join("\n");
return `${header}\n${rowsString}`; return `${header}\n${rowsString}`;

516
yarn.lock
View File

@@ -191,7 +191,7 @@
"@emotion/utils" "0.11.3" "@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5" "@emotion/weak-memoize" "0.2.5"
"@emotion/cache@^11.13.0": "@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0":
version "11.13.1" version "11.13.1"
resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz" resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz"
integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==
@@ -202,27 +202,16 @@
"@emotion/weak-memoize" "^0.4.0" "@emotion/weak-memoize" "^0.4.0"
stylis "4.2.0" stylis "4.2.0"
"@emotion/cache@^11.4.0":
version "11.13.1"
resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz"
integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==
dependencies:
"@emotion/memoize" "^0.9.0"
"@emotion/sheet" "^1.4.0"
"@emotion/utils" "^1.4.0"
"@emotion/weak-memoize" "^0.4.0"
stylis "4.2.0"
"@emotion/hash@^0.9.2":
version "0.9.2"
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz"
integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
"@emotion/hash@0.8.0": "@emotion/hash@0.8.0":
version "0.8.0" version "0.8.0"
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
"@emotion/hash@^0.9.2":
version "0.9.2"
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz"
integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==
"@emotion/is-prop-valid@^0.8.2": "@emotion/is-prop-valid@^0.8.2":
version "0.8.8" version "0.8.8"
resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz" resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz"
@@ -230,16 +219,16 @@
dependencies: dependencies:
"@emotion/memoize" "0.7.4" "@emotion/memoize" "0.7.4"
"@emotion/memoize@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz"
integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
"@emotion/memoize@0.7.4": "@emotion/memoize@0.7.4":
version "0.7.4" version "0.7.4"
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz" resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
"@emotion/memoize@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz"
integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
"@emotion/react@^11.8.1": "@emotion/react@^11.8.1":
version "11.13.0" version "11.13.0"
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz" resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz"
@@ -265,7 +254,7 @@
"@emotion/utils" "0.11.3" "@emotion/utils" "0.11.3"
csstype "^2.5.7" csstype "^2.5.7"
"@emotion/serialize@^1.2.0": "@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0":
version "1.3.0" version "1.3.0"
resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz" resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz"
integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA== integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==
@@ -276,67 +265,56 @@
"@emotion/utils" "^1.4.0" "@emotion/utils" "^1.4.0"
csstype "^3.0.2" csstype "^3.0.2"
"@emotion/serialize@^1.3.0":
version "1.3.0"
resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz"
integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==
dependencies:
"@emotion/hash" "^0.9.2"
"@emotion/memoize" "^0.9.0"
"@emotion/unitless" "^0.9.0"
"@emotion/utils" "^1.4.0"
csstype "^3.0.2"
"@emotion/sheet@^1.4.0":
version "1.4.0"
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
"@emotion/sheet@0.9.4": "@emotion/sheet@0.9.4":
version "0.9.4" version "0.9.4"
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz" resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz"
integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==
"@emotion/sheet@^1.4.0":
version "1.4.0"
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
"@emotion/stylis@0.8.5": "@emotion/stylis@0.8.5":
version "0.8.5" version "0.8.5"
resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz" resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
"@emotion/unitless@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz"
integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==
"@emotion/unitless@0.7.5": "@emotion/unitless@0.7.5":
version "0.7.5" version "0.7.5"
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
"@emotion/unitless@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz"
integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==
"@emotion/use-insertion-effect-with-fallbacks@^1.1.0": "@emotion/use-insertion-effect-with-fallbacks@^1.1.0":
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz" resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz"
integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==
"@emotion/utils@^1.4.0":
version "1.4.0"
resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz"
integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==
"@emotion/utils@0.11.3": "@emotion/utils@0.11.3":
version "0.11.3" version "0.11.3"
resolved "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz" resolved "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz"
integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
"@emotion/weak-memoize@^0.4.0": "@emotion/utils@^1.4.0":
version "0.4.0" version "1.4.0"
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==
"@emotion/weak-memoize@0.2.5": "@emotion/weak-memoize@0.2.5":
version "0.2.5" version "0.2.5"
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz" resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@emotion/weak-memoize@^0.4.0":
version "0.4.0"
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
"@eslint/eslintrc@^1.4.1": "@eslint/eslintrc@^1.4.1":
version "1.4.1" version "1.4.1"
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz"
@@ -511,7 +489,7 @@
"@firebase/util" "1.9.3" "@firebase/util" "1.9.3"
tslib "^2.1.0" tslib "^2.1.0"
"@firebase/database-compat@^0.3.4", "@firebase/database-compat@0.3.4": "@firebase/database-compat@0.3.4", "@firebase/database-compat@^0.3.4":
version "0.3.4" version "0.3.4"
resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz" resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz"
integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg== integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==
@@ -523,7 +501,7 @@
"@firebase/util" "1.9.3" "@firebase/util" "1.9.3"
tslib "^2.1.0" tslib "^2.1.0"
"@firebase/database-types@^0.10.4", "@firebase/database-types@0.10.4": "@firebase/database-types@0.10.4", "@firebase/database-types@^0.10.4":
version "0.10.4" version "0.10.4"
resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz" resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz"
integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ== integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==
@@ -744,13 +722,6 @@
node-fetch "2.6.7" node-fetch "2.6.7"
tslib "^2.1.0" tslib "^2.1.0"
"@firebase/util@^1.9.7":
version "1.9.7"
resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz"
integrity sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA==
dependencies:
tslib "^2.1.0"
"@firebase/util@1.9.3": "@firebase/util@1.9.3":
version "1.9.3" version "1.9.3"
resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz" resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz"
@@ -758,6 +729,13 @@
dependencies: dependencies:
tslib "^2.1.0" tslib "^2.1.0"
"@firebase/util@^1.9.7":
version "1.9.7"
resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz"
integrity sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA==
dependencies:
tslib "^2.1.0"
"@firebase/webchannel-wrapper@0.9.0": "@firebase/webchannel-wrapper@0.9.0":
version "0.9.0" version "0.9.0"
resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz" resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz"
@@ -1013,6 +991,46 @@
dependencies: dependencies:
glob "7.1.7" glob "7.1.7"
"@next/swc-darwin-arm64@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz#d0a160cf78c18731c51cc0bff131c706b3e9bb05"
integrity sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==
"@next/swc-darwin-x64@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz#eb832a992407f6e6352eed05a073379f1ce0589c"
integrity sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==
"@next/swc-linux-arm64-gnu@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz#098fdab57a4664969bc905f5801ef5a89582c689"
integrity sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==
"@next/swc-linux-arm64-musl@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz#243a1cc1087fb75481726dd289c7b219fa01f2b5"
integrity sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==
"@next/swc-linux-x64-gnu@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz#b8a2e436387ee4a52aa9719b718992e0330c4953"
integrity sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==
"@next/swc-linux-x64-musl@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz#cb8a9adad5fb8df86112cfbd363aab5c6d32757b"
integrity sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==
"@next/swc-win32-arm64-msvc@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz#81f996c1c38ea0900d4e7719cc8814be8a835da0"
integrity sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==
"@next/swc-win32-ia32-msvc@14.2.5":
version "14.2.5"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz#f61c74ce823e10b2bc150e648fc192a7056422e0"
integrity sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==
"@next/swc-win32-x64-msvc@14.2.5": "@next/swc-win32-x64-msvc@14.2.5":
version "14.2.5" version "14.2.5"
resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz" resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz"
@@ -1026,7 +1044,7 @@
"@nodelib/fs.stat" "2.0.5" "@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9" run-parallel "^1.1.9"
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5" version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -1579,6 +1597,14 @@
resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz"
integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
"@swc/helpers@0.5.5":
version "0.5.5"
resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz"
integrity sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==
dependencies:
"@swc/counter" "^0.1.3"
tslib "^2.4.0"
"@swc/helpers@^0.4.2": "@swc/helpers@^0.4.2":
version "0.4.14" version "0.4.14"
resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz" resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz"
@@ -1593,14 +1619,6 @@
dependencies: dependencies:
tslib "^2.4.0" tslib "^2.4.0"
"@swc/helpers@0.5.5":
version "0.5.5"
resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz"
integrity sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==
dependencies:
"@swc/counter" "^0.1.3"
tslib "^2.4.0"
"@tanstack/react-table@^8.10.1": "@tanstack/react-table@^8.10.1":
version "8.19.3" version "8.19.3"
resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz" resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz"
@@ -1814,7 +1832,7 @@
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz"
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.1.0", "@types/node@18.13.0": "@types/node@*", "@types/node@18.13.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.1.0":
version "18.13.0" version "18.13.0"
resolved "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz" resolved "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz"
integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==
@@ -2624,20 +2642,16 @@ cliui@^7.0.2:
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi "^7.0.0" wrap-ansi "^7.0.0"
cliui@^8.0.1:
version "8.0.1"
resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz"
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"
clone@^2.1.2: clone@^2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==
clsx@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz"
integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
clsx@^1.1.1: clsx@^1.1.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
@@ -2648,11 +2662,6 @@ clsx@^2.0.0, clsx@^2.1.1:
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
clsx@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz"
integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==
color-convert@^1.9.0: color-convert@^1.9.0:
version "1.9.3" version "1.9.3"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz"
@@ -2667,16 +2676,16 @@ color-convert@^2.0.1:
dependencies: dependencies:
color-name "~1.1.4" color-name "~1.1.4"
color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-name@1.1.3: color-name@1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.9.1: color-string@^1.9.1:
version "1.9.1" version "1.9.1"
resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz"
@@ -2897,6 +2906,13 @@ dayjs@^1.8.34:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
debug@^3.2.7: debug@^3.2.7:
version "3.2.7" version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
@@ -2904,13 +2920,6 @@ debug@^3.2.7:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
decamelize@^1.2.0: decamelize@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
@@ -3085,7 +3094,7 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
ecdsa-sig-formatter@^1.0.11, ecdsa-sig-formatter@1.0.11: ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11" version "1.0.11"
resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
@@ -3826,6 +3835,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
fstream@^1.0.12: fstream@^1.0.12:
version "1.0.12" version "1.0.12"
resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz" resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz"
@@ -3928,7 +3942,7 @@ get-tsconfig@^4.2.0:
resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz" resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz"
integrity sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ== integrity sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==
glob-parent@^5.1.2: glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -3942,12 +3956,29 @@ glob-parent@^6.0.2:
dependencies: dependencies:
is-glob "^4.0.3" is-glob "^4.0.3"
glob-parent@~5.1.2: glob@7.1.6:
version "5.1.2" version "7.1.6"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies: dependencies:
is-glob "^4.0.1" fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.7, glob@^7.1.3, glob@^7.1.4:
version "7.1.7"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz"
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^10.4.2: glob@^10.4.2:
version "10.4.5" version "10.4.5"
@@ -3961,18 +3992,6 @@ glob@^10.4.2:
package-json-from-dist "^1.0.0" package-json-from-dist "^1.0.0"
path-scurry "^1.11.1" path-scurry "^1.11.1"
glob@^7.1.3, glob@^7.1.4, glob@7.1.7:
version "7.1.7"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz"
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.2.3: glob@^7.2.3:
version "7.2.3" version "7.2.3"
resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
@@ -3996,18 +4015,6 @@ glob@^8.0.0:
minimatch "^5.0.1" minimatch "^5.0.1"
once "^1.3.0" once "^1.3.0"
glob@7.1.6:
version "7.1.6"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0: globals@^11.1.0:
version "11.12.0" version "11.12.0"
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
@@ -4306,7 +4313,7 @@ inflight@^1.0.4:
once "^1.3.0" once "^1.3.0"
wrappy "1" wrappy "1"
inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3, inherits@2: inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3:
version "2.0.4" version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -4994,18 +5001,18 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies: dependencies:
js-tokens "^3.0.0 || ^4.0.0" js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^10.2.0: lru-cache@6.0.0, lru-cache@^6.0.0:
version "10.4.3"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lru-cache@^6.0.0, lru-cache@6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies: dependencies:
yallist "^4.0.0" yallist "^4.0.0"
lru-cache@^10.2.0:
version "10.4.3"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lru-memoizer@^2.2.0: lru-memoizer@^2.2.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz" resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz"
@@ -5076,7 +5083,7 @@ micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.2" braces "^3.0.2"
picomatch "^2.3.1" picomatch "^2.3.1"
"mime-db@>= 1.43.0 < 2", mime-db@1.52.0: mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
version "1.52.0" version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
@@ -5100,14 +5107,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimatch@^5.0.1: minimatch@^5.0.1, minimatch@^5.1.0:
version "5.1.6"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
dependencies:
brace-expansion "^2.0.1"
minimatch@^5.1.0:
version "5.1.6" version "5.1.6"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
@@ -5151,11 +5151,6 @@ minizlib@^2.1.1:
minipass "^3.0.0" minipass "^3.0.0"
yallist "^4.0.0" yallist "^4.0.0"
mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
"mkdirp@>=0.5 0": "mkdirp@>=0.5 0":
version "0.5.6" version "0.5.6"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz"
@@ -5163,6 +5158,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
dependencies: dependencies:
minimist "^1.2.6" minimist "^1.2.6"
mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment-timezone@^0.5.44: moment-timezone@^0.5.44:
version "0.5.45" version "0.5.45"
resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz" resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz"
@@ -5175,7 +5175,7 @@ moment@^2.29.4:
resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz" resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
ms@^2.1.1, ms@2.1.2: ms@2.1.2, ms@^2.1.1:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@@ -5237,21 +5237,14 @@ node-addon-api@^5.0.0:
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz"
integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==
node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@2.6.7: node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.6.7" version "2.6.7"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies: dependencies:
whatwg-url "^5.0.0" whatwg-url "^5.0.0"
node-fetch@^2.6.12: node-fetch@^2.6.12, node-fetch@^2.6.9:
version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.6.9:
version "2.7.0" version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
@@ -5607,7 +5600,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8, postcss@^8.0.9, postcss@^8.4.21, postcss@8.4.31: postcss@8.4.31, postcss@^8, postcss@^8.0.9, postcss@^8.4.21:
version "8.4.31" version "8.4.31"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
@@ -5649,15 +5642,6 @@ promise-polyfill@^8.3.0:
resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz" resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz"
integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
prop-types@15.7.2: prop-types@15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz"
@@ -5667,6 +5651,15 @@ prop-types@15.7.2:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.8.1" react-is "^16.8.1"
prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
proto3-json-serializer@^1.0.0: proto3-json-serializer@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz" resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz"
@@ -5690,6 +5683,24 @@ protobufjs-cli@1.1.1:
tmp "^0.2.1" tmp "^0.2.1"
uglify-js "^3.7.7" uglify-js "^3.7.7"
protobufjs@7.2.4:
version "7.2.4"
resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz"
integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==
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"
protobufjs@^6.11.3: protobufjs@^6.11.3:
version "6.11.3" version "6.11.3"
resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz"
@@ -5745,24 +5756,6 @@ protobufjs@^7.2.5:
"@types/node" ">=13.7.0" "@types/node" ">=13.7.0"
long "^5.0.0" long "^5.0.0"
protobufjs@7.2.4:
version "7.2.4"
resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz"
integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==
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-from-env@^1.1.0: proxy-from-env@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -5893,10 +5886,10 @@ react-firebase-hooks@^5.1.1:
resolved "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz" resolved "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz"
integrity sha512-y2UpWs82xs+39q5Rc/wq316ca52QsC0n8m801V+yM4IC4hbfOL4yQPVSh7w+ydstdvjN9F+lvs1WrO2VYxpmdA== integrity sha512-y2UpWs82xs+39q5Rc/wq316ca52QsC0n8m801V+yM4IC4hbfOL4yQPVSh7w+ydstdvjN9F+lvs1WrO2VYxpmdA==
react-icons@^4.8.0: react-icons@^5.3.0:
version "4.10.1" version "5.3.0"
resolved "https://registry.npmjs.org/react-icons/-/react-icons-4.10.1.tgz" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c"
integrity sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw== integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1: react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1" version "16.13.1"
@@ -6064,33 +6057,7 @@ read-excel-file@^5.7.1:
fflate "^0.7.3" fflate "^0.7.3"
unzipper "^0.12.2" unzipper "^0.12.2"
readable-stream@^2.0.0: readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^2.0.2:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^2.0.5:
version "2.3.8" version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
@@ -6112,19 +6079,6 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
string_decoder "^1.1.1" string_decoder "^1.1.1"
util-deprecate "^1.0.1" util-deprecate "^1.0.1"
readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readdir-glob@^1.1.2: readdir-glob@^1.1.2:
version "1.1.3" version "1.1.3"
resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz" resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz"
@@ -6226,13 +6180,6 @@ reusify@^1.0.4:
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
rimraf@2: rimraf@2:
version "2.7.1" version "2.7.1"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
@@ -6240,6 +6187,13 @@ rimraf@2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
run-parallel@^1.1.9: run-parallel@^1.1.9:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
@@ -6247,7 +6201,7 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" queue-microtask "^1.2.2"
safe-buffer@^5.0.1, safe-buffer@>=5.1.0, safe-buffer@~5.2.0: safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@~5.2.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -6447,30 +6401,7 @@ streamsearch@^1.1.0:
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
string_decoder@^1.1.1: "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 "1.3.0"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
"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:
version "4.2.3" version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -6520,14 +6451,21 @@ string.prototype.trimstart@^1.0.6:
define-properties "^1.1.4" define-properties "^1.1.4"
es-abstract "^1.20.4" es-abstract "^1.20.4"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": string_decoder@^1.1.1:
version "6.0.1" version "1.3.0"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies: dependencies:
ansi-regex "^5.0.1" safe-buffer "~5.2.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1: string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -6966,7 +6904,7 @@ use-sidecar@^1.1.2:
detect-node-es "^1.1.0" detect-node-es "^1.1.0"
tslib "^2.0.0" tslib "^2.0.0"
use-sync-external-store@^1.2.0, use-sync-external-store@1.2.0: use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
@@ -6976,12 +6914,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
uuid@^8.0.0: uuid@^8.0.0, uuid@^8.3.0:
version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
uuid@^8.3.0:
version "8.3.2" version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
@@ -7119,7 +7052,7 @@ wordwrap@^1.0.0:
resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== 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:
version "7.0.0" version "7.0.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -7137,15 +7070,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0" string-width "^4.1.0"
strip-ansi "^6.0.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: wrap-ansi@^8.1.0:
version "8.1.0" version "8.1.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
@@ -7203,11 +7127,6 @@ yargs-parser@^20.2.2:
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-parser@^21.1.1:
version "21.1.1"
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz"
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
yargs@^15.3.1: yargs@^15.3.1:
version "15.4.1" version "15.4.1"
resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz"
@@ -7238,19 +7157,6 @@ yargs@^16.2.0:
y18n "^5.0.5" y18n "^5.0.5"
yargs-parser "^20.2.2" yargs-parser "^20.2.2"
yargs@^17.7.2:
version "17.7.2"
resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz"
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: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"