From d62ae97c98d8272df4f792c2e4d1ee25fdc884c8 Mon Sep 17 00:00:00 2001 From: Nanfe Yarnap <120315173+Nanfe01@users.noreply.github.com> Date: Sat, 25 Apr 2026 17:35:15 +0000 Subject: [PATCH 1/4] feat(frontend): implement follow system UI with hooks and components --- src/components/follow/follow.api.ts | 91 +++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/components/follow/follow.api.ts diff --git a/src/components/follow/follow.api.ts b/src/components/follow/follow.api.ts new file mode 100644 index 0000000..72d768c --- /dev/null +++ b/src/components/follow/follow.api.ts @@ -0,0 +1,91 @@ +import React, { useEffect, useState } from 'react'; + +interface Props { + userId: string; // target user +} + +const FollowCard: React.FC = ({ userId }) => { + const [isFollowing, setIsFollowing] = useState(false); + const [followers, setFollowers] = useState(0); + const [loading, setLoading] = useState(false); + + // 🔹 Fetch initial data + useEffect(() => { + const fetchData = async () => { + try { + const [statusRes, countRes] = await Promise.all([ + fetch(`/api/follow/status/${userId}`), + fetch(`/api/follow/count/${userId}`), + ]); + + const status = await statusRes.json(); + const counts = await countRes.json(); + + setIsFollowing(status); + setFollowers(counts.followers); + } catch (err) { + console.error(err); + } + }; + + fetchData(); + }, [userId]); + + // 🔹 Handle follow/unfollow + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + if (isFollowing) { + await fetch(`/api/follow/${userId}`, { + method: 'DELETE', + }); + + setIsFollowing(false); + setFollowers((prev) => prev - 1); // optimistic + } else { + await fetch(`/api/follow`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userId }), + }); + + setIsFollowing(true); + setFollowers((prev) => prev + 1); // optimistic + } + } catch (err) { + console.error(err); + } finally { + setLoading(false); + } + }; + + return ( +
+

+ {followers} Followers +

+ +
+ +
+
+ ); +}; + +export default FollowCard; \ No newline at end of file From 85275966900cccf0cee43dcb24a56b3e981d26d0 Mon Sep 17 00:00:00 2001 From: Nanfe Yarnap <120315173+Nanfe01@users.noreply.github.com> Date: Sat, 25 Apr 2026 17:47:51 +0000 Subject: [PATCH 2/4] feat(frontend): implement quiz result feedback UI --- src/components/QuizResult.tsx | 100 ++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/components/QuizResult.tsx diff --git a/src/components/QuizResult.tsx b/src/components/QuizResult.tsx new file mode 100644 index 0000000..eb645cb --- /dev/null +++ b/src/components/QuizResult.tsx @@ -0,0 +1,100 @@ +import React from 'react'; + +type QuestionResult = { + question: string; + options: string[]; + correctAnswer: string; + selectedAnswer: string; + explanation: string; +}; + +interface Props { + results: QuestionResult[]; +} + +const QuizResult: React.FC = ({ results }) => { + const total = results.length; + const correctCount = results.filter( + (q) => q.selectedAnswer === q.correctAnswer + ).length; + + const scorePercent = Math.round((correctCount / total) * 100); + + return ( +
+ {/* 🔹 Score Summary */} +
+

Quiz Results

+

+ You got {correctCount} out of{' '} + {total} correct +

+ +
+
+
+
+

{scorePercent}% Score

+
+
+ + {/* 🔹 Questions Review */} +
+ {results.map((q, index) => { + const isCorrect = q.selectedAnswer === q.correctAnswer; + + return ( +
+

+ {index + 1}. {q.question} +

+ +
+ {q.options.map((option, i) => { + const isSelected = option === q.selectedAnswer; + const isCorrectOption = option === q.correctAnswer; + + let className = 'block px-3 py-2 rounded-md text-sm'; + + if (isCorrectOption) { + className += ' bg-green-600/30 border border-green-500'; + } else if (isSelected && !isCorrect) { + className += ' bg-red-600/30 border border-red-500'; + } else { + className += ' bg-gray-800'; + } + + return ( +
+ {option} +
+ ); + })} +
+ + {/* 🔹 Explanation */} +
+

+ + {isCorrect ? 'Correct ✅' : 'Incorrect ❌'} + +

+

+ {q.explanation} +

+
+
+ ); + })} +
+
+ ); +}; + +export default QuizResult; \ No newline at end of file From 73a8fc036ae66bbbbb783662f1de859f70437b58 Mon Sep 17 00:00:00 2001 From: Nanfe Yarnap <120315173+Nanfe01@users.noreply.github.com> Date: Sat, 25 Apr 2026 18:07:04 +0000 Subject: [PATCH 3/4] feat(quiz): implement hint reveal system with usage tracking --- src/components/HintBox.tsx | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/components/HintBox.tsx diff --git a/src/components/HintBox.tsx b/src/components/HintBox.tsx new file mode 100644 index 0000000..5a5931c --- /dev/null +++ b/src/components/HintBox.tsx @@ -0,0 +1,37 @@ +// HintBox.tsx +import React from 'react'; + +interface Props { + hint?: string; + revealed: boolean; + onReveal: () => void; + penalty?: number; +} + +const HintBox: React.FC = ({ + hint, + revealed, + onReveal, + penalty = 0, +}) => { + if (!hint) return null; + + return ( +
+ {!revealed ? ( + + ) : ( +
+ {hint} +
+ )} +
+ ); +}; + +export default HintBox; \ No newline at end of file From 17253fd8f5d3b6d31c449808d7b968d1eecd9977 Mon Sep 17 00:00:00 2001 From: Nanfe Yarnap <120315173+Nanfe01@users.noreply.github.com> Date: Sat, 25 Apr 2026 18:12:07 +0000 Subject: [PATCH 4/4] feat(analytics): implement quiz performance analytics dashboard --- src/components/AnalyticsDashboard.tsx | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/components/AnalyticsDashboard.tsx diff --git a/src/components/AnalyticsDashboard.tsx b/src/components/AnalyticsDashboard.tsx new file mode 100644 index 0000000..d592f69 --- /dev/null +++ b/src/components/AnalyticsDashboard.tsx @@ -0,0 +1,32 @@ +// AnalyticsDashboard.tsx +import QuestionBreakdown from './QuestionBreakdown'; +import TopicPerformance from './TopicPerformance'; +import ScoreChart from './ScoreChart'; +import ExportButton from './ExportButton'; +import { groupByTopic } from '../utils/analytics.helpers'; + +const AnalyticsDashboard = ({ questions }) => { + const topics = groupByTopic(questions); + + return ( +
+
+

+ Test Analytics +

+ +
+ + {/* 🔹 Chart */} + + + {/* 🔹 Topic Performance */} + + + {/* 🔹 Question Breakdown */} + +
+ ); +}; + +export default AnalyticsDashboard; \ No newline at end of file