Skip to content

(SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start#324

Merged
ViktorSvertoka merged 3 commits into
developfrom
fix/qa-nextjs-tab-border-loader-speed
Feb 13, 2026
Merged

(SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start#324
ViktorSvertoka merged 3 commits into
developfrom
fix/qa-nextjs-tab-border-loader-speed

Conversation

@ViktorSvertoka
Copy link
Copy Markdown
Member

@ViktorSvertoka ViktorSvertoka commented Feb 13, 2026

Adds missing Next.js tab hover/focus border behavior, removes thick active border on that tab, and accelerates loader initial animation appearance.

Closes #323

Summary by CodeRabbit

  • New Features

    • Scroll-enabled multi-section homepage with updated hero sections and footer visibility control.
    • Pagination: selectable page size (10/20/40/60/80/100), persisted in URL, and larger API page limit (up to 100).
    • Localized labels for items-per-page.
  • Bug Fixes

    • Improved active-state border styling in dark mode.
  • Chores

    • Removed legacy DB seeding scripts; updated site fonts and accent color.
  • Tests

    • Added tests for page-size behavior in pagination and tab hooks.

@ViktorSvertoka ViktorSvertoka self-assigned this Feb 13, 2026
@ViktorSvertoka ViktorSvertoka added bug Something isn't working performance Performance and efficiency optimizations without functional changes. labels Feb 13, 2026
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
devlovers-net Ready Ready Preview, Comment Feb 13, 2026 10:57pm

@netlify
Copy link
Copy Markdown

netlify Bot commented Feb 13, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit f6d10be
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/698fac0693791b0008d4681a
😎 Deploy Preview https://deploy-preview-324--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Removes many legacy frontend DB seed scripts and shared seed types; adds page-size pagination support in Q&A (URL persistence + server limit); introduces HomePageScroll and homepage layout changes; adds loader startup warmup; adjusts styles and Footer API; adds i18n keys and tests.

Changes

Cohort / File(s) Summary
Loader & Styles
frontend/components/shared/Loader.tsx, frontend/data/categoryStyles.ts
Adds a 100‑frame startup warmup loop before main animation and an initial draw; tweaks accent color and forces explicit active-state border rules.
Homepage UI
frontend/app/[locale]/page.tsx, frontend/components/home/HomePageScroll.tsx, frontend/components/home/WelcomeHeroSection.tsx, frontend/components/home/FeaturesHeroSection.tsx, frontend/components/shared/Footer.tsx
Introduces HomePageScroll and rewires home to snap-scroll sections; changes viewport sizing to dvh; Footer signature adds className and forceVisible and hides on home by pathname unless forced.
Q&A Pagination & Hooks
frontend/components/q&a/Pagination.tsx, frontend/components/q&a/QaSection.tsx, frontend/components/q&a/useQaTabs.ts
Implements configurable page sizes (options, default, URL persistence), exposes pageSize APIs in hook and component, updates client fetch limit to use pageSize, and updates pagination UI and controls.
API Pagination Limit
frontend/app/api/questions/[category]/route.ts
Increases server-side maximum pagination limit from 50 → 100 (affects query limit, offset, totalPages, and cache keys).
I18n & Tests
frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json, frontend/components/tests/q&a/pagination.test.tsx, frontend/components/tests/q&a/use-qa-tabs.test.tsx
Adds "items per page" translation keys and aria label; adds tests asserting page-size select triggers onPageSizeChange and that URL/fetch use the selected size.
Seed Scripts — Removals
frontend/db/seed-quiz-*.ts (multiple), frontend/db/seed-demo-leaderboard.ts, frontend/db/seed-users.ts, frontend/db/seed-quiz-from-json.ts, frontend/db/seed-quiz-verify.ts
Removes ~18 quiz seeding scripts and several utility seed scripts; seed-questions.ts was retained and modernized (locale normalization, upsert).
Seed Types Removed
frontend/db/seed-quiz-types.ts
Deletes shared seed typings and helper constructors (Locale, AnswerBlock, QuizQuestionSeed, p/t/code/ul/ol helpers).
Header tweaks
frontend/components/header/MainSwitcher.tsx
Replaces hard-coded locale checks with dynamic locales array usage for home path detection.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Q&A Pagination UI
    participant Hook as useQaTabs
    participant Router as NextRouter
    participant API as /api/questions route
    participant ServerDB as Database

    User->>UI: Change page size
    UI->>Hook: handlePageSizeChange(size)
    Hook->>Router: replace URL with ?size=N (scroll:false)
    Hook->>API: fetch /api/questions/{cat}?page=1&limit=N&locale=...
    API->>ServerDB: query questions LIMIT N OFFSET 0
    ServerDB-->>API: return paged results
    API-->>Hook: respond with questions, totalPages
    Hook-->>UI: update items, pagination controls
    UI-->>User: render updated page with new page size
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • #304 — modifies frontend/components/shared/Loader.tsx; may overlap with loader warmup changes.
  • #169 — touches Q&A pagination/hooks (useQaTabs, Pagination, QaSection`) and is closely related to page-size changes.
  • #301 — also edits frontend/components/shared/Loader.tsx and may conflict on loader behavior.

Suggested labels

UI

Suggested reviewers

  • AM1007

Poem

🐰 I hopped in code at break of day,
I warmed the loader, swept seeds away,
Pages snap and sizes bloom,
Colors nudged to earn their room,
A little hop — the site says "Hooray!" 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Significant out-of-scope changes detected: multiple seed files and types deleted (seed-quiz-*.ts, seed-demo-leaderboard.ts, seed-users.ts, seed-quiz-types.ts), pagination system completely refactored with new page size options, font loading mechanism changed from geist imports to Google fonts, and home page layout restructured with new scroll components. Remove all out-of-scope changes unrelated to #323: revert seed file deletions, pagination refactoring, font loading changes, and home page layout restructuring. Focus PR solely on categoryStyles.ts tab borders and Loader.tsx animation speed.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main changes: Q&A Next.js tab states and faster loader startup, which align with the core changeset modifications.
Linked Issues check ✅ Passed The PR addresses issue #323 requirements: Next.js tab border styling updates are in categoryStyles.ts, and loader startup acceleration is implemented in Loader.tsx with STARTUP_WARMUP_FRAMES.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into develop

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/qa-nextjs-tab-border-loader-speed

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/db/seed-questions.ts`:
- Around line 87-94: The translations array currently maps raw keys then
normalizes locales causing duplicate (questionId, locale) entries to silently
overwrite and produce a misleading mismatch between expectedLocales and
uniqueInsertedLocales; fix this by deduplicating after calling normalizeLocale
(e.g., build a Map keyed by normalized locale while iterating q.translations in
the block that constructs translations) so you explicitly enforce
last-writer-wins, then derive both expectedLocales and uniqueInsertedLocales
from that deduplicated Map (update the code paths that reference translations,
expectedLocales, and uniqueInsertedLocales accordingly) to avoid spurious
warnings.
🧹 Nitpick comments (2)
frontend/data/categoryStyles.ts (1)

54-61: dark:data-[state=active]:!border may be redundant.

data-[state=active]:!border already forces border-width: 1px !important regardless of color scheme. The dark: variant of !border re-declares the same width override and is likely unnecessary — only the border-color needs a dark-mode variant. Removing it would reduce the already-long class string:

-      'group-hover:border-black/50 dark:group-hover:border-white/50 group-hover:bg-black/5 dark:group-hover:bg-white/10 data-[state=active]:!border data-[state=active]:border-black/50 dark:data-[state=active]:!border dark:data-[state=active]:border-white/50 data-[state=active]:bg-black/5 dark:data-[state=active]:bg-white/10',
+      'group-hover:border-black/50 dark:group-hover:border-white/50 group-hover:bg-black/5 dark:group-hover:bg-white/10 data-[state=active]:!border data-[state=active]:border-black/50 dark:data-[state=active]:border-white/50 data-[state=active]:bg-black/5 dark:data-[state=active]:bg-white/10',

That said, if the !border is needed to override a thicker border from a parent component, consider documenting why this override is necessary (a brief comment), so future maintainers don't accidentally remove it or wonder about the inconsistency with other tabs.

frontend/db/seed-questions.ts (1)

75-85: Non-null assertion on insert result could produce an opaque crash.

If db.insert(...).returning(...) ever resolves to an empty array (e.g., a DB trigger suppresses the row), [0]!.id throws a generic TypeError instead of a meaningful seed error. A guard or explicit check would make failures easier to diagnose.

Proposed improvement
-    const questionId =
-      existingQuestion?.id ??
-      (
-        await db
-          .insert(questions)
-          .values({
-            categoryId: category.id,
-            sortOrder,
-          })
-          .returning({ id: questions.id })
-      )[0]!.id;
+    let questionId = existingQuestion?.id;
+    if (!questionId) {
+      const [inserted] = await db
+        .insert(questions)
+        .values({
+          categoryId: category.id,
+          sortOrder,
+        })
+        .returning({ id: questions.id });
+      if (!inserted) {
+        console.error(
+          `[seed] Failed to insert question for category ${q.category}, order ${sortOrder}`
+        );
+        continue;
+      }
+      questionId = inserted.id;
+    }

Comment thread frontend/db/seed-questions.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@frontend/app/layout.tsx`:
- Around line 52-60: The Geist font configs (geistSans and geistMono) currently
only include the 'latin' subset; update both Geist() and Geist_Mono() calls to
include 'cyrillic' in their subsets arrays (e.g., subsets: ['latin',
'cyrillic']) so Ukrainian/Cyrillic text renders correctly; locate the geistSans
and geistMono initializations in layout.tsx and modify their subsets
accordingly.

In `@frontend/components/q`&a/Pagination.tsx:
- Around line 163-176: The Next button's disabled visual state uses the wrong
variable: replace the className condition that checks currentPage === totalPages
with a check against effectiveTotalPages (the same logic used for the disabled
prop) so the 'cursor-not-allowed text-gray-400 dark:text-gray-600' styles are
applied when currentPage >= effectiveTotalPages; update the conditional in the
className expression inside the button (referencing currentPage, totalPages, and
effectiveTotalPages) to use effectiveTotalPages for consistent functional and
visual disabling.

In `@frontend/components/shared/Footer.tsx`:
- Around line 35-42: Replace the hardcoded locale list in Footer by importing
the centralized locales array and using it to detect locale-only pathnames:
update the top of the file to import { locales } from '@/frontend/i18n/config'
and change the isHome check that currently uses ['en','pl','uk'] to use
locales.includes(pathSegments[0]) instead; adjust the logic around pathname,
pathSegments, isHome and forceVisible accordingly and make the same change in
MainSwitcher.tsx to avoid duplicate hardcoding.
🧹 Nitpick comments (3)
frontend/components/q&a/QaSection.tsx (1)

129-141: Consider scrolling to top on page-size change for UX consistency.

onPageChange (Line 63) wraps handlePageChange with clearSelection() and scrollToTop(), but handlePageSizeChange is passed directly without these wrappers. When the page size changes (and the page resets to 1), the user may end up viewing stale scroll position if they were scrolled down. This is a minor UX gap.

frontend/components/q&a/useQaTabs.ts (1)

46-50: Consider: state is dual-sourced from both direct setState calls and URL-derived sync effects.

Both currentPage and pageSize are set directly in handlers (e.g., setCurrentPage(1) in handlePageSizeChange) and synced from URL via effects (lines 72-78). This works because React skips re-renders on same-value updates, but it means every handler-triggered URL change still runs through the sync effects redundantly.

A cleaner alternative would be to derive currentPage/pageSize solely from the URL (removing the sync effects and local state), or solely from local state (removing the sync effects). This is a pre-existing pattern though, so fine to defer.

Also applies to: 72-78

frontend/components/home/HomePageScroll.tsx (1)

20-29: Cache the step reference instead of querying the DOM on every scroll event.

querySelectorAll('[data-home-step]') runs on every scroll tick. Since the step elements don't change, cache the second step's reference and only refresh it on resize.

♻️ Proposed refactor to cache the step element
 export default function HomePageScroll({ children }: HomePageScrollProps) {
   const containerRef = useRef<HTMLDivElement | null>(null);
+  const secondStepRef = useRef<HTMLElement | null>(null);
   const [snapEnabled, setSnapEnabled] = useState(true);

   useEffect(() => {
     const container = containerRef.current;
     if (!container) return;

-    const updateSnapMode = () => {
+    const cacheSecondStep = () => {
       const steps = container.querySelectorAll<HTMLElement>('[data-home-step]');
-      const secondStep = steps[1];
-      if (!secondStep) return;
+      secondStepRef.current = steps[1] ?? null;
+    };
+
+    const updateSnapMode = () => {
+      const secondStep = secondStepRef.current;
+      if (!secondStep) return;

       const shouldEnableSnap = container.scrollTop <= secondStep.offsetTop + 4;
       setSnapEnabled(prev =>
         prev === shouldEnableSnap ? prev : shouldEnableSnap
       );
     };

+    cacheSecondStep();
     updateSnapMode();
     container.addEventListener('scroll', updateSnapMode, { passive: true });
-    window.addEventListener('resize', updateSnapMode);
+    window.addEventListener('resize', () => {
+      cacheSecondStep();
+      updateSnapMode();
+    });

     return () => {
       container.removeEventListener('scroll', updateSnapMode);
-      window.removeEventListener('resize', updateSnapMode);
+      // Note: store the resize handler in a variable to properly remove it
     };
   }, []);

(You'd also need to store the resize handler in a local variable for proper cleanup.)

Comment thread frontend/app/layout.tsx
Comment thread frontend/components/q&amp;a/Pagination.tsx
Comment thread frontend/components/shared/Footer.tsx
@ViktorSvertoka ViktorSvertoka merged commit bd66f7d into develop Feb 13, 2026
10 of 11 checks passed
@ViktorSvertoka ViktorSvertoka deleted the fix/qa-nextjs-tab-border-loader-speed branch February 13, 2026 23:03
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@frontend/components/q`&a/Pagination.tsx:
- Around line 179-210: The Pagination component uses a hardcoded id
"qa-page-size" which can duplicate when multiple <Pagination> instances render;
import React's useId and generate a unique id inside the Pagination component
(e.g., const pageSizeId = useId()), then replace the hardcoded id and label
htmlFor with that generated id so the <label htmlFor={pageSizeId}> and <select
id={pageSizeId}> pair remain unique per instance.

In `@frontend/db/seed-questions.ts`:
- Around line 75-85: The non-null assertion on the insert result (the [0]!.id
used when computing questionId) can crash with a cryptic TypeError if
.returning() returns an empty array; update the logic around existingQuestion
and the db.insert(questions).values(...).returning(...) call to check the
returned rows array exists and has an element before accessing .id, and if not,
throw or log a clear, descriptive error (e.g., "Failed to insert question for
categoryId X: no rows returned") or handle the failure path gracefully so
questionId is never derived from an undefined value.
- Line 62: Add an optional difficulty?: string field to the RawQuestion type and
propagate it into the seed insert so seed data can specify difficulty instead of
defaulting to 'medium'; update the RawQuestion declaration to include
difficulty, read it when building each seeded row (alongside sortOrder = q.order
?? 0) and include that difficulty value in the insert values for the questions
insert call so optional difficulties from seed data are persisted.
🧹 Nitpick comments (2)
frontend/components/q&a/Pagination.tsx (1)

33-35: Default array literal [10] is re-created on every render.

This is a minor allocation on each render. Hoisting to a module-level constant avoids it.

♻️ Suggested improvement

Add a constant before the component:

+const DEFAULT_PAGE_SIZE_OPTIONS: readonly number[] = [10];
+
 export function Pagination({
   ...
-  pageSizeOptions = [10],
+  pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,
frontend/db/seed-questions.ts (1)

111-127: Consider batching translation upserts instead of one query per locale.

The current loop issues a separate INSERT … ON CONFLICT per translation. For a seed script the volume is typically small, but a single batched upsert (or a Promise.all of the individual calls) would reduce round-trips and speed up seeding for larger datasets.

Comment on lines +179 to +210
{onPageSizeChange && pageSizeOptions.length > 1 && (
<div className="absolute top-1/2 right-0 hidden -translate-y-1/2 items-center gap-2 lg:flex">
<label
htmlFor="qa-page-size"
className="text-xs font-medium whitespace-nowrap text-gray-600 dark:text-gray-300"
>
{t('itemsPerPage')}
</label>
<div
className="relative overflow-hidden rounded-lg border bg-white/90 shadow-sm dark:bg-neutral-900/80"
style={{
borderColor: accentColor,
boxShadow: `0 0 0 1px ${accentSoft}`,
}}
>
<select
id="qa-page-size"
value={pageSize}
onChange={event => onPageSizeChange(Number(event.target.value))}
aria-label={t('itemsPerPageAria')}
className="min-w-20 appearance-none bg-transparent px-3 py-2 pr-9 text-sm font-medium text-gray-800 outline-none transition-colors hover:bg-[var(--qa-accent-soft)] focus:bg-[var(--qa-accent-soft)] dark:text-gray-200"
>
...
</span>
) : (
<button
key={page}
onClick={() => onPageChange(page)}
disabled={page === currentPage}
className={cn(
'min-w-[40px] overflow-hidden rounded-lg border border-transparent bg-white/90 px-3 py-2 text-sm font-medium transition-colors dark:bg-neutral-900/80',
page === currentPage
? 'text-gray-700 shadow-sm dark:text-gray-300'
: 'text-gray-700 hover:bg-[var(--qa-accent-soft)] dark:text-gray-300'
)}
style={
page === currentPage
? {
backgroundColor: accentSoft,
borderColor: accentColor,
boxShadow: `inset 0 0 18px ${accentGlow}`,
}
: undefined
}
aria-label={t('page', { page })}
aria-current={page === currentPage ? 'page' : undefined}
>
{page}
</button>
)
)}
</div>

<button
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className={cn(
'rounded-lg px-2 py-2 text-sm font-medium transition-colors sm:px-3',
'border border-gray-300 bg-white/90 dark:border-gray-700 dark:bg-neutral-900/80',
currentPage === totalPages
? 'cursor-not-allowed text-gray-400 dark:text-gray-600'
: 'text-gray-700 hover:bg-[var(--qa-accent-soft)] dark:text-gray-300'
)}
aria-label={t('nextPage')}
>
<span className="hidden sm:inline">{t('next')}</span> →
</button>
</nav>
{pageSizeOptions.map(size => (
<option key={size} value={size}>
{size}
</option>
))}
</select>
<ChevronDown className="pointer-events-none absolute top-1/2 right-3 h-4 w-4 -translate-y-1/2 text-gray-600 dark:text-gray-300" />
</div>
</div>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded id="qa-page-size" risks duplicate IDs if multiple <Pagination> instances coexist.

If two pagination components render on the same page (e.g., top and bottom of a list), the duplicate id breaks the <label htmlFor> association and is invalid HTML. Consider using useId() from React to generate a unique ID per instance.

🛠️ Proposed fix

Add useId to the React import on Line 5:

-import { useEffect, useState } from 'react';
+import { useEffect, useId, useState } from 'react';

Then inside the component body:

+  const pageSizeId = useId();

And update the label/select pair:

-          <label
-            htmlFor="qa-page-size"
+          <label
+            htmlFor={pageSizeId}
             ...
           >
           ...
           <select
-              id="qa-page-size"
+              id={pageSizeId}
🤖 Prompt for AI Agents
In `@frontend/components/q`&a/Pagination.tsx around lines 179 - 210, The
Pagination component uses a hardcoded id "qa-page-size" which can duplicate when
multiple <Pagination> instances render; import React's useId and generate a
unique id inside the Pagination component (e.g., const pageSizeId = useId()),
then replace the hardcoded id and label htmlFor with that generated id so the
<label htmlFor={pageSizeId}> and <select id={pageSizeId}> pair remain unique per
instance.

sortOrder: q.order ?? 0,
})
.returning();
const sortOrder = q.order ?? 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the seed data file
fd 'questions' --type f | grep -E '\.(json|ts)$' | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 209


🏁 Script executed:

#!/bin/bash
# Look at the seed-questions.ts file structure
cat -n frontend/db/seed-questions.ts | head -100

Repository: DevLoversTeam/devlovers.net

Length of output: 3287


🏁 Script executed:

#!/bin/bash
# Search for RawQuestion type definition
rg 'type RawQuestion|interface RawQuestion' -A 10 frontend/

Repository: DevLoversTeam/devlovers.net

Length of output: 568


🏁 Script executed:

#!/bin/bash
# Check the questions schema
cat -n frontend/db/schema/questions.ts

Repository: DevLoversTeam/devlovers.net

Length of output: 2393


🏁 Script executed:

#!/bin/bash
# Check the structure of the seed data
head -100 frontend/parse/questions.json | jq '.[0]' 2>/dev/null || head -100 frontend/parse/questions.json

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check seed data structure - try a different approach
file frontend/parse/questions.json && wc -l frontend/parse/questions.json

Repository: DevLoversTeam/devlovers.net

Length of output: 115


🏁 Script executed:

#!/bin/bash
# Check if questions.json contains 'difficulty' field
grep -c '"difficulty"' frontend/parse/questions.json 2>/dev/null || echo "No matches found"

Repository: DevLoversTeam/devlovers.net

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Get a small sample from questions.json to see structure
head -200 frontend/parse/questions.json | python3 -c "import json, sys; data = json.load(sys.stdin); print(json.dumps(data[0] if isinstance(data, list) and len(data) > 0 else data, indent=2))" 2>/dev/null || head -50 frontend/parse/questions.json

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Try to view questions.json with a simpler approach
head -30 frontend/parse/questions.json

Repository: DevLoversTeam/devlovers.net

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check file size and try different viewing approach
wc -c frontend/parse/questions.json
stat -c%s frontend/parse/questions.json 2>/dev/null || stat -f%z frontend/parse/questions.json 2>/dev/null || echo "Unknown size"
hexdump -C frontend/parse/questions.json | head -20

Repository: DevLoversTeam/devlovers.net

Length of output: 178


🏁 Script executed:

#!/bin/bash
# Try with python to parse the JSON
python3 << 'EOF'
import json
try:
    with open('frontend/parse/questions.json', 'r') as f:
        data = json.load(f)
        if isinstance(data, list) and len(data) > 0:
            print("Sample question:")
            print(json.dumps(data[0], indent=2)[:500])
            print(f"\nTotal questions: {len(data)}")
            print(f"Keys in first question: {list(data[0].keys())}")
        else:
            print(f"Data type: {type(data)}")
except Exception as e:
    print(f"Error: {e}")
EOF

Repository: DevLoversTeam/devlovers.net

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Check if the file exists and is empty
ls -la frontend/parse/questions.json
# Also check if there are any other JSON files in the parse directory
ls -la frontend/parse/

Repository: DevLoversTeam/devlovers.net

Length of output: 449


🏁 Script executed:

#!/bin/bash
# Check the parse.ts to understand how data is generated
cat -n frontend/parse/parse.ts | head -50

Repository: DevLoversTeam/devlovers.net

Length of output: 1312


🏁 Script executed:

#!/bin/bash
# Check if there are any tests or other references to understand intent
rg 'seedQuestions|RawQuestion' --type ts -B 2 -A 2

Repository: DevLoversTeam/devlovers.net

Length of output: 1132


🏁 Script executed:

#!/bin/bash
# Let's check if there are any git history or other evidence of how questions data should be structured
git log --oneline frontend/parse/questions.json 2>/dev/null | head -10 || echo "No git history"

Repository: DevLoversTeam/devlovers.net

Length of output: 160


🏁 Script executed:

#!/bin/bash
# Check if difficulty is used or referenced anywhere else in the codebase
rg 'difficulty' frontend/ --type ts -i

Repository: DevLoversTeam/devlovers.net

Length of output: 1299


Add difficulty field to RawQuestion type and include it in the insert statement to allow seed data to specify question difficulty.

Currently, the RawQuestion type (lines 9-19) lacks a difficulty field, and the insert on lines 80-83 omits it. This means all seeded questions will silently default to 'medium' per the schema definition. To allow seed data to optionally specify difficulty levels for questions, update the RawQuestion type to include an optional difficulty field and pass it to the insert values.

🤖 Prompt for AI Agents
In `@frontend/db/seed-questions.ts` at line 62, Add an optional difficulty?:
string field to the RawQuestion type and propagate it into the seed insert so
seed data can specify difficulty instead of defaulting to 'medium'; update the
RawQuestion declaration to include difficulty, read it when building each seeded
row (alongside sortOrder = q.order ?? 0) and include that difficulty value in
the insert values for the questions insert call so optional difficulties from
seed data are persisted.

Comment on lines +75 to +85
const questionId =
existingQuestion?.id ??
(
await db
.insert(questions)
.values({
categoryId: category.id,
sortOrder,
})
.returning({ id: questions.id })
)[0]!.id;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Non-null assertion on insert result could produce a cryptic crash.

[0]!.id on Line 85 will throw an unhelpful TypeError if .returning() yields an empty array (e.g., unexpected DB behavior). A guard or descriptive error would be safer.

🛡️ Proposed fix
-    const questionId =
-      existingQuestion?.id ??
-      (
-        await db
-          .insert(questions)
-          .values({
-            categoryId: category.id,
-            sortOrder,
-          })
-          .returning({ id: questions.id })
-      )[0]!.id;
+    let questionId = existingQuestion?.id;
+    if (!questionId) {
+      const [inserted] = await db
+        .insert(questions)
+        .values({
+          categoryId: category.id,
+          sortOrder,
+        })
+        .returning({ id: questions.id });
+      if (!inserted) {
+        throw new Error(
+          `Failed to insert question for category ${q.category}, sortOrder ${sortOrder}`
+        );
+      }
+      questionId = inserted.id;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const questionId =
existingQuestion?.id ??
(
await db
.insert(questions)
.values({
categoryId: category.id,
sortOrder,
})
.returning({ id: questions.id })
)[0]!.id;
let questionId = existingQuestion?.id;
if (!questionId) {
const [inserted] = await db
.insert(questions)
.values({
categoryId: category.id,
sortOrder,
})
.returning({ id: questions.id });
if (!inserted) {
throw new Error(
`Failed to insert question for category ${q.category}, sortOrder ${sortOrder}`
);
}
questionId = inserted.id;
}
🤖 Prompt for AI Agents
In `@frontend/db/seed-questions.ts` around lines 75 - 85, The non-null assertion
on the insert result (the [0]!.id used when computing questionId) can crash with
a cryptic TypeError if .returning() returns an empty array; update the logic
around existingQuestion and the db.insert(questions).values(...).returning(...)
call to check the returned rows array exists and has an element before accessing
.id, and if not, throw or log a clear, descriptive error (e.g., "Failed to
insert question for categoryId X: no rows returned") or handle the failure path
gracefully so questionId is never derived from an undefined value.

ViktorSvertoka added a commit that referenced this pull request Feb 14, 2026
* feat(mobile): improve dashboard, leaderboard & AI helper UX for touch devices

  - Add touch drag support for AI helper modal and explained terms reorder
  - Position explain button below selected word on mobile
  - Show delete/restore buttons always visible on mobile (no hover)
  - Add user avatar to dashboard profile card (same as leaderboard)
  - Fix leaderboard page layout
  - Fix Tailwind v4 canonical class warnings

* Added touchcancel listener

* (SP: 2) [Frontend] Quiz results dashboard, review cache fix, UX improvements (#317)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) (#318)

* (SP: 2) [Frontend] Redesign Home Hero & Add Features Section (#319)

* refactor(home): rename hero sections and add complete i18n support

- Rename LegacyHeroSection → WelcomeHeroSection
- Rename HeroSection → FeaturesHeroSection
- Add welcomeDescription translation key to eliminate duplication
- Translate all hardcoded text (headings, badges, CTAs)
- Improve Ukrainian/Polish translations for better readability
- Remove unused legacy components and images

* feat(about): update LinkedIn follower count to reflect current stat (1.5k+)

* refactor(home): implement i18n for FlipCardQA & fix memory leaks

* fix(home): resolve rotateY conflict & scope keyboard events in FlipCardQA

* fix(home): resolve all issues

* chore(home): cleanup comments, remove dead code & fix trailing spaces

* (SP: 2) [Frontend] Quiz UX improvements: violations counter, breadcrumbs, status badges (#320)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* Header UX polish, quiz highlight fix, Blog button styling, shop i18n product    descriptions (#322)

* Header UX: reorder languages, swap controls, fix quiz highlight, style Blog button

* shop i18n product descriptions

* (SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start (#324)

* fix(qa): align Next.js tab states and speed up loader startup

* feat(home,qa): improve home snap flow and add configurable Q&A page size

* fix(i18n,qa,seed): address review issues for locale handling and pagination state

* (SP: 1) [Frontend] Align quiz result messages with status badges, fix locale switch on result page (#325)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* fix(quiz): align result messages with status badges, persist result on locale switch

* chore(release): v1.0.0

---------

Co-authored-by: tetiana zorii <tanyusha.zoriy@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Co-authored-by: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com>
Co-authored-by: Yevhenii Datsenko <134847096+yevheniidatsenko@users.noreply.github.com>
Co-authored-by: Tetiana Zorii <131365289+TiZorii@users.noreply.github.com>
ViktorSvertoka added a commit that referenced this pull request Feb 14, 2026
* feat(mobile): improve dashboard, leaderboard & AI helper UX for touch devices

  - Add touch drag support for AI helper modal and explained terms reorder
  - Position explain button below selected word on mobile
  - Show delete/restore buttons always visible on mobile (no hover)
  - Add user avatar to dashboard profile card (same as leaderboard)
  - Fix leaderboard page layout
  - Fix Tailwind v4 canonical class warnings

* Added touchcancel listener

* (SP: 2) [Frontend] Quiz results dashboard, review cache fix, UX improvements (#317)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) (#318)

* (SP: 2) [Frontend] Redesign Home Hero & Add Features Section (#319)

* refactor(home): rename hero sections and add complete i18n support

- Rename LegacyHeroSection → WelcomeHeroSection
- Rename HeroSection → FeaturesHeroSection
- Add welcomeDescription translation key to eliminate duplication
- Translate all hardcoded text (headings, badges, CTAs)
- Improve Ukrainian/Polish translations for better readability
- Remove unused legacy components and images

* feat(about): update LinkedIn follower count to reflect current stat (1.5k+)

* refactor(home): implement i18n for FlipCardQA & fix memory leaks

* fix(home): resolve rotateY conflict & scope keyboard events in FlipCardQA

* fix(home): resolve all issues

* chore(home): cleanup comments, remove dead code & fix trailing spaces

* (SP: 2) [Frontend] Quiz UX improvements: violations counter, breadcrumbs, status badges (#320)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* Header UX polish, quiz highlight fix, Blog button styling, shop i18n product    descriptions (#322)

* Header UX: reorder languages, swap controls, fix quiz highlight, style Blog button

* shop i18n product descriptions

* (SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start (#324)

* fix(qa): align Next.js tab states and speed up loader startup

* feat(home,qa): improve home snap flow and add configurable Q&A page size

* fix(i18n,qa,seed): address review issues for locale handling and pagination state

* (SP: 1) [Frontend] Align quiz result messages with status badges, fix locale switch on result page (#325)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* fix(quiz): align result messages with status badges, persist result on locale switch

* chore(release): v1.0.0

* feat(jpg): add images for shop

---------

Co-authored-by: tetiana zorii <tanyusha.zoriy@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Co-authored-by: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com>
Co-authored-by: Yevhenii Datsenko <134847096+yevheniidatsenko@users.noreply.github.com>
Co-authored-by: Tetiana Zorii <131365289+TiZorii@users.noreply.github.com>
ViktorSvertoka added a commit that referenced this pull request Feb 15, 2026
* feat(mobile): improve dashboard, leaderboard & AI helper UX for touch devices

  - Add touch drag support for AI helper modal and explained terms reorder
  - Position explain button below selected word on mobile
  - Show delete/restore buttons always visible on mobile (no hover)
  - Add user avatar to dashboard profile card (same as leaderboard)
  - Fix leaderboard page layout
  - Fix Tailwind v4 canonical class warnings

* Added touchcancel listener

* (SP: 2) [Frontend] Quiz results dashboard, review cache fix, UX improvements (#317)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6) (#318)

* (SP: 2) [Frontend] Redesign Home Hero & Add Features Section (#319)

* refactor(home): rename hero sections and add complete i18n support

- Rename LegacyHeroSection → WelcomeHeroSection
- Rename HeroSection → FeaturesHeroSection
- Add welcomeDescription translation key to eliminate duplication
- Translate all hardcoded text (headings, badges, CTAs)
- Improve Ukrainian/Polish translations for better readability
- Remove unused legacy components and images

* feat(about): update LinkedIn follower count to reflect current stat (1.5k+)

* refactor(home): implement i18n for FlipCardQA & fix memory leaks

* fix(home): resolve rotateY conflict & scope keyboard events in FlipCardQA

* fix(home): resolve all issues

* chore(home): cleanup comments, remove dead code & fix trailing spaces

* (SP: 2) [Frontend] Quiz UX improvements: violations counter, breadcrumbs, status badges (#320)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* Header UX polish, quiz highlight fix, Blog button styling, shop i18n product    descriptions (#322)

* Header UX: reorder languages, swap controls, fix quiz highlight, style Blog button

* shop i18n product descriptions

* (SP: 1) [Frontend] Q&A: Next.js tab states + faster loader start (#324)

* fix(qa): align Next.js tab states and speed up loader startup

* feat(home,qa): improve home snap flow and add configurable Q&A page size

* fix(i18n,qa,seed): address review issues for locale handling and pagination state

* (SP: 1) [Frontend] Align quiz result messages with status badges, fix locale switch on result page (#325)

* feat(quiz): add guest warning before start and bot protection

Guest warning: show login/signup/continue buttons for unauthenticated
users on quiz rules screen before starting.

Bot protection: multi-attempt verification via Redis - each question
can only be verified once per user per attempt. Keys use dynamic TTL
matching quiz time limit and are cleared on retake.

Additional fixes:
- Footer flash on quiz navigation (added loading.tsx, eliminated redirect)
- Renamed QaLoader to Loader for reuse across pages
- React compiler purity errors (crypto.getRandomValues in handlers)
- Start button disabled after retake (isStarting not reset)

* refactor(quiz): PR review feedback

- Extract shared resolveRequestIdentifier() helper to eliminate
  duplicated auth/IP resolution logic in route.ts and actions/quiz.ts
- Return null instead of 'unknown' when identifier unresolvable,
  skip verification tracking for unidentifiable users
- Cap Redis TTL with MAX_TTL (3600s) to prevent client-supplied
  timeLimitSeconds from persisting keys indefinitely
- Add locale prefix to returnTo paths in guest warning links
- Replace nested Button inside Link with styled Link to fix
  invalid HTML (interactive element nesting)

* fix(quiz): fall through to IP when auth cookie is expired/invalid

* feat(quiz): add quiz results dashboard and review page

- Add quiz history section to dashboard with last attempt per quiz
- Add review page showing incorrect questions with explanations
- Add collapsible cards with expand/collapse all toggle
- Add "Review Mistakes" button on quiz result screen
- Add category icons to quiz page and review page headers
- Add BookOpen icon to explanation block in QuizQuestion
- Update guest message to mention error review benefit
- Add i18n translations (en/uk/pl) for all new features

* fix(quiz): scroll to next button on answer reveal, scope review cache by userId

* fix(quiz): restore type imports and userId cache key after merge conflict

* fix: restore type imports, sync @swc/helpers, fix indentation after merge

* feat(quiz): add violations counter UI, fix disqualification threshold

- Add ViolationsCounter component with color escalation (green/yellow/red)
- Sticky top bar keeps counter visible on scroll (mobile/tablet)
- Add i18n counter keys for en/uk/pl with ICU plural forms
- Fix threshold bug: violations warning now triggers at 4+ (was 3+)
  to match actual integrity score calculation (100 - violations * 10 < 70)

* fix(quiz): fix points mismatch between leaderboard and dashboard

Dashboard showed raw pointsEarned from last quiz_attempt, while
leaderboard summed improvement deltas from point_transactions.
Additionally, orphaned transactions from re-seeded quizzes inflated
leaderboard totals (12 rows, 83 ghost points cleaned up in DB).

- Dashboard query now joins point_transactions to show actual awarded
  points per quiz instead of raw attempt score
- Leaderboard query filters out orphaned transactions where the
  source attempt no longer exists in quiz_attempts

* OBfix(quiz): fix points mismatch, consistent status badges, mobile UX

Dashboard showed raw pointsEarned from last attempt while leaderboard
summed improvement deltas from point_transactions. Orphaned transactions
from re-seeded quizzes inflated leaderboard totals (cleaned up in DB).

- Dashboard query joins point_transactions for actual awarded points
- Leaderboard query filters orphaned transactions (source_id not in quiz_attempts)
- Quiz cards use 3-level badges (Mastered/Review/Study) matching dashboard
- Mobile quiz results show dash for zero points, added chevron indicator

* fix(quiz): add breadcrumbs to review page, fix recommendation tautology

* fix(quiz): align result messages with status badges, persist result on locale switch

* chore(release): v1.0.0

* feat(jpg): add images for shop

* (SP: 3) [Shop][Monobank] Janitor map + internal janitor endpoint stub + status UX + security/obs + J test gate (#328)

* (SP: 3) [Backend] add internal janitor (jobs 1-4), claim/lease + runbook (G0-G6)

* (SP: 3) [Backend] add provider selector, fix payments gating, i18n checkout errors

* Add shop category images to public

* (SP: 3) [Shop][Monobank] I1 structured logging: codes + logging safety checks

* (SP: 3) [Shop][Monobank] Fail-closed non-browser origin posture for webhook + janitor (ORIGIN_BLOCKED)

* (SP: 3) [Shop][Monobank] [Shop][Monobank] J gate: add orders status ownership test and pass all pre-prod invariants

* (SP: 3) [Shop][Monobank]  review fixes (tests, logging, success UI)

* (SP: 1) [Shop][Monobank] Tighten webhook log-code typing; harden DB tests; minor security/log/UI cleanups

* (SP: 1) [Shop][Monobank] harden Monobank webhook (origin/PII-safe logs) and remove duplicate sha256 hashing

* (SP:2) [Frontend] Fix duplicated Q&A items after content updates (#330)

* fix(qa): prevent duplicate questions and improve cache invalidation

* fix(qa): keep pagination totals consistent after deduplication

* (SP: 1) [Frontend] Integrate online users counter popup and fix header (#331)

* feat(home): add online users counter + fix header breakpoint

* deleted scrollY in OnlineCounterPopup

* fixed fetch in OnlineCounterPopup

* Bug/fix qa (#332)

* fix(qa): prevent duplicate questions and improve cache invalidation

* fix(qa): keep pagination totals consistent after deduplication

* fix(qa): paginate by unique questions and bump cache namespace

* chore(release): v1.0.1

---------

Co-authored-by: tetiana zorii <tanyusha.zoriy@gmail.com>
Co-authored-by: Lesia Soloviova <106915140+LesiaUKR@users.noreply.github.com>
Co-authored-by: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com>
Co-authored-by: Yevhenii Datsenko <134847096+yevheniidatsenko@users.noreply.github.com>
Co-authored-by: Tetiana Zorii <131365289+TiZorii@users.noreply.github.com>
Co-authored-by: Yuliia Nazymko <122815071+YNazymko12@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working performance Performance and efficiency optimizations without functional changes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant