feat: implement quiz detail screen and crud - #11
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 13 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
📝 WalkthroughWalkthroughAdds a dynamic quiz management page with metadata editing, question editing and deletion, attempt statistics, link copying, quiz deletion, CSV question previews, and enriched quiz-bank attempt counts. Quiz cards now link to the dashboard management route. ChangesQuiz management dashboard
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
actor Creator
participant QuizDetailPage
participant QuizDetailView
participant useQuizDetail
participant Supabase
participant QuestionEditor
Creator->>QuizDetailPage: Open quiz management route
QuizDetailPage->>QuizDetailView: Pass quizId
QuizDetailView->>useQuizDetail: Fetch quiz detail
useQuizDetail->>Supabase: Query quiz, questions, and attempts
Supabase-->>useQuizDetail: Return quiz detail data
useQuizDetail-->>QuizDetailView: Provide state and handlers
QuizDetailView->>QuestionEditor: Render editable question
Creator->>QuestionEditor: Save question
QuestionEditor->>useQuizDetail: Submit updated question
useQuizDetail->>Supabase: Update question record
Supabase-->>useQuizDetail: Confirm update
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 15
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/quiz-detail/AttemptStats.tsx`:
- Around line 77-81: Update AttemptStats and the underlying Pick in
useQuizDetail so each attempt includes its id, then replace the index-based key
in the attempts.map rendering with the attempt’s stable id. Preserve the
existing descending order and display behavior.
- Line 89: Update the date rendering in AttemptStats to avoid calling
toLocaleDateString() during render, matching the hydration-safe approach used by
QuizDetailView. Use a deterministic date format or the existing shared formatter
so server and client produce identical output.
- Around line 23-31: Guard score percentage calculations in AttemptStats,
including the avgScore reduce and the per-row percentage near the row rendering,
so attempts with total_points === 0 do not divide by zero or propagate NaN.
Preserve normal percentage calculations for positive totals and use the
component’s appropriate zero-score fallback consistently in both locations.
In `@src/components/quiz-detail/MetaEditor.tsx`:
- Around line 59-249: Associate every form label in MetaEditor with its control
by assigning matching unique id/htmlFor pairs to the title input, description
textarea, time-limit checkbox and number input, visibility radios, category
select, and difficulty select. Ensure each radio receives a distinct id while
retaining the existing shared name and behavior, and keep the current labels and
handlers unchanged.
- Line 19: Update the MetaEditor onSave prop to use the same specific
Partial<Pick<...>> metadata-update type accepted by
useQuizDetail.updateQuizMeta, covering only title, description, time_limit,
visibility, category, and difficulty; avoid the broad Record<string, unknown>
type.
- Around line 114-131: Update the time-limit input in MetaEditor so onChange
only updates the displayed value, and defer onSave({ time_limit: ... }) until
onBlur, matching the title/description persistence behavior. Preserve the
existing 1–180 validation and null-rendering behavior.
In `@src/components/quiz-detail/QuestionEditor.tsx`:
- Around line 89-215: Update the buttons in QuestionEditor, including the
Edit/Delete controls, correct-answer toggle within OPTION_KEYS.map, Save
question, and Cancel buttons, to explicitly declare their intended HTML button
type; use type="button" for controls that should not submit a form and preserve
the existing save behavior appropriately.
- Around line 178-190: Clamp the parsed points value in the QuestionEditor
input’s onChange handler to the inclusive range 1–100 before updating
draft.points. Preserve the existing fallback for empty or invalid input while
ensuring negative values and values above 100 cannot be saved.
In `@src/hooks/useQuizDetail.ts`:
- Around line 87-109: Update the updateQuizMeta parameter type to use
NonNullable<QuizDetailState["quiz"]> instead of QuizDetailState["quiz"] & object
when defining the selected fields, preserving the existing update behavior and
field restrictions.
- Around line 172-177: Update the copyLink function to await
navigator.clipboard.writeText and show the success toast only after it resolves;
catch rejected writes and handle them with an appropriate failure path instead
of reporting success.
- Around line 43-84: Update fetchQuizDetail to guard against stale asynchronous
responses when quizId changes or the effect/refetch runs again. Track the latest
request or use an abort/ignore mechanism, and check it before each state update
so only the current request can modify state; preserve the existing
quiz-not-found and successful data behavior.
- Around line 61-79: Handle the errors returned by the questions and
quiz_attempts queries in the quiz detail loading flow before updating state,
alongside the existing quizError handling. Surface any query failure through the
existing error state and avoid rendering empty questions or attempts when their
fetch fails; retain the successful mapping and state update behavior otherwise.
In `@src/views/CreateQuiz.tsx`:
- Around line 143-168: Update the option rendering inside the options.map
callback to visibly display a “Correct” label whenever j === q.correctIndex,
alongside the existing option content. Keep the current color styling and ensure
incorrect options do not show the label.
In `@src/views/QuizDetailView.tsx`:
- Around line 107-227: Set type="button" on the Copy link button, every tab
button rendered by TABS.map, and the danger-zone delete and cancel buttons in
QuizDetailView. Keep their existing click handlers and behavior unchanged.
- Around line 98-104: Update the date rendering in QuizDetailView to use
deterministic, locale- and timezone-independent formatting instead of new
Date(quiz.created_at).toLocaleDateString(). Preserve the existing created-date
display while ensuring SSR and client hydration produce identical text.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 50ff64cf-324a-4be4-999a-394a1a568b2a
📒 Files selected for processing (8)
app/dashboard/quiz/[quizId]/page.tsxsrc/components/QuizCard.tsxsrc/components/quiz-detail/AttemptStats.tsxsrc/components/quiz-detail/MetaEditor.tsxsrc/components/quiz-detail/QuestionEditor.tsxsrc/hooks/useQuizDetail.tssrc/views/CreateQuiz.tsxsrc/views/QuizDetailView.tsx
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/hooks/useQuizDetail.ts (1)
52-57: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDistinguish missing quizzes from load failures
quizErroris treated the same as an absent row here, so network, RLS, and server errors are surfaced as"Quiz not found". Handle the not-found case separately and show a load-failure message for other errors.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useQuizDetail.ts` around lines 52 - 57, Update the error handling in useQuizDetail so quizError and missing quizData are handled separately: keep “Quiz not found” only when the query succeeds without a row, and use a load-failure message when quizError is present. Preserve loading=false and the existing state update structure for both outcomes.src/hooks/useQuizBank.test.ts (1)
129-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMock the
quiz_attemptsquery separately and assert enrichment.This fixture is also returned for
getAttemptCounts, but its rows haveid, notquiz_id; the reducer therefore produces no usable counts. Mockfrom("quizzes")andfrom("quiz_attempts")independently, then assert that each returned quiz receives the expectedtimes_takenvalue.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useQuizBank.test.ts` around lines 129 - 140, Update the test around useQuizBank so supabase.from returns separate chains for “quizzes” and “quiz_attempts”, using attempt rows with quiz_id values that match the fixture quizzes. Assert the client-side search result still contains only “Math Quiz” and verify its enriched times_taken value, ensuring the attempt-count reducer receives valid data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useQuizBank.ts`:
- Around line 68-74: In the quiz-loading flow around getAttemptCounts and
setAllQuizzes, re-check currentGen after the awaited count request and before
mutating state; return or skip the stale result when the generation changed.
Only clear the loading state after the current request commits its enriched
quizzes, preserving the existing enrichment behavior.
In `@src/utils/attempts.ts`:
- Around line 8-13: Update the quiz-attempts query flow around the Supabase call
in the attempts utility to inspect and propagate its error instead of treating
missing data as an empty result. Return a distinct failure outcome, then update
both callers to preserve their existing attempt counts or display an error state
when the query fails, while retaining zero counts for successful queries with no
matching attempts.
- Around line 8-18: Update the attempt-counting query in the visible Supabase
flow to aggregate counts by quiz_id in SQL, using an appropriate view or RPC
that performs COUNT(*) and GROUP BY quiz_id. Refactor the result handling in the
surrounding function to map the aggregated rows into the existing Record<string,
number> shape, without fetching individual quiz_attempts rows or relying on
client-side reduce counting.
---
Outside diff comments:
In `@src/hooks/useQuizBank.test.ts`:
- Around line 129-140: Update the test around useQuizBank so supabase.from
returns separate chains for “quizzes” and “quiz_attempts”, using attempt rows
with quiz_id values that match the fixture quizzes. Assert the client-side
search result still contains only “Math Quiz” and verify its enriched
times_taken value, ensuring the attempt-count reducer receives valid data.
In `@src/hooks/useQuizDetail.ts`:
- Around line 52-57: Update the error handling in useQuizDetail so quizError and
missing quizData are handled separately: keep “Quiz not found” only when the
query succeeds without a row, and use a load-failure message when quizError is
present. Preserve loading=false and the existing state update structure for both
outcomes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 60a91c3e-d717-4a35-b7b3-515298c399d1
📒 Files selected for processing (7)
app/dashboard/quiz/[quizId]/page.tsxsrc/components/home/QuizBankPreview.tsxsrc/hooks/useQuizBank.test.tssrc/hooks/useQuizBank.tssrc/hooks/useQuizDetail.tssrc/test-utils/chain.tssrc/utils/attempts.ts
…nd enhance useQuizzes to fetch attempt counts
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useQuizzes.ts`:
- Around line 53-55: Update getAttemptCounts, used by useQuizzes, to aggregate
attempt totals in the database or through an RPC grouped by quiz ID, returning
one count per requested ID. Preserve the existing counts mapping contract so
callers receive accurate times_taken values without fetching individual attempt
rows or reducing them in the browser.
- Around line 55-57: Update getAttemptCounts to propagate Supabase query errors
instead of returning an empty object. Preserve the existing successful counts
mapping so useQuizzes can continue assigning counts via counts[q.id] ?? 0 only
when the query succeeds.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: af1e74f5-624a-44ab-be71-da3708e5a25b
📒 Files selected for processing (2)
src/components/QuizCard.tsxsrc/hooks/useQuizzes.ts
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Mistura <101476258+Truella@users.noreply.github.com>
…date related functions
… into feat/quiz-detail-crud
Summary
Quiz detail page at
/dashboard/quiz/[quizId]with three tabs: Questions (edit/delete each question inline), Settings (edit metadata, visibility, time limit, category/difficulty, delete quiz with confirm), and Stats (attempt count, avg score, avg time, recent attempts list).useQuizDetailhook handling all data fetching and mutations. CSV question preview before publish on the create page. "View" button on QuizCard replaced with "Manage" linking to the detail page.New files
src/hooks/useQuizDetail.tssrc/components/quiz-detail/MetaEditor.tsxsrc/components/quiz-detail/QuestionEditor.tsxsrc/components/quiz-detail/AttemptStats.tsxsrc/views/QuizDetailView.tsxapp/dashboard/quiz/[quizId]/page.tsxChanged files
src/components/QuizCard.tsxsrc/views/CreateQuiz.tsxChecklist
npx tsc --noEmitpassesnpx next lintpassesconsole.logstatementsmainSummary by CodeRabbit