Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
ce4661a
feat(quiz-ui): quiz UI polish - tabs, category accents, color scheme …
LesiaUKR Jan 24, 2026
521ce1c
docs: update .gitignore
LesiaUKR Jan 24, 2026
9345289
fix(quiz): align disqualification threshold with warning banner
LesiaUKR Jan 24, 2026
c8317eb
feat(quiz-testing): add quiz unit tests
LesiaUKR Jan 24, 2026
6530094
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Jan 24, 2026
7f65782
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Jan 25, 2026
cdd20a0
test(quiz): add integration tests for verify-answer API and useAntiCh…
LesiaUKR Jan 25, 2026
077ae76
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Jan 25, 2026
d3cad15
test(quiz): expand test coverage to 90%+ with hooks, API routes, and …
LesiaUKR Jan 25, 2026
7cc8f16
chore: remove coverage-quiz from git, add to .gitignore
LesiaUKR Jan 25, 2026
786b33c
chore: add coverage-quiz to .gitignore, fix quiz guards test
LesiaUKR Jan 25, 2026
221be15
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Jan 27, 2026
98fca31
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Jan 28, 2026
bc93cba
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Jan 29, 2026
16ef693
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Jan 30, 2026
82d01ea
chore: bump Node.js to 20 for Netlify
ViktorSvertoka Jan 31, 2026
b7c49a5
feat(md) add netlify status (#234)
ViktorSvertoka Jan 31, 2026
4041589
(SP 2) [Shop UI] Unify storefront styles across components and intera…
liudmylasovetovs Jan 31, 2026
124bc04
(SP 2) [Shop UI] Unify storefront styles across components and intera…
liudmylasovetovs Jan 31, 2026
4bffb88
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Jan 31, 2026
be2ddbb
Host (#237)
ViktorSvertoka Jan 31, 2026
0b726bf
Host (#238)
ViktorSvertoka Jan 31, 2026
1128c88
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Jan 31, 2026
7edf42b
(SP 1) [Shop UI] Add page metadata across shop routes
liudmylasovetovs Jan 31, 2026
d0a0586
(SP 1) [Shop UI] Add page metadata across shop routes (#239)
liudmylasovetovs Jan 31, 2026
c606ab4
(SP: 3) [Cache] Add Upstash Redis cache for Q&A (#241)
ViktorSvertoka Jan 31, 2026
d73f5fb
Fix Q&A Redis cache parsing for Upstash REST (#243)
ViktorSvertoka Feb 1, 2026
c23f2c0
feat(Blog):Adding pagination (#244)
KomrakovaAnna Feb 1, 2026
f1730f3
(SP:2) feat(api): clean up AI helper for Vercel & fix orders i18n (#…
TiZorii Feb 1, 2026
7a353d1
refactor(home): update button, cards, and online counter UI (#248)
YNazymko12 Feb 1, 2026
9ace93d
fix(api): enforce rate limiting (#246)
TiZorii Feb 1, 2026
91a7a93
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Feb 1, 2026
77936b6
Merge branch 'main' into sl/feat/quiz
LesiaUKR Feb 1, 2026
45d6e83
fix(a11y): improve quiz accessibility and i18n compliance
LesiaUKR Feb 1, 2026
4585ddb
fix(a11y): improve quiz accessibility and i18n compliance
LesiaUKR Feb 1, 2026
fb63cc3
feat(Blog):formating text (#249)
KomrakovaAnna Feb 1, 2026
eaf3eb0
ref(files): refactoring code & bag fix (#250)
ViktorSvertoka Feb 1, 2026
2afb681
chore(release): v0.5.2
ViktorSvertoka Feb 1, 2026
e969382
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Feb 1, 2026
cad16e7
(SP 1) [FIX] names of components, replacing tests, clean code
liudmylasovetovs Feb 2, 2026
2e015ba
(SP 1) [FIX] remove magic constant and align restock sweep test typing
liudmylasovetovs Feb 2, 2026
579512e
(SP 1) [FIX] remove duplicate
liudmylasovetovs Feb 2, 2026
2bcafdd
Lso/feat/shop design (#252)
liudmylasovetovs Feb 2, 2026
d6d4906
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Feb 2, 2026
9ade51d
feat(i18n): add translations for blog categories, and UI components…
TiZorii Feb 2, 2026
9615bff
feat blog: fix for paddings on mobile (#254)
KomrakovaAnna Feb 2, 2026
0c12d6a
Merge remote-tracking branch 'origin/main' into develop
ViktorSvertoka Feb 2, 2026
2f9aca2
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
ViktorSvertoka Feb 2, 2026
7b8f89a
chore(lint): finalize ESLint + Prettier (#256)
ViktorSvertoka Feb 3, 2026
c2ac8e4
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Feb 3, 2026
d33349e
fix(sl/feat/quiz): replace with correct name for react icon
LesiaUKR Feb 3, 2026
239436f
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Feb 3, 2026
601e032
feat(leaderboard): finalize components and fix lint errors (#259)
AlinaRyabova Feb 3, 2026
f7c7b63
feat(quiz): implement Redis caching + session fixes + cleanup
LesiaUKR Feb 3, 2026
07b59c8
chore(quiz): delete unused start-session route
LesiaUKR Feb 3, 2026
3b9588e
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Feb 3, 2026
bf21695
git commit -m "fix(quiz): add NaN seed validation and cache/DB fallback
LesiaUKR Feb 3, 2026
09654b9
(SP 1) [FIX] set up eslint/prettier + stabilize formatting workflow (…
liudmylasovetovs Feb 3, 2026
06db3e8
(SP 1) [FIX] Tailwind hints
liudmylasovetovs Feb 3, 2026
fa8ecaf
(SP 1) [FIX] Tailwind hints revert
liudmylasovetovs Feb 3, 2026
4694478
(SP 1) [FIX] set up eslint/prettier + stabilize formatting workflow (…
liudmylasovetovs Feb 3, 2026
1ad1d7f
(SP: 5) [Quiz] Redis caching + guest session fix + cleanup (#263)
LesiaUKR Feb 3, 2026
f7bfae4
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Feb 3, 2026
274a218
fix(layout): remove duplicate padding from quiz routes
LesiaUKR Feb 3, 2026
b3cdf48
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Feb 3, 2026
6d678fc
chore: remove redis ttl for static quiz and qa caches (#265)
ViktorSvertoka Feb 3, 2026
10f1434
fix(layout): remove duplicate padding from quiz routes (#266)
LesiaUKR Feb 3, 2026
9b1bf11
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Feb 3, 2026
9d99457
docs(changelog): add v0.5.3 release notes
ViktorSvertoka Feb 3, 2026
fc64c59
Merge branch 'main' into develop
ViktorSvertoka Feb 3, 2026
4ef0806
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
ViktorSvertoka Feb 3, 2026
622cde3
Blog and Sanity: fixes and refactoring (#267)
KomrakovaAnna Feb 4, 2026
0e2c3c4
refactor(home): Update CodeCards, button behavior, and layout
YNazymko12 Feb 4, 2026
895654d
fix(ui): correct particle angles and tailwind typo
YNazymko12 Feb 4, 2026
839ac57
Merge pull request #270 from DevLoversTeam/yn/fix/home/ui
ViktorSvertoka Feb 4, 2026
ca5fc1f
Merge branch 'develop' into sl/feat/quiz
LesiaUKR Feb 4, 2026
725561e
perf(quiz): improve Lighthouse Performance and SEO scores
LesiaUKR Feb 4, 2026
8e6b4cb
fix(quiz): improve mobile UX and timer hydration
LesiaUKR Feb 4, 2026
56f76e5
fix(quiz): prevent timer progress bar animation glitches
LesiaUKR Feb 4, 2026
6cf267b
Merge branch 'develop' of https://github.com/DevLoversTeam/devlovers.…
liudmylasovetovs Feb 5, 2026
3d9e744
(SP 1) [Shop] Fix View all pagination by canonicalizing legacy filter…
liudmylasovetovs Feb 5, 2026
bd93adf
(SP 1) [SHOP] prevent checkout CTA label wrap and keep cart badge abo…
liudmylasovetovs Feb 5, 2026
638d0a4
(SP 1) [SHOP] preserve pagination params in products canonical redirect
liudmylasovetovs Feb 5, 2026
8af9a6c
(SP 1) [SHOP] Fix i18n-aware canonical redirect for legacy catalog fi…
liudmylasovetovs Feb 5, 2026
be76fca
Merge pull request #272 from DevLoversTeam/sl/feat/quiz
ViktorSvertoka Feb 5, 2026
8ef0ad2
Merge branch 'develop' into lso/feat/shop-design
ViktorSvertoka Feb 5, 2026
cdf4460
Merge pull request #274 from DevLoversTeam/lso/feat/shop-design
ViktorSvertoka Feb 5, 2026
c253133
fix/qa-mobile-tap-pagination-table-contrast
ViktorSvertoka Feb 5, 2026
3df6b02
Merge pull request #276 from DevLoversTeam/fix/qa-mobile-tap-paginati…
ViktorSvertoka Feb 5, 2026
a8afc1c
style: update dashboard UI and fix canonical Tailwind v4 classes
TiZorii Feb 5, 2026
ffb9d68
fix: correct invalid Tailwind classes and text capitalization
TiZorii Feb 5, 2026
277c945
Merge pull request #277 from DevLoversTeam/fix/ui-text-and-dashboard
ViktorSvertoka Feb 5, 2026
d0d6406
chore(release): v0.5.4
ViktorSvertoka Feb 5, 2026
9c0a867
Merge branch 'main' of https://github.com/DevLoversTeam/devlovers.net…
ViktorSvertoka Feb 5, 2026
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
94 changes: 94 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,97 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- Improved stability of text selection detection for AI helper
- Fixed locale duplication and routing edge cases
- Reduced visual overlap issues on small mobile screens

## [0.5.3] - 2026-02-04

### Added

- Quiz performance improvements:
- Redis-based answer verification replacing AES encryption
- Server-side quiz cache initialization to reduce verification latency
- Debug endpoints for inspecting and clearing quiz caches (development only)
- Caching & data layer:
- Persistent Redis caching for static Quiz and Q&A data (TTL removed)
- Cache-aside strategy for quiz answers and Q&A content
- Internationalization & accessibility:
- Translations for blog categories, CTA variants, and UI components (en / uk / pl)
- Improved aria-label coverage for navigation, cart, theme toggle, and search
- Developer experience:
- Finalized ESLint Flat Config for frontend
- Stable Prettier + Tailwind class sorting workflow
- Consistent format-on-save behavior across the team

### Changed

- Quiz system refactor:
- Simplified answer verification flow using Redis lookups
- Improved guest session restoration after quiz completion
- Language switch now preserves quiz results for guest users
- Layout & UI refinements:
- Removed duplicate padding on quiz routes
- Improved mobile alignment for Quiz Rules and headers
- Refined leaderboard component structure and lint stability
- Shop module cleanup:
- Normalized component naming (PascalCase)
- Reorganized test structure under domain boundaries
- Unified active-state and hover styling across shop routes
- Blog UI improvements:
- Fixed mobile paddings and spacing consistency
- Improved responsive header and layout behavior

### Fixed

- Fixed mobile layout misalignment on quiz pages
- Fixed guest language switch issues on quiz result screen
- Improved WCAG color contrast compliance across quiz UI
- Fixed ESLint, Prettier, and test configuration inconsistencies
- Removed unused files, dead code, and outdated utilities
- Improved reliability of quiz session restoration and state handling

## [0.5.4] - 2026-02-05

### Added

- Quiz SEO & performance improvements:
- Dynamic metadata generation for quizzes list and quiz detail pages
- i18n-aware meta titles and descriptions (en / uk / pl)
- Browserslist configuration targeting modern browsers
- Quiz content updates:
- Expanded JavaScript Fundamentals quiz from 10 to 40 questions
- Dashboard UI improvements:
- New DynamicGridBackground for cleaner visual hierarchy
- Refined ProfileCard and StatsCard layouts
- Accessibility & i18n:
- Improved aria-label coverage across navigation and UI controls
- Refined English, Polish, and Ukrainian UI copy and punctuation

### Changed

- Quiz UX refinements:
- Countdown timer animation stabilized on tab switch and session restore
- Emoji replaced with icon-based indicators for consistent styling
- Anti-cheat logic improved to distinguish touch vs mouse events
- Q&A experience improvements:
- Pagination scroll now targets section instead of page top
- Mobile tap lock resolved by clearing text selection on interaction
- Home & layout updates:
- Improved code card sizing and responsive behavior
- Online users counter repositioned for better mobile UX
- Shop UX refinements:
- Canonicalized legacy “View all” filters
- Improved cart CTA behavior and badge layering
- Blog & CMS:
- Refactored blog image rendering and filtering logic
- Improved pagination state handling
- Styling & consistency:
- Fixed Tailwind v4 canonical class warnings
- Unified token-based styling across dashboard, 404 page, and controls

### Fixed

- Fixed mobile anti-cheat false positives on quiz pages
- Removed render-blocking Font Awesome CSS
- Fixed quiz timer progress bar desynchronization
- Improved table text contrast in dark mode
- Fixed cart badge overlay issues in header
- Resolved multiple mobile spacing and padding inconsistencies
1 change: 1 addition & 0 deletions frontend/.browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
defaults and fully supports es6-module
8 changes: 5 additions & 3 deletions frontend/app/[locale]/blog/[slug]/PostDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ function renderPortableTextSpans(
const text = child?.text || '';
if (!text) return null;
const marks = child?.marks || [];
const linkKey = marks.find(mark => linkMap.has(mark));

let node: React.ReactNode = marks.length === 0 ? linkifyText(text) : text;

Expand Down Expand Up @@ -280,11 +279,14 @@ function renderPortableText(

if (block?._type === 'image' && block?.url) {
nodes.push(
<img
<Image
key={block._key || `image-${i}`}
src={block.url}
alt={postTitle || 'Post image'}
className="my-6 rounded-xl border border-gray-200"
width={1200}
height={800}
sizes="100vw"
className="my-6 h-auto w-full rounded-xl border border-gray-200"
/>
);
i += 1;
Expand Down
58 changes: 26 additions & 32 deletions frontend/app/[locale]/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PostAuthQuizSync } from '@/components/auth/PostAuthQuizSync';
import { ProfileCard } from '@/components/dashboard/ProfileCard';
import { QuizSavedBanner } from '@/components/dashboard/QuizSavedBanner';
import { StatsCard } from '@/components/dashboard/StatsCard';
import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground';
import { getUserQuizStats } from '@/db/queries/quiz';
import { getUserProfile } from '@/db/queries/users';
import { redirect } from '@/i18n/routing';
Expand Down Expand Up @@ -75,42 +76,35 @@ export default async function DashboardPage({
};

const outlineBtnStyles =
'inline-flex items-center justify-center rounded-full border border-slate-200 dark:border-slate-700 bg-white/50 dark:bg-slate-900/50 backdrop-blur-sm px-6 py-2 text-sm font-medium text-slate-600 dark:text-slate-300 transition-colors hover:bg-white hover:text-sky-600 dark:hover:bg-slate-800 dark:hover:text-sky-400';
'inline-flex items-center justify-center rounded-full border border-gray-200 dark:border-white/10 bg-white/50 dark:bg-neutral-900/50 backdrop-blur-sm px-6 py-2 text-sm font-medium text-gray-600 dark:text-gray-300 transition-colors hover:bg-white hover:text-(--accent-primary) dark:hover:bg-neutral-800 dark:hover:text-(--accent-primary)';

return (
<main className="relative min-h-[calc(100vh-80px)] overflow-hidden">
<div className="min-h-screen">
<PostAuthQuizSync />
<div
className="pointer-events-none absolute inset-0 -z-10"
aria-hidden="true"
<DynamicGridBackground
showStaticGrid
className="min-h-screen bg-gray-50 py-12 transition-colors duration-300 dark:bg-transparent"
>
<div className="absolute inset-0 bg-linear-to-b from-sky-50 via-white to-rose-50 dark:from-slate-950 dark:via-slate-950 dark:to-black" />
<div className="absolute top-0 left-1/4 h-96 w-xl -translate-x-1/2 rounded-full bg-sky-300/20 blur-3xl dark:bg-sky-500/10" />
<div className="absolute right-0 bottom-0 h-104 w-104 rounded-full bg-violet-300/30 blur-3xl dark:bg-violet-500/10" />
<div className="absolute bottom-10 left-10 h-80 w-[20rem] rounded-full bg-pink-300/20 blur-3xl dark:bg-fuchsia-500/10" />
</div>

<div className="relative z-10 mx-auto max-w-5xl px-6 py-12">
<header className="mb-12 flex flex-col justify-between gap-6 md:flex-row md:items-center">
<div>
<h1 className="text-4xl font-black tracking-tight drop-shadow-sm md:text-5xl">
<span className="bg-linear-to-r from-sky-400 via-violet-400 to-pink-400 bg-clip-text text-transparent dark:from-sky-400 dark:via-indigo-400 dark:to-fuchsia-500">
{t('title')}
</span>
</h1>
<p className="mt-2 text-lg text-slate-600 dark:text-slate-400">
{t('subtitle')}
</p>
<main className="relative z-10 mx-auto max-w-5xl px-6">
<header className="mb-12 flex flex-col justify-between gap-6 md:flex-row md:items-center">
<div>
<h1 className="text-4xl font-black tracking-tight md:text-5xl">
<span className="text-(--accent-primary)">{t('title')}</span>
</h1>
<p className="mt-2 text-lg text-gray-600 dark:text-gray-400">
{t('subtitle')}
</p>
</div>

<span className={outlineBtnStyles}>{t('supportLink')}</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The support link is rendered as a non-interactive span.

The supportLink translation key suggests this should be a clickable link, but it's rendered as a <span> with button styling and no interaction handlers. If this is intentional (e.g., a placeholder for future functionality or just a decorative badge), consider:

  • Adding aria-hidden="true" if purely decorative
  • Renaming the translation key to something like supportBadge

If it should be interactive, wrap it in a <Link> or <a> element with the appropriate destination.

🤖 Prompt for AI Agents
In `@frontend/app/`[locale]/dashboard/page.tsx at line 99, The support item is
rendered as a non-interactive <span> with the class outlineBtnStyles and
translation key supportLink; if it should be a clickable link, replace the
<span> with a proper interactive element (e.g., Next.js <Link> or an <a> with
the correct href) and preserve outlineBtnStyles and the t('supportLink') text,
or if it is purely decorative add aria-hidden="true" and/or rename the
translation key (supportBadge) to avoid implying interactivity; update the
element where t('supportLink') is used in page.tsx accordingly.

</header>
<QuizSavedBanner />
<div className="grid gap-8 md:grid-cols-2">
<ProfileCard user={userForDisplay} locale={locale} />
<StatsCard stats={stats} />
</div>

<span className={outlineBtnStyles}>{t('supportLink')}</span>
</header>
<QuizSavedBanner />
<div className="grid gap-8 md:grid-cols-2">
<ProfileCard user={userForDisplay} locale={locale} />
<StatsCard stats={stats} />
</div>
</div>
</main>
</main>
</DynamicGridBackground>
</div>
);
}
2 changes: 0 additions & 2 deletions frontend/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { AppChrome } from '@/components/header/AppChrome';
import { MainSwitcher } from '@/components/header/MainSwitcher';
import { CookieBanner } from '@/components/shared/CookieBanner';
import Footer from '@/components/shared/Footer';
import { OnlineCounterPopup } from '@/components/shared/OnlineCounterPopup';
import { ThemeProvider } from '@/components/theme/ThemeProvider';
import { locales } from '@/i18n/config';
import { getCurrentUser } from '@/lib/auth';
Expand Down Expand Up @@ -73,7 +72,6 @@ export default async function LocaleLayout({
{children}
</MainSwitcher>
</AppChrome>
<OnlineCounterPopup />

<Footer />
<Toaster position="top-right" richColors expand />
Expand Down
30 changes: 16 additions & 14 deletions frontend/app/[locale]/q&a/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,21 @@ export default async function QAPage({
const t = await getTranslations({ locale, namespace: 'qa' });

return (
<DynamicGridBackground className="bg-gray-50 py-10 transition-colors duration-300 dark:bg-transparent">
<main className="relative z-10 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-8">
<p className="text-sm font-semibold text-(--accent-primary)">
{t('pretitle')}
</p>
<h1 className="text-3xl font-bold">{t('title')}</h1>
<p className="text-gray-600 dark:text-gray-400">{t('subtitle')}</p>
</div>
<Suspense fallback={<>...</>}>
<QaSection />
</Suspense>
</main>
</DynamicGridBackground>
<div className="min-h-screen">
<DynamicGridBackground className="bg-gray-50 py-10 transition-colors duration-300 dark:bg-transparent">
<main className="relative z-10 mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mb-8">
<p className="text-sm font-semibold text-(--accent-primary)">
{t('pretitle')}
</p>
<h1 className="text-3xl font-bold">{t('title')}</h1>
<p className="text-gray-600 dark:text-gray-400">{t('subtitle')}</p>
</div>
<Suspense fallback={<>...</>}>
<QaSection />
</Suspense>
</main>
</DynamicGridBackground>
</div>
);
}
26 changes: 24 additions & 2 deletions frontend/app/[locale]/quiz/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Metadata } from 'next';
import { notFound, redirect } from 'next/navigation';
import { getTranslations } from 'next-intl/server';

Expand All @@ -6,6 +7,27 @@ import { stripCorrectAnswers } from '@/db/queries/quiz';
import { getQuizBySlug, getQuizQuestionsRandomized } from '@/db/queries/quiz';
import { getCurrentUser } from '@/lib/auth';

type MetadataProps = { params: Promise<{ locale: string; slug: string }> };

export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale, slug } = await params;
const t = await getTranslations({ locale, namespace: 'quiz.page' });
const quiz = await getQuizBySlug(slug, locale);

if (!quiz) {
return { title: t('notFoundTitle') };
}

return {
title: `${quiz.title} | ${t('metaSuffix')}`,
description:
quiz.description ??
t('metaDescriptionFallback', { title: quiz.title ?? '' }),
};
}

interface QuizPageProps {
params: Promise<{ locale: string; slug: string }>;
searchParams: Promise<{ seed?: string }>;
Expand Down Expand Up @@ -34,10 +56,10 @@ export default async function QuizPage({

const seed = Number.parseInt(seedParam, 10);
if (Number.isNaN(seed)) {
// eslint-disable-next-line react-hooks/purity -- redirect throws, value never used in render
// eslint-disable-next-line react-hooks/purity -- redirect throws, value never used in render
redirect(`/${locale}/quiz/${slug}?seed=${Date.now()}`);
}

const questions = await getQuizQuestionsRandomized(quiz.id, locale, seed);

const clientQuestions = stripCorrectAnswers(questions);
Expand Down
13 changes: 13 additions & 0 deletions frontend/app/[locale]/quizzes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Metadata } from 'next';
import { getTranslations } from 'next-intl/server';

import QuizzesSection from '@/components/quiz/QuizzesSection';
Expand All @@ -7,6 +8,18 @@ import { getCurrentUser } from '@/lib/auth';

type PageProps = { params: Promise<{ locale: string }> };

export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'quiz.list' });

return {
title: t('metaTitle'),
description: t('metaDescription'),
};
}

export const dynamic = 'force-dynamic';

export default async function QuizzesPage({ params }: PageProps) {
Expand Down
23 changes: 18 additions & 5 deletions frontend/app/[locale]/shop/cart/CartPageClient.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { Minus, Plus, ShoppingBag, Trash2 } from 'lucide-react';
import { Loader2, Minus, Plus, ShoppingBag, Trash2 } from 'lucide-react';
import Image from 'next/image';
import { useParams } from 'next/navigation';
import { useTranslations } from 'next-intl';
Expand Down Expand Up @@ -411,10 +411,23 @@ export default function CartPage() {
/>
<span className={SHOP_CTA_INSET} aria-hidden="true" />

<span className="relative z-10">
{isCheckingOut
? t('checkout.placing')
: t('checkout.placeOrder')}
<span className="relative z-10 inline-flex min-w-0 items-center justify-center gap-2">
{isCheckingOut ? (
<Loader2
className="h-4 w-4 animate-spin"
aria-hidden="true"
/>
) : null}

{/* visible label stays stable to avoid wrapping/layout shift */}
<span className="truncate whitespace-nowrap">
{t('checkout.placeOrder')}
</span>

{/* screen readers can still get the “placing” state */}
{isCheckingOut ? (
<span className="sr-only">{t('checkout.placing')}</span>
) : null}
</span>
</button>

Expand Down
2 changes: 1 addition & 1 deletion frontend/app/[locale]/shop/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default async function HomePage({
</h2>

<Link
href="/shop/products?filter=new"
href="/shop/products?filter=newest"
className="group border-border text-muted-foreground hover:text-foreground focus-visible:ring-offset-background inline-flex items-center gap-2 rounded-md border bg-transparent px-4 py-2 text-xs font-semibold tracking-[0.25em] uppercase shadow-none transition-[transform,box-shadow,color,filter] duration-500 ease-out hover:-translate-y-0.5 hover:shadow-[var(--shop-card-shadow-hover)] hover:brightness-110 focus-visible:ring-2 focus-visible:ring-[color:var(--color-ring)] focus-visible:ring-offset-2 focus-visible:outline-none sm:text-sm"
aria-label={t('viewAll')}
Comment on lines 54 to 57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "filter.*newest|newest.*filter" --type=tsx --type=ts -C 3

Repository: DevLoversTeam/devlovers.net

Length of output: 98


🏁 Script executed:

# Also check the products page to see how it handles filter parameters
fd "products" --type=f -e tsx -e ts | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 370


🏁 Script executed:

# Search for filter parameter handling in general
rg -n "filter=|filter:" --type=tsx --type=ts -C 2 | grep -A 5 -B 5 "shop/products"

Repository: DevLoversTeam/devlovers.net

Length of output: 98


🏁 Script executed:

rg -n "newest" --type=ts -C 3

Repository: DevLoversTeam/devlovers.net

Length of output: 4664


🏁 Script executed:

# Check the CatalogProductsClient to see how it handles filter parameters
cat -n frontend/components/shop/CatalogProductsClient.tsx | head -100

Repository: DevLoversTeam/devlovers.net

Length of output: 3520


🏁 Script executed:

# Check the products.ts query file
cat -n frontend/db/queries/shop/products.ts | head -100

Repository: DevLoversTeam/devlovers.net

Length of output: 3042


🏁 Script executed:

# Search for filter parameter handling more broadly
rg -n "filter" frontend/components/shop/ -C 2

Repository: DevLoversTeam/devlovers.net

Length of output: 3486


🏁 Script executed:

# Check the full catalog route handler to see if filter=newest is handled
cat -n frontend/app/api/shop/catalog/route.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 3350


🏁 Script executed:

# Check the catalogQuerySchema to see what parameters it accepts
rg -n "catalogQuerySchema" --type=ts -B 5 -A 15

Repository: DevLoversTeam/devlovers.net

Length of output: 9428


🏁 Script executed:

# Check the products page to see how it handles filter=newest
cat -n frontend/app/\[locale\]/shop/products/page.tsx | head -60

Repository: DevLoversTeam/devlovers.net

Length of output: 2293


🏁 Script executed:

# Verify what happens when filter parameter doesn't match any legacy case
rg -n "filter" frontend/app/\[locale\]/shop/products/page.tsx -B 2 -A 2

Repository: DevLoversTeam/devlovers.net

Length of output: 668


Use sort=newest instead of filter=newest

The link uses filter=newest, but the products page and catalog API only handle the legacy filter=new parameter. The modern parameter is sort=newest. Update the href to /shop/products?sort=newest to properly apply the newest sort when the "View All" link is clicked.

🤖 Prompt for AI Agents
In `@frontend/app/`[locale]/shop/page.tsx around lines 54 - 57, Update the Link
that renders the "View All" button so its query param uses the modern sort key:
change the href on the Link component (the JSX starting with <Link
href="/shop/products?filter=newest" ...>) to use /shop/products?sort=newest
instead of filter=newest so the products page and catalog API receive the
supported sort=newest parameter.

>
Expand Down
Loading