generated from muhtadeetaron/nextjs-template
feat(ui): add avatar and badge components
This commit is contained in:
@ -10,6 +10,7 @@ import BackgroundWrapper from "@/components/BackgroundWrapper";
|
||||
import DestructibleAlert from "@/components/DestructibleAlert";
|
||||
import { ChevronRight } from "lucide-react"; // Using Lucide React for icons
|
||||
import styles from "@/css/Home.module.css";
|
||||
import facebookStyles from "@/css/SlidingGallery.module.css";
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000/api";
|
||||
|
||||
@ -30,6 +31,37 @@ const page = () => {
|
||||
{ label: "Chemistry", progress: 57 },
|
||||
];
|
||||
|
||||
const facebookViews = [
|
||||
{
|
||||
id: "1",
|
||||
content: (
|
||||
<Link
|
||||
href="https://www.facebook.com/share/g/15jdqESvWV/?mibextid=wwXIfr"
|
||||
className="w-full h-full block text-inherit box-border"
|
||||
>
|
||||
<div className="w-full h-full p-6 flex text-black bg-blue-50 rounded-4xl border-[0.5px] border-[#113768]/30">
|
||||
<div className="">
|
||||
<h3 className="text-2xl text-[#113768] font-black">
|
||||
Meet, Share, and Learn!
|
||||
</h3>
|
||||
<p className="font-bold text-sm text-[#113768] ">
|
||||
Join Facebook Community
|
||||
</p>
|
||||
</div>
|
||||
<div className={facebookStyles.logoView}>
|
||||
<Image
|
||||
src="/images/static/facebook-logo.png"
|
||||
alt="Facebook Logo"
|
||||
width={150}
|
||||
height={150}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// Fetch function for leaderboard data
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
@ -70,7 +102,7 @@ const page = () => {
|
||||
<Header displayTabTitle={null} displayUser image={profileImg} />
|
||||
<div className={styles.scrollContainer}>
|
||||
<div className={styles.contentWrapper}>
|
||||
<SlidingGallery />
|
||||
<SlidingGallery views={facebookViews} height="23vh" />
|
||||
<div className={styles.mainContent}>
|
||||
{/* Categories Section */}
|
||||
<div>
|
||||
|
||||
@ -2,15 +2,19 @@
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useExam, useExamResults } from "@/context/ExamContext";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Image from "next/image";
|
||||
import SlidingGallery from "@/components/SlidingGallery";
|
||||
|
||||
interface Question {
|
||||
solution: string;
|
||||
correctAnswer: string;
|
||||
id: number;
|
||||
question: string;
|
||||
options: Record<string, string>;
|
||||
solution?: string;
|
||||
}
|
||||
|
||||
interface QuestionItemProps {
|
||||
@ -18,49 +22,78 @@ interface QuestionItemProps {
|
||||
selectedAnswer: string | undefined;
|
||||
}
|
||||
|
||||
const QuestionItem = React.memo<QuestionItemProps>(
|
||||
({ question, selectedAnswer }) => (
|
||||
<div className="border border-[#8abdff]/50 rounded-2xl p-4 flex flex-col gap-7">
|
||||
<h3 className="text-xl font-medium">
|
||||
{question.id}. {question.question}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-4 items-start">
|
||||
{Object.entries(question.options).map(([key, value]) => (
|
||||
<button key={key} className="flex items-center gap-3">
|
||||
<span
|
||||
className={`flex items-center rounded-full border px-1.5 ${
|
||||
selectedAnswer === key
|
||||
? "text-white bg-[#113768] border-[#113768]"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{key.toUpperCase()}
|
||||
</span>
|
||||
<span className="option-description">{value}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<h3 className="text-xl font-bold text-black/40">Solution:</h3>
|
||||
<p className="text-lg font-medium">{question.solution}</p>
|
||||
</div>
|
||||
const QuestionItem = ({ question, selectedAnswer }: QuestionItemProps) => (
|
||||
<div className="border border-[#8abdff]/50 rounded-2xl p-4 flex flex-col gap-7">
|
||||
<h3 className="text-xl font-medium">
|
||||
{question.id}. {question.question}
|
||||
</h3>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<div></div>
|
||||
|
||||
{selectedAnswer?.answer === question.correctAnswer ? (
|
||||
<Badge className="bg-green-500 text-white" variant="default">
|
||||
Correct
|
||||
</Badge>
|
||||
) : selectedAnswer?.answer !== question.correctAnswer ? (
|
||||
<Badge className="bg-red-500 text-white" variant="default">
|
||||
Incorrect
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className="bg-yellow-500" variant="destructive">
|
||||
Skipped
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
<div className="flex flex-col gap-4 items-start">
|
||||
{Object.entries(question.options).map(([key, value]) => {
|
||||
const isCorrect = key === question.correctAnswer;
|
||||
const isSelected = key === selectedAnswer?.answer;
|
||||
|
||||
let optionStyle =
|
||||
"px-2 py-1 flex items-center rounded-full border font-medium text-sm";
|
||||
|
||||
if (isCorrect) {
|
||||
optionStyle += " bg-green-600 text-white border-green-600";
|
||||
}
|
||||
|
||||
if (isSelected && !isCorrect) {
|
||||
optionStyle += " bg-red-600 text-white border-red-600";
|
||||
}
|
||||
|
||||
if (!isCorrect && !isSelected) {
|
||||
optionStyle += " border-gray-300 text-gray-700";
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} className="flex items-center gap-3">
|
||||
<span className={optionStyle}>{key.toUpperCase()}</span>
|
||||
<span className="option-description">{value}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-lg font-bold text-black/40">Solution:</h3>
|
||||
<p className="text-lg">{question.solution}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function ResultsPage() {
|
||||
const router = useRouter();
|
||||
const { clearExam, isExamCompleted, getApiResponse } = useExam();
|
||||
let examResults;
|
||||
|
||||
useEffect(() => {
|
||||
// Redirect if no completed exam
|
||||
if (!isExamCompleted()) {
|
||||
router.push("/unit");
|
||||
return;
|
||||
}
|
||||
}, [isExamCompleted, router]);
|
||||
|
||||
let examResults;
|
||||
try {
|
||||
examResults = useExamResults();
|
||||
} catch (error) {
|
||||
@ -98,28 +131,106 @@ export default function ResultsPage() {
|
||||
)
|
||||
: 0;
|
||||
|
||||
const resultViews = [
|
||||
{
|
||||
id: 1,
|
||||
content: (
|
||||
<div className="w-full">
|
||||
<div className="bg-blue-50/60 border border-[#113678]/50 rounded-4xl h-[170px] flex flex-col items-center justify-center gap-4">
|
||||
<div className="text-xl text-black ">
|
||||
<span className="font-bold">Accuracy</span> Rate:
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Image
|
||||
src="/images/icons/accuracy.png"
|
||||
alt="accuracy"
|
||||
width={60}
|
||||
height={60}
|
||||
/>
|
||||
<h2 className="text-6xl font-bold text-[#113678]">
|
||||
{(
|
||||
(examResults.score / examResults.totalQuestions) *
|
||||
100
|
||||
).toFixed(1)}
|
||||
%
|
||||
</h2>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: (
|
||||
<div className=" w-full">
|
||||
<div className="bg-blue-50/60 border border-[#113678]/50 rounded-4xl h-[170px] flex flex-col items-center justify-center gap-3">
|
||||
<div className="text-xl text-black ">
|
||||
<span className="font-bold">Error</span> Rate:
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Image
|
||||
src="/images/icons/error.png"
|
||||
alt="accuracy"
|
||||
width={60}
|
||||
height={60}
|
||||
/>
|
||||
<h2 className="text-6xl font-bold text-[#113678]">
|
||||
{(
|
||||
((examResults.totalQuestions - examResults.score) /
|
||||
examResults.totalQuestions) *
|
||||
100
|
||||
).toFixed(1)}
|
||||
%
|
||||
</h2>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content: (
|
||||
<div className="my-8 w-full">
|
||||
<div className="bg-blue-50/60 border border-[#113678]/50 rounded-4xl h-[170px] flex flex-col items-center justify-center gap-4">
|
||||
<div className="text-xl text-black">
|
||||
<span className="font-bold">Attempt</span> Rate:
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<Image
|
||||
src="/images/icons/attempt.png"
|
||||
alt="accuracy"
|
||||
width={60}
|
||||
height={60}
|
||||
/>
|
||||
<h2 className="text-6xl font-bold text-[#113678]">
|
||||
{(
|
||||
(examResults.answers.length / examResults.totalQuestions) *
|
||||
100
|
||||
).toFixed(1)}
|
||||
%
|
||||
</h2>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-white">
|
||||
<button className="p-10" onClick={() => router.push("/unit")}>
|
||||
<ArrowLeft size={30} color="black" />
|
||||
</button>
|
||||
<div className="bg-white rounded-lg shadow-lg px-10 pb-20">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2 text-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-4 text-center">
|
||||
Keep up the good work!
|
||||
</h1>
|
||||
|
||||
{/* Score Display */}
|
||||
<div className="mb-8">
|
||||
<div className="bg-blue-50/60 border border-[#113678]/50 rounded-4xl h-[150px] flex flex-col items-center justify-center">
|
||||
<div className="text-xl text-black mb-2">Accuracy:</div>
|
||||
<div className="text-5xl font-bold text-[#113678]">
|
||||
{((examResults.score / examResults.totalQuestions) * 100).toFixed(
|
||||
1
|
||||
)}
|
||||
%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SlidingGallery className="my-8" views={resultViews} height="170px" />
|
||||
|
||||
{apiResponse && (
|
||||
<div className="mb-8">
|
||||
@ -131,10 +242,20 @@ export default function ResultsPage() {
|
||||
<QuestionItem
|
||||
key={question.id}
|
||||
question={question}
|
||||
selectedAnswer={undefined}
|
||||
selectedAnswer={examResults.answers?.[question.id]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-4 items-center mb-6 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-green-600 rounded-full"></div>{" "}
|
||||
Correct
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-red-600 rounded-full"></div> Your
|
||||
Answer (Incorrect)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user