Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/components/quiz/CountdownTimer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useState } from 'react';
import { useTranslations } from 'next-intl';
import { cn } from '@/lib/utils';
import { AlertTriangle } from 'lucide-react';

interface CountdownTimerProps {
timeLimitSeconds: number;
Expand Down Expand Up @@ -105,7 +106,7 @@ export function CountdownTimer({
<p className="text-xs mt-2 font-medium">
{percentage <= 10 ? (
<>
<span aria-hidden="true">⚠️</span> {t('almostDone')}
<AlertTriangle className="w-4 h-4 inline text-amber-500" /> {t('almostDone')}
</>
) : (
<>
Expand Down
17 changes: 11 additions & 6 deletions frontend/components/quiz/QuizCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/routing';
import { Badge } from '@/components/ui/badge';
import { FileText, Clock } from 'lucide-react';

interface QuizCardProps {
quiz: {
Expand Down Expand Up @@ -44,12 +45,16 @@ export function QuizCard({ quiz, userProgress }: QuizCardProps) {
{quiz.description}
</p>
)}
<div className="flex gap-3 text-xs text-gray-500 mb-3">
<span>📝 {quiz.questionsCount} {t('questions')}</span>
<span>
⏱️ {Math.floor((quiz.timeLimitSeconds ?? quiz.questionsCount * 30) / 60)} {t('min')}
</span>
</div>
<div className="flex gap-3 text-xs text-gray-500 mb-3">
<span className="flex items-center gap-1">
<FileText className="w-3.5 h-3.5 text-blue-500 dark:text-blue-400" />
{quiz.questionsCount} {t('questions')}
</span>
<span className="flex items-center gap-1">
<Clock className="w-3.5 h-3.5 text-blue-500 dark:text-blue-400" />
{Math.floor((quiz.timeLimitSeconds ?? quiz.questionsCount * 30) / 60)} {t('min')}
</span>
</div>
</div>
{userProgress && (
<div className="mb-6">
Expand Down
12 changes: 6 additions & 6 deletions frontend/components/quiz/QuizContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { savePendingQuizResult } from '@/lib/quiz/guest-quiz';
import type { QuizQuestionClient } from '@/db/queries/quiz';
import { ConfirmModal } from '@/components/ui/confirm-modal';
import { Button } from '@/components/ui/button';
import { FileText, Ban, AlertTriangle, Clock } from 'lucide-react';

interface Answer {
questionId: string;
Expand Down Expand Up @@ -380,8 +381,8 @@ const confirmQuit = () => {
</h2>

<div className="space-y-4 text-gray-700 dark:text-gray-300">
<div className="flex gap-3">
<span className="text-xl">📝</span>
<div className="flex gap-3">
<FileText className="w-5 h-5 text-blue-500 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium">{tRules('general.title')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
Expand All @@ -391,7 +392,7 @@ const confirmQuit = () => {
</div>

<div className="flex gap-3">
<span className="text-xl">🚫</span>
<Ban className="w-5 h-5 text-red-500 dark:text-red-400 flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium">{tRules('forbidden.title')}</p>
<ul className="text-sm text-gray-600 dark:text-gray-400 list-disc list-inside space-y-1">
Expand All @@ -404,17 +405,16 @@ const confirmQuit = () => {
</div>

<div className="flex gap-3">
<span className="text-xl">⚠️</span>
<AlertTriangle className="w-5 h-5 text-amber-500 dark:text-amber-400 flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium">{tRules('control.title')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
{tRules('control.description')}
</p>
</div>
</div>

<div className="flex gap-3">
<span className="text-xl">⏱️</span>
<Clock className="w-5 h-5 text-blue-500 dark:text-blue-400 flex-shrink-0 mt-0.5" />
<div>
<p className="font-medium">{tRules('time.title')}</p>
<p className="text-sm text-gray-600 dark:text-gray-400">
Expand Down
3 changes: 2 additions & 1 deletion frontend/components/quiz/QuizProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { useTranslations } from 'next-intl';
import { cn } from '@/lib/utils';
import { Check, X } from 'lucide-react';

interface Answer {
questionId: string;
Expand Down Expand Up @@ -90,7 +91,7 @@ export function QuizProgress({ current, total, answers }: QuizProgressProps) {
>
{isAnswered ? (
<span className="text-white font-bold">
{isCorrect ? '✓' : '✗'}
{isCorrect ? <Check className="w-3 h-3" /> : <X className="w-3 h-3" />}
</span>
) : (
<span
Expand Down
7 changes: 4 additions & 3 deletions frontend/components/quiz/QuizQuestion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Button } from '@/components/ui/button';
import ExplanationRenderer from './ExplanationRenderer';
import { cn } from '@/lib/utils';
import { Check, X, Lightbulb } from 'lucide-react';

interface QuizQuestionProps {
question: QuizQuestionClient;
Expand Down Expand Up @@ -66,12 +67,12 @@ export function QuizQuestion({
<span className="flex-1 text-base">{answer.answerText}</span>
{showCorrect && (
<span className="text-green-600 dark:text-green-400 text-sm font-medium">
{t('correct')}
<Check className="w-4 h-4 inline" /> {t('correct')}
</span>
)}
{showIncorrect && (
<span className="text-red-600 dark:text-red-400 text-sm font-medium">
{t('incorrect')}
<X className="w-4 h-4 inline" /> {t('incorrect')}
</span>
)}
</label>
Expand Down Expand Up @@ -99,7 +100,7 @@ export function QuizQuestion({
)}
>
<div className="flex items-start gap-3">
<div className="text-2xl">💡</div>
<Lightbulb className="w-6 h-6 text-amber-500 flex-shrink-0" />
<div className="flex-1">
<h4 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">
{t('recommendation.title')}
Expand Down
56 changes: 28 additions & 28 deletions frontend/components/quiz/QuizResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useLocale, useTranslations } from 'next-intl';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Clock, BookOpen, TrendingUp, Trophy, AlertTriangle } from 'lucide-react';

interface QuizResultProps {
score: number;
Expand Down Expand Up @@ -35,49 +36,48 @@ export function QuizResult({
const t = useTranslations('quiz.result');
const getMotivationalMessage = () => {
if (isIncomplete && answeredCount > 0) {
return {
emoji: '⏱️',
title: 'Час вийшов',
message: `Ви відповіли на ${answeredCount} з ${total} питань. Результат не зараховано.`,
color: 'text-orange-600 dark:text-orange-400',
};
}

return {
icon: <Clock className="w-14 h-14 text-orange-500" />,
title: t('incomplete.title'),
message: t('incomplete.message', { answeredCount, total }),
color: 'text-orange-600 dark:text-orange-400',
};
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (score === 0 && answeredCount === 0) {
return {
emoji: '⏱️',
title: t('timeUp.title'),
message: t('timeUp.message'),
color: 'text-gray-600 dark:text-gray-400',
};
}
return {
icon: <Clock className="w-14 h-14 text-gray-500" />,
title: t('timeUp.title'),
message: t('timeUp.message'),
color: 'text-gray-600 dark:text-gray-400',
};
}

if (score === 0 && answeredCount > 0) {
return {
emoji: '📚',
title: t('allWrong.title'),
message: t('allWrong.message'),
color: 'text-red-600 dark:text-red-400',
};
}
if (score === 0 && answeredCount > 0) {
return {
icon: <BookOpen className="w-14 h-14 text-red-500" />,
title: t('allWrong.title'),
message: t('allWrong.message'),
color: 'text-red-600 dark:text-red-400',
};
}

if (percentage < 50) {
return {
emoji: '📚',
icon: <BookOpen className="w-14 h-14 text-red-500" />,
title: t('needPractice.title'),
message: t('needPractice.message'),
color: 'text-red-600 dark:text-red-400',
};
} else if (percentage < 80) {
return {
emoji: '💪',
icon: <TrendingUp className="w-14 h-14 text-orange-500" />,
title: t('goodJob.title'),
message: t('goodJob.message'),
color: 'text-orange-600 dark:text-orange-400',
};
} else {
return {
emoji: '🎉',
icon: <Trophy className="w-14 h-14 text-amber-500" />,
title: t('excellent.title'),
message: t('excellent.message'),
color: 'text-green-600 dark:text-green-400',
Expand All @@ -89,7 +89,7 @@ export function QuizResult({

return (
<div className="max-w-2xl mx-auto space-y-8">
<div className="text-center text-6xl">{motivation.emoji}</div>
<div className="flex justify-center">{motivation.icon}</div>
{!isIncomplete && (
<>
<div className="text-center space-y-2">
Expand Down Expand Up @@ -124,7 +124,7 @@ export function QuizResult({
{violationsCount >= 3 && (
<div className="p-4 rounded-xl bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800">
<p className="text-center text-orange-800 dark:text-orange-200 font-medium">
<span aria-hidden="true">⚠️</span> {t('violations', { count: violationsCount })}
<AlertTriangle className="w-4 h-4 inline" /> {t('violations', { count: violationsCount })}
</p>
</div>
)}
Expand Down
4 changes: 4 additions & 0 deletions frontend/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
"title": "Excellent Work!",
"message": "You have mastered the material well"
},
"incomplete": {
"title": "Time's up",
"message": "You answered {answeredCount} out of {total} questions. Result not counted."
},
"violations": "Quiz completed with rule violations ({count} violations). Result not counted towards leaderboard.",
"pointsAwarded": "+{points} points added to rating",
"noPointsAwarded": "No points awarded (result not improved)",
Expand Down
4 changes: 4 additions & 0 deletions frontend/messages/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
"title": "Doskonała Praca!",
"message": "Dobrze opanowałeś materiał"
},
"incomplete": {
"title": "Czas minął",
"message": "Odpowiedzi udzielono na {answeredCount} z {total} pytań. Wynik nie został zaliczony."
},
"violations": "Quiz ukończony z naruszeniami zasad ({count} naruszeń). Wynik nie zaliczony do rankingu.",
"pointsAwarded": "+{points} punktów dodanych do oceny",
"noPointsAwarded": "Nie przyznano punktów (wynik nie poprawiony)",
Expand Down
4 changes: 4 additions & 0 deletions frontend/messages/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
"title": "Чудова робота!",
"message": "Ви добре засвоїли матеріал"
},
"incomplete": {
"title": "Час вийшов",
"message": "Ви відповіли на {answeredCount} з {total} питань. Результат не зараховано."
},
"violations": "Квіз завершено з порушеннями правил ({count} порушень). Результат не зараховано до рейтингу.",
"pointsAwarded": "+{points} балів додано до рейтингу",
"noPointsAwarded": "Бали не нараховано (результат не покращено)",
Expand Down