Updated the Finish screen along with other tweaks

This commit is contained in:
Tiago Ribeiro
2023-06-21 14:54:22 +01:00
parent d2cf50be68
commit 808ec6315b
20 changed files with 313 additions and 158 deletions

View File

@@ -1,6 +1,5 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint"
]
}
}

View File

@@ -19,7 +19,7 @@
"axios": "^1.3.5",
"chart.js": "^4.2.1",
"clsx": "^1.2.1",
"daisyui": "^2.50.0",
"daisyui": "^3.1.5",
"eslint": "8.33.0",
"eslint-config-next": "13.1.6",
"firebase": "9.19.1",

View File

@@ -88,8 +88,9 @@ export default function FillBlanks({
const calculateScore = () => {
const total = text.match(/({{\d+}})/g)?.length || 0;
const correct = answers.filter((x) => solutions.find((y) => x.id === y.id)?.solution === x.solution.toLowerCase() || false).length;
const missing = total - answers.filter((x) => solutions.find((y) => x.id === y.id)).length;
return {total, correct};
return {total, correct, missing};
};
const renderLines = (line: string) => {

View File

@@ -6,6 +6,7 @@ import clsx from "clsx";
import {Fragment, useState} from "react";
import LineTo from "react-lineto";
import {CommonProps} from ".";
import Button from "../Low/Button";
export default function MatchSentences({id, options, type, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) {
const [selectedQuestion, setSelectedQuestion] = useState<string>();
@@ -14,8 +15,9 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
const calculateScore = () => {
const total = sentences.length;
const correct = answers.filter((x) => sentences.find((y) => y.id === x.question)?.solution === x.option || false).length;
const missing = total - answers.filter((x) => sentences.find((y) => y.id === x.question)).length;
return {total, correct};
return {total, correct, missing};
};
const selectOption = (option: string) => {
@@ -92,23 +94,21 @@ export default function MatchSentences({id, options, type, prompt, sentences, us
</div>
</div>
<div className="self-end flex flex-col-reverse items-center w-full md:justify-between md:items-start md:flex-row gap-8">
<button
className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}
onClick={() => onBack({exercise: id, solutions: answers, score: calculateScore(), type})}>
<div className="absolute left-4">
<Icon path={mdiArrowLeft} color="white" size={1} />
</div>
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
<Button
color="green"
variant="outline"
onClick={() => onBack({exercise: id, solutions: answers, score: calculateScore(), type})}
className="max-w-[200px] w-full">
Back
</button>
<button
className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)}
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}>
</Button>
<Button
color="green"
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}
className="max-w-[200px] self-end w-full">
Next
<div className="absolute right-4">
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
</Button>
</div>
</>
);

View File

@@ -59,8 +59,9 @@ export default function MultipleChoice({id, prompt, type, questions, userSolutio
const calculateScore = () => {
const total = questions.length;
const correct = answers.filter((x) => questions.find((y) => y.id === x.question)?.solution === x.option || false).length;
const missing = total - answers.filter((x) => questions.find((y) => y.id === x.question)).length;
return {total, correct};
return {total, correct, missing};
};
const next = () => {

View File

@@ -180,14 +180,14 @@ export default function Speaking({id, title, text, type, prompts, onNext, onBack
<Button
color="green"
variant="outline"
onClick={() => onBack({exercise: id, solutions: [], score: {correct: 1, total: 1}, type})}
onClick={() => onBack({exercise: id, solutions: [], score: {correct: 1, total: 1, missing: 0}, type})}
className="max-w-[200px] self-end w-full">
Back
</Button>
<Button
color="green"
disabled={!mediaBlob}
onClick={() => onNext({exercise: id, solutions: [], score: {correct: 1, total: 1}, type})}
onClick={() => onNext({exercise: id, solutions: [], score: {correct: 1, total: 1, missing: 0}, type})}
className="max-w-[200px] self-end w-full">
Next
</Button>

View File

@@ -57,8 +57,9 @@ export default function WriteBlanks({id, prompt, type, maxWords, solutions, user
?.solution.map((y) => y.toLowerCase())
.includes(x.solution.toLowerCase()) || false,
).length;
const missing = total - answers.filter((x) => solutions.find((y) => x.id === y.id)).length;
return {total, correct};
return {total, correct, missing};
};
const renderLines = (line: string) => {

View File

@@ -58,14 +58,14 @@ export default function Writing({id, prompt, info, type, wordCounter, attachment
<Button
color="green"
variant="outline"
onClick={() => onBack({exercise: id, solutions: [inputText], score: {correct: 1, total: 1}, type})}
onClick={() => onBack({exercise: id, solutions: [inputText], score: {correct: 1, total: 1, missing: 0}, type})}
className="max-w-[200px] self-end w-full">
Back
</Button>
<Button
color="green"
disabled={!isSubmitEnabled}
onClick={() => onNext({exercise: id, solutions: [inputText], score: {correct: 1, total: 1}, type})}
onClick={() => onNext({exercise: id, solutions: [inputText], score: {correct: 1, total: 1, missing: 0}, type})}
className="max-w-[200px] self-end w-full">
Next
</Button>

View File

@@ -9,18 +9,21 @@ interface Props {
module: Module;
exerciseIndex: number;
totalExercises: number;
disableTimer?: boolean;
}
export default function ModuleTitle({minTimer, module, exerciseIndex, totalExercises}: Props) {
export default function ModuleTitle({minTimer, module, exerciseIndex, totalExercises, disableTimer = false}: Props) {
const [timer, setTimer] = useState(minTimer * 60);
useEffect(() => {
const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000);
if (!disableTimer) {
const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000);
return () => {
clearInterval(timerInterval);
};
}, [minTimer]);
return () => {
clearInterval(timerInterval);
};
}
}, [disableTimer, minTimer]);
const moduleIcon: {[key in Module]: ReactNode} = {
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
@@ -57,7 +60,7 @@ export default function ModuleTitle({minTimer, module, exerciseIndex, totalExerc
Question {exerciseIndex}/{totalExercises}
</span>
</div>
<ProgressBar color={module} label="" percentage={((exerciseIndex - 1) * 100) / totalExercises} className="h-2 w-full" />
<ProgressBar color={module} label="" percentage={(exerciseIndex * 100) / totalExercises} className="h-2 w-full" />
</div>
</div>
</>

View File

@@ -6,6 +6,7 @@ import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
import Icon from "@mdi/react";
import {Fragment} from "react";
import Button from "../Low/Button";
export default function MatchSentencesSolutions({options, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) {
return (
@@ -63,19 +64,14 @@ export default function MatchSentencesSolutions({options, prompt, sentences, use
</div>
</div>
<div className="self-end flex flex-col-reverse items-center w-full md:justify-between md:items-start md:flex-row gap-8">
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)} onClick={onBack}>
<div className="absolute left-4">
<Icon path={mdiArrowLeft} color="white" size={1} />
</div>
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
<Button color="green" variant="outline" onClick={() => onBack()} className="max-w-[200px] w-full">
Back
</button>
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} onClick={onNext}>
</Button>
<Button color="green" onClick={() => onNext()} className="max-w-[200px] self-end w-full">
Next
<div className="absolute right-4">
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
</Button>
</div>
</>
);

View File

@@ -1,92 +1,199 @@
import ProfileLevel from "@/components/ProfileLevel";
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
import Button from "@/components/Low/Button";
import ModuleTitle from "@/components/Medium/ModuleTitle";
import {Module} from "@/interfaces";
import {User} from "@/interfaces/user";
import {ICONS} from "@/resources/modules";
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
import Icon from "@mdi/react";
import useExamStore from "@/stores/examStore";
import {calculateBandScore} from "@/utils/score";
import clsx from "clsx";
import Link from "next/link";
import router from "next/router";
import {useRouter} from "next/router";
import {useEffect, useState} from "react";
import {BsArrowCounterclockwise, BsBook, BsEyeFill, BsHeadphones, BsMegaphone, BsPen, BsShareFill} from "react-icons/bs";
interface Score {
module: Module;
correct: number;
total: number;
missing: number;
}
interface Props {
user: User;
modules: Module[];
scores: {
module?: Module;
correct: number;
total: number;
}[];
scores: Score[];
onViewResults: () => void;
}
export default function Finish({user, scores, modules, onViewResults}: Props) {
const renderModuleScore = (module: Module) => {
const moduleScores = scores.filter((x) => x.module === module);
if (moduleScores.length === 0) {
return <>0</>;
}
const [selectedModule, setSelectedModule] = useState(modules[0]);
const [selectedScore, setSelectedScore] = useState<Score>(scores.find((x) => x.module === modules[0])!);
return moduleScores.map((x, index) => (
<span key={index}>
{x.correct} / {x.total}
</span>
));
};
const exams = useExamStore((state) => state.exams);
const renderModuleTotal = (module: Module) => {
const moduleScores = scores.filter((x) => x.module === module);
if (moduleScores.length === 0) {
return <>0</>;
}
useEffect(() => setSelectedScore(scores.find((x) => x.module === selectedModule)!), [scores, selectedModule]);
const total = moduleScores.reduce((accumulator, current) => accumulator + current.total, 0);
const correct = moduleScores.reduce((accumulator, current) => accumulator + current.correct, 0);
return (
<span>
{correct} / {total} | {Math.floor((correct / total) * 100)}%
</span>
);
const moduleColors: {[key in Module]: {progress: string; inner: string}} = {
reading: {
progress: "text-ielts-reading",
inner: "bg-ielts-reading-light",
},
listening: {
progress: "text-ielts-listening",
inner: "bg-ielts-listening-light",
},
writing: {
progress: "text-ielts-writing",
inner: "bg-ielts-writing-light",
},
speaking: {
progress: "text-ielts-speaking",
inner: "bg-ielts-speaking-light",
},
};
return (
<div className="w-full h-full relative">
<section className="h-full w-full flex flex-col items-center justify-center gap-4">
<ProfileLevel user={user} className="h-1/2" />
<div className="h-2/3 w-2/3 flex flex-col items-center gap-4">
<div className="rounded-xl p-2 md:p-4 items-center flex lg:justify-center gap-2 md:gap-4 max-w-[100%] overflow-auto">
{modules.map((module) => (
<>
<div className="w-full min-h-full h-fit flex flex-col items-center justify-between gap-8">
<ModuleTitle
module={selectedModule}
totalExercises={exams.find((x) => x.module === selectedModule)!.exercises.length}
exerciseIndex={exams.find((x) => x.module === selectedModule)!.exercises.length}
minTimer={exams.find((x) => x.module === selectedModule)!.minTimer}
disableTimer
/>
<div className="flex gap-4 self-start">
{modules.includes("reading") && (
<div
onClick={() => setSelectedModule("reading")}
className={clsx(
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-reading hover:text-white",
selectedModule === "reading" ? "bg-ielts-reading text-white" : "bg-mti-gray-smoke text-ielts-reading",
)}>
<BsBook className="w-6 h-6" />
<span className="font-semibold">Reading</span>
</div>
)}
{modules.includes("listening") && (
<div
onClick={() => setSelectedModule("listening")}
className={clsx(
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-listening hover:text-white",
selectedModule === "listening" ? "bg-ielts-listening text-white" : "bg-mti-gray-smoke text-ielts-listening",
)}>
<BsHeadphones className="w-6 h-6" />
<span className="font-semibold">Listening</span>
</div>
)}
{modules.includes("writing") && (
<div
onClick={() => setSelectedModule("writing")}
className={clsx(
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-writing hover:text-white",
selectedModule === "writing" ? "bg-ielts-writing text-white" : "bg-mti-gray-smoke text-ielts-writing",
)}>
<BsPen className="w-6 h-6" />
<span className="font-semibold">Writing</span>
</div>
)}
{modules.includes("speaking") && (
<div
onClick={() => setSelectedModule("speaking")}
className={clsx(
"flex gap-2 items-center rounded-xl p-4 cursor-pointer hover:shadow-lg transition duration-300 ease-in-out hover:bg-ielts-speaking hover:text-white",
selectedModule === "speaking" ? "bg-ielts-speaking text-white" : "bg-mti-gray-smoke text-ielts-speaking",
)}>
<BsMegaphone className="w-6 h-6" />
<span className="font-semibold">Speaking</span>
</div>
)}
</div>
<div className="w-full flex gap-9 mt-32 items-center justify-between">
<span className="max-w-3xl">
Congratulations on your exam performance! You achieved an impressive{" "}
<span className="font-bold">
level {calculateBandScore(selectedScore.correct, selectedScore.total, selectedModule, user.focus)}
</span>
, demonstrating excellent mastery of the assessed knowledge.
<br />
<br />
If you disagree with the result, you can request a review by a qualified teacher. We are committed to the accuracy and
transparency of the results. Please contact us for further information.
<br />
<br />
Congratulations again on your outstanding achievement! We are here to support you on your academic journey.
</span>
<div className="flex gap-9 px-16">
<div
className={clsx("radial-progress overflow-hidden", moduleColors[selectedModule].progress)}
style={{"--value": (selectedScore.correct / selectedScore.total) * 100, "--thickness": "12px", "--size": "13rem"} as any}>
<div
className={`flex flex-col gap-12 min-w-[176px] items-center justify-center border-2 border-ielts-${module} bg-ielts-${module}-transparent py-4 px-2 rounded-xl text-white font-semibold`}
key={module}>
<div className="flex flex-col items-center">
<Icon path={ICONS[module]} color="white" size={2} />
<span>{module.toUpperCase()}</span>
</div>
<div className="flex flex-col">{renderModuleScore(module)}</div>
<div>TOTAL: {renderModuleTotal(module)}</div>
className={clsx(
"w-48 h-48 rounded-full flex flex-col items-center justify-center",
moduleColors[selectedModule].inner,
)}>
<span className="text-xl">Level</span>
<span className="text-3xl font-bold">
{calculateBandScore(selectedScore.correct, selectedScore.total, selectedModule, user.focus)}
</span>
</div>
))}
</div>
<div className="w-full flex flex-col gap-4 lg:gap-0 md:flex-row justify-center items-center lg:justify-between">
<Link href="/">
<button className={clsx("btn btn-wide gap-4 relative text-white", errorButtonStyle)}>
<div className="absolute left-4">
<Icon path={mdiArrowLeft} color="white" size={1} />
</div>
<div className="flex flex-col gap-5">
<div className="flex gap-2">
<div className="w-3 h-3 bg-mti-blue-light rounded-full mt-1" />
<div className="flex flex-col">
<span className="text-mti-blue-light">
{(((selectedScore.total - selectedScore.missing) / selectedScore.total) * 100).toFixed(0)}%
</span>
<span className="text-lg">Completion</span>
</div>
Go Home
</button>
</Link>
<button className={clsx("btn btn-wide gap-4 relative text-white", infoButtonStyle)} onClick={onViewResults}>
View Solutions
<div className="absolute right-4">
<Icon path={mdiArrowRight} color="white" size={1} />
</div>
</button>
<div className="flex gap-2">
<div className="w-3 h-3 bg-mti-green-light rounded-full mt-1" />
<div className="flex flex-col">
<span className="text-mti-green-light">{selectedScore.total.toString().padStart(2, "0")}</span>
<span className="text-lg">Correct</span>
</div>
</div>
<div className="flex gap-2">
<div className="w-3 h-3 bg-mti-orange-light rounded-full mt-1" />
<div className="flex flex-col">
<span className="text-mti-orange-light">
{(selectedScore.total - selectedScore.correct).toString().padStart(2, "0")}
</span>
<span className="text-lg">Wrong</span>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
<div className="flex gap-8">
<div className="w-fit flex flex-col items-center gap-1 cursor-pointer">
<button
onClick={() => window.location.reload()}
className="w-11 h-11 rounded-full bg-mti-green-light hover:bg-mti-green flex items-center justify-center transition duration-300 ease-in-out">
<BsArrowCounterclockwise className="text-white w-7 h-7" />
</button>
<span>Play Again</span>
</div>
<div className="w-fit flex flex-col items-center gap-1 cursor-pointer">
<button
onClick={onViewResults}
className="w-11 h-11 rounded-full bg-mti-green-light hover:bg-mti-green flex items-center justify-center transition duration-300 ease-in-out">
<BsEyeFill className="text-white w-7 h-7" />
</button>
<span>Review Answers</span>
</div>
</div>
<Link href="/" className="max-w-[200px] w-full self-end">
<Button color="green" className="max-w-[200px] self-end w-full">
Dashboard
</Button>
</Link>
</div>
</>
);
}

View File

@@ -80,7 +80,13 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
return (
<>
<div className="flex flex-col h-full w-full gap-8 justify-between">
<ModuleTitle exerciseIndex={exerciseIndex + 1} minTimer={exam.minTimer} module="listening" totalExercises={exam.exercises.length} />
<ModuleTitle
exerciseIndex={exerciseIndex + 1}
minTimer={exam.minTimer}
module="listening"
totalExercises={exam.exercises.length}
disableTimer={showSolutions}
/>
{exerciseIndex === -1 && renderAudioPlayer()}
{exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length &&

View File

@@ -142,7 +142,13 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props)
<>
<div className="flex flex-col h-full w-full gap-8">
<TextModal {...exam.text} isOpen={showTextModal} onClose={() => setShowTextModal(false)} />
<ModuleTitle minTimer={exam.minTimer} exerciseIndex={exerciseIndex + 1} module="reading" totalExercises={exam.exercises.length} />
<ModuleTitle
minTimer={exam.minTimer}
exerciseIndex={exerciseIndex + 1}
module="reading"
totalExercises={exam.exercises.length}
disableTimer={showSolutions}
/>
{exerciseIndex === -1 && renderText()}
{exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length &&

View File

@@ -59,7 +59,13 @@ export default function Speaking({exam, showSolutions = false, onFinish}: Props)
return (
<>
<div className="flex flex-col h-full w-full gap-8 items-center">
<ModuleTitle minTimer={exam.minTimer} exerciseIndex={exerciseIndex + 1} module="speaking" totalExercises={exam.exercises.length} />
<ModuleTitle
minTimer={exam.minTimer}
exerciseIndex={exerciseIndex + 1}
module="speaking"
totalExercises={exam.exercises.length}
disableTimer={showSolutions}
/>
{exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length &&
!showSolutions &&

View File

@@ -59,7 +59,13 @@ export default function Writing({exam, showSolutions = false, onFinish}: Props)
return (
<>
<div className="flex flex-col h-full w-full gap-8 items-center">
<ModuleTitle minTimer={exam.minTimer} exerciseIndex={exerciseIndex + 1} module="writing" totalExercises={exam.exercises.length} />
<ModuleTitle
minTimer={exam.minTimer}
exerciseIndex={exerciseIndex + 1}
module="writing"
totalExercises={exam.exercises.length}
disableTimer={showSolutions}
/>
{exerciseIndex > -1 &&
exerciseIndex < exam.exercises.length &&
!showSolutions &&

View File

@@ -35,6 +35,7 @@ export interface UserSolution {
score: {
correct: number;
total: number;
missing: number;
};
exercise: string;
}

View File

@@ -148,6 +148,43 @@ export default function Page() {
setModuleIndex((prev) => prev + 1);
};
const aggregateScoresByModule = (answers: UserSolution[]): {module: Module; total: number; missing: number; correct: number}[] => {
const scores: {[key in Module]: {total: number; missing: number; correct: number}} = {
reading: {
total: 0,
correct: 0,
missing: 0,
},
listening: {
total: 0,
correct: 0,
missing: 0,
},
writing: {
total: 0,
correct: 0,
missing: 0,
},
speaking: {
total: 0,
correct: 0,
missing: 0,
},
};
answers.forEach((x) => {
scores[x.module!] = {
total: scores[x.module!].total + x.score.total,
correct: scores[x.module!].correct + x.score.correct,
missing: scores[x.module!].missing + x.score.missing,
};
});
return Object.keys(scores)
.filter((x) => scores[x as Module].total > 0)
.map((x) => ({module: x as Module, ...scores[x as Module]}));
};
const renderScreen = () => {
if (selectedModules.length === 0) {
return <Selection user={user!} onStart={setSelectedModules} />;
@@ -163,7 +200,7 @@ export default function Page() {
setModuleIndex(0);
setExam(exams[0]);
}}
scores={userSolutions.map((x) => ({...x.score, module: x.module}))}
scores={aggregateScoresByModule(userSolutions)}
/>
);
}

View File

@@ -36,7 +36,7 @@ const academicMarking: {[key: number]: number} = {
10: 2.5,
};
const moduleMarkings: {[key in "reading" | "listening"]: {[key in Type]: {[key: number]: number}}} = {
const moduleMarkings: {[key in Module]: {[key in Type]: {[key: number]: number}}} = {
reading: {
academic: academicMarking,
general: readingGeneralMarking,
@@ -45,9 +45,17 @@ const moduleMarkings: {[key in "reading" | "listening"]: {[key in Type]: {[key:
academic: academicMarking,
general: academicMarking,
},
writing: {
academic: academicMarking,
general: readingGeneralMarking,
},
speaking: {
academic: academicMarking,
general: academicMarking,
},
};
export const calculateBandScore = (correct: number, total: number, module: "reading" | "listening", type: Type) => {
export const calculateBandScore = (correct: number, total: number, module: Module, type: Type) => {
const marking = moduleMarkings[module][type];
const percentage = (correct * 100) / total;

View File

@@ -22,10 +22,10 @@ module.exports = {
black: "#353338",
},
ielts: {
reading: {DEFAULT: "#1EB3FF", transparent: "rgba(255, 99, 132, 0.5)"},
listening: {DEFAULT: "#FF790A", transparent: "rgba(54, 162, 235, 0.5)"},
writing: {DEFAULT: "#3D9F11", transparent: "rgba(255, 206, 86, 0.5)"},
speaking: {DEFAULT: "#EF5DA8", transparent: "rgba(75, 192, 192, 0.5)"},
reading: {DEFAULT: "#1EB3FF", light: "#F0F9FF", transparent: "rgba(255, 99, 132, 0.5)"},
listening: {DEFAULT: "#FF790A", light: "#FFF1E5", transparent: "rgba(54, 162, 235, 0.5)"},
writing: {DEFAULT: "#3D9F11", light: "#E8FCDF", transparent: "rgba(255, 206, 86, 0.5)"},
speaking: {DEFAULT: "#EF5DA8", light: "#FEF6FA", transparent: "rgba(75, 192, 192, 0.5)"},
},
},
},

View File

@@ -1338,26 +1338,15 @@ color-convert@^2.0.1:
dependencies:
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.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4"
integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color@^4.2:
version "4.2.3"
resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
dependencies:
color-convert "^2.0.1"
color-string "^1.9.0"
colord@^2.9:
version "2.9.3"
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
combined-stream@^1.0.8:
version "1.0.8"
@@ -1390,7 +1379,7 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
css-selector-tokenizer@^0.8.0:
css-selector-tokenizer@^0.8:
version "0.8.0"
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz#88267ef6238e64f2215ea2764b3e2cf498b845dd"
integrity sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==
@@ -1408,14 +1397,14 @@ csstype@^3.0.2:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
daisyui@^2.50.0:
version "2.52.0"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-2.52.0.tgz#1e22abd655bf4a5cb466e1a1f264bb597e9ec254"
integrity sha512-LQTA5/IVXAJHBMFoeaEMfd7/akAFPPcdQPR3O9fzzcFiczneJFM73CFPnScmW2sOgn/D83cvkP854ep2T9OfTg==
daisyui@^3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/daisyui/-/daisyui-3.1.5.tgz#49cb961d03504beebd238d58f0169dbe81103c95"
integrity sha512-3nk5L//IU31ISzsonKEFG8ris8hS/MRAc+PrOTOyRbssjv1fYmNKSdIrOhG6op2ED3RyJL4p6I81JUwd7RWqTw==
dependencies:
color "^4.2"
css-selector-tokenizer "^0.8.0"
postcss-js "^4.0.0"
colord "^2.9"
css-selector-tokenizer "^0.8"
postcss-js "^4"
tailwindcss "^3"
damerau-levenshtein@^1.0.8:
@@ -2323,11 +2312,6 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
get-intrinsic "^1.2.0"
is-typed-array "^1.1.10"
is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
is-bigint@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -2962,7 +2946,7 @@ postcss-import@^15.1.0:
read-cache "^1.0.0"
resolve "^1.1.7"
postcss-js@^4.0.0, postcss-js@^4.0.1:
postcss-js@^4, postcss-js@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
@@ -3360,13 +3344,6 @@ signal-exit@^3.0.3, signal-exit@^3.0.7:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
simple-swizzle@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==
dependencies:
is-arrayish "^0.3.1"
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"