Skip to content

Commit 0451759

Browse files
authored
Feat: Implement JEE quiz interface with navigation and answer handling
* feat: add JEE quiz interface with navigation, timer, and answer handling * refactor: improve quiz page UI and layout styling * fix: resolve issue identified by GitHub Copilot suggestion
1 parent 1524125 commit 0451759

8 files changed

Lines changed: 571 additions & 9 deletions

File tree

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import Card from "../ui/Card";
5+
import Button from "../ui/Button";
6+
import ProgressBar from "../ui/Progressbar";
7+
import Timer from "../ui/Timer";
8+
import QuestionNavigation from "../ui/QuestionNavigation";
9+
import { Flag, ChevronRight } from "lucide-react";
10+
11+
export default function Retentiaquiz() {
12+
13+
const totalQuestions = 50;
14+
15+
const [showSubmitModal, setShowSubmitModal] = useState(false);
16+
const [currentQuestion, setCurrentQuestion] = useState(1);
17+
const [answers, setAnswers] = useState<Record<number, string | null>>({});
18+
const [confirmedAnswers, setConfirmedAnswers] = useState<Record<number, boolean>>({});
19+
const [markedReview, setMarkedReview] = useState<Record<number, boolean>>({});
20+
const [skipped, setSkipped] = useState<Record<number, boolean>>({});
21+
22+
const answeredCount = Object.keys(confirmedAnswers).length;
23+
24+
const options = [
25+
{ id: "A", text: "Solar power" },
26+
{ id: "B", text: "Wind Power" },
27+
{ id: "C", text: "Natural Gas" },
28+
{ id: "D", text: "Hydroelectric Power" },
29+
];
30+
31+
const confirmSubmit = () => {
32+
setShowSubmitModal(false);
33+
34+
console.log("Quiz submitted successfully");
35+
36+
};
37+
38+
const handleOptionClick = (optionId: string) => {
39+
40+
setAnswers((prev) => {
41+
42+
const current = prev[currentQuestion];
43+
44+
return {
45+
...prev,
46+
[currentQuestion]: current === optionId ? null : optionId,
47+
};
48+
49+
});
50+
51+
setSkipped((prev) => {
52+
const updated = { ...prev };
53+
delete updated[currentQuestion];
54+
return updated;
55+
});
56+
57+
};
58+
59+
const handleNext = () => {
60+
61+
if (answers[currentQuestion]) {
62+
63+
setConfirmedAnswers((prev) => ({
64+
...prev,
65+
[currentQuestion]: true,
66+
}));
67+
68+
setSkipped((prev) => {
69+
const updated = { ...prev };
70+
delete updated[currentQuestion];
71+
return updated;
72+
});
73+
74+
} else {
75+
76+
setSkipped((prev) => ({
77+
...prev,
78+
[currentQuestion]: true,
79+
}));
80+
81+
setConfirmedAnswers((prev) => {
82+
const updated = { ...prev };
83+
delete updated[currentQuestion];
84+
return updated;
85+
});
86+
87+
}
88+
89+
if (currentQuestion < totalQuestions) {
90+
setCurrentQuestion((prev) => prev + 1);
91+
}
92+
93+
};
94+
95+
const toggleReview = () => {
96+
97+
setMarkedReview((prev) => ({
98+
...prev,
99+
[currentQuestion]: !prev[currentQuestion],
100+
}));
101+
102+
setSkipped((prev) => {
103+
const updated = { ...prev };
104+
delete updated[currentQuestion];
105+
return updated;
106+
});
107+
108+
};
109+
const handleSubmit = () => {
110+
setShowSubmitModal(true);
111+
};
112+
113+
return (
114+
115+
<main className="min-h-screen flex flex-col justify-center bg-background py-10
116+
px-4 sm:px-10 md:px-20 lg:px-80">
117+
118+
<div className="grid grid-cols-3 items-center mt-3 gap-4">
119+
120+
<div></div>
121+
122+
<div className="flex justify-center">
123+
<Timer totalQuestions={totalQuestions} />
124+
</div>
125+
126+
<div className="flex justify-center sm:justify-end">
127+
<Button
128+
variant="default"
129+
className="bg-answered px-6 py-2 text-white"
130+
onClick={handleSubmit}
131+
>
132+
Submit
133+
</Button>
134+
</div>
135+
136+
</div>
137+
138+
<div className="mb-10 mt-10">
139+
<ProgressBar
140+
answered={answeredCount}
141+
total={totalQuestions}
142+
/>
143+
</div>
144+
145+
<QuestionNavigation
146+
currentQuestion={currentQuestion}
147+
totalQuestions={totalQuestions}
148+
onQuestionChange={setCurrentQuestion}
149+
confirmedAnswers={confirmedAnswers}
150+
markedReview={markedReview}
151+
skipped={skipped}
152+
/>
153+
154+
<Card
155+
variant="primary"
156+
className="w-full max-h-212.5 px-4 sm:px-6 md:px-10"
157+
>
158+
159+
<Card
160+
variant="secondary"
161+
className="w-full mb-15 flex items-center justify-center"
162+
>
163+
<p className="text-xl sm:text-2xl md:text-3xl font-bold leading-relaxed p-6 sm:p-10 md:p-15 text-center">
164+
Which of the following energy sources cannot be
165+
replenished naturally on a human timescale?
166+
</p>
167+
</Card>
168+
169+
<div className="space-y-5 text-lg sm:text-xl">
170+
171+
{options.map((option) => {
172+
173+
const isSelected = answers[currentQuestion] === option.id;
174+
const isAnswered = confirmedAnswers[currentQuestion];
175+
176+
let colorClass = "";
177+
178+
if (isAnswered && isSelected) colorClass = "border-secondary ";
179+
else if (isSelected) colorClass = "border-secondary";
180+
181+
return (
182+
183+
<Button
184+
key={option.id}
185+
variant="opt_btn"
186+
onClick={() => handleOptionClick(option.id)}
187+
className={`${colorClass}`}
188+
>
189+
190+
<div className="flex items-center justify-center w-8 h-8 rounded-full bg-white text-black font-semibold">
191+
{option.id}
192+
</div>
193+
194+
<span className="flex-1 text-left">
195+
{option.text}
196+
</span>
197+
198+
</Button>
199+
200+
);
201+
})}
202+
203+
</div>
204+
205+
</Card>
206+
207+
<div className="flex flex-col sm:flex-row justify-end items-center gap-5 mt-20 mb-5">
208+
209+
<Button
210+
variant="default"
211+
onClick={toggleReview}
212+
className="bg-purple-600 text-white px-6 py-2 flex items-center gap-2 w-full sm:w-auto justify-center"
213+
>
214+
<Flag size={18} />
215+
Mark For Review
216+
</Button>
217+
218+
<Button
219+
variant="default"
220+
onClick={handleNext}
221+
className="bg-secondary px-6 py-2 flex text-white items-center gap-2 w-full sm:w-auto justify-center"
222+
>
223+
Next Question
224+
<ChevronRight size={18} />
225+
</Button>
226+
227+
</div>
228+
{showSubmitModal && (
229+
<div className="fixed inset-0 bg-background flex items-center justify-center z-50">
230+
<Card variant="primary" className="p-8 max-w-md w-full text-center">
231+
232+
<h2 className="text-xl font-bold mb-4">
233+
Submit Quiz?
234+
</h2>
235+
236+
<p className="text-text mb-6">
237+
Are you sure you want to submit the quiz?
238+
</p>
239+
240+
<div className="flex justify-center gap-4">
241+
242+
<Button
243+
variant="default"
244+
onClick={() => setShowSubmitModal(false)}
245+
>
246+
Cancel
247+
</Button>
248+
249+
<Button
250+
variant="default"
251+
className="bg-answered text-text"
252+
onClick={confirmSubmit}
253+
>
254+
Submit
255+
</Button>
256+
257+
</div>
258+
259+
</Card>
260+
</div>
261+
)}
262+
263+
</main>
264+
);
265+
}

app/components/ui/Button.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,35 @@
11
type ButtonProps = {
22
children: React.ReactNode;
3-
variant?: "default" | "sidebar_btn" ;
3+
variant?: "default" | "sidebar_btn" | "opt_btn";
44
className?: string;
5+
onClick?: () => void;
56
};
67

7-
export default function Button({children,variant = "default",className = "", }: ButtonProps) {
8-
const base = "flex items-center cursor-pointer ";
8+
export default function Button({
9+
children,
10+
variant = "default",
11+
className = "",
12+
onClick,
13+
}: ButtonProps) {
14+
const base = "cursor-pointer min-h-[48px]";
15+
916
const variants = {
10-
default: "text-text text-2xl",
11-
sidebar_btn: "group text-2xl px-4 gap-4 py-2 font-semibold rounded-full hover:bg-primary-light",
12-
17+
default:
18+
"flex p-2 sm:p-3 md:p-4 rounded-2xl items-center text-text text-base sm:text-lg md:text-xl",
19+
20+
sidebar_btn:
21+
"flex items-center group text-lg sm:text-xl md:text-2xl px-2 sm:px-4 gap-2 sm:gap-4 py-2 font-semibold rounded-full hover:bg-primary-light",
22+
23+
opt_btn:
24+
"w-full text-lg sm:text-xl md:text-2xl font-bold rounded-3xl sm:py-6 md:py-8 px-4 sm:px-6 md:px-7 items-start text-left break-words bg-background/70 border-background flex justify-center gap-5 border-2 hover:bg-background/30 hover:border-text/60 transition-colors overflow-y-auto",
1325
};
1426

1527
return (
16-
<button className={`${base} ${variants[variant]} ${className}`}>
28+
<button
29+
type="button"
30+
onClick={onClick}
31+
className={`${base} ${variants[variant]} ${className}`}
32+
>
1733
{children}
1834
</button>
1935
);

app/components/ui/Card.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
type CardProps = {
2+
children: React.ReactNode;
3+
variant?: "primary" | "secondary";
4+
className?: string;
5+
};
6+
7+
export default function Card({
8+
children,
9+
variant = "primary",
10+
className = "",
11+
}: CardProps) {
12+
const base = "rounded-3xl p-6 ";
13+
const variants = {
14+
primary: "bg-foreground flex overflow-y-auto flex-col text-text",
15+
secondary: "bg-background/70 text-text",
16+
};
17+
18+
return (
19+
<div className={`${base} ${variants[variant]} ${className}`}>
20+
{children}
21+
</div>
22+
);
23+
}

app/components/ui/Progressbar.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
type ProgressBarProps = {
2+
answered: number;
3+
total: number;
4+
className?: string;
5+
};
6+
7+
export default function ProgressBar({
8+
answered,
9+
total,
10+
className = "",
11+
}: ProgressBarProps) {
12+
13+
const progress = total > 0 ? (answered / total) * 100 : 0;
14+
15+
return (
16+
<div className={`w-full ${className}`}>
17+
18+
19+
<div className="flex justify-between text-xl mb-1 text-text/70">
20+
<span>{answered} / {total} answered</span>
21+
</div>
22+
23+
24+
<div className="w-full h-4 rounded-full bg-text overflow-hidden">
25+
26+
<div
27+
className="h-full rounded-full bg-[#22C55E] transition-all duration-300 ease-out"
28+
style={{ width: `${progress}%` }}
29+
/>
30+
31+
</div>
32+
33+
</div>
34+
);
35+
}

0 commit comments

Comments
 (0)