Created
June 5, 2024 06:05
-
-
Save MannyGozzi/c989c060fbaaeed0c1acbd36db8cca8f to your computer and use it in GitHub Desktop.
A Practice Page Fragment for an IELTS Learning App
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use client"; | |
import AnimatedHeader from "@/components/AnimatedHeader"; | |
import QuestionsDropdown from "@/components/DropQ/QuestionsDropdown"; | |
import Timer from "@/components/Timer"; | |
import { AnimButton } from "@/components/ui/button"; | |
import { AnimCard } from "@/components/ui/card"; | |
import { Separator } from "@/components/ui/separator"; | |
import { animFadeInWithDelay, animFadeUpWithDelay } from "@/constants/anim"; | |
import { | |
dynamicFillBlankContent, | |
multipleChoiceQuestions, | |
questionOptions, | |
readingQuestions, | |
} from "@/constants/mockData"; | |
import { ContentType } from "@/contracts/types"; | |
import { motion } from "framer-motion"; | |
import { useEffect, useState } from "react"; | |
import QuestionsMultipleChoice from "@/components/MCQ/QuestionsMultipleChoice"; | |
import ExerciseHeader from "@/components/ExerciseHeader"; | |
import WritingQuestion from "@/components/WritingQuestion"; | |
import { countWords } from "@/app/helpers/WordCounter"; | |
import WordCounter from "@/components/WordCount"; | |
import { FormatType, IModuleFull, QuestionType } from "@/firebase/types"; | |
import { getPracticeModule } from "@/firebase/getPracticeModule"; | |
import { | |
AlertDialog, | |
AlertDialogAction, | |
AlertDialogCancel, | |
AlertDialogContent, | |
AlertDialogDescription, | |
AlertDialogFooter, | |
AlertDialogHeader, | |
AlertDialogTitle, | |
} from "@/components/ui/alert-dialog"; | |
import QuestionsFillBlank from "@/components/QuestionsFillBlank"; | |
import { | |
getSectionType, | |
getQuestionRange, | |
getRangeSlice, | |
getReadingIndex, | |
} from "@/app/helpers/ModuleHelpers"; | |
export interface PracticePageProps { | |
params: { | |
id: string; | |
}; | |
} | |
const exerciseType: ContentType = "reading"; | |
const ReadingPage = ({ params }: PracticePageProps) => { | |
const { id } = params; | |
/* PRACTICE STATE */ | |
const [module, setModule] = useState<IModuleFull | undefined>(undefined); | |
const [questionType, setQuestionType] = useState<QuestionType | undefined>( | |
undefined, | |
); | |
const [sectionType, setSectionType] = useState<FormatType | undefined>( | |
undefined, | |
); | |
const [sectionIndex, setSectionIndex] = useState(0); | |
const [questionRange, setQuestionRange] = useState<[number, number]>([0, 1]); | |
const [isDialogOpen, setIsDialogOpen] = useState(false); | |
/* WRITING STATE */ | |
const [text, setText] = useState(""); | |
const [wordCount, setWordCount] = useState(0); | |
const [wordLimit, setWordLimit] = useState(10); | |
const onWritingChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | |
const newText = e.target.value; | |
setText(newText); | |
setWordCount(countWords(newText)); | |
}; | |
/* Question format answer stores */ | |
const [responses, setResponses] = useState<string[][]>([]); | |
useEffect(() => { | |
getPracticeModule(id).then((module) => { | |
setModule(module); | |
setQuestionType(module.p_type as QuestionType); | |
setQuestionRange(getQuestionRange(module.p_sections[0])); | |
setSectionType(getSectionType(module.p_sections[0])); | |
setResponses(new Array(module.p_questions.length).fill([])); | |
}); | |
}, [id]); | |
/* DEBUG HELPER */ | |
// console.log(inputValues); | |
// console.log(dropdownQuestionsVal) | |
// console.log(MCQSValues) | |
const onNext = () => { | |
saveQuestions(); | |
if ( | |
module?.p_sections[sectionIndex + 1] && | |
sectionIndex < module?.p_sections.length - 1 | |
) { | |
setSectionType(getSectionType(module.p_sections[sectionIndex + 1])); | |
setQuestionRange(getQuestionRange(module?.p_sections[sectionIndex + 1])); | |
setSectionIndex((prev) => prev + 1); | |
} else { | |
onFinishDialog(); | |
} | |
}; | |
const onBack = () => { | |
saveQuestions(); | |
if (sectionIndex > 0 && module?.p_sections[sectionIndex - 1]) { | |
setSectionIndex((prev) => prev - 1); | |
setSectionType(getSectionType(module.p_sections[sectionIndex - 1])); | |
setQuestionRange(getQuestionRange(module?.p_sections[sectionIndex - 1])); | |
} | |
}; | |
const saveQuestions = () => { | |
console.log(responses.map((val) => val.join("|"))); | |
// TODO SAVE DATA AND SEND TO SERVER | |
}; | |
const onFinishDialog = () => { | |
setIsDialogOpen(true); | |
}; | |
const onFinish = () => { | |
// NAVIGATE BACK | |
history.back(); | |
}; | |
const commonProps = { | |
responses, | |
setResponses, | |
questionRange: questionRange, | |
questions: getRangeSlice(module?.p_questions, questionRange), | |
options: getRangeSlice(module?.p_options, questionRange), | |
answers: getRangeSlice(module?.p_answers, questionRange), | |
}; | |
return ( | |
<motion.div | |
className="flex justify-center bg-muted/40 flex-col overflow-y-hidden" | |
layoutId="mainBackground" | |
> | |
<div className="flex flex-col items-center h-[100vh]"> | |
<ExerciseHeader | |
currentQuestion={2} | |
questionRange={questionRange} | |
currentPart={sectionIndex + 1} | |
numParts={module?.p_sections.length || 1} | |
onNext={onNext} | |
onBack={onBack} | |
onSubmit={onFinishDialog} | |
/> | |
<div className="flex flex-col p-4 gap-4 sm:flex-row max-w-2xl h-full pb-20 w-full"> | |
<AnimCard | |
className="p-6 flex flex-col rounded-2xl gap-3 flex-1 overflow-y-auto" | |
layoutId={`anim-card-${id}`} | |
> | |
<div className="flex flex-row justify-between items-center flex-wrap gap-x-2"> | |
<AnimatedHeader | |
id={id} | |
title={module?.mod_title || "Loading..."} | |
type={exerciseType} | |
/> | |
<motion.p className="font-medium" {...animFadeInWithDelay(0.2)}> | |
{questionType === "writing" | |
? `Word Limit: ${wordLimit}` | |
: `Questions ${questionRange[0]}-${questionRange[1]}`} | |
</motion.p> | |
</div> | |
<Separator /> | |
<motion.h2 | |
className="text-2xl font-bold" | |
{...animFadeUpWithDelay(0.1)} | |
> | |
{module?.mod_title || "Loading..."} | |
</motion.h2> | |
<motion.h3 | |
className="text-xl font-bold" | |
{...animFadeUpWithDelay(0.2)} | |
> | |
{module?.mod_subTitle} | |
</motion.h3> | |
<motion.p | |
className="reading-content text-base" | |
{...animFadeUpWithDelay(0.3)} | |
> | |
{module?.p_readings[ | |
getReadingIndex(module?.p_sections[sectionIndex]) - 1 | |
] || "Loading..."} | |
</motion.p> | |
</AnimCard> | |
<AnimCard | |
className="flex-1 p-4 flex flex-col gap-3 overflow-y-auto" | |
{...animFadeUpWithDelay(0.15)} | |
> | |
<div className="flex flex-row justify-between"> | |
<Timer /> | |
{questionType === "writing" && ( | |
<WordCounter wordCount={wordCount} /> | |
)} | |
</div> | |
{questionType === "writing" && ( | |
<WritingQuestion | |
text={text} | |
handleTextChange={onWritingChange} | |
wordCount={wordCount} | |
wordLimit={wordLimit} | |
/> | |
)} | |
{questionType === "reading" && sectionType === "dropdown" && ( | |
<QuestionsDropdown {...commonProps} /> | |
)} | |
{questionType === "reading" && sectionType === "fill" && ( | |
<QuestionsFillBlank {...commonProps} /> | |
)} | |
{questionType === "reading" && sectionType === "mcq" && ( | |
<QuestionsMultipleChoice {...commonProps} /> | |
)} | |
<AnimButton | |
className="self-end m-4 py-5 px-6" | |
variant="secondary" | |
onClick={onNext} | |
{...animFadeUpWithDelay(0.7)} | |
> | |
Next | |
</AnimButton> | |
</AnimCard> | |
<AlertDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> | |
<AlertDialogContent> | |
<AlertDialogHeader> | |
<AlertDialogTitle>Practice Complete</AlertDialogTitle> | |
<AlertDialogDescription> | |
{ | |
// check if there are any unanswered questions | |
responses.some((val) => val.length === 0) | |
? `You have unanswered questions. Are you sure you want to | |
continue?` | |
: `You're about to submit your answers. Are you sure you want to | |
continue?` | |
} | |
</AlertDialogDescription> | |
</AlertDialogHeader> | |
<AlertDialogFooter> | |
<AlertDialogCancel onClick={() => setIsDialogOpen(false)}> | |
Cancel | |
</AlertDialogCancel> | |
<AlertDialogAction onClick={onFinish}> | |
Continue | |
</AlertDialogAction> | |
</AlertDialogFooter> | |
</AlertDialogContent> | |
</AlertDialog> | |
</div> | |
</div> | |
</motion.div> | |
); | |
}; | |
export default ReadingPage; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment