diff --git a/src/components/Diagnostic.tsx b/src/components/Diagnostic.tsx index 943d90e7..a221f1d2 100644 --- a/src/components/Diagnostic.tsx +++ b/src/components/Diagnostic.tsx @@ -22,8 +22,8 @@ interface Props { export default function Diagnostic({onFinish}: Props) { const [focus, setFocus] = useState<"academic" | "general">(); - const [levels, setLevels] = useState({reading: -1, listening: -1, writing: -1, speaking: -1}); - const [desiredLevels, setDesiredLevels] = useState({reading: 9, listening: 9, writing: 9, speaking: 9}); + const [levels, setLevels] = useState({reading: -1, listening: -1, writing: -1, speaking: -1, level: 0}); + const [desiredLevels, setDesiredLevels] = useState({reading: 9, listening: 9, writing: 9, speaking: 9, level: 9}); const router = useRouter(); @@ -51,7 +51,7 @@ export default function Diagnostic({onFinish}: Props) { axios .patch("/api/users/update", { focus, - levels: Object.values(levels).includes(-1) ? {reading: 0, listening: 0, writing: 0, speaking: 0} : levels, + levels: Object.values(levels).includes(-1) ? {reading: 0, listening: 0, writing: 0, speaking: 0, level: 0} : levels, desiredLevels, isFirstLogin: false, }) diff --git a/src/components/Exercises/TrueFalse.tsx b/src/components/Exercises/TrueFalse.tsx index 19330684..4c7ffae9 100644 --- a/src/components/Exercises/TrueFalse.tsx +++ b/src/components/Exercises/TrueFalse.tsx @@ -17,7 +17,7 @@ export default function TrueFalse({id, type, prompt, questions, userSolutions, o const calculateScore = () => { const total = questions.length || 0; const correct = answers.filter( - (x) => questions.find((y) => x.id.toString() === y.id.toString())?.solution === x.solution.toLowerCase() || false, + (x) => questions.find((y) => x.id.toString() === y.id.toString())?.solution?.toLowerCase() === x.solution.toLowerCase() || false, ).length; const missing = total - answers.filter((x) => questions.find((y) => x.id.toString() === y.id.toString())).length; @@ -62,41 +62,37 @@ export default function TrueFalse({id, type, prompt, questions, userSolutions, o You can click a selected option again to deselect it.
- {questions.map((question, index) => ( -
- - {index + 1}. {question.prompt} - -
- - - + {questions.map((question, index) => { + const id = question.id.toString(); + + return ( +
+ + {index + 1}. {question.prompt} + +
+ + + +
-
- ))} + ); + })}
diff --git a/src/components/Exercises/Writing.tsx b/src/components/Exercises/Writing.tsx index 0edf3689..5560c2b7 100644 --- a/src/components/Exercises/Writing.tsx +++ b/src/components/Exercises/Writing.tsx @@ -26,6 +26,8 @@ export default function Writing({ const hasExamEnded = useExamStore((state) => state.hasExamEnded); useEffect(() => { + if (localStorage.getItem("enable_paste")) return; + const listener = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === "v") { e.preventDefault(); @@ -93,8 +95,8 @@ export default function Writing({ )}
- {prefix} - {prompt} + {prefix.replaceAll("\\n", "\n")} + {prompt.replaceAll("\\n", "\n")} {attachment && ( setIsModalOpen(true)} diff --git a/src/components/Low/Button.tsx b/src/components/Low/Button.tsx index 29d529e2..9bd207f0 100644 --- a/src/components/Low/Button.tsx +++ b/src/components/Low/Button.tsx @@ -4,7 +4,7 @@ import {BsArrowRepeat} from "react-icons/bs"; interface Props { children: ReactNode; - color?: "rose" | "purple" | "red" | "green"; + color?: "rose" | "purple" | "red" | "green" | "gray"; variant?: "outline" | "solid"; className?: string; disabled?: boolean; @@ -39,6 +39,11 @@ export default function Button({ outline: "bg-transparent text-mti-red-light border border-mti-red-light hover:bg-mti-red-light disabled:text-mti-red disabled:bg-mti-red-ultralight disabled:border-none selection:bg-mti-red-dark hover:text-white selection:text-white", }, + gray: { + solid: "bg-mti-gray-davy text-white border border-mti-gray-davy hover:bg-mti-gray-davy disabled:text-mti-gray-davy disabled:bg-mti-gray-davy selection:bg-mti-gray-davy", + outline: + "bg-transparent text-mti-gray-davy border border-mti-gray-davy hover:bg-mti-gray-davy disabled:text-mti-gray-davy disabled:bg-mti-gray-davy disabled:border-none selection:bg-mti-gray-davy hover:text-white selection:text-white", + }, rose: { solid: "bg-mti-rose-light text-white border border-mti-rose-light hover:bg-mti-rose disabled:text-mti-rose disabled:bg-mti-rose-ultralight selection:bg-mti-rose-dark", outline: diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx index 0e042287..dd31e85e 100644 --- a/src/components/MobileMenu.tsx +++ b/src/components/MobileMenu.tsx @@ -103,7 +103,7 @@ export default function MobileMenu({isOpen, onClose, path, user}: Props) { )}> Record - {["admin", "developer", "agent"].includes(user.type) && ( + {["admin", "developer", "agent", "corporate"].includes(user.type) && ( void; + paymentId: string; }) => { - const { asset, permissions, type, paymentId } = props; + const {asset, permissions, type, paymentId} = props; - const fileInputRef = React.useRef(null); - const fileInputReplaceRef = React.useRef(null); + const fileInputRef = React.useRef(null); + const fileInputReplaceRef = React.useRef(null); - const [managingAsset, setManagingAsset] = React.useState({ - file: asset || "", - complete: asset ? true : false, - }); + const [managingAsset, setManagingAsset] = React.useState({ + file: asset || "", + complete: asset ? true : false, + }); - const { file, complete } = managingAsset; + const {file, complete} = managingAsset; - const deleteAsset = () => { - if (confirm("Are you sure you want to delete this document?")) { - axios - .delete(`/api/payments/files/${type}/${paymentId}`) - .then((response) => { - if (response.status === 200) { - console.log("File deleted successfully!"); - setManagingAsset({ - file: "", - complete: false, - }); - return; - } + const deleteAsset = () => { + if (confirm("Are you sure you want to delete this document?")) { + axios + .delete(`/api/payments/files/${type}/${paymentId}`) + .then((response) => { + if (response.status === 200) { + console.log("File deleted successfully!"); + setManagingAsset({ + file: "", + complete: false, + }); + return; + } - console.error("File deletion failed"); - }) - .catch((error) => { - console.error("Error occurred during file deletion:", error); - }); - } - }; + console.error("File deletion failed"); + }) + .catch((error) => { + console.error("Error occurred during file deletion:", error); + }) + .finally(props.reload); + } + }; - const renderFileInput = ( - onChange: any, - ref: React.RefObject - ) => ( - - ); + const renderFileInput = (onChange: any, ref: React.RefObject) => ( + + ); - const handleFileChange = async (e: Event, method: "post" | "patch") => { - const newFile = (e.target as HTMLInputElement).files?.[0]; - if (newFile) { - setManagingAsset({ - file: newFile, - complete: false, - }); + const handleFileChange = async (e: Event, method: "post" | "patch") => { + const newFile = (e.target as HTMLInputElement).files?.[0]; + if (newFile) { + setManagingAsset({ + file: newFile, + complete: false, + }); - const formData = new FormData(); - formData.append("file", newFile); + const formData = new FormData(); + formData.append("file", newFile); - axios[method](`/api/payments/files/${type}/${paymentId}`, formData, { - headers: { - "Content-Type": "multipart/form-data", - }, - }) - .then((response) => { - if (response.status === 200) { - console.log("File uploaded successfully!"); - console.log("Uploaded File URL:", response.data.ref); - // Further actions upon successful upload - setManagingAsset({ - file: response.data.ref, - complete: true, - }); - return; - } + axios[method](`/api/payments/files/${type}/${paymentId}`, formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }) + .then((response) => { + if (response.status === 200) { + console.log("File uploaded successfully!"); + console.log("Uploaded File URL:", response.data.ref); + // Further actions upon successful upload + setManagingAsset({ + file: response.data.ref, + complete: true, + }); + return; + } - console.error("File upload failed"); - }) - .catch((error) => { - console.error("Error occurred during file upload:", error); - }); - } - }; + console.error("File upload failed"); + }) + .catch((error) => { + console.error("Error occurred during file upload:", error); + }) + .finally(props.reload); + } + }; - const downloadAsset = () => { - axios - .get(`/api/payments/files/${type}/${paymentId}`) - .then((response) => { - if (response.status === 200) { - console.log("Uploaded File URL:", response.data.url); - const link = document.createElement("a"); - link.download = response.data.filename; - link.href = response.data.url; - link.click(); - return; - } + const downloadAsset = () => { + axios + .get(`/api/payments/files/${type}/${paymentId}`) + .then((response) => { + if (response.status === 200) { + console.log("Uploaded File URL:", response.data.url); + const link = document.createElement("a"); + link.download = response.data.filename; + link.href = response.data.url; + link.click(); + return; + } - console.error("Failed to download file"); - }) - .catch((error) => { - console.error("Error occurred during file upload:", error); - }); - }; + console.error("Failed to download file"); + }) + .catch((error) => { + console.error("Error occurred during file upload:", error); + }); + }; - if (permissions === "read") { - if (file) return ; - return null; - } + if (permissions === "read") { + if (file) return ; + return null; + } - if (file) { - if (complete) { - return ( - <> - - fileInputReplaceRef.current?.click()} /> - - {renderFileInput( - (e: Event) => handleFileChange(e, "patch"), - fileInputReplaceRef - )} - {renderFileInput( - (e: Event) => handleFileChange(e, "post"), - fileInputRef - )} - - ); - } + if (file) { + if (complete) { + return ( + <> + + fileInputReplaceRef.current?.click()} /> + + {renderFileInput((e: Event) => handleFileChange(e, "patch"), fileInputReplaceRef)} + {renderFileInput((e: Event) => handleFileChange(e, "post"), fileInputRef)} + + ); + } - return ; - } + return ; + } - return ( - <> - fileInputRef.current?.click()} /> - {renderFileInput((e: Event) => handleFileChange(e, "post"), fileInputRef)} - - ); + return permissions === "write" ? ( + <> + fileInputRef.current?.click()} /> + {renderFileInput((e: Event) => handleFileChange(e, "post"), fileInputRef)} + + ) : ( + + ); }; export default PaymentAssetManager; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index c445cf4b..b9da2b6c 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -99,7 +99,7 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u )}
-
+
Unanswered
diff --git a/src/components/Solutions/MatchSentences.tsx b/src/components/Solutions/MatchSentences.tsx index c2537202..27b7dfcc 100644 --- a/src/components/Solutions/MatchSentences.tsx +++ b/src/components/Solutions/MatchSentences.tsx @@ -50,7 +50,7 @@ export default function MatchSentencesSolutions({ className={clsx( "w-8 h-8 rounded-full z-10 text-white", "transition duration-300 ease-in-out", - !userSolutions.find((x) => x.question.toString() === id.toString()) && "!bg-mti-red", + !userSolutions.find((x) => x.question.toString() === id.toString()) && "!bg-mti-gray-davy", userSolutions.find((x) => x.question.toString() === id.toString())?.option === solution && "bg-mti-purple", userSolutions.find((x) => x.question.toString() === id.toString())?.option !== solution && "bg-mti-rose", )}> @@ -96,7 +96,7 @@ export default function MatchSentencesSolutions({
Correct
-
Unanswered +
Unanswered
Wrong diff --git a/src/components/Solutions/MultipleChoice.tsx b/src/components/Solutions/MultipleChoice.tsx index 7a81ad16..0dfe1785 100644 --- a/src/components/Solutions/MultipleChoice.tsx +++ b/src/components/Solutions/MultipleChoice.tsx @@ -14,7 +14,7 @@ function Question({ }: MultipleChoiceQuestion & {userSolution: string | undefined; onSelectOption?: (option: string) => void; showSolution?: boolean}) { const optionColor = (option: string) => { if (option === solution && !userSolution) { - return "!border-mti-red-light !text-mti-red-light"; + return "!border-mti-gray-davy !text-mti-gray-davy"; } if (option === solution) { @@ -114,7 +114,7 @@ export default function MultipleChoice({ Correct
-
+
Unanswered
diff --git a/src/components/Solutions/Speaking.tsx b/src/components/Solutions/Speaking.tsx index ae5febaa..8447b8e3 100644 --- a/src/components/Solutions/Speaking.tsx +++ b/src/components/Solutions/Speaking.tsx @@ -80,7 +80,8 @@ export default function Speaking({id, type, title, video_url, text, prompts, use
))}
- {userSolutions[0].evaluation && userSolutions[0].evaluation.perfect_answer ? ( + {userSolutions[0].evaluation && + (userSolutions[0].evaluation.perfect_answer || userSolutions[0].evaluation.perfect_answer_1) ? ( - {userSolutions[0].evaluation!.perfect_answer.replaceAll(/\s{2,}/g, "\n\n")} + {userSolutions[0].evaluation!.perfect_answer && + userSolutions[0].evaluation!.perfect_answer.replaceAll(/\s{2,}/g, "\n\n")} + {userSolutions[0].evaluation!.perfect_answer_1 && + userSolutions[0].evaluation!.perfect_answer_1.replaceAll(/\s{2,}/g, "\n\n")} diff --git a/src/components/Solutions/TrueFalse.tsx b/src/components/Solutions/TrueFalse.tsx index a80df152..44699bfb 100644 --- a/src/components/Solutions/TrueFalse.tsx +++ b/src/components/Solutions/TrueFalse.tsx @@ -33,7 +33,7 @@ export default function TrueFalseSolution({prompt, type, id, questions, userSolu return "rose"; } - return "red"; + return "gray"; }; return ( @@ -67,6 +67,7 @@ export default function TrueFalseSolution({prompt, type, id, questions, userSolu {userSolutions && questions.map((question, index) => { const userSolution = userSolutions.find((x) => x.id === question.id.toString()); + const solution = question.solution.toString().toLowerCase() as Solution; return (
@@ -75,23 +76,23 @@ export default function TrueFalseSolution({prompt, type, id, questions, userSolu
@@ -105,7 +106,7 @@ export default function TrueFalseSolution({prompt, type, id, questions, userSolu Correct
-
+
Unanswered
diff --git a/src/components/Solutions/WriteBlanks.tsx b/src/components/Solutions/WriteBlanks.tsx index a7ef4f9b..6740a4c5 100644 --- a/src/components/Solutions/WriteBlanks.tsx +++ b/src/components/Solutions/WriteBlanks.tsx @@ -38,7 +38,7 @@ function Blank({ const getSolutionStyling = () => { if (!userSolution) { - return "bg-mti-red-ultralight text-mti-red-light"; + return "bg-mti-gray-davy text-mti-gray-davy"; } return "bg-mti-purple-ultralight text-mti-purple-light"; @@ -131,7 +131,7 @@ export default function WriteBlanksSolutions({ Correct
-
+
Unanswered
diff --git a/src/components/Solutions/Writing.tsx b/src/components/Solutions/Writing.tsx index c6557825..804df4fa 100644 --- a/src/components/Solutions/Writing.tsx +++ b/src/components/Solutions/Writing.tsx @@ -6,10 +6,31 @@ import Button from "../Low/Button"; import {Dialog, Tab, Transition} from "@headlessui/react"; import {writingReverseMarking} from "@/utils/score"; import clsx from "clsx"; +import reactStringReplace from "react-string-replace"; export default function Writing({id, type, prompt, attachment, userSolutions, onNext, onBack}: WritingExercise & CommonProps) { const [isModalOpen, setIsModalOpen] = useState(false); + const formatSolution = (solution: string, errors: {correction: string | null; misspelled: string}[]) => { + const errorRegex = new RegExp(errors.map((x) => `(${x.misspelled})`).join("|")); + + return ( + <> + {reactStringReplace(solution, errorRegex, (match) => { + const correction = errors.find((x) => x.misspelled === match)?.correction; + + return ( + + {match} + + ); + })} + + ); + }; + return ( <> {attachment && ( @@ -67,12 +88,14 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on {userSolutions && (
Your answer: -