feat(ui): add avatar and badge components

This commit is contained in:
shafin-r
2025-07-10 14:51:45 +06:00
parent d42a42a8d1
commit 64fc4d9a9a
12 changed files with 597 additions and 256 deletions

View File

@ -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>

View File

@ -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>
)}