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
38 changes: 24 additions & 14 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,7 @@
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);

--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
Expand All @@ -44,7 +32,7 @@
--animate-pulse-soft: pulse-soft 2.8s ease-in-out infinite;
}

:root {
@theme {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
Expand Down Expand Up @@ -77,6 +65,9 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);

--accent-primary: #1e5eff;
--accent-hover: #174ad6;
}

.dark {
Expand Down Expand Up @@ -111,15 +102,34 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);

--accent-primary: #ff2d55;
--accent-hover: #e0264b;
}

@layer base {
* {
@apply border-border outline-ring/50;
}

body {
@apply bg-background text-foreground;
}

button,
[role='button'],
a {
cursor: pointer;
}
}

@layer components {
.btn {
background-color: var(--accent-primary);
}
.btn:hover {
background-color: var(--accent-hover);
}
}

.no-select {
Expand Down
81 changes: 43 additions & 38 deletions frontend/components/quiz/QuizCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,62 +24,67 @@ interface QuizCardProps {

export function QuizCard({ quiz, userProgress }: QuizCardProps) {
const t = useTranslations('quiz.card');
const percentage = userProgress && userProgress.totalQuestions > 0
? Math.round((userProgress.bestScore / userProgress.totalQuestions) * 100)
: 0;
const percentage =
userProgress && userProgress.totalQuestions > 0
? Math.round((userProgress.bestScore / userProgress.totalQuestions) * 100)
: 0;

return (
<div className="flex flex-col rounded-xl border border-gray-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 p-5 shadow-sm hover:shadow-md transition-shadow">
<div className="flex-grow">
<div className="flex gap-2 mb-3">
<Badge variant="blue">{quiz.categoryName ?? t('uncategorized')}</Badge>
{userProgress && (
<Badge variant="success">{t('completed')}</Badge>
<div className="flex gap-2 mb-3">
<Badge variant="blue">
{quiz.categoryName ?? t('uncategorized')}
</Badge>
{userProgress && <Badge variant="success">{t('completed')}</Badge>}
</div>
<h2 className="text-xl font-semibold mb-2">
{quiz.title ?? quiz.slug}
</h2>
{quiz.description && (
<p className="line-clamp-2 text-sm text-gray-600 dark:text-gray-400 mb-3">
{quiz.description}
</p>
)}
</div>
<h2 className="text-xl font-semibold mb-2">
{quiz.title ?? quiz.slug}
</h2>
{quiz.description && (
<p className="line-clamp-2 text-sm text-gray-600 dark:text-gray-400 mb-3">
{quiz.description}
</p>
)}
<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')}
{Math.floor(
(quiz.timeLimitSeconds ?? quiz.questionsCount * 30) / 60
)}{' '}
{t('min')}
</span>
</div>
</div>
{userProgress && (
<div className="mb-6">
<div className="flex justify-between text-xs mb-1.5">
<span className="text-gray-600 dark:text-gray-400">
{t('best')} {userProgress.bestScore}/{userProgress.totalQuestions}
</span>
<span className="text-gray-500">
{userProgress.attemptsCount} {userProgress.attemptsCount === 1 ? t('attempt') : t('attempts')}
</span>
<span className="font-medium text-gray-900 dark:text-gray-100">
{percentage}%
</span>
</div>
<div className="h-1.5 bg-gray-200 dark:bg-neutral-800 rounded-full overflow-hidden">
<div
className="h-full bg-blue-600 rounded-full transition-all"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
)}
<div className="mb-6">
<div className="flex justify-between text-xs mb-1.5">
<span className="text-gray-600 dark:text-gray-400">
{t('best')} {userProgress.bestScore}/{userProgress.totalQuestions}
</span>
<span className="text-gray-500">
{userProgress.attemptsCount}{' '}
{userProgress.attemptsCount === 1 ? t('attempt') : t('attempts')}
</span>
<span className="font-medium text-gray-900 dark:text-gray-100">
{percentage}%
</span>
</div>
<div className="h-1.5 bg-gray-200 dark:bg-neutral-800 rounded-full overflow-hidden">
<div
className="h-full bg-blue-600 rounded-full transition-all"
style={{ width: `${percentage}%` }}
/>
</div>
</div>
)}
<Link
href={`/quiz/${quiz.slug}`}
className="block w-full text-center rounded-lg bg-blue-600 text-white px-4 py-2.5 text-sm font-medium hover:bg-blue-500 transition-colors"
className="btn block w-full text-center rounded-lg text-white px-4 py-2.5 text-sm font-medium transition-colors"
>
{userProgress ? t('retake') : t('start')}
</Link>
Expand Down