(SP: 2) [Frontend] Unify admin panel under /admin/ with collapsible sidebar#342
(SP: 2) [Frontend] Unify admin panel under /admin/ with collapsible sidebar#342ViktorSvertoka merged 25 commits intodevelopfrom
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
✅ Deploy Preview for develop-devlovers ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
No actionable comments were generated in the recent review. 🎉 📝 WalkthroughWalkthroughConsolidates 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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-intlfor 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
useSyncExternalStorecall (lines 90–94) andtogglefunction (lines 96–104) have inconsistent indentation relative to the rest of the component.useSyncExternalStoreargs are indented at 2 spaces from column 0 instead of from the function body, andtoggleuses 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 vianext-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", |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.
| "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
"Адміністрування магазину").
Closes #341
Summary
/shop/admin/*to/admin/shop/*under unified admin layoutguardAdminPagein layout (replaces per-pageguardShopAdminPage)/admin/with section cardsShopAdminTopbar,guard-shop-admin-page.ts, and/shop/admin/routes/adminTest plan
/en/adminas 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/adminfrom any page/loginnpm run buildpassesSummary by CodeRabbit
New Features
Refactor