Skip to content

Sl/feat/quiz#70

Merged
ViktorSvertoka merged 9 commits into
developfrom
sl/feat/quiz
Dec 21, 2025
Merged

Sl/feat/quiz#70
ViktorSvertoka merged 9 commits into
developfrom
sl/feat/quiz

Conversation

@LesiaUKR
Copy link
Copy Markdown
Collaborator

@LesiaUKR LesiaUKR commented Dec 21, 2025

Quiz flow (guest -> auth)

  • Guest quiz results are temporarily stored in localStorage
  • Added POST /api/quiz/guest-result to persist pending guest results in DB
  • After login/signup, pending results are automatically saved once userId is available
  • Persistence logic is scoped to the Quiz page via PendingResultHandler
  • Guest data is cleared only after successful save
  • Added one-time success feedback via QuizSavedBanner (reads from sessionStorage) showing recently saved quiz results and quick actions
  • Supports JavaScript quizzes (10 added) and React (40 added)
  • Added All Quizzes page
  • Middleware (i18n + auth)

Improved i18n + auth middleware behavior

  • Correct handling of locale-aware routes for guest and authenticated users
  • Cleaner separation of access rules without breaking localized routing

Database & schema changes

  • Unified id format across all tables to match quiz tables
  • Migrated foreign keys (including categoryId) from integer - uuid
  • Standardized primary key pattern: uuid('id').defaultRandom().primaryKey()
  • Database now generates UUIDs via gen_random_uuid()

Summary by CodeRabbit

  • New Features

    • Guest quiz submission—users can attempt quizzes without logging in and save results upon signup/login.
    • Quiz listing page displaying all available quizzes with metadata and quick-start options.
    • Quiz completion confirmation banner showing score, points earned, and action links.
  • Improvements

    • Enhanced login/signup flows with better return-to navigation.
    • Refined quiz progress indicator for improved visibility.
    • Leaderboard and quiz pages now accessible to guests.
    • Locale-aware routing across authentication flows.
  • Chores

    • Cache revalidation after authentication changes.
    • New quiz content added (JavaScript and React courses).

✏️ Tip: You can customize this high-level summary in your review settings.

  - POST /api/quiz/guest-result saves pending results to DB
  - Login/signup auto-save pending quiz from localStorage
  - Middleware improvements for i18n + auth
  - Component updates for guest flow
  - Unify id format across all tables (consistent with quiz tables)
  - Use uuid('id').defaultRandom().primaryKey() pattern
  - Change categoryId foreign key from integer to uuid
  - DB will generate UUIDs via gen_random_uuid()
- eliminate variable shadowing
- add fetch error handling
- fix ESM __dirname usage in seed scripts
- normalize quiz difficulty values
- minor formatting and redirect logic fixes
@netlify
Copy link
Copy Markdown

netlify Bot commented Dec 21, 2025

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 93fbd53
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/69474bd24af5e300088471e2
😎 Deploy Preview https://deploy-preview-70--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 Dec 21, 2025

Walkthrough

This PR introduces guest quiz functionality with locale-aware navigation throughout the application. It adds quiz result persistence for guests, a new quizzes listing page, updates authentication flows with proper locale context, migrates database schema from serial to UUID primary keys, and includes comprehensive JavaScript and React quiz data with seed scripts.

Changes

Cohort / File(s) Summary
Configuration & Ignore Rules
frontend/.claude/settings.local.json, frontend/.gitignore
Removed Claude settings file; updated ignore patterns to skip .claude files while preserving docs/ ignore.
Auth & Logout Flows
frontend/app/api/auth/login/route.ts, frontend/app/api/auth/logout/route.ts, frontend/app/api/auth/signup/route.ts, frontend/components/auth/logoutButton.tsx, frontend/lib/logout.ts
Added cache revalidation on auth state changes; extended login/signup responses with userId; introduced locale-aware logout redirect; removed client-side redirect post-logout.
Login & Signup Pages
frontend/app/[locale]/login/page.tsx, frontend/app/[locale]/signup/page.tsx
Integrated locale and returnTo URL parameters; added guest quiz result submission on login/signup success with sessionStorage persistence; added error handling around quiz save operations; replaced anchor tags with locale-aware Link components.
Dashboard & Layout
frontend/app/[locale]/dashboard/page.tsx, frontend/app/[locale]/layout.tsx
Dashboard now accepts locale params and redirects with locale context; added QuizSavedBanner component; updated navigation links (quiz → quizzes); added Dashboard link alongside logout in authenticated header.
Quizzes Pages
frontend/app/[locale]/quiz/[slug]/page.tsx, frontend/app/[locale]/quizzes/page.tsx
Removed auth redirect for quiz page; added PendingResultHandler for guest result persistence; new quizzes listing page displaying active quizzes with details.
Quiz Components
frontend/components/quiz/QuizContainer.tsx, frontend/components/quiz/QuizResult.tsx, frontend/components/quiz/QuizProgress.tsx
QuizContainer accepts quizSlug and handles guest vs logged-in submission flows; QuizResult conditionally renders login/signup prompts for guests; QuizProgress compresses display with ellipsis for large question counts.
Guest Quiz Utilities
frontend/components/quiz/PendingResultHandler.tsx, frontend/components/dashboard/QuizSavedBanner.tsx, frontend/lib/guest-quiz.ts
New PendingResultHandler component syncs pending guest results to API; QuizSavedBanner displays post-quiz success with score and points; guest-quiz utility manages localStorage persistence with 24-hour expiry.
Guest Quiz API
frontend/app/api/quiz/guest-result/route.ts
New POST endpoint validates and persists guest quiz submissions, computes score metrics, calculates integrity score from violations, awards points, and returns structured response.
Database Schema & Queries
frontend/db/schema/categories.ts, frontend/db/queries/quiz.ts
Migrated categories and questions IDs from serial to uuid with random defaults; added getActiveQuizzes query to fetch active quizzes for a given locale.
Quiz Seed Scripts & Types
frontend/db/seed-quiz-types.ts, frontend/db/seed-quiz-from-json.ts, frontend/db/seed-quiz-javascript.ts
New type definitions for locale and seed data structures with helper builders; seed scripts load quiz metadata and questions from JSON, handle multi-locale content, and persist to database with error handling.
Quiz Data Files
frontend/parse/javascript-quiz-part*.json, frontend/parse/react-quiz-data-part*.json
Added 40 JavaScript quiz questions (4 parts) and 40 React quiz questions (2 parts) in JSON format with multilingual prompts (uk, en, pl), explanations, and answer options.
Minor Updates
frontend/actions/quiz.ts, frontend/proxy.ts
Added trailing newline to quiz actions; simplified middleware to redirect to login only for /dashboard when unauthenticated, not for other quiz paths.

Sequence Diagram

sequenceDiagram
    participant Guest as Guest User
    participant QuizUI as Quiz Page
    participant Browser as Browser Storage
    participant API as /api/quiz/guest-result
    participant DB as Database
    participant Dashboard as Dashboard Page

    Guest->>QuizUI: Completes quiz
    QuizUI->>QuizUI: Compute score, violations, time spent
    QuizUI->>Browser: savePendingQuizResult() to localStorage
    QuizUI->>Guest: Redirect to login/signup

    Guest->>QuizUI: Authenticates (login/signup page)
    QuizUI->>Browser: Retrieve pending quiz result
    QuizUI->>API: POST /api/quiz/guest-result with userId & quiz data
    API->>DB: Insert quizAttempts & quizAttemptAnswers
    API->>DB: Award points via awardQuizPoints()
    API-->>QuizUI: Return { success, attemptId, score, pointsAwarded }
    QuizUI->>Browser: Store quiz_just_saved to sessionStorage
    QuizUI->>Browser: Clear pending quiz data
    QuizUI->>Dashboard: Redirect to dashboard with locale context

    Dashboard->>Dashboard: Check sessionStorage for quiz_just_saved
    Dashboard->>Guest: Render QuizSavedBanner with score & points earned
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • Guest quiz result submission flow (/api/quiz/guest-result/route.ts) — validates input, computes multiple score metrics, persists related records, and awards points
  • Authentication flow changes in login/signup pages — complex interaction between guest result persistence, sessionStorage, and locale-aware redirects
  • Database schema migration from serial to UUID IDs (frontend/db/schema/categories.ts) — ensure foreign key relationships and existing data compatibility
  • PendingResultHandler component — side-effect timing and error handling on mount/userId changes
  • QuizContainer guest vs logged-in branching logic — ensure both submission paths are properly tested

Possibly related PRs

Suggested reviewers

  • AM1007
  • ViktorSvertoka

Poem

🐰 A guest arrives, completes the quiz quest,
Scores saved in local storage, put to the test,
Locale-aware paths guide them to sign,
Results persist and points align,
Leaderboards await—no data lost, it's fine! 🏆

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Sl/feat/quiz' is vague and generic, using a branch-like prefix format that does not convey meaningful information about the changeset. Replace with a clear, descriptive title summarizing the main change, such as 'Add guest quiz flow and All Quizzes page with locale-aware routing' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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 sl/feat/quiz

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: 13

🧹 Nitpick comments (12)
frontend/.gitignore (1)

48-49: Consider using .claude/ instead of .claude for clarity.

The new ignore rules for docs/ and .claude are appropriate for keeping local development artifacts and Claude-related files out of version control. However, if .claude is specifically a directory (as suggested by the PR context mentioning .claude/settings.local.json), using .claude/ would be more explicit and conventional.

🔎 Proposed change for clarity
 docs/
-.claude
+.claude/
frontend/app/[locale]/layout.tsx (1)

109-112: LGTM: Dashboard navigation added for authenticated users.

The addition of a Dashboard link alongside the LogoutButton improves authenticated user navigation. The fragment wrapper is appropriate for multiple children, and the styling is consistent with other navigation links.

Optional: Consider adding active route highlighting

For improved UX, you could add active state styling to highlight the current route:

<Link 
  href="/dashboard" 
  className={`hover:text-blue-600 dark:hover:text-blue-400 transition ${
    pathname === '/dashboard' ? 'text-blue-600 dark:text-blue-400 font-semibold' : ''
  }`}
>
  Dashboard
</Link>

This would require importing usePathname from next/navigation and marking the header as a client component, or creating a separate client component for the navigation.

frontend/db/queries/quiz.ts (1)

93-117: LGTM with optional improvements for scalability.

The getActiveQuizzes function follows the same pattern as getQuizBySlug, maintaining consistency in locale handling and query structure.

Consider these enhancements for better UX and scalability:

Optional improvements
  1. Add ordering to ensure consistent quiz display:
+import { eq, and, desc, sql, asc } from 'drizzle-orm';

 export async function getActiveQuizzes(
   locale: string = 'uk'
 ): Promise<Quiz[]> {
   const rows = await db
     .select({
       id: quizzes.id,
       slug: quizzes.slug,
       questionsCount: quizzes.questionsCount,
       timeLimitSeconds: quizzes.timeLimitSeconds,
       isActive: quizzes.isActive,
       title: quizTranslations.title,
       description: quizTranslations.description,
     })
     .from(quizzes)
     .leftJoin(
       quizTranslations,
       and(
         eq(quizTranslations.quizId, quizzes.id),
         eq(quizTranslations.locale, locale)
       )
     )
-    .where(eq(quizzes.isActive, true));
+    .where(eq(quizzes.isActive, true))
+    .orderBy(asc(quizzes.displayOrder), asc(quizTranslations.title));

   return rows;
 }
  1. Consider pagination if the number of quizzes grows significantly (10 JavaScript + 40 React quizzes = 50 total mentioned in PR):
export async function getActiveQuizzes(
  locale: string = 'uk',
  limit?: number,
  offset?: number
): Promise<Quiz[]> {
  let query = db.select(/* ... */)
    .from(quizzes)
    .leftJoin(/* ... */)
    .where(eq(quizzes.isActive, true))
    .orderBy(asc(quizzes.displayOrder));
    
  if (limit !== undefined) {
    query = query.limit(limit);
  }
  if (offset !== undefined) {
    query = query.offset(offset);
  }
  
  return query;
}
frontend/db/seed-quiz-from-json.ts (2)

18-19: Consider importing Locale type from shared module.

The Locale type is already defined in frontend/db/seed-quiz-types.ts. Importing it would reduce duplication and ensure consistency if the supported locales change.

🔎 Proposed fix
+import { Locale } from './seed-quiz-types';
+
-type Locale = 'uk' | 'en' | 'pl';
 const LOCALES: Locale[] = ['uk', 'en', 'pl'];

65-66: Inconsistent indentation in try block.

The code inside the try block (lines 66-165) has inconsistent indentation compared to the surrounding code. This appears to be a formatting issue.

frontend/components/quiz/QuizResult.tsx (2)

103-109: Consider URL-encoding quizSlug in redirect URLs.

If quizSlug contains special characters (spaces, slashes, etc.), the URL could become malformed. While quiz slugs are typically URL-safe, defensive encoding prevents edge-case issues.

🔎 Proposed fix
   <Button
-    onClick={() => window.location.href = `/${locale}/login?returnTo=/quiz/${quizSlug}`}
+    onClick={() => window.location.href = `/${locale}/login?returnTo=${encodeURIComponent(`/quiz/${quizSlug}`)}`}
     variant="primary"
   >
     Увійти
   </Button>
   <Button
-    onClick={() => window.location.href = `/${locale}/signup?returnTo=/quiz/${quizSlug}`}
+    onClick={() => window.location.href = `/${locale}/signup?returnTo=${encodeURIComponent(`/quiz/${quizSlug}`)}`}
     variant="secondary"
   >

94-125: Inconsistent indentation in conditional rendering block.

The JSX inside the isGuest conditional has inconsistent indentation that reduces readability. Consider formatting for consistency.

frontend/db/seed-quiz-javascript.ts (1)

22-23: Consider importing Locale type from shared module.

Same suggestion as for seed-quiz-from-json.ts - the Locale type exists in frontend/db/seed-quiz-types.ts.

frontend/app/api/quiz/guest-result/route.ts (1)

30-30: Type mismatch: toFixed() returns a string.

toFixed(2) returns a string, which is then stored in the DB (line 46) and later parsed back to a number (line 78). Consider using parseFloat immediately or Number.toFixed for consistency:

Suggested fix
-    const percentage = ((correctAnswersCount / totalQuestions) * 100).toFixed(2);
+    const percentage = parseFloat(((correctAnswersCount / totalQuestions) * 100).toFixed(2));

Then on line 78:

-      percentage: parseFloat(percentage),
+      percentage,
frontend/components/quiz/QuizContainer.tsx (3)

156-181: Inconsistent indentation in handleSubmit.

The code inside handleSubmit has inconsistent indentation - some lines start at column 2, others at column 6. This affects readability.

Suggested fix
   const handleSubmit = () => {
-      const correctAnswers = state.answers.filter(a => a.isCorrect).length;
-  const percentage = (correctAnswers / totalQuestions) * 100;
-  const timeSpentSeconds = state.startedAt
-    ? Math.floor((Date.now() - state.startedAt.getTime()) / 1000)
-    : 0;
-
-  if (isGuest) {
-    savePendingQuizResult({
+    const correctAnswers = state.answers.filter(a => a.isCorrect).length;
+    const percentage = (correctAnswers / totalQuestions) * 100;
+    const timeSpentSeconds = state.startedAt
+      ? Math.floor((Date.now() - state.startedAt.getTime()) / 1000)
+      : 0;
+
+    if (isGuest) {
+      savePendingQuizResult({
         quizId,
         quizSlug,
         // ... rest of the object
-    });
-    dispatch({ type: 'COMPLETE_QUIZ' });
-    return;
-  }
+      });
+      dispatch({ type: 'COMPLETE_QUIZ' });
+      return;
+    }
     startTransition(async () => {

120-122: Consider grouping hooks together.

useLocale() on line 120 is separated from other hooks (lines 111-119). Consider moving it to be adjacent to other hook calls for better organization.


40-41: Remove extra blank lines.

Two consecutive blank lines here appear to be formatting noise.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d80f95 and 93fbd53.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (33)
  • frontend/.claude/settings.local.json (0 hunks)
  • frontend/.gitignore (1 hunks)
  • frontend/actions/quiz.ts (1 hunks)
  • frontend/app/[locale]/dashboard/page.tsx (3 hunks)
  • frontend/app/[locale]/layout.tsx (2 hunks)
  • frontend/app/[locale]/login/page.tsx (3 hunks)
  • frontend/app/[locale]/quiz/[slug]/page.tsx (2 hunks)
  • frontend/app/[locale]/quizzes/page.tsx (1 hunks)
  • frontend/app/[locale]/signup/page.tsx (2 hunks)
  • frontend/app/api/auth/login/route.ts (2 hunks)
  • frontend/app/api/auth/logout/route.ts (1 hunks)
  • frontend/app/api/auth/signup/route.ts (2 hunks)
  • frontend/app/api/quiz/guest-result/route.ts (1 hunks)
  • frontend/components/auth/logoutButton.tsx (1 hunks)
  • frontend/components/dashboard/QuizSavedBanner.tsx (1 hunks)
  • frontend/components/quiz/PendingResultHandler.tsx (1 hunks)
  • frontend/components/quiz/QuizContainer.tsx (7 hunks)
  • frontend/components/quiz/QuizProgress.tsx (2 hunks)
  • frontend/components/quiz/QuizResult.tsx (4 hunks)
  • frontend/db/queries/quiz.ts (1 hunks)
  • frontend/db/schema/categories.ts (1 hunks)
  • frontend/db/seed-quiz-from-json.ts (1 hunks)
  • frontend/db/seed-quiz-javascript.ts (1 hunks)
  • frontend/db/seed-quiz-types.ts (1 hunks)
  • frontend/lib/guest-quiz.ts (1 hunks)
  • frontend/lib/logout.ts (1 hunks)
  • frontend/parse/javascript-quiz-part1.json (1 hunks)
  • frontend/parse/javascript-quiz-part2.json (1 hunks)
  • frontend/parse/javascript-quiz-part3.json (1 hunks)
  • frontend/parse/javascript-quiz-part4.json (1 hunks)
  • frontend/parse/react-quiz-data-part1.json (1 hunks)
  • frontend/parse/react-quiz-data-part2.json (1 hunks)
  • frontend/proxy.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • frontend/.claude/settings.local.json
🧰 Additional context used
🧬 Code graph analysis (20)
frontend/app/api/auth/logout/route.ts (3)
frontend/app/api/auth/login/route.ts (1)
  • POST (18-62)
frontend/app/api/auth/signup/route.ts (1)
  • POST (19-71)
frontend/lib/auth.ts (1)
  • clearAuthCookie (88-91)
frontend/parse/javascript-quiz-part2.json (4)
frontend/db/schema/quiz.ts (3)
  • quiz (197-202)
  • translations (189-193)
  • question (228-235)
frontend/db/seed-quiz-react.ts (3)
  • seedReactQuiz (880-965)
  • QuizQuestionSeed (30-46)
  • cleanupReactQuiz (967-978)
frontend/db/verify-quiz-seed.ts (1)
  • c (84-86)
frontend/data/parse.ts (1)
  • run (627-668)
frontend/parse/react-quiz-data-part2.json (1)
frontend/db/seed-quiz-react.ts (2)
  • seedReactQuiz (880-965)
  • QuizQuestionSeed (30-46)
frontend/app/[locale]/dashboard/page.tsx (4)
frontend/lib/auth.ts (1)
  • getCurrentUser (94-115)
frontend/drizzle/schema.ts (1)
  • user (119-130)
frontend/db/queries/users.ts (1)
  • getUserProfile (7-28)
frontend/components/dashboard/QuizSavedBanner.tsx (1)
  • QuizSavedBanner (14-59)
frontend/components/quiz/QuizProgress.tsx (2)
frontend/lib/utils.ts (1)
  • cn (4-6)
frontend/components/quiz/QuizQuestion.tsx (2)
  • QuizQuestion (18-122)
  • QuizQuestionProps (9-16)
frontend/parse/javascript-quiz-part3.json (3)
frontend/db/schema/quiz.ts (3)
  • quiz (197-202)
  • question (228-235)
  • quiz (207-215)
frontend/db/seed-quiz-react.ts (1)
  • QuizQuestionSeed (30-46)
frontend/db/verify-quiz-seed.ts (1)
  • q (165-176)
frontend/components/quiz/PendingResultHandler.tsx (1)
frontend/lib/guest-quiz.ts (2)
  • getPendingQuizResult (30-54)
  • clearPendingQuizResult (56-59)
frontend/app/[locale]/signup/page.tsx (1)
frontend/lib/guest-quiz.ts (2)
  • getPendingQuizResult (30-54)
  • clearPendingQuizResult (56-59)
frontend/db/seed-quiz-from-json.ts (3)
frontend/db/seed-quiz-types.ts (1)
  • Locale (4-4)
frontend/db/index.ts (1)
  • db (17-17)
frontend/db/schema/quiz.ts (2)
  • quiz (197-202)
  • quiz (207-215)
frontend/app/[locale]/quizzes/page.tsx (2)
frontend/app/[locale]/layout.tsx (1)
  • dynamic (15-15)
frontend/db/queries/quiz.ts (1)
  • getActiveQuizzes (93-117)
frontend/db/schema/categories.ts (1)
frontend/drizzle/relations.ts (1)
  • category (4-9)
frontend/app/api/quiz/guest-result/route.ts (3)
frontend/db/index.ts (1)
  • db (17-17)
frontend/db/queries/points.ts (1)
  • awardQuizPoints (49-94)
frontend/db/schema/quiz.ts (1)
  • attempt (265-278)
frontend/db/seed-quiz-javascript.ts (4)
frontend/db/seed-quiz-types.ts (1)
  • Locale (4-4)
frontend/db/index.ts (1)
  • db (17-17)
frontend/db/seed-quiz-react.ts (1)
  • QuizQuestionSeed (30-46)
frontend/db/schema/quiz.ts (2)
  • quiz (197-202)
  • quiz (207-215)
frontend/db/queries/quiz.ts (3)
frontend/db/index.ts (1)
  • db (17-17)
frontend/db/schema/quiz.ts (3)
  • quizzes (18-38)
  • quizTranslations (40-53)
  • quiz (197-202)
frontend/db/verify-quiz-seed.ts (1)
  • getQuizForLocale (125-182)
frontend/app/[locale]/login/page.tsx (1)
frontend/lib/guest-quiz.ts (2)
  • getPendingQuizResult (30-54)
  • clearPendingQuizResult (56-59)
frontend/app/api/auth/signup/route.ts (1)
frontend/drizzle/schema.ts (1)
  • user (119-130)
frontend/components/auth/logoutButton.tsx (2)
frontend/lib/logout.ts (1)
  • logout (3-9)
frontend/components/ui/button.tsx (1)
  • Button (42-42)
frontend/app/[locale]/layout.tsx (1)
frontend/components/auth/logoutButton.tsx (1)
  • LogoutButton (7-18)
frontend/components/quiz/QuizContainer.tsx (1)
frontend/lib/guest-quiz.ts (1)
  • savePendingQuizResult (20-28)
frontend/app/[locale]/quiz/[slug]/page.tsx (2)
frontend/drizzle/schema.ts (1)
  • user (119-130)
frontend/components/quiz/PendingResultHandler.tsx (1)
  • PendingResultHandler (10-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Redirect rules - develop-devlovers
  • GitHub Check: Header rules - develop-devlovers
  • GitHub Check: Pages changed - develop-devlovers
🔇 Additional comments (37)
frontend/.gitignore (1)

47-49: Summary inconsistency: CLAUDE.md appears unchanged despite claim of removal.

The AI summary states "Remove ignore for CLAUDE.md," but line 47 shows CLAUDE.md without a change marker (~), indicating it was not modified in this PR. Either the summary is inaccurate or there's a mismatch in what was expected to change.

frontend/db/schema/categories.ts (2)

1-2: LGTM! Imports correctly updated for UUID migration.

The imports properly reflect the schema migration from serial integers to UUIDs, and the relations import supports the new relation definitions.


4-7: UUID migration is complete and properly implemented—no further action needed.

All consuming code correctly imports from @/db/schema where the UUID schema is defined. Seed scripts properly generate UUIDs via defaultRandom(), and migration files exist in the drizzle/ directory. The schema change is consistent across the codebase.

frontend/actions/quiz.ts (1)

172-172: LGTM!

The trailing newline is a good practice and commonly required by linters.

frontend/components/auth/logoutButton.tsx (1)

3-12: LGTM!

The locale-aware logout flow correctly aligns with the PR's locale-aware routing improvements. The redirect to /${locale}/login ensures users land on the localized login page after logout.

frontend/parse/react-quiz-data-part1.json (1)

1-254: LGTM! Comprehensive React fundamentals quiz data.

The quiz metadata and first 20 questions (react-q01 to react-q20) are well-structured with proper multilingual support. The questionsCount of 40 aligns with the expectation that part2 will provide the remaining questions.

frontend/parse/javascript-quiz-part4.json (1)

1-434: LGTM! Well-structured advanced JavaScript quiz data.

The questions (js-q31 to js-q40) covering ES6+ features and prototypes are well-organized with proper multilingual support and appropriate difficulty levels.

frontend/components/quiz/QuizProgress.tsx (2)

16-46: LGTM! Well-implemented progressive disclosure.

The getVisibleIndices helper correctly implements a collapsible progress indicator that shows the first, last, and current question with neighbors, using ellipsis for gaps. This improves UX for longer quizzes.


59-107: LGTM! Clean rendering implementation.

The updated rendering logic correctly handles both ellipsis placeholders and actual progress items. The reduced size (w-9 h-9) and streamlined styling improve the visual presentation.

frontend/app/api/auth/logout/route.ts (1)

2-7: LGTM! Proper cache invalidation after logout.

The revalidatePath call for the locale layout ensures the UI reflects the logout state consistently across locale-aware routes. This aligns well with similar patterns in login and signup routes.

frontend/parse/javascript-quiz-part3.json (1)

1-434: LGTM! Well-structured quiz data with proper multilingual support.

The JavaScript quiz questions (js-q21 to js-q30) are well-organized with consistent three-language translations (Ukrainian, English, Polish), appropriate difficulty levels (medium and advanced), and proper answer structures. No duplicate question IDs detected across all four JavaScript quiz parts.

frontend/app/api/auth/login/route.ts (1)

2-2: LGTM: Locale-aware cache invalidation pattern.

The addition of revalidatePath('/[locale]', 'layout') ensures that locale-specific layout caching is properly invalidated after successful login. The pattern is consistent with the signup and logout routes mentioned in the AI summary, providing a unified cache management strategy across authentication flows.

The inclusion of userId in the response aligns with the guest quiz persistence flow introduced in this PR, enabling client-side components to transition pending guest results to authenticated user records.

Also applies to: 60-61

frontend/app/[locale]/layout.tsx (1)

65-65: LGTM: Updated navigation to quizzes listing page.

The Quiz link now correctly points to /quizzes instead of a specific quiz, aligning with the new All Quizzes page introduced in this PR.

frontend/app/api/auth/signup/route.ts (1)

2-2: LGTM: Consistent cache invalidation and response pattern.

The changes mirror the login route implementation, maintaining consistency across authentication flows:

  • revalidatePath('/[locale]', 'layout') ensures locale-specific layout cache is invalidated after signup
  • userId in the response enables the guest quiz persistence flow

The pattern is appropriate and aligns with Next.js 15 cache management practices.

Also applies to: 69-70

frontend/proxy.ts (1)

34-37: LGTM: Appropriate access control adjustment for guest quiz flow.

The middleware now only protects /dashboard, allowing unauthenticated access to quizzes and leaderboard. This aligns with the guest quiz functionality introduced in this PR. The quiz page properly handles guest users by passing a null userId to QuizContainer, and the leaderboard page is publicly accessible to all users.

frontend/app/[locale]/quiz/[slug]/page.tsx (1)

4-4: LGTM: Guest quiz flow integration looks solid.

The changes appropriately support both guest and authenticated quiz flows:

  • quizSlug prop enables locale-aware navigation in guest flows
  • userId accepts null for guest users
  • PendingResultHandler is conditionally rendered only when a user is authenticated, ensuring pending guest results are persisted after login/signup
frontend/lib/logout.ts (1)

3-9: Verify that all logout call sites handle navigation.

The removal of the client-side redirect from the logout() function is appropriate. The LogoutButton component is the only location calling logout() directly, and it correctly handles locale-aware navigation via window.location.href = /${locale}/login``. No other call sites exist that require additional verification.

frontend/parse/javascript-quiz-part1.json (1)

1-434: LGTM!

The quiz data file is well-structured with consistent multilingual content (uk, en, pl), proper difficulty classifications, and factually accurate JavaScript fundamentals questions. Each question follows a consistent schema with exactly one correct answer.

frontend/parse/javascript-quiz-part2.json (1)

1-434: LGTM!

The quiz data continues the consistent structure from part1, covering intermediate JavaScript topics (closures, hoisting, arrow functions, event propagation). Content is technically accurate and translations are consistent across all three locales.

frontend/db/seed-quiz-javascript.ts (1)

95-96: Potential orphaned records when deleting quiz questions.

Same concern as in seed-quiz-from-json.ts: deleting quizQuestions and quizTranslations may leave orphaned records in child tables (quizQuestionContent, quizAnswers, quizAnswerTranslations) unless cascading deletes are configured.

frontend/app/[locale]/login/page.tsx (2)

40-78: LGTM - Well-implemented guest quiz result flow.

The implementation correctly handles the guest-to-authenticated flow:

  • Checks for pending quiz result after successful login
  • Posts result to the API with proper error handling
  • Stores result in sessionStorage for the banner
  • Cleans up localStorage in finally block to ensure no stale data
  • Appropriate fallback to returnTo or dashboard

One note: After saving a pending quiz result, the user is redirected to the dashboard (line 75) rather than returnTo. This appears intentional since the quiz was already completed and the user should see their saved result on the dashboard.


111-113: Good practice: preserving returnTo parameter in signup link.

Properly encoding the returnTo parameter ensures the signup flow can redirect back to the intended page after registration.

frontend/db/seed-quiz-from-json.ts (1)

80-81: FK constraints already configured with ON DELETE CASCADE — no data integrity risk.

The schema shows all related tables have proper cascading delete configuration: quizAnswers (→ quizQuestions), quizQuestionContent (→ quizQuestions), and quizAnswerTranslations (→ quizAnswers) all use .onDelete("cascade"). Deleting quizQuestions will automatically cascade to dependent records in the database.

frontend/app/api/quiz/guest-result/route.ts (2)

18-25: Consider validating that the userId belongs to the authenticated session.

The userId is accepted directly from the request body without server-side verification. While the current flow passes the userId from a trusted signup/login response, an attacker could POST arbitrary results for any user.

Consider either:

  1. Extracting the userId from the session/auth token instead of the request body
  2. Adding rate limiting to mitigate abuse

1-6: LGTM!

Imports are appropriate and runtime = "nodejs" is correctly set for database operations.

frontend/app/[locale]/signup/page.tsx (2)

79-83: Navigation flow looks intentional.

After a successful quiz save, the user is directed to the dashboard to see the QuizSavedBanner. The returnTo parameter is only used when there's no pending quiz. This aligns with the intended UX.


134-139: LGTM!

The login link correctly uses the locale-aware Link component and properly encodes the returnTo parameter.

frontend/app/[locale]/dashboard/page.tsx (3)

16-22: LGTM!

The async params pattern follows Next.js 15 conventions. The return statements after redirect() are defensive and ensure TypeScript understands the control flow terminates.


43-49: LGTM!

Explicit field mapping with null-coalescing provides type safety and clear data shaping for the ProfileCard component.


89-89: LGTM!

QuizSavedBanner is correctly placed to provide immediate feedback to users who just saved a quiz result. Being a client component, it properly handles sessionStorage interactions.

frontend/lib/guest-quiz.ts (2)

1-18: LGTM!

The PendingQuizResult interface is well-defined with all necessary fields. The 24-hour expiry and storage key constants are appropriately scoped.


56-59: LGTM!

Simple and effective cleanup function with appropriate SSR guard.

frontend/components/quiz/QuizContainer.tsx (2)

163-181: Guest save flow works correctly.

The guest branch properly computes quiz metrics, saves to localStorage, and completes the quiz. The flow integrates well with the pending result handler that will POST results after authentication.


288-289: LGTM!

QuizResult correctly receives the new isGuest and quizSlug props to enable appropriate UI for guest users.

frontend/db/seed-quiz-types.ts (3)

1-4: LGTM! Clean type definition.

The Locale union type provides good type safety for the supported languages.


21-35: LGTM! Well-structured quiz question interface.

The QuizQuestionSeed interface is well-designed with clear locale-based content organization and properly typed answer structures.


38-66: LGTM! Clean and functional helper builders.

The helper functions provide a clean API for constructing quiz content blocks:

  • Good use of the spread operator in t() for optional formatting
  • Sensible default ('javascript') for the code() helper
  • Proper use of as const in list helpers for type narrowing

Note: If the type refactoring suggested above is implemented, these helpers will need minor adjustments to match the new discriminated union types.

Comment on lines +16 to +18
<h1 className="text-3xl font-bold mb-4">Quizzes</h1>
<p className="text-gray-600 dark:text-gray-400">
No quizzes available yet. Please check back soon.
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 English strings in locale-aware page.

This page is under [locale] routing but contains hardcoded English text. Other components in the PR (e.g., QuizSavedBanner.tsx, QuizResult.tsx) use Ukrainian. Consider using a translation system or at minimum matching the language used elsewhere for consistency.

Affected strings: "No quizzes available yet", "Practice", "Quizzes", "Choose a quiz to test your knowledge", "questions", "min limit", "Start quiz".

🤖 Prompt for AI Agents
In frontend/app/[locale]/quizzes/page.tsx around lines 16 to 18, the page
contains hardcoded English strings ("Quizzes", "No quizzes available yet") while
the app is locale-aware and other components use Ukrainian; replace these
hardcoded texts with locale-aware strings by using the project's translation
mechanism (e.g., import and call the i18n/translation hook or util used
elsewhere) or at minimum supply the Ukrainian equivalents to match existing
components; also update the other listed strings ("Practice", "Choose a quiz to
test your knowledge", "questions", "min limit", "Start quiz") throughout this
file to use translation keys or the Ukrainian text so language/locale is
consistent.

Comment on lines +54 to +60
<div className="flex gap-3 text-xs text-gray-500">
<span>{quiz.questionsCount} questions</span>
{quiz.timeLimitSeconds && (
<span>
{Math.floor(quiz.timeLimitSeconds / 60)} min limit
</span>
)}
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

Consider edge case when timeLimitSeconds is less than 60.

If timeLimitSeconds is between 1-59, Math.floor(timeLimitSeconds / 60) will display "0 min limit", which may be confusing to users.

🔎 Proposed fix
                 <div className="flex gap-3 text-xs text-gray-500">
                   <span>{quiz.questionsCount} questions</span>
                   {quiz.timeLimitSeconds && (
                     <span>
-                      {Math.floor(quiz.timeLimitSeconds / 60)} min limit
+                      {quiz.timeLimitSeconds >= 60 
+                        ? `${Math.floor(quiz.timeLimitSeconds / 60)} min limit`
+                        : `${quiz.timeLimitSeconds} sec limit`}
                     </span>
                   )}
                 </div>
📝 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
<div className="flex gap-3 text-xs text-gray-500">
<span>{quiz.questionsCount} questions</span>
{quiz.timeLimitSeconds && (
<span>
{Math.floor(quiz.timeLimitSeconds / 60)} min limit
</span>
)}
<div className="flex gap-3 text-xs text-gray-500">
<span>{quiz.questionsCount} questions</span>
{quiz.timeLimitSeconds && (
<span>
{quiz.timeLimitSeconds >= 60
? `${Math.floor(quiz.timeLimitSeconds / 60)} min limit`
: `${quiz.timeLimitSeconds} sec limit`}
</span>
)}
</div>
🤖 Prompt for AI Agents
In frontend/app/[locale]/quizzes/page.tsx around lines 54 to 60, the UI
currently renders Math.floor(quiz.timeLimitSeconds / 60) which will show "0 min
limit" for timeLimitSeconds between 1–59; change the rendering to compute
minutes = Math.floor(timeLimitSeconds / 60) and, if minutes === 0, render a
user-friendly label such as "Less than 1 min limit" (or "Under 1 min") else
render "{minutes} min limit"; keep the existing conditional so falsy/undefined
timeLimitSeconds remain hidden.

Comment on lines +73 to +77
} catch (err) {
console.error('Failed to save quiz result:', err);
} finally {
clearPendingQuizResult();
}
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 | 🟠 Major

Guest quiz data is cleared even when save fails.

The finally block executes regardless of whether the quiz save succeeded. This contradicts the PR description stating "Guest data is cleared only after a successful save." If the save fails, the user loses their quiz data without it being persisted.

Suggested fix

Move clearPendingQuizResult() inside the success branch:

           if (result.success) {
             sessionStorage.setItem('quiz_just_saved', JSON.stringify({
               score: result.score,
               total: result.totalQuestions,
               percentage: result.percentage,
               pointsAwarded: result.pointsAwarded,
               quizSlug: pendingResult.quizSlug,
             }));
+            clearPendingQuizResult();
           }
         } catch (err) {
           console.error('Failed to save quiz result:', err);
-        } finally {
-          clearPendingQuizResult();
         }
📝 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
} catch (err) {
console.error('Failed to save quiz result:', err);
} finally {
clearPendingQuizResult();
}
if (result.success) {
sessionStorage.setItem('quiz_just_saved', JSON.stringify({
score: result.score,
total: result.totalQuestions,
percentage: result.percentage,
pointsAwarded: result.pointsAwarded,
quizSlug: pendingResult.quizSlug,
}));
clearPendingQuizResult();
}
} catch (err) {
console.error('Failed to save quiz result:', err);
}
🤖 Prompt for AI Agents
In frontend/app/[locale]/signup/page.tsx around lines 73 to 77, the finally
block always calls clearPendingQuizResult(), which clears guest quiz data even
when saving fails; move clearPendingQuizResult() out of the finally and invoke
it only after a confirmed successful save (i.e., inside the try block after the
save completes and returns success), leaving the catch to log or handle the
error without clearing the pending quiz so users retain their data on failure.

Comment thread frontend/app/api/quiz/guest-result/route.ts
Comment on lines +17 to +23
useEffect(() => {
const saved = sessionStorage.getItem('quiz_just_saved');
if (saved) {
setInfo(JSON.parse(saved));
sessionStorage.removeItem('quiz_just_saved');
}
}, []);
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

Add error handling for JSON.parse.

If sessionStorage contains malformed JSON (e.g., due to corruption or manual tampering), JSON.parse will throw and break the component. Wrap in try-catch for defensive coding.

🔎 Proposed fix
   useEffect(() => {
     const saved = sessionStorage.getItem('quiz_just_saved');
     if (saved) {
-      setInfo(JSON.parse(saved));
-      sessionStorage.removeItem('quiz_just_saved');
+      try {
+        setInfo(JSON.parse(saved));
+      } catch {
+        // Ignore malformed data
+      } finally {
+        sessionStorage.removeItem('quiz_just_saved');
+      }
     }
   }, []);
📝 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
useEffect(() => {
const saved = sessionStorage.getItem('quiz_just_saved');
if (saved) {
setInfo(JSON.parse(saved));
sessionStorage.removeItem('quiz_just_saved');
}
}, []);
useEffect(() => {
const saved = sessionStorage.getItem('quiz_just_saved');
if (saved) {
try {
setInfo(JSON.parse(saved));
} catch {
// Ignore malformed data
} finally {
sessionStorage.removeItem('quiz_just_saved');
}
}
}, []);
🤖 Prompt for AI Agents
In frontend/components/dashboard/QuizSavedBanner.tsx around lines 17 to 23,
JSON.parse is called directly on sessionStorage data which can throw on
malformed input; wrap the parse in a try-catch, only call setInfo when parsing
succeeds, and ensure sessionStorage.removeItem('quiz_just_saved') still runs
(e.g., in finally or after handling the error) so the bad payload doesn't cause
repeated errors; also log the parse error (console.error or a logger) to aid
debugging and keep the component rendering safely when parsing fails.

Comment on lines +74 to +78
async function loadQuestions(partNumber: number): Promise<QuestionData[]> {
const partPath = join(process.cwd(), 'data', `javascript-quiz-part${partNumber}.json`);
const partData: QuizPartData = JSON.parse(readFileSync(partPath, 'utf-8'));
return partData.questions;
}
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd "javascript-quiz-part" --type f

Repository: DevLoversTeam/devlovers.net

Length of output: 238


Fix incorrect path to JavaScript quiz JSON files.

The loadQuestions() function uses join(process.cwd(), 'data', ...) but the JavaScript quiz JSON files are located in frontend/parse/, not frontend/data/. This will cause an ENOENT error at runtime.

 async function loadQuestions(partNumber: number): Promise<QuestionData[]> {
-  const partPath = join(process.cwd(), 'data', `javascript-quiz-part${partNumber}.json`);
+  const partPath = join(process.cwd(), 'parse', `javascript-quiz-part${partNumber}.json`);
   const partData: QuizPartData = JSON.parse(readFileSync(partPath, 'utf-8'));
   return partData.questions;
 }
📝 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
async function loadQuestions(partNumber: number): Promise<QuestionData[]> {
const partPath = join(process.cwd(), 'data', `javascript-quiz-part${partNumber}.json`);
const partData: QuizPartData = JSON.parse(readFileSync(partPath, 'utf-8'));
return partData.questions;
}
async function loadQuestions(partNumber: number): Promise<QuestionData[]> {
const partPath = join(process.cwd(), 'parse', `javascript-quiz-part${partNumber}.json`);
const partData: QuizPartData = JSON.parse(readFileSync(partPath, 'utf-8'));
return partData.questions;
}
🤖 Prompt for AI Agents
In frontend/db/seed-quiz-javascript.ts (around lines 74-78) the file path is
pointing to 'data' but the JSON files live in frontend/parse; update the path
construction to point to the correct directory (frontend/parse) and ensure the
base is resolved correctly (e.g., from process.cwd() include 'frontend' then
'parse', or use __dirname to build a path to the parse folder) so the JSON files
can be found at runtime.

Comment on lines +6 to +19
export interface AnswerBlock {
type: 'paragraph' | 'numberedList' | 'bulletList' | 'code';
language?: string;
children: AnswerBlockChild[];
}

export interface AnswerBlockChild {
type?: 'listItem';
text?: string;
bold?: boolean;
italic?: boolean;
code?: boolean;
children?: AnswerBlockChild[];
}
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 | 🟠 Major

Strengthen type safety with discriminated unions and required fields.

The current interfaces have type safety gaps:

  1. AnswerBlockChild: All properties are optional, allowing empty objects {} to satisfy the type. Text nodes should require text, and list items should require children.

  2. AnswerBlock: The language property should be required when type is 'code', optional otherwise.

Consider refactoring to discriminated unions:

🔎 Suggested refactor using discriminated unions
-export interface AnswerBlock {
-  type: 'paragraph' | 'numberedList' | 'bulletList' | 'code';
-  language?: string;
-  children: AnswerBlockChild[];
-}
+export type AnswerBlock =
+  | { type: 'paragraph'; children: AnswerBlockChild[] }
+  | { type: 'numberedList'; children: ListItemChild[] }
+  | { type: 'bulletList'; children: ListItemChild[] }
+  | { type: 'code'; language: string; children: TextChild[] };

-export interface AnswerBlockChild {
-  type?: 'listItem';
-  text?: string;
-  bold?: boolean;
-  italic?: boolean;
-  code?: boolean;
-  children?: AnswerBlockChild[];
-}
+export type AnswerBlockChild = TextChild | ListItemChild;
+
+export interface TextChild {
+  text: string;
+  bold?: boolean;
+  italic?: boolean;
+  code?: boolean;
+}
+
+export interface ListItemChild {
+  type: 'listItem';
+  children: AnswerBlockChild[];
+}

This ensures TypeScript enforces:

  • Code blocks must have a language
  • Text nodes must have text
  • List items must have children
📝 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
export interface AnswerBlock {
type: 'paragraph' | 'numberedList' | 'bulletList' | 'code';
language?: string;
children: AnswerBlockChild[];
}
export interface AnswerBlockChild {
type?: 'listItem';
text?: string;
bold?: boolean;
italic?: boolean;
code?: boolean;
children?: AnswerBlockChild[];
}
export type AnswerBlock =
| { type: 'paragraph'; children: AnswerBlockChild[] }
| { type: 'numberedList'; children: ListItemChild[] }
| { type: 'bulletList'; children: ListItemChild[] }
| { type: 'code'; language: string; children: TextChild[] };
export type AnswerBlockChild = TextChild | ListItemChild;
export interface TextChild {
text: string;
bold?: boolean;
italic?: boolean;
code?: boolean;
}
export interface ListItemChild {
type: 'listItem';
children: AnswerBlockChild[];
}
🤖 Prompt for AI Agents
In frontend/db/seed-quiz-types.ts around lines 6 to 19, the current interfaces
are too permissive (all optional) and allow invalid shapes like {} and missing
language for code blocks; replace them with discriminated unions: make
AnswerBlock a union of { type: 'paragraph'|'numberedList'|'bulletList',
children: AnswerBlockChild[] } and { type: 'code', language: string, children:
AnswerBlockChild[] } so language is required for code blocks, and make
AnswerBlockChild a discriminated union that requires { type?: undefined, text:
string, bold?: boolean, italic?: boolean, code?: boolean } for plain text nodes
and { type: 'listItem', children: AnswerBlockChild[] } for list items so text is
required for text nodes and children is required for list items.

Comment on lines +20 to +28
export function savePendingQuizResult(result: PendingQuizResult): void {
if (typeof window === 'undefined') {
console.log('[DEBUG] savePendingQuizResult: window is undefined (SSR)');
return;
}
console.log('[DEBUG] savePendingQuizResult: saving to localStorage', result);
localStorage.setItem(STORAGE_KEY, JSON.stringify(result));
console.log('[DEBUG] savePendingQuizResult: saved successfully');
}
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.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statements before merging.

These [DEBUG] logs will pollute the browser console in production. Consider removing them or using a debug utility that can be disabled in production builds.

Suggested fix
 export function savePendingQuizResult(result: PendingQuizResult): void {
   if (typeof window === 'undefined') {
-    console.log('[DEBUG] savePendingQuizResult: window is undefined (SSR)');
     return;
   }
-  console.log('[DEBUG] savePendingQuizResult: saving to localStorage', result);
   localStorage.setItem(STORAGE_KEY, JSON.stringify(result));
-  console.log('[DEBUG] savePendingQuizResult: saved successfully');
 }
📝 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
export function savePendingQuizResult(result: PendingQuizResult): void {
if (typeof window === 'undefined') {
console.log('[DEBUG] savePendingQuizResult: window is undefined (SSR)');
return;
}
console.log('[DEBUG] savePendingQuizResult: saving to localStorage', result);
localStorage.setItem(STORAGE_KEY, JSON.stringify(result));
console.log('[DEBUG] savePendingQuizResult: saved successfully');
}
export function savePendingQuizResult(result: PendingQuizResult): void {
if (typeof window === 'undefined') {
return;
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(result));
}
🤖 Prompt for AI Agents
frontend/lib/guest-quiz.ts lines 20-28: the function contains three console.log
debug statements that should not ship to production; remove those console.log
calls (or replace them with a debug/logger utility that is disabled in
production) while keeping the SSR check and the localStorage.setItem call intact
so behavior is unchanged — if you introduce a logger, use an environment-flagged
debug function (e.g., only log when process.env.NODE_ENV !== 'production' or
when a debug flag is true).

Comment on lines +30 to +54
export function getPendingQuizResult(): PendingQuizResult | null {
if (typeof window === 'undefined') {
console.log('[DEBUG] getPendingQuizResult: window is undefined (SSR)');
return null;
}

const stored = localStorage.getItem(STORAGE_KEY);
console.log('[DEBUG] getPendingQuizResult: localStorage value =', stored);
if (!stored) return null;

try {
const result: PendingQuizResult = JSON.parse(stored);
const expiryTime = result.savedAt + EXPIRY_HOURS * 60 * 60 * 1000;

if (Date.now() > expiryTime) {
clearPendingQuizResult();
return null;
}

return result;
} catch {
clearPendingQuizResult();
return null;
}
}
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.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statements.

Same as above - these debug logs should be removed for production.

Suggested fix
 export function getPendingQuizResult(): PendingQuizResult | null {
   if (typeof window === 'undefined') {
-    console.log('[DEBUG] getPendingQuizResult: window is undefined (SSR)');
     return null;
   }
   
   const stored = localStorage.getItem(STORAGE_KEY);
-  console.log('[DEBUG] getPendingQuizResult: localStorage value =', stored);
   if (!stored) return null;
📝 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
export function getPendingQuizResult(): PendingQuizResult | null {
if (typeof window === 'undefined') {
console.log('[DEBUG] getPendingQuizResult: window is undefined (SSR)');
return null;
}
const stored = localStorage.getItem(STORAGE_KEY);
console.log('[DEBUG] getPendingQuizResult: localStorage value =', stored);
if (!stored) return null;
try {
const result: PendingQuizResult = JSON.parse(stored);
const expiryTime = result.savedAt + EXPIRY_HOURS * 60 * 60 * 1000;
if (Date.now() > expiryTime) {
clearPendingQuizResult();
return null;
}
return result;
} catch {
clearPendingQuizResult();
return null;
}
}
export function getPendingQuizResult(): PendingQuizResult | null {
if (typeof window === 'undefined') {
return null;
}
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) return null;
try {
const result: PendingQuizResult = JSON.parse(stored);
const expiryTime = result.savedAt + EXPIRY_HOURS * 60 * 60 * 1000;
if (Date.now() > expiryTime) {
clearPendingQuizResult();
return null;
}
return result;
} catch {
clearPendingQuizResult();
return null;
}
}
🤖 Prompt for AI Agents
In frontend/lib/guest-quiz.ts around lines 30 to 54, remove the debug
console.log statements that print SSR and localStorage values; specifically
delete the two console.log calls ("[DEBUG] getPendingQuizResult: window is
undefined (SSR)" and "[DEBUG] getPendingQuizResult: localStorage value =") while
keeping all existing behavior intact (early return on SSR, reading/parsing
STORAGE_KEY, expiry check, and clearing on error/expiry).

Comment on lines +1 to +244
{
"questions": [
{
"id": "react-q21", "order": 21, "difficulty": "medium",
"uk": { "q": "Як отримати значення контексту у функціональному компоненті?", "exp": "useContext — хук для читання контексту. Він приймає сам об'єкт контексту (не Consumer!) і повертає поточне значення." },
"en": { "q": "How to get context value in a functional component?", "exp": "useContext is a hook for reading context. It takes the context object itself (not Consumer!) and returns the current value." },
"pl": { "q": "Jak uzyskać wartość kontekstu w komponencie funkcyjnym?", "exp": "useContext to hook do odczytu kontekstu. Przyjmuje sam obiekt kontekstu (nie Consumer!) i zwraca aktualną wartość." },
"answers": [
{ "uk": "this.context", "en": "this.context", "pl": "this.context", "correct": false },
{ "uk": "useContext(MyContext)", "en": "useContext(MyContext)", "pl": "useContext(MyContext)", "correct": true },
{ "uk": "getContext(MyContext)", "en": "getContext(MyContext)", "pl": "getContext(MyContext)", "correct": false },
{ "uk": "MyContext.get()", "en": "MyContext.get()", "pl": "MyContext.get()", "correct": false }
]
},
{
"id": "react-q22", "order": 22, "difficulty": "medium",
"uk": { "q": "Коли доцільно використовувати useReducer замість useState?", "exp": "useReducer корисний, коли: стан — об'єкт з кількома полями; оновлення залежать від типу дії (action); логіка оновлення складна." },
"en": { "q": "When is it appropriate to use useReducer instead of useState?", "exp": "useReducer is useful when: state is an object with multiple fields; updates depend on action type; update logic is complex." },
"pl": { "q": "Kiedy warto używać useReducer zamiast useState?", "exp": "useReducer jest przydatny, gdy: stan to obiekt z wieloma polami; aktualizacje zależą od typu akcji; logika aktualizacji jest złożona." },
"answers": [
{ "uk": "Завжди, бо він швидший", "en": "Always, because it's faster", "pl": "Zawsze, bo jest szybszy", "correct": false },
{ "uk": "Коли стан має складну логіку або кілька взаємопов'язаних значень", "en": "When state has complex logic or multiple interrelated values", "pl": "Gdy stan ma złożoną logikę lub kilka powiązanych wartości", "correct": true },
{ "uk": "Тільки для глобального стану", "en": "Only for global state", "pl": "Tylko dla globalnego stanu", "correct": false },
{ "uk": "Тільки в класових компонентах", "en": "Only in class components", "pl": "Tylko w komponentach klasowych", "correct": false }
]
},
{
"id": "react-q23", "order": 23, "difficulty": "medium",
"uk": { "q": "Для чого використовується хук useMemo?", "exp": "useMemo 'запам'ятовує' результат обчислення і повертає закешоване значення, поки залежності не зміняться. Корисно для важких обчислень." },
"en": { "q": "What is the useMemo hook used for?", "exp": "useMemo 'remembers' computation result and returns cached value until dependencies change. Useful for heavy computations." },
"pl": { "q": "Do czego służy hook useMemo?", "exp": "useMemo 'zapamiętuje' wynik obliczeń i zwraca zbuforowaną wartość, dopóki zależności się nie zmienią. Przydatny dla ciężkich obliczeń." },
"answers": [
{ "uk": "Для мемоізації посилання на функцію", "en": "For memoizing function reference", "pl": "Do memoizacji referencji do funkcji", "correct": false },
{ "uk": "Для мемоізації результату обчислень, щоб не перераховувати на кожному рендері", "en": "For memoizing computation result to avoid recalculating on every render", "pl": "Do memoizacji wyniku obliczeń, aby nie przeliczać przy każdym renderze", "correct": true },
{ "uk": "Для збереження даних у localStorage", "en": "For storing data in localStorage", "pl": "Do przechowywania danych w localStorage", "correct": false },
{ "uk": "Для створення мемо-нотаток у коді", "en": "For creating memo notes in code", "pl": "Do tworzenia notatek memo w kodzie", "correct": false }
]
},
{
"id": "react-q24", "order": 24, "difficulty": "medium",
"uk": { "q": "Чим useCallback відрізняється від useMemo?", "exp": "useCallback(fn, deps) еквівалентний useMemo(() => fn, deps). useCallback повертає саму функцію, useMemo — результат її виконання." },
"en": { "q": "How does useCallback differ from useMemo?", "exp": "useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). useCallback returns the function itself, useMemo returns the result of execution." },
"pl": { "q": "Czym różni się useCallback od useMemo?", "exp": "useCallback(fn, deps) jest równoważne useMemo(() => fn, deps). useCallback zwraca samą funkcję, useMemo zwraca wynik jej wykonania." },
"answers": [
{ "uk": "useCallback для класових компонентів, useMemo для функціональних", "en": "useCallback for class components, useMemo for functional", "pl": "useCallback dla komponentów klasowych, useMemo dla funkcyjnych", "correct": false },
{ "uk": "useCallback мемоізує функцію, useMemo мемоізує результат виклику", "en": "useCallback memoizes function, useMemo memoizes call result", "pl": "useCallback memoizuje funkcję, useMemo memoizuje wynik wywołania", "correct": true },
{ "uk": "Вони абсолютно однакові", "en": "They are absolutely identical", "pl": "Są absolutnie identyczne", "correct": false },
{ "uk": "useCallback працює асинхронно", "en": "useCallback works asynchronously", "pl": "useCallback działa asynchronicznie", "correct": false }
]
},
{
"id": "react-q25", "order": 25, "difficulty": "medium",
"uk": { "q": "Що зберігає useRef і коли його змінювати?", "exp": "useRef створює 'коробку' з властивістю .current, яку можна змінювати. На відміну від state, зміна ref не викликає ререндер." },
"en": { "q": "What does useRef store and when to modify it?", "exp": "useRef creates a 'box' with a .current property that can be modified. Unlike state, changing ref doesn't cause re-render." },
"pl": { "q": "Co przechowuje useRef i kiedy go modyfikować?", "exp": "useRef tworzy 'pudełko' z właściwością .current, którą można modyfikować. W przeciwieństwie do state, zmiana ref nie powoduje ponownego renderowania." },
"answers": [
{ "uk": "Змінне значення, яке не викликає ререндер при зміні", "en": "A mutable value that doesn't cause re-render when changed", "pl": "Zmienną wartość, która nie powoduje ponownego renderowania przy zmianie", "correct": true },
{ "uk": "Незмінне значення, доступне тільки для читання", "en": "An immutable value, read-only", "pl": "Niezmienną wartość, tylko do odczytu", "correct": false },
{ "uk": "Посилання на батьківський компонент", "en": "Reference to parent component", "pl": "Referencję do komponentu nadrzędnego", "correct": false },
{ "uk": "Історію всіх станів компонента", "en": "History of all component states", "pl": "Historię wszystkich stanów komponentu", "correct": false }
]
},
{
"id": "react-q26", "order": 26, "difficulty": "beginner",
"uk": { "q": "Як у React отримати прямий доступ до DOM-елемента (наприклад, для фокусу на input)?", "exp": "const inputRef = useRef(); <input ref={inputRef} /> — тепер inputRef.current містить DOM-елемент. Це 'аварійний вихід' з React у реальний DOM." },
"en": { "q": "How to get direct DOM element access in React (e.g., to focus an input)?", "exp": "const inputRef = useRef(); <input ref={inputRef} /> — now inputRef.current contains the DOM element. This is an 'escape hatch' from React to the real DOM." },
"pl": { "q": "Jak uzyskać bezpośredni dostęp do elementu DOM w React (np. do ustawienia fokusa na input)?", "exp": "const inputRef = useRef(); <input ref={inputRef} /> — teraz inputRef.current zawiera element DOM. To 'wyjście awaryjne' z React do prawdziwego DOM." },
"answers": [
{ "uk": "document.getElementById()", "en": "document.getElementById()", "pl": "document.getElementById()", "correct": false },
{ "uk": "Через атрибут ref і хук useRef", "en": "Through ref attribute and useRef hook", "pl": "Przez atrybut ref i hook useRef", "correct": true },
{ "uk": "Через props", "en": "Through props", "pl": "Przez props", "correct": false },
{ "uk": "DOM недоступний у React", "en": "DOM is not accessible in React", "pl": "DOM nie jest dostępny w React", "correct": false }
]
},
{
"id": "react-q27", "order": 27, "difficulty": "medium",
"uk": { "q": "Що таке 'контрольований компонент' у контексті форм React?", "exp": "У контрольованому компоненті React є 'єдиним джерелом правди': <input value={state} onChange={...} />. Дає більше контролю (валідація, форматування)." },
"en": { "q": "What is a 'controlled component' in React forms context?", "exp": "In a controlled component, React is the 'single source of truth': <input value={state} onChange={...} />. Gives more control (validation, formatting)." },
"pl": { "q": "Czym jest 'komponent kontrolowany' w kontekście formularzy React?", "exp": "W komponencie kontrolowanym React jest 'jedynym źródłem prawdy': <input value={state} onChange={...} />. Daje większą kontrolę (walidacja, formatowanie)." },
"answers": [
{ "uk": "Компонент, захищений від XSS-атак", "en": "A component protected from XSS attacks", "pl": "Komponent chroniony przed atakami XSS", "correct": false },
{ "uk": "Input, значення якого контролюється станом React", "en": "An input whose value is controlled by React state", "pl": "Input, którego wartość jest kontrolowana przez stan React", "correct": true },
{ "uk": "Компонент, який не можна редагувати", "en": "A component that cannot be edited", "pl": "Komponent, którego nie można edytować", "correct": false },
{ "uk": "Компонент з обмеженим доступом для користувачів", "en": "A component with restricted user access", "pl": "Komponent z ograniczonym dostępem dla użytkowników", "correct": false }
]
},
{
"id": "react-q28", "order": 28, "difficulty": "beginner",
"uk": { "q": "Яка головна перевага функціональних компонентів над класовими?", "exp": "Функціональні компоненти — це просто функції, що повертають JSX. Не потрібно писати class, constructor, this, bind. Код коротший, читабельніший." },
"en": { "q": "What is the main advantage of functional components over class components?", "exp": "Functional components are just functions that return JSX. No need to write class, constructor, this, bind. Code is shorter, more readable." },
"pl": { "q": "Jaka jest główna zaleta komponentów funkcyjnych nad klasowymi?", "exp": "Komponenty funkcyjne to po prostu funkcje zwracające JSX. Nie trzeba pisać class, constructor, this, bind. Kod jest krótszy, bardziej czytelny." },
"answers": [
{ "uk": "Вони працюють швидше в 10 разів", "en": "They work 10 times faster", "pl": "Działają 10 razy szybciej", "correct": false },
{ "uk": "Простіший синтаксис, менше boilerplate-коду, легше тестувати", "en": "Simpler syntax, less boilerplate code, easier to test", "pl": "Prostsza składnia, mniej boilerplate'u, łatwiejsze testowanie", "correct": true },
{ "uk": "Вони підтримують більше методів життєвого циклу", "en": "They support more lifecycle methods", "pl": "Obsługują więcej metod cyklu życia", "correct": false },
{ "uk": "Тільки функціональні компоненти можуть мати стан", "en": "Only functional components can have state", "pl": "Tylko komponenty funkcyjne mogą mieć stan", "correct": false }
]
},
{
"id": "react-q29", "order": 29, "difficulty": "medium",
"uk": { "q": "Що робить React.memo?", "exp": "React.memo(Component) — це HOC, що 'обгортає' функціональний компонент. Якщо props однакові — компонент не ререндериться. Порівнює props поверхнево (shallow)." },
"en": { "q": "What does React.memo do?", "exp": "React.memo(Component) is a HOC that 'wraps' a functional component. If props are the same — component doesn't re-render. Compares props shallowly." },
"pl": { "q": "Co robi React.memo?", "exp": "React.memo(Component) to HOC, który 'opakowuje' komponent funkcyjny. Jeśli props są takie same — komponent nie renderuje się ponownie. Porównuje props płytko." },
"answers": [
{ "uk": "Додає компонент у пам'ять браузера", "en": "Adds component to browser memory", "pl": "Dodaje komponent do pamięci przeglądarki", "correct": false },
{ "uk": "Запобігає ререндеру компонента, якщо props не змінились", "en": "Prevents component re-render if props haven't changed", "pl": "Zapobiega ponownemu renderowaniu komponentu, jeśli props się nie zmieniły", "correct": true },
{ "uk": "Створює мемо-нотатку для розробників", "en": "Creates a memo note for developers", "pl": "Tworzy notatkę memo dla deweloperów", "correct": false },
{ "uk": "Конвертує класовий компонент у функціональний", "en": "Converts class component to functional", "pl": "Konwertuje komponent klasowy na funkcyjny", "correct": false }
]
},
{
"id": "react-q30", "order": 30, "difficulty": "medium",
"uk": { "q": "Що означає 'підняття стану' (Lifting State Up) у React?", "exp": "Коли кільком компонентам потрібен спільний стан — його 'піднімають' до найближчого спільного предка. Батько зберігає стан і передає його через props." },
"en": { "q": "What does 'Lifting State Up' mean in React?", "exp": "When multiple components need shared state — it's 'lifted' to the nearest common ancestor. The parent stores the state and passes it via props." },
"pl": { "q": "Co oznacza 'podnoszenie stanu' (Lifting State Up) w React?", "exp": "Gdy wiele komponentów potrzebuje wspólnego stanu — jest on 'podnoszony' do najbliższego wspólnego przodka. Rodzic przechowuje stan i przekazuje go przez props." },
"answers": [
{ "uk": "Переміщення стану в глобальну змінну", "en": "Moving state to a global variable", "pl": "Przenoszenie stanu do zmiennej globalnej", "correct": false },
{ "uk": "Переміщення стану в спільного батьківського компонента для кількох дочірніх", "en": "Moving state to a common parent component for multiple children", "pl": "Przenoszenie stanu do wspólnego komponentu nadrzędnego dla wielu potomnych", "correct": true },
{ "uk": "Видалення стану з компонента", "en": "Removing state from component", "pl": "Usuwanie stanu z komponentu", "correct": false },
{ "uk": "Копіювання стану в localStorage", "en": "Copying state to localStorage", "pl": "Kopiowanie stanu do localStorage", "correct": false }
]
},
{
"id": "react-q31", "order": 31, "difficulty": "medium",
"uk": { "q": "Який хук відповідає за логіку при монтуванні компонента (аналог componentDidMount)?", "exp": "useEffect(() => { /* код */ }, []) виконується один раз після першого рендеру. Порожній масив залежностей [] — ключова деталь." },
"en": { "q": "Which hook handles logic when component mounts (equivalent to componentDidMount)?", "exp": "useEffect(() => { /* code */ }, []) runs once after the first render. Empty dependency array [] is the key detail." },
"pl": { "q": "Który hook obsługuje logikę przy montowaniu komponentu (odpowiednik componentDidMount)?", "exp": "useEffect(() => { /* kod */ }, []) wykonuje się raz po pierwszym renderze. Pusta tablica zależności [] to kluczowy szczegół." },
"answers": [
{ "uk": "useState", "en": "useState", "pl": "useState", "correct": false },
{ "uk": "useEffect з порожнім масивом залежностей []", "en": "useEffect with empty dependency array []", "pl": "useEffect z pustą tablicą zależności []", "correct": true },
{ "uk": "useRef", "en": "useRef", "pl": "useRef", "correct": false },
{ "uk": "useCallback", "en": "useCallback", "pl": "useCallback", "correct": false }
]
},
{
"id": "react-q32", "order": 32, "difficulty": "medium",
"uk": { "q": "Як виконати код при розмонтуванні компонента (аналог componentWillUnmount)?", "exp": "Функція, яку повертає useEffect, викликається при розмонтуванні. Cleanup важливий для уникнення витоків пам'яті." },
"en": { "q": "How to execute code when component unmounts (equivalent to componentWillUnmount)?", "exp": "The function returned by useEffect is called on unmount. Cleanup is important to avoid memory leaks." },
"pl": { "q": "Jak wykonać kod przy odmontowaniu komponentu (odpowiednik componentWillUnmount)?", "exp": "Funkcja zwracana przez useEffect jest wywoływana przy odmontowaniu. Cleanup jest ważny, aby uniknąć wycieków pamięci." },
"answers": [
{ "uk": "Викликати useUnmount()", "en": "Call useUnmount()", "pl": "Wywołać useUnmount()", "correct": false },
{ "uk": "Повернути cleanup-функцію з useEffect", "en": "Return cleanup function from useEffect", "pl": "Zwrócić funkcję cleanup z useEffect", "correct": true },
{ "uk": "Використати useState з параметром onUnmount", "en": "Use useState with onUnmount parameter", "pl": "Użyć useState z parametrem onUnmount", "correct": false },
{ "uk": "Це неможливо у функціональних компонентах", "en": "This is impossible in functional components", "pl": "To niemożliwe w komponentach funkcyjnych", "correct": false }
]
},
{
"id": "react-q33", "order": 33, "difficulty": "beginner",
"uk": { "q": "Навіщо в React існують <Fragment> або <> </>?", "exp": "React-компонент має повертати один кореневий елемент. <Fragment> (або скорочено <></>) групує елементи 'невидимо', без додаткового DOM-вузла." },
"en": { "q": "Why do <Fragment> or <> </> exist in React?", "exp": "A React component must return a single root element. <Fragment> (or short <></>) groups elements 'invisibly', without an extra DOM node." },
"pl": { "q": "Po co w React istnieją <Fragment> lub <> </>?", "exp": "Komponent React musi zwracać jeden element główny. <Fragment> (lub skrótowo <></>) grupuje elementy 'niewidocznie', bez dodatkowego węzła DOM." },
"answers": [
{ "uk": "Для створення анімацій", "en": "For creating animations", "pl": "Do tworzenia animacji", "correct": false },
{ "uk": "Щоб групувати елементи без додавання зайвого DOM-вузла", "en": "To group elements without adding an extra DOM node", "pl": "Aby grupować elementy bez dodawania dodatkowego węzła DOM", "correct": true },
{ "uk": "Для кешування компонентів", "en": "For caching components", "pl": "Do buforowania komponentów", "correct": false },
{ "uk": "Для підключення стилів", "en": "For connecting styles", "pl": "Do podłączania stylów", "correct": false }
]
},
{
"id": "react-q34", "order": 34, "difficulty": "advanced",
"uk": { "q": "Що дозволяють робити React Portals?", "exp": "createPortal(child, domNode) рендерить компонент у вказаний DOM-вузол, навіть якщо в React-ієрархії він вкладений глибоко. Типове використання: модальні вікна, tooltips." },
"en": { "q": "What do React Portals allow you to do?", "exp": "createPortal(child, domNode) renders a component to a specified DOM node, even if it's deeply nested in the React hierarchy. Typical use: modals, tooltips." },
"pl": { "q": "Co pozwalają robić React Portals?", "exp": "createPortal(child, domNode) renderuje komponent do określonego węzła DOM, nawet jeśli jest głęboko zagnieżdżony w hierarchii React. Typowe użycie: modale, tooltipy." },
"answers": [
{ "uk": "Переносити компоненти на інший сервер", "en": "Transfer components to another server", "pl": "Przenosić komponenty na inny serwer", "correct": false },
{ "uk": "Рендерити дочірній компонент в DOM-вузол поза ієрархією батьківського", "en": "Render child component to a DOM node outside the parent hierarchy", "pl": "Renderować komponent potomny do węzła DOM poza hierarchią nadrzędnego", "correct": true },
{ "uk": "Створювати посилання між сторінками", "en": "Create links between pages", "pl": "Tworzyć linki między stronami", "correct": false },
{ "uk": "Телепортувати стан між компонентами", "en": "Teleport state between components", "pl": "Teleportować stan między komponentami", "correct": false }
]
},
{
"id": "react-q35", "order": 35, "difficulty": "advanced",
"uk": { "q": "Що таке Error Boundary в React?", "exp": "Error Boundary — класовий компонент з методами getDerivedStateFromError() або componentDidCatch(). Він 'ловить' помилки рендерингу і показує fallback UI." },
"en": { "q": "What is an Error Boundary in React?", "exp": "Error Boundary is a class component with methods getDerivedStateFromError() or componentDidCatch(). It 'catches' rendering errors and shows fallback UI." },
"pl": { "q": "Czym jest Error Boundary w React?", "exp": "Error Boundary to komponent klasowy z metodami getDerivedStateFromError() lub componentDidCatch(). 'Łapie' błędy renderowania i pokazuje fallback UI." },
"answers": [
{ "uk": "Стиль CSS для відображення помилок", "en": "CSS style for displaying errors", "pl": "Styl CSS do wyświetlania błędów", "correct": false },
{ "uk": "Компонент, який перехоплює JavaScript-помилки в дочірніх компонентах", "en": "A component that catches JavaScript errors in child components", "pl": "Komponent, który przechwytuje błędy JavaScript w komponentach potomnych", "correct": true },
{ "uk": "Функція для логування помилок на сервер", "en": "A function for logging errors to server", "pl": "Funkcja do logowania błędów na serwer", "correct": false },
{ "uk": "Middleware для обробки помилок API", "en": "Middleware for handling API errors", "pl": "Middleware do obsługi błędów API", "correct": false }
]
},
{
"id": "react-q36", "order": 36, "difficulty": "medium",
"uk": { "q": "Що робить <React.StrictMode>?", "exp": "StrictMode — інструмент для розробки. Викликає деякі функції двічі (щоб виявити side effects), попереджає про застарілі API. В production — не впливає." },
"en": { "q": "What does <React.StrictMode> do?", "exp": "StrictMode is a development tool. Calls some functions twice (to detect side effects), warns about deprecated APIs. In production — has no effect." },
"pl": { "q": "Co robi <React.StrictMode>?", "exp": "StrictMode to narzędzie deweloperskie. Wywołuje niektóre funkcje dwukrotnie (aby wykryć efekty uboczne), ostrzega o przestarzałych API. W produkcji — nie ma wpływu." },
"answers": [
{ "uk": "Забороняє використання застарілого синтаксису JavaScript", "en": "Forbids using deprecated JavaScript syntax", "pl": "Zabrania używania przestarzałej składni JavaScript", "correct": false },
{ "uk": "Активує додаткові перевірки і попередження в режимі розробки", "en": "Activates additional checks and warnings in development mode", "pl": "Aktywuje dodatkowe sprawdzenia i ostrzeżenia w trybie deweloperskim", "correct": true },
{ "uk": "Прискорює рендеринг у production", "en": "Speeds up rendering in production", "pl": "Przyspiesza renderowanie w produkcji", "correct": false },
{ "uk": "Вимикає всі console.log", "en": "Disables all console.log", "pl": "Wyłącza wszystkie console.log", "correct": false }
]
},
{
"id": "react-q37", "order": 37, "difficulty": "beginner",
"uk": { "q": "Для чого використовують React DevTools?", "exp": "React DevTools — розширення для Chrome/Firefox. Показує ієрархію компонентів, дозволяє переглядати і змінювати props/state, профілює рендери." },
"en": { "q": "What is React DevTools used for?", "exp": "React DevTools is an extension for Chrome/Firefox. Shows component hierarchy, allows viewing and changing props/state, profiles render performance." },
"pl": { "q": "Do czego służy React DevTools?", "exp": "React DevTools to rozszerzenie dla Chrome/Firefox. Pokazuje hierarchię komponentów, pozwala przeglądać i zmieniać props/state, profiluje wydajność renderowania." },
"answers": [
{ "uk": "Для написання коду React", "en": "For writing React code", "pl": "Do pisania kodu React", "correct": false },
{ "uk": "Для інспектування дерева компонентів, props і state в браузері", "en": "For inspecting component tree, props and state in browser", "pl": "Do inspekcji drzewa komponentów, props i state w przeglądarce", "correct": true },
{ "uk": "Для деплою React-додатків", "en": "For deploying React applications", "pl": "Do wdrażania aplikacji React", "correct": false },
{ "uk": "Для генерації компонентів", "en": "For generating components", "pl": "Do generowania komponentów", "correct": false }
]
},
{
"id": "react-q38", "order": 38, "difficulty": "advanced",
"uk": { "q": "Яка головна перевага Server-Side Rendering для React-додатків?", "exp": "SSR генерує HTML на сервері. Пошукові боти бачать контент одразу (важливо для SEO). Користувач швидше бачить сторінку. Next.js — популярний SSR-фреймворк." },
"en": { "q": "What is the main advantage of Server-Side Rendering for React apps?", "exp": "SSR generates HTML on the server. Search bots see content immediately (important for SEO). User sees the page faster. Next.js is a popular SSR framework." },
"pl": { "q": "Jaka jest główna zaleta Server-Side Rendering dla aplikacji React?", "exp": "SSR generuje HTML na serwerze. Boty wyszukiwarek widzą treść natychmiast (ważne dla SEO). Użytkownik szybciej widzi stronę. Next.js to popularny framework SSR." },
"answers": [
{ "uk": "Простіший код компонентів", "en": "Simpler component code", "pl": "Prostszy kod komponentów", "correct": false },
{ "uk": "Краще SEO і швидше перше відображення сторінки", "en": "Better SEO and faster first page render", "pl": "Lepsze SEO i szybsze pierwsze wyświetlenie strony", "correct": true },
{ "uk": "Менше коду JavaScript", "en": "Less JavaScript code", "pl": "Mniej kodu JavaScript", "correct": false },
{ "uk": "Автоматичне кешування даних", "en": "Automatic data caching", "pl": "Automatyczne buforowanie danych", "correct": false }
]
},
{
"id": "react-q39", "order": 39, "difficulty": "advanced",
"uk": { "q": "Що привніс React 18 у роботу з рендерингом?", "exp": "Concurrent Mode дозволяє React 'призупиняти' менш важливі оновлення. Нові API: useTransition, useDeferredValue, автоматичний batching setState." },
"en": { "q": "What did React 18 introduce for rendering?", "exp": "Concurrent Mode allows React to 'pause' less important updates. New APIs: useTransition, useDeferredValue, automatic setState batching." },
"pl": { "q": "Co React 18 wprowadził dla renderowania?", "exp": "Concurrent Mode pozwala React 'wstrzymywać' mniej ważne aktualizacje. Nowe API: useTransition, useDeferredValue, automatyczny batching setState." },
"answers": [
{ "uk": "Заборонив класові компоненти", "en": "Banned class components", "pl": "Zakazał komponentów klasowych", "correct": false },
{ "uk": "Concurrent Rendering — можливість переривати і пріоритезувати оновлення", "en": "Concurrent Rendering — ability to interrupt and prioritize updates", "pl": "Concurrent Rendering — możliwość przerywania i priorytetyzacji aktualizacji", "correct": true },
{ "uk": "Новий синтаксис JSX", "en": "New JSX syntax", "pl": "Nowa składnia JSX", "correct": false },
{ "uk": "Вбудовану підтримку TypeScript", "en": "Built-in TypeScript support", "pl": "Wbudowaną obsługę TypeScript", "correct": false }
]
},
{
"id": "react-q40", "order": 40, "difficulty": "beginner",
"uk": { "q": "Що рекомендується використовувати замість Create React App (CRA) для нових проєктів?", "exp": "CRA став deprecated у 2025. Альтернативи: Vite (для SPA), React Router (для маршрутизації), Next.js (для SSR, SEO, production-ready)." },
"en": { "q": "What is recommended instead of Create React App (CRA) for new projects?", "exp": "CRA became deprecated in 2025. Alternatives: Vite (for SPAs), React Router (for routing), Next.js (for SSR, SEO, production-ready)." },
"pl": { "q": "Co jest zalecane zamiast Create React App (CRA) dla nowych projektów?", "exp": "CRA został zdeprecjonowany w 2025. Alternatywy: Vite (dla SPA), React Router (dla routingu), Next.js (dla SSR, SEO, production-ready)." },
"answers": [
{ "uk": "Тільки ванільний JavaScript без інструментів", "en": "Only vanilla JavaScript without tools", "pl": "Tylko czysty JavaScript bez narzędzi", "correct": false },
{ "uk": "Next.js, Vite або React Router (залежно від потреб)", "en": "Next.js, Vite or React Router (depending on needs)", "pl": "Next.js, Vite lub React Router (w zależności od potrzeb)", "correct": true },
{ "uk": "jQuery", "en": "jQuery", "pl": "jQuery", "correct": false },
{ "uk": "CRA досі рекомендований варіант", "en": "CRA is still the recommended option", "pl": "CRA wciąż jest zalecaną opcją", "correct": false }
]
}
]
}
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

Fix difficulty classification: This is a mixed-difficulty quiz, not advanced.

The questions range from beginner (5 questions) to medium (13 questions) to advanced (4 questions). Remove the "Advanced" descriptor from the review. The multilingual support (Ukrainian, English, Polish) is confirmed and comprehensive. All questions demonstrate accurate React content including correct information about Concurrent Mode features in React 18.

🤖 Prompt for AI Agents
In frontend/parse/react-quiz-data-part2.json around lines 1-244, the dataset
labels several questions as "advanced" but the review notes this is a
mixed-difficulty quiz; change any question objects with "difficulty": "advanced"
to a less strict level (e.g., "medium") so the file reflects mixed difficulty,
and if there is any external metadata or summary that states the quiz is
"advanced", update it to "mixed" or remove the "advanced" descriptor to match
the actual distribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants