Skip to content

(SP: 2) [Frontend] Unify admin panel under /admin/ with collapsible sidebar#342

Merged
ViktorSvertoka merged 25 commits intodevelopfrom
sl/feat/quiz
Feb 17, 2026
Merged

(SP: 2) [Frontend] Unify admin panel under /admin/ with collapsible sidebar#342
ViktorSvertoka merged 25 commits intodevelopfrom
sl/feat/quiz

Conversation

@LesiaUKR
Copy link
Collaborator

@LesiaUKR LesiaUKR commented Feb 17, 2026

Closes #341

Summary

  • Migrate shop admin from /shop/admin/* to /admin/shop/* under unified admin layout
  • Add collapsible sidebar with sections: Shop, Quiz, Q&A
  • Create shared guardAdminPage in layout (replaces per-page guardShopAdminPage)
  • Add admin hub page at /admin/ with section cards
  • Add placeholder pages for Quiz and Q&A admin sections
  • Remove old ShopAdminTopbar, guard-shop-admin-page.ts, and /shop/admin/ routes
  • Update header links (DesktopActions, AppMobileMenu) to point to /admin

Test plan

  • Navigate to /en/admin as admin — hub page with Shop and Quiz cards
  • /en/admin/shop — shop admin home with Products/Orders links
  • /en/admin/shop/products — product list table, pagination works
  • /en/admin/shop/products/new — create product form renders
  • /en/admin/shop/orders — order list, view order detail
  • /en/admin/quiz — placeholder page renders
  • /en/admin/quiz/statistics — placeholder page, only Statistics highlighted (not Quizzes)
  • /en/admin/q&a — placeholder page renders
  • Sidebar collapse/expand persists across page reloads (localStorage)
  • Settings gear icon in header links to /admin from any page
  • Non-admin user redirected to /login
  • npm run build passes

Summary by CodeRabbit

  • New Features

    • Added an admin dashboard layout with collapsible sidebar.
    • Added Admin home, Q&A, Quiz, and Quiz Statistics admin pages (UI scaffolds).
  • Refactor

    • Updated admin navigation paths across the app.
    • Simplified admin page layouts (removed legacy top bar).
    • Updated accessibility labels and translations for the admin section.

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)
- 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)
- 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
- 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)
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
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
…ebar (#341)

- Migrate shop admin from /shop/admin/* to /admin/shop/*.
- Add shared admin layout with collapsible sidebar, generic guard,
and placeholder pages for Quiz and Q&A sections.
@vercel
Copy link
Contributor

vercel bot commented Feb 17, 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 17, 2026 7:54pm

@netlify
Copy link

netlify bot commented Feb 17, 2026

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 650e960
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/6994c6e6218bcb0008e3dec1
😎 Deploy Preview https://deploy-preview-342--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
Contributor

coderabbitai bot commented Feb 17, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Consolidates admin UI under a unified /admin route: adds an AdminLayout with guardAdminPage and collapsible AdminSidebar, creates admin hub and placeholder pages (Quiz, Q&A, Quiz statistics), migrates shop admin routes to /admin/shop/*, renames guard and translation keys, updates header links, and removes ShopAdminTopbar.

Changes

Cohort / File(s) Summary
New Admin Layout & Core Pages
frontend/app/[locale]/admin/layout.tsx, frontend/app/[locale]/admin/page.tsx, frontend/app/[locale]/admin/q&a/page.tsx, frontend/app/[locale]/admin/quiz/page.tsx, frontend/app/[locale]/admin/quiz/statistics/page.tsx
Adds server-side AdminLayout that awaits guardAdminPage() and exports dynamic = 'force-dynamic'; introduces admin hub and placeholder pages (Quiz, Q&A, Quiz statistics) with page metadata.
Admin Navigation Sidebar
frontend/components/admin/AdminSidebar.tsx
New client component: collapsible sidebar with persisted state (localStorage), storage-event sync, section/link data (Shop, Quiz, Q&A), and active-link logic.
Shop Admin Route Migration
frontend/app/[locale]/admin/shop/page.tsx, frontend/app/[locale]/admin/shop/orders/page.tsx, frontend/app/[locale]/admin/shop/orders/[id]/page.tsx, frontend/app/[locale]/admin/shop/products/page.tsx, frontend/app/[locale]/admin/shop/products/new/page.tsx, frontend/app/[locale]/admin/shop/products/[id]/edit/page.tsx
Migrates routes/links from /shop/admin/... to /admin/shop/...; removes per-page guardShopAdminPage calls and ShopAdminTopbar usage; deletes export const dynamic = 'force-dynamic' from several pages.
Header & Navigation Updates
frontend/components/header/AppChrome.tsx, frontend/components/header/AppMobileMenu.tsx, frontend/components/header/DesktopActions.tsx
Updates admin links/active checks and ARIA label keys from shopAdmin to admin; passes showAdminLink prop to UnifiedHeader; adjusts mobile/desktop admin link targets.
Auth Guard Rename
frontend/lib/auth/guard-admin-page.ts
Renames exported guard from guardShopAdminPageguardAdminPage, preserving implementation.
Removed Topbar Component
frontend/components/shop/admin/ShopAdminTopbar.tsx
Removes the ShopAdminTopbar component and its exports/imports.
Translations
frontend/messages/en.json, frontend/messages/pl.json, frontend/messages/uk.json
Renames translation key aria.shopAdminaria.admin and updates localized strings to use "Admin panel".

Sequence Diagram(s)

sequenceDiagram
  participant User as User (Browser)
  participant Next as Next.js Server (App Router)
  participant Guard as guardAdminPage (Auth)
  participant DB as Auth/Session Store

  User->>Next: Request /[locale]/admin/ (SSR)
  Next->>Guard: call guardAdminPage()
  Guard->>DB: validate session / token
  DB-->>Guard: session valid / invalid
  Guard-->>Next: allow or redirect
  Next-->>User: render AdminLayout + AdminSidebar or redirect to login
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ViktorSvertoka

Poem

🐰 A tiny rabbit hops down the lane,
Found a sidebar hiding in the grain.
Links united beneath one sign,
Shop, Quiz, Q&A all in line.
Hops away — the admin's fine! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 PR title clearly identifies the main change: unifying the admin panel under /admin/ with a collapsible sidebar, directly matching the core objectives in the PR description.
Linked Issues check ✅ Passed All coding requirements from #341 are met: shared admin layout created, collapsible sidebar implemented, guardAdminPage replaces guardShopAdminPage, routes migrated to /admin/shop/*, admin hub page added with placeholders, header links updated, and legacy components removed.
Out of Scope Changes check ✅ Passed All changes are within scope. New layout, sidebar, and route structure are intended; placeholder pages for Quiz/Q&A are expected; no full implementations added; API routes unchanged; legacy shop-admin components properly removed.

✏️ 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 sl/feat/quiz

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

🧹 Nitpick comments (4)
frontend/components/header/DesktopActions.tsx (1)

43-50: Consider pointing the admin link to /admin (the hub) instead of /admin/shop.

The PR objective states the settings gear should point to the unified admin entry (/admin), which shows section cards for Shop, Quiz, and Q&A. Currently it deep-links to /admin/shop, bypassing the hub. This works fine today but won't scale well once Quiz and Q&A admin sections are implemented — users would have no direct header-level path to the hub.

If the intent is to land on the shop section directly, this is fine; just flagging the discrepancy with the stated objective.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/header/DesktopActions.tsx` around lines 43 - 50, The
header currently renders an admin HeaderButton that deep-links to "/admin/shop";
update the HeaderButton usage in DesktopActions (the showAdminLink conditional
that renders HeaderButton) to use href="/admin" so the icon opens the unified
admin hub rather than the shop subpage; keep the same variant, icon (Settings)
and label (tAria('admin')) and only change the href prop.
frontend/app/[locale]/admin/page.tsx (1)

26-54: Hardcoded English strings — consider i18n for admin pages.

All admin pages (hub, quiz, Q&A, statistics) use hardcoded English strings for headings and descriptions. If the rest of the app uses next-intl for translations, these admin pages will not respect the user's locale. This is acceptable as a first pass if admin is English-only by design, but worth tracking if localization is planned for admin.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`[locale]/admin/page.tsx around lines 26 - 54, AdminHomePage
currently renders hardcoded English strings (title, description, and each
SECTIONS item's title/description) which prevents localization; use next-intl
(or your app's i18n) to load translations inside AdminHomePage (e.g., call
useTranslations or intl.formatMessage) and replace the literal strings in the
component and in the SECTIONS definitions with translation keys, or map SECTIONS
to resolved strings inside AdminHomePage before rendering so section.title and
section.description use translated text; update references to AdminHomePage,
SECTIONS, and section.title/section.description accordingly.
frontend/components/admin/AdminSidebar.tsx (2)

87-104: Inconsistent indentation inside the component body.

The useSyncExternalStore call (lines 90–94) and toggle function (lines 96–104) have inconsistent indentation relative to the rest of the component. useSyncExternalStore args are indented at 2 spaces from column 0 instead of from the function body, and toggle uses 4-space indent for its body while the rest uses 2.

Proposed fix
   const collapsed = useSyncExternalStore(
-  subscribeToStorage,
-  getCollapsedSnapshot,
-  getCollapsedServerSnapshot
-);
+    subscribeToStorage,
+    getCollapsedSnapshot,
+    getCollapsedServerSnapshot
+  );
 
-const toggle = () => {
+  const toggle = () => {
     const next = !collapsed;
     try {
       localStorage.setItem(STORAGE_KEY, String(next));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/admin/AdminSidebar.tsx` around lines 87 - 104, The
AdminSidebar component has inconsistent indentation for the useSyncExternalStore
call and the toggle function; reformat the block so all statements inside
AdminSidebar are indented uniformly (same indent width as other component body
lines). Adjust the useSyncExternalStore invocation so its arguments
(subscribeToStorage, getCollapsedSnapshot, getCollapsedServerSnapshot) align
under the call, and normalize the toggle function body and its try/catch to the
same indentation level as the rest of the component; keep references to
STORAGE_KEY and the window.dispatchEvent line intact.

119-227: Sidebar aria-labels and link text are hardcoded in English.

All sidebar labels ("Expand sidebar", "Collapse sidebar", "Back to site", "Admin Dashboard", section/item labels) are hardcoded English strings. If the app supports multiple locales via next-intl, consider using translations here for consistency with the rest of the UI, or document that admin UI is English-only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/admin/AdminSidebar.tsx` around lines 119 - 227, Replace
hardcoded English UI strings in AdminSidebar with next-intl translations: import
and call useTranslations (e.g., const t = useTranslations('AdminSidebar'))
inside the component, then use t(...) for the aria-label on the collapse button
(replace "Expand sidebar"/"Collapse sidebar"), the Link title for the dashboard
("Admin Dashboard"), the footer "Back to site" title/text, and any visible
labels. For NAV_SECTIONS and its items, stop rendering raw
section.label/item.label strings and instead either store translation keys in
NAV_SECTIONS (e.g., section.labelKey/item.labelKey) and call
t(section.labelKey)/t(item.labelKey), or map NAV_SECTIONS to a translated list
before rendering; keep references to toggle, collapsed, isActive, NAV_SECTIONS,
section.icon and item.icon unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/`[locale]/admin/page.tsx:
- Around line 11-24: The SECTIONS array in frontend/app/[locale]/admin/page.tsx
is missing the Q&A card present in AdminSidebar; add a new section object to
SECTIONS with title "Q&A", a short description like "Manage questions and
answers", href set to '/admin/q&a', and an appropriate icon (similar to
ShoppingBag and FileQuestion) so the admin hub matches the sidebar and the
existing frontend/app/[locale]/admin/q&a/page.tsx placeholder route.

In `@frontend/app/`[locale]/admin/shop/orders/page.tsx:
- Line 98: The form's action attribute currently points to
"/api/admin/shop/orders/reconcile-stale" but the actual route handler is
implemented in api/shop/admin/orders/reconcile-stale/route.ts, causing a 404;
fix by either updating the form action in
frontend/app/[locale]/admin/shop/orders/page.tsx back to
"/api/shop/admin/orders/reconcile-stale" to match the existing route, or
move/rename the handler file to the path matching
"/api/admin/shop/orders/reconcile-stale" (and update any imports/exports
accordingly) so the route and the form action are consistent.

In `@frontend/messages/pl.json`:
- Line 15: The "admin" translation key currently contains an English value
("Admin panel"); replace it with an appropriate Polish string such as "Panel
administracyjny" or "Panel admina" so the pl.json locale is consistent (update
the value for the "admin" key in the JSON).

In `@frontend/messages/uk.json`:
- Line 15: Replace the English string for the aria.admin key with a Ukrainian
translation: update the "admin" value (in the Ukrainian locale object) from
"Admin panel" to an appropriate Ukrainian string such as "Панель адміністратора"
(or "Адмін панель") so it matches the existing Ukrainian translation style (see
adjacent key "shopAdmin" which is "Адміністрування магазину").

---

Nitpick comments:
In `@frontend/app/`[locale]/admin/page.tsx:
- Around line 26-54: AdminHomePage currently renders hardcoded English strings
(title, description, and each SECTIONS item's title/description) which prevents
localization; use next-intl (or your app's i18n) to load translations inside
AdminHomePage (e.g., call useTranslations or intl.formatMessage) and replace the
literal strings in the component and in the SECTIONS definitions with
translation keys, or map SECTIONS to resolved strings inside AdminHomePage
before rendering so section.title and section.description use translated text;
update references to AdminHomePage, SECTIONS, and
section.title/section.description accordingly.

In `@frontend/components/admin/AdminSidebar.tsx`:
- Around line 87-104: The AdminSidebar component has inconsistent indentation
for the useSyncExternalStore call and the toggle function; reformat the block so
all statements inside AdminSidebar are indented uniformly (same indent width as
other component body lines). Adjust the useSyncExternalStore invocation so its
arguments (subscribeToStorage, getCollapsedSnapshot, getCollapsedServerSnapshot)
align under the call, and normalize the toggle function body and its try/catch
to the same indentation level as the rest of the component; keep references to
STORAGE_KEY and the window.dispatchEvent line intact.
- Around line 119-227: Replace hardcoded English UI strings in AdminSidebar with
next-intl translations: import and call useTranslations (e.g., const t =
useTranslations('AdminSidebar')) inside the component, then use t(...) for the
aria-label on the collapse button (replace "Expand sidebar"/"Collapse sidebar"),
the Link title for the dashboard ("Admin Dashboard"), the footer "Back to site"
title/text, and any visible labels. For NAV_SECTIONS and its items, stop
rendering raw section.label/item.label strings and instead either store
translation keys in NAV_SECTIONS (e.g., section.labelKey/item.labelKey) and call
t(section.labelKey)/t(item.labelKey), or map NAV_SECTIONS to a translated list
before rendering; keep references to toggle, collapsed, isActive, NAV_SECTIONS,
section.icon and item.icon unchanged.

In `@frontend/components/header/DesktopActions.tsx`:
- Around line 43-50: The header currently renders an admin HeaderButton that
deep-links to "/admin/shop"; update the HeaderButton usage in DesktopActions
(the showAdminLink conditional that renders HeaderButton) to use href="/admin"
so the icon opens the unified admin hub rather than the shop subpage; keep the
same variant, icon (Settings) and label (tAria('admin')) and only change the
href prop.

"primaryNav": "Nawigacja główna",
"dashboard": "Panel",
"shopAdmin": "Panel administracyjny sklepu",
"admin": "Admin panel",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Polish translation missing — value is English.

The old value was "Panel administracyjny sklepu" (proper Polish). The new "Admin panel" is identical to the English locale. Consider using a Polish equivalent like "Panel administracyjny" or "Panel admina" for consistency with the rest of the Polish translations in this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/messages/pl.json` at line 15, The "admin" translation key currently
contains an English value ("Admin panel"); replace it with an appropriate Polish
string such as "Panel administracyjny" or "Panel admina" so the pl.json locale
is consistent (update the value for the "admin" key in the JSON).

"primaryNav": "Головна навігація",
"dashboard": "Панель керування",
"shopAdmin": "Адміністрування магазину",
"admin": "Admin panel",
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Ukrainian locale contains English text instead of a Ukrainian translation.

The aria.admin value is "Admin panel" (English) in the Ukrainian locale file. The previous key (shopAdmin) had a proper Ukrainian value ("Адміністрування магазину"). This should be translated — e.g., "Панель адміністратора" or "Адмін панель" — to match the rest of the file.

Proposed fix
-    "admin": "Admin panel",
+    "admin": "Панель адміністратора",
📝 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
"admin": "Admin panel",
"admin": "Панель адміністратора",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/messages/uk.json` at line 15, Replace the English string for the
aria.admin key with a Ukrainian translation: update the "admin" value (in the
Ukrainian locale object) from "Admin panel" to an appropriate Ukrainian string
such as "Панель адміністратора" (or "Адмін панель") so it matches the existing
Ukrainian translation style (see adjacent key "shopAdmin" which is
"Адміністрування магазину").

@ViktorSvertoka ViktorSvertoka merged commit 6a9a01c into develop Feb 17, 2026
11 checks passed
@ViktorSvertoka ViktorSvertoka deleted the sl/feat/quiz branch February 17, 2026 19:58
@LesiaUKR LesiaUKR restored the sl/feat/quiz branch February 19, 2026 14:48
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