Skip to content

Commit

Permalink
add test
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamed-helmy22020 committed Nov 5, 2024
1 parent 6191db9 commit ac494fa
Show file tree
Hide file tree
Showing 41 changed files with 1,197 additions and 304 deletions.
4 changes: 1 addition & 3 deletions app/[locale]/(main)/[[...path]]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import FoldersBreadcrumb from "@/components/FoldersBreadcrumb";
import { Suspense } from "react";
import Loading from "./loading";

const Layout = async ({
children,
Expand All @@ -13,7 +11,7 @@ const Layout = async ({
<>
<div className="flex flex-col flex-wrap flex-1 h-fit overflow-auto p-5 gap-7 ">
<FoldersBreadcrumb path={params.path} />
<Suspense fallback={<Loading />}>{children}</Suspense>
{children}
</div>
</>
);
Expand Down
1 change: 0 additions & 1 deletion app/[locale]/(main)/[[...path]]/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import LoadingComponent from "@/components/LoadingComponent";

export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <LoadingComponent />;
}
242 changes: 242 additions & 0 deletions app/[locale]/(main)/take_test/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
"use client";

import LoadingComponent from "@/components/LoadingComponent";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { useTakeTestsStore } from "@/store/takeTestsStore";
import { useLanguagesStore } from "@/store/userLanguagesStore";
import { AnimatePresence, motion, useWillChange } from "framer-motion";
import { ChevronLeft, ChevronRight, MoveLeft, Volume2 } from "lucide-react";
import { redirect, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";

const TakeTestPage = () => {
const willChange = useWillChange();
const router = useRouter();

const testId = useSearchParams().get("testId");
const [tests, currentTest, finishTest, setTestWord] = useTakeTestsStore(
(state) => [
state.tests,
state.tests?.find((test) => test.id === testId),
state.finishTest,
state.setTestWord,
]
);
const testWords = currentTest?.words;
const [currentWordIndex, setCurrentWordIndex] = useState<number>(0);
const currentLanguage = useLanguagesStore((state) => state.currentLanguage);
const [answer, setAnswer] = useState<string>("");

useEffect(() => {
setAnswer(testWords?.[currentWordIndex].answer || "");
}, [currentWordIndex || testWords]);

const handleSubmit = () => {
if (answer.trim() === "") return;
if (!testWords) return;
const isCorrect = answer === testWords[currentWordIndex]?.secondLang;
setTestWord(
testWords[currentWordIndex]?.$id,
{
...testWords[currentWordIndex],
isCorrect,
isSolved: true,
answer: answer.trim(),
},
currentTest?.id
);
setCurrentWordIndex((prev) => Math.min(prev + 1, testWords.length - 1));
};

useEffect(() => {
console.log({ testWords, currentTest, tests });
}, [currentTest, tests]);

if (tests?.length !== undefined && tests.length === 0) {
redirect("/");
}

if (!currentTest || !testWords || !tests) {
return <LoadingComponent />;
}

const handleFinishTest = () => {
const score =
testWords.reduce((acc, word) => {
if (word.isSolved && word.isCorrect) {
return acc + 1;
}
return acc;
}, 0) / testWords.length;

finishTest(currentTest?.id, score);
router.push("/test_results?testId=" + currentTest?.id);
};

const handleListen = () => {
const speech = new SpeechSynthesisUtterance(
testWords[currentWordIndex]?.secondLang
);
speech.lang = currentLanguage.code;
speech.rate = 0.8;
speechSynthesis.speak(speech);
};

return (
<div className="p-5 flex flex-col flex-1 overflow-x-hidden">
<TopPanel testWords={testWords} />
<AnimatePresence mode={"popLayout"}>
<motion.div
style={{ willChange }}
className="q-card flex justify-center flex-col gap-3 items-center flex-1"
key={currentWordIndex}
initial={{ x: "100%", opacity: 0 }} // Start from right
animate={{ x: 0, opacity: 1 }} // Slide in
exit={{ x: "-100%", opacity: 0 }} // Slide out
transition={{ stiffness: 300 }} // Animation configuration
>
<div className="flex flex-col gap-3 justify-start items-start border-2 border-gray-500 rounded-lg p-10 w-2/4">
<div className="flex justify-between items-center w-full">
<div className="font-bold">
Word No.:{" "}
<span className="inline-block text-[#f39200]">
{currentWordIndex + 1}
</span>
</div>
</div>
<div className="flex flex-col gap-3 justify-start items-start w-full">
{currentTest?.showItems.firstLang && (
<div className="text-4xl">
{testWords[currentWordIndex]?.firstLang}
</div>
)}
{currentTest?.showItems.secondLang && (
<div>
{testWords[currentWordIndex]?.secondLang}
</div>
)}
{currentTest?.showItems.voice && (
<button
className="flex mt-3 items-center cursor-pointer"
onClick={handleListen}
tabIndex={0}
>
<Volume2 />
<span className="inline-block ml-2 font-semibold text-lg">
Listen
</span>
</button>
)}
</div>
</div>
<div className="w-2/4 rounded-full flex justify-center items-center border-2 border-white overflow-hidden">
<Input
className="w-5/6 border-0 focus-visible:ring-transparent h-full"
onChange={(e) => setAnswer(e.target.value)}
value={answer}
/>
<Button
className="w-1/6 rounded-full bg-[#f39200] hover:bg-[#c57b0c] font-semibold text-gray-100 h-full p-3"
onClick={handleSubmit}
>
Submit
</Button>
</div>
</motion.div>
</AnimatePresence>
<div className="finish-button flex justify-center items-center my-10">
<Button
className="bg-[#f39200] hover:bg-[#c57b0c] font-semibold text-gray-100 h-full p-3 px-10"
onClick={handleFinishTest}
>
Finish Test
</Button>
</div>
<BottomPanel
currentWordIndex={currentWordIndex}
setCurrentWordIndex={setCurrentWordIndex}
testWords={testWords}
/>
</div>
);
};

const TopPanel = ({ testWords }: { testWords: WordTestType[] }) => {
const router = useRouter();
return (
<div className="flex justify-between items-center mb-5">
<button
tabIndex={0}
className="flex justify-center items-center gap-3 cursor-pointer hover:bg-slate-800 p-2 rounded-md"
onClick={() => router.push("/")}
>
<MoveLeft color="#ffffff70" /> Back
</button>
<div className="font-bold">
Remaining Words:{" "}
<span className="inline-block text-[#f39200]">
{testWords.length -
testWords.filter((w) => w.isSolved === true).length}
</span>
</div>
</div>
);
};

const BottomPanel = ({
testWords,
currentWordIndex,
setCurrentWordIndex,
}: {
testWords: WordTestType[];
currentWordIndex: number;
setCurrentWordIndex: React.Dispatch<React.SetStateAction<number>>;
}) => {
return (
<div>
<h2 className="text-center text-3xl my-5">Question Panel</h2>
<div className="flex justify-center items-center p-5 gap-10">
<button
onClick={() => {
setCurrentWordIndex((prev) => Math.max(prev - 1, 0));
}}
className="flex justify-center items-center p-5 hover:bg-slate-400 cursor-pointer rounded-md"
>
<ChevronLeft />
</button>
<button
onClick={() => {
setCurrentWordIndex((prev) =>
Math.min(prev + 1, testWords.length - 1)
);
}}
className="flex justify-center items-center p-5 hover:bg-slate-400 cursor-pointer rounded-md"
>
<ChevronRight />
</button>
</div>
<div className="flex flex-wrap gap-5">
{testWords.map((word, index) => (
<button
key={index}
className={cn(
`cursor-pointer bg-slate-800 p-2 rounded-md w-16 h-16 flex justify-center items-center transition-colors duration-500`,
word.isSolved && word.isCorrect && "bg-green-500",
word.isSolved && !word.isCorrect && "bg-red-500",
index === currentWordIndex && "bg-[#f39200]"
)}
onClick={() => {
setCurrentWordIndex(index);
}}
>
{index + 1}
</button>
))}
</div>
</div>
);
};

export default TakeTestPage;
17 changes: 17 additions & 0 deletions app/[locale]/(main)/test_results/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import TestResultDataComponent from "@/components/TestResultData";
import TestResultsComponent from "@/components/TestResults";

const TestResultsPage = ({
searchParams: { testId },
}: {
searchParams: {
testId?: string;
};
}) => {
if (testId) {
return <TestResultDataComponent testId={testId} />;
}
return <TestResultsComponent />;
};

export default TestResultsPage;
2 changes: 1 addition & 1 deletion app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const metadata: Metadata = {
title: "Word Memorizing",
description: "app to help you to memorize word",
icons: {
icon: "/favicon.png",
icon: "/favicon.ico",
},
};
export default async function LocaleLayout({
Expand Down
19 changes: 17 additions & 2 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,15 @@
@apply flex items-center justify-center;
}
.sidebar {
@apply bg-[#242526] sticky left-0 top-0 flex h-screen w-fit flex-col gap-6 border-r border-l border-gray-700 pt-8 text-white max-md:hidden sm:p-4 xl:p-6 2xl:w-[355px];
@apply bg-[#242526] left-0 top-0 flex h-screen w-fit flex-col gap-6 border-r border-l border-gray-700 pt-8 text-white p-6 2xl:w-[355px] sticky max-md:fixed max-md:-translate-x-full transition-all z-50 pb-0;
}
.sidebar.show {
@apply max-md:translate-x-0;
}
.sidebar .show-side-bar {
@apply absolute right-0 top-2/4 bg-white text-black p-1 h-[52px] flex items-center justify-center md:hidden cursor-pointer;
translate: 100% -50%;
border-radius: 0% 10px 10px 0%;
}
.user-details {
@apply flex flex-col justify-around w-3/5;
Expand Down Expand Up @@ -125,7 +133,7 @@
.add-new-word {
@apply w-60 mt-4;
}
.word-card {
.data-card {
background: rgba(17, 25, 40, 0.35);
border: 1px solid rgba(255, 255, 255, 0.125);
@apply flex flex-col h-fit p-8 items-center backdrop-blur-lg rounded-xl;
Expand Down Expand Up @@ -172,4 +180,11 @@
height: 0;
}
}

.to-top-button {
@apply hidden;
}
.to-top-button.show {
@apply rounded-full right-5 bottom-5 w-16 h-16 fixed flex hover:animate-bounce;
}
}
8 changes: 1 addition & 7 deletions components/AddNewFolder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { useState } from "react";
import FolderDialog from "./FolderDialog";

const AddNewFolder = ({
path,
setFolders,
}: {
path: string;
setFolders: React.Dispatch<React.SetStateAction<FolderType[]>>;
}) => {
const [open, setOpen] = useState(false);
Expand All @@ -20,11 +18,7 @@ const AddNewFolder = ({
Add Folder <PlusIcon />
</DialogTrigger>
<DialogContent>
<FolderDialog
setOpen={setOpen}
path={path}
setFolders={setFolders}
/>
<FolderDialog setOpen={setOpen} setFolders={setFolders} />
</DialogContent>
</Dialog>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/AddNewLanguageDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import { useLanguagesStore } from "@/store/userLanguages";
import { useLanguagesStore } from "@/store/userLanguagesStore";
import { Loader2 } from "lucide-react";
import { useTranslations } from "next-intl";
import { useState } from "react";
Expand Down
8 changes: 1 addition & 7 deletions components/AddNewWord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import { useState } from "react";
import WordDialog from "./WordDialog";

const AddNewWord = ({
path,
setWords,
}: {
path: string;
setWords: React.Dispatch<React.SetStateAction<WordType[]>>;
}) => {
const [open, setOpen] = useState(false);
Expand All @@ -20,11 +18,7 @@ const AddNewWord = ({
Add Word <PlusIcon />
</DialogTrigger>
<DialogContent>
<WordDialog
setOpen={setOpen}
path={path}
setWords={setWords}
/>
<WordDialog setOpen={setOpen} setWords={setWords} />
</DialogContent>
</Dialog>
</div>
Expand Down
2 changes: 1 addition & 1 deletion components/AuthForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import z from "zod";
import CustomInput from "./CustomInput";
import { Button } from "./ui/button";
import { Form } from "./ui/form";
Expand Down
Loading

0 comments on commit ac494fa

Please sign in to comment.