Skip to content

dashboard-routing-fix#299

Open
CloudM22 wants to merge 1 commit into
mainfrom
fix/298-dashboard-404
Open

dashboard-routing-fix#299
CloudM22 wants to merge 1 commit into
mainfrom
fix/298-dashboard-404

Conversation

@CloudM22

@CloudM22 CloudM22 commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Wire the dashboard pages to a shared, data-driven dashboard component backed by real CMS statistics instead of hardcoded mock data.

New Features:

  • Introduce a reusable learning dashboard stats module that aggregates module and lesson counts from Sanity for each course.
  • Add configurable dashboard course metadata to drive dashboard cards and routing paths.

Bug Fixes:

  • Align dashboard navigation links with the new course paths, ensuring routing from dashboard cards points to the correct course URLs.

Enhancements:

  • Refactor the main dashboard and learn dashboard routes to use the shared DashboardAlt component with server-fetched course statistics.
  • Update dashboard progress calculations to combine CMS-provided lesson counts with per-course completion data from the course progress service.
  • Simplify the dashboard stats API route to delegate to the shared learning dashboard stats helper.

Summary by CodeRabbit

  • Refactor
    • Improved dashboard data architecture for enhanced performance and maintainability.
    • Dashboard pages now fetch data server-side for optimized data handling.
    • Consolidated course statistics retrieval into centralized modules for better code organization.

Review Change Stack

@vercel

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
thinkthroo-github-app Ready Ready Preview, Comment May 18, 2026 4:40pm
thinkthroo-platform Ready Ready Preview, Comment May 18, 2026 4:40pm
thinkthroo-www Ready Ready Preview, Comment May 18, 2026 4:40pm

Request Review

@thinkthroo

thinkthroo Bot commented May 18, 2026

Copy link
Copy Markdown

🔒 Invite-Only Access

Hi @CloudM22, the Think Throo AI code review features are currently invite-only.

Your account (CloudM22) is not on the invite list, so AI-powered reviews (PR summaries, architecture checks, etc.) have been skipped for this pull request.

Want access? Request access here.


This check is temporary while we control usage during the early access phase.

@sourcery-ai

sourcery-ai Bot commented May 18, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors the learning dashboard to use shared dashboard metadata and server-derived course stats, centralizing Sanity queries and wiring both dashboard routes through a common client component that computes progress from real lesson completion data.

Sequence diagram for loading the learning dashboard with shared stats and progress

sequenceDiagram
  actor User
  participant DashboardPage
  participant getLearningDashboardStats
  participant sanityClient
  participant DashboardAltPage
  participant courseProgressClient

  User->>DashboardPage: request /dashboard or /learn/dashboard
  DashboardPage->>getLearningDashboardStats: getLearningDashboardStats()
  getLearningDashboardStats->>sanityClient: fetch(ARCHITECTURE_STATS_QUERY)
  getLearningDashboardStats->>sanityClient: fetch(PRODUCTION_STATS_QUERY)
  getLearningDashboardStats-->>DashboardPage: CourseStat[]
  DashboardPage-->>User: HTML with DashboardAltPage(courseStats)

  User->>DashboardAltPage: load client dashboard
  loop for each course in dashboardCourses
    DashboardAltPage->>courseProgressClient: getCompletedLessons(courseSlug)
    courseProgressClient-->>DashboardAltPage: completed lesson rows
  end
  DashboardAltPage-->>User: render progress cards and links
Loading

File-Level Changes

Change Details Files
Convert DashboardAlt into a client component that consumes server-provided course stats and live completion counts to render overall and per-course progress.
  • Add DashboardAltPageProps that accepts a list of CourseStat objects from the server.
  • Replace hard-coded courseData with dashboardCourses metadata and a useMemo lookup map keyed by courseSlug.
  • Use courseProgressClient in a useEffect to fetch completed lessons per course and accumulate completed counts in local state.
  • Compute totalLessons and completedLessons from courseStats and completedByCourse, guarding for zero to avoid NaN progress.
  • Update the course cards to derive module counts, lesson counts, and progress from statsByCourseSlug and dashboardCourses, and to use course.path instead of hardcoded /learn URLs.
apps/platform/components/DashboardAlt.tsx
Move dashboard rendering to use the shared DashboardAlt component fed by server-side learning dashboard stats on both the main dashboard and learn dashboard routes.
  • Replace the prior client-side DashboardPage implementation with an async server component that calls getLearningDashboardStats and renders DashboardAltPage with courseStats.
  • Similarly convert LearnDashboardPage to an async server component that fetches courseStats via getLearningDashboardStats and passes them into DashboardAltPage.
  • Remove duplicated dashboard layout/stats JSX from the dashboard page, delegating layout to DashboardAltPage instead.
apps/platform/app/(platform)/dashboard/page.tsx
apps/platform/app/(platform)/learn/dashboard/page.tsx
Simplify and repurpose course-data to hold only dashboard course metadata, decoupled from lesson/module structures.
  • Replace the previous nested Category/module/chapter/lesson types and mock data with a compact DashboardCourse interface containing id, title, slug, path, and courseSlug.
  • Export a dashboardCourses array for the two current courses with their corresponding paths and course slugs.
  • Remove the getCategoryProgress helper in favor of stats-driven progress elsewhere.
apps/platform/lib/course-data.ts
Extract learning dashboard stats fetching and transformation logic into a reusable library function and reuse it in the API route and pages.
  • Create learning-dashboard-stats.ts defining ModuleStat and CourseStat types plus Sanity queries for architecture and production-grade modules.
  • Add helper types and functions (SanityModuleRow, buildHref, mapModules) to normalize Sanity rows into ModuleStat with firstLessonHref.
  • Implement getLearningDashboardStats to fetch both course queries in parallel, compute totalLessons and moduleCount, and return a CourseStat[] for the two courses.
  • Update the API route for /api/dashboard/stats to delegate to getLearningDashboardStats rather than duplicating the query and transformation logic inline, deleting the prior local types, queries, and helper functions from the route.
apps/platform/lib/learning-dashboard-stats.ts
apps/platform/app/(backend)/api/dashboard/stats/route.ts

Possibly linked issues

  • #(unknown): PR implements analytics-backed dashboard and adds /learn/dashboard route, matching the requested analytics section and relocation.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR refactors the learning dashboard to centralize Sanity data fetching. A new getLearningDashboardStats() helper encapsulates Sanity queries, dashboard pages convert to async server components that fetch stats and pass them as props, the component becomes prop-driven with runtime completion-data fetching, and course metadata shifts from mock hierarchical data to simple routing metadata.

Changes

Dashboard Server-Side Data Flow

Layer / File(s) Summary
Learning dashboard stats data contract and queries
apps/platform/lib/learning-dashboard-stats.ts
Introduces ModuleStat and CourseStat interfaces and implements getLearningDashboardStats() with GROQ queries to fetch module/lesson metadata from Sanity for two course categories, computing lesson counts and first-lesson hrefs.
Dashboard course routing metadata
apps/platform/lib/course-data.ts
Replaces mock hierarchical course model with DashboardCourse interface containing route path and courseSlug, enabling the dashboard component to iterate courses and join with fetched CourseStat data.
DashboardAlt component prop-driven refactor
apps/platform/components/DashboardAlt.tsx
DashboardAltPage accepts courseStats prop, builds a courseSlug lookup, fetches completed lesson counts per course via courseProgressClient, computes overall progress and per-course metrics, and renders cards that navigate via course paths and display progress from combined stats and completion data.
Dashboard pages server-side rendering
apps/platform/app/(platform)/dashboard/page.tsx, apps/platform/app/(platform)/learn/dashboard/page.tsx
DashboardPage and LearnDashboardPage convert from client components to async server components that fetch courseStats via getLearningDashboardStats() and pass the result to DashboardAltPage, removing prior client-side progress calculations and "use client" directives.
API stats endpoint refactoring
apps/platform/app/(backend)/api/dashboard/stats/route.ts
The /api/dashboard/stats route handler delegates to getLearningDashboardStats() instead of performing inline Sanity queries, removing embedded stats logic and centralizing data fetching.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant DashboardServer as DashboardPage (Server)
    participant StatsHelper as getLearningDashboardStats
    participant Sanity
    participant Component as DashboardAlt (Client)
    participant ProgressAPI as courseProgressClient
    
    Browser->>DashboardServer: request dashboard
    DashboardServer->>StatsHelper: await getLearningDashboardStats()
    StatsHelper->>Sanity: fetch modules for both courses
    Sanity-->>StatsHelper: module data
    StatsHelper-->>DashboardServer: CourseStat[]
    DashboardServer->>Browser: render DashboardAlt with courseStats prop
    Component->>ProgressAPI: fetch completed lessons per course
    ProgressAPI-->>Component: completedByCourse map
    Component->>Component: compute overall/per-course progress
    Component->>Browser: render dashboard with progress metrics
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ramu-narasinga/thinkthroo#268: Both PRs modify the same learning dashboard UI stack—especially apps/platform/components/DashboardAlt.tsx and the dashboard/learn dashboard routes—but they implement different data sources/contracts, so the changes are directly related at the code level.
  • ramu-narasinga/thinkthroo#182: Both PRs touch apps/platform/app/(backend)/api/dashboard/stats/route.ts, with the retrieved PR adding the inline Sanity GROQ-based stats assembly and the main PR refactoring that handler to delegate to getLearningDashboardStats() instead.

Poem

🐰 A dashboard once scattered, now unified and bright,
Stats centralized in one helper, fetched just right,
Server components soar, client side takes flight,
Completion counts dance with stats—a metric delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'dashboard-routing-fix' directly summarizes the main change: converting dashboard pages from client components to server components that fetch stats via a centralized helper, fixing routing/data-fetching issues.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/298-dashboard-404

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Fetching per-course completion data in DashboardAltPage via courseProgressClient.getCompletedLessons on the client for every course can be expensive; consider moving this aggregation into a server-side helper (or extending getLearningDashboardStats) so the page can render with progress data from a single server call.
  • Since getLearningDashboardStats exposes firstLessonHref per module, consider using that (or a similar server-derived URL) for the "Start Learning" link instead of hard-coding path in dashboardCourses, so routing stays aligned with CMS data and avoids manual path duplication.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Fetching per-course completion data in `DashboardAltPage` via `courseProgressClient.getCompletedLessons` on the client for every course can be expensive; consider moving this aggregation into a server-side helper (or extending `getLearningDashboardStats`) so the page can render with progress data from a single server call.
- Since `getLearningDashboardStats` exposes `firstLessonHref` per module, consider using that (or a similar server-derived URL) for the "Start Learning" link instead of hard-coding `path` in `dashboardCourses`, so routing stays aligned with CMS data and avoids manual path duplication.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot left a comment

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.

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 `@apps/platform/components/DashboardAlt.tsx`:
- Around line 25-35: The current useEffect uses Promise.all over
dashboardCourses so a single rejected courseProgressClient.getCompletedLessons
call rejects the whole batch and leaves completedByCourse unset; change the
logic to use Promise.allSettled over the mapped promises (inside the useEffect),
then iterate the settled results to build entries for successful ones (and
optionally log failures) and finally call
setCompletedByCourse(Object.fromEntries(entries)) so partial successes populate
completedByCourse while individual failures are handled per-course.

In `@apps/platform/lib/learning-dashboard-stats.ts`:
- Around line 18-33: ARCHITECTURE_STATS_QUERY and PRODUCTION_STATS_QUERY
currently project slug and chapterSlug as Sanity slug objects but
SanityModuleRow and buildHref() expect string slugs; update both queries so the
module slug and chapterSlug use slug.current (same pattern already used for
lessonSlug) so they return string values instead of objects, ensuring fields
named "slug" and "chapterSlug" are projected as slug.current to avoid `[object
Object]` in generated URLs.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c044578-55b2-4f3e-89a8-118ca2cda9d5

📥 Commits

Reviewing files that changed from the base of the PR and between 58aec63 and 2e31540.

📒 Files selected for processing (6)
  • apps/platform/app/(backend)/api/dashboard/stats/route.ts
  • apps/platform/app/(platform)/dashboard/page.tsx
  • apps/platform/app/(platform)/learn/dashboard/page.tsx
  • apps/platform/components/DashboardAlt.tsx
  • apps/platform/lib/course-data.ts
  • apps/platform/lib/learning-dashboard-stats.ts

Comment on lines +25 to +35
useEffect(() => {
Promise.all(
dashboardCourses.map(async (course) => {
const rows = await courseProgressClient.getCompletedLessons({
courseSlug: course.courseSlug,
})
return [course.courseSlug, rows.length] as const
}),
)
.then((entries) => setCompletedByCourse(Object.fromEntries(entries)))
.catch(console.error)

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 | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does JavaScript Promise.all reject as soon as one input promise rejects, and does Promise.allSettled wait for all input promises to settle?

💡 Result:

Yes, both of your premises are correct. Promise.all follows a fail-fast behavior: it rejects immediately as soon as any of the input promises reject, with the reason of that first rejection [1][2]. It does not wait for the other pending promises to settle [3][4]. Promise.allSettled waits for all input promises to settle, regardless of whether they fulfill or reject [5][1]. Once all promises have settled, it fulfills with an array of objects describing the outcome of each promise [5].

Citations:


Use Promise.allSettled to prevent one failed course from zeroing the entire dashboard.

With Promise.all, a single rejected getCompletedLessons() call rejects the entire operation, leaving completedByCourse empty and falling back to 0% progress everywhere. Switch to Promise.allSettled to wait for all courses and preserve successful results while handling per-course failures individually.

🔧 Proposed fix
   useEffect(() => {
-    Promise.all(
+    Promise.allSettled(
       dashboardCourses.map(async (course) => {
         const rows = await courseProgressClient.getCompletedLessons({
           courseSlug: course.courseSlug,
         })
         return [course.courseSlug, rows.length] as const
       }),
     )
-      .then((entries) => setCompletedByCourse(Object.fromEntries(entries)))
-      .catch(console.error)
+      .then((results) => {
+        const entries = results.flatMap((result, index) => {
+          if (result.status === "fulfilled") return [result.value]
+
+          console.error(
+            "Failed to load progress for",
+            dashboardCourses[index].courseSlug,
+            result.reason,
+          )
+          return []
+        })
+
+        setCompletedByCourse(Object.fromEntries(entries))
+      })
   }, [])
🤖 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 `@apps/platform/components/DashboardAlt.tsx` around lines 25 - 35, The current
useEffect uses Promise.all over dashboardCourses so a single rejected
courseProgressClient.getCompletedLessons call rejects the whole batch and leaves
completedByCourse unset; change the logic to use Promise.allSettled over the
mapped promises (inside the useEffect), then iterate the settled results to
build entries for successful ones (and optionally log failures) and finally call
setCompletedByCourse(Object.fromEntries(entries)) so partial successes populate
completedByCourse while individual failures are handled per-course.

Comment on lines +18 to +33
const ARCHITECTURE_STATS_QUERY = `
*[_type == "module" && "Codebase Architecture" in categories[]->title]{
"slug": slug,
title,
"lessonCount": count(*[_type == "codebaseArchitecture" && ^._id in chapter[]->module[]._ref]),
"chapter": *[
_type == "chapter" && references(^._id) && order == 1
][0]{
"chapterSlug": slug,
"lesson": *[
_type == "codebaseArchitecture" && references(^._id) && order == 1
][0]{
"lessonSlug": slug.current
}
}
}

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether module/chapter slugs are Sanity slug fields and compare them with the current projections.
set -e

echo "== Schema fields =="
rg -n -C3 'name:\s*"module"|name:\s*"chapter"|name:\s*"slug"|type:\s*"slug"' --glob '!**/node_modules/**'

echo
echo "== Dashboard query projections =="
rg -n -C2 '"slug":\s*slug(\.current)?|"chapterSlug":\s*slug(\.current)?' apps/platform/lib/learning-dashboard-stats.ts

Repository: ramu-narasinga/thinkthroo

Length of output: 1308


🏁 Script executed:

#!/bin/bash
# Find buildHref function definition
rg -n "buildHref" --glob '*.ts' --glob '*.tsx' -A 5 | head -40

Repository: ramu-narasinga/thinkthroo

Length of output: 1051


🏁 Script executed:

#!/bin/bash
# Check the complete learning-dashboard-stats.ts file to see buildHref usage
wc -l apps/platform/lib/learning-dashboard-stats.ts

Repository: ramu-narasinga/thinkthroo

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Read the complete file to understand the full context
cat -n apps/platform/lib/learning-dashboard-stats.ts

Repository: ramu-narasinga/thinkthroo

Length of output: 3873


Use slug.current in both dashboard stats queries to match the SanityModuleRow type expectations.

The ARCHITECTURE_STATS_QUERY and PRODUCTION_STATS_QUERY queries project slug and chapterSlug as Sanity slug type objects, but SanityModuleRow expects these as strings (lines 55, 59). When these objects are passed to buildHref() and stringified in the template literal (line 70), they will serialize as [object Object], producing malformed URLs. The lessonSlug field already uses slug.current correctly; apply the same pattern to module and chapter slugs.

Fix required
 const ARCHITECTURE_STATS_QUERY = `
 *[_type == "module" && "Codebase Architecture" in categories[]->title]{
-  "slug": slug,
+  "slug": slug.current,
   title,
   "lessonCount": count(*[_type == "codebaseArchitecture" && ^._id in chapter[]->module[]._ref]),
   "chapter": *[
     _type == "chapter" && references(^._id) && order == 1
   ][0]{
-    "chapterSlug": slug,
+    "chapterSlug": slug.current,
     "lesson": *[
       _type == "codebaseArchitecture" && references(^._id) && order == 1
     ][0]{
       "lessonSlug": slug.current
     }
   }
 }
 `;
 
 const PRODUCTION_STATS_QUERY = `
 *[_type == "module" && "Production Grade Projects" in categories[]->title]{
-  "slug": slug,
+  "slug": slug.current,
   title,
   "lessonCount": count(*[_type == "productionGradeProjects" && ^._id in chapter[]->module[]._ref]),
   "chapter": *[
     _type == "chapter" && references(^._id) && order == 1
   ][0]{
-    "chapterSlug": slug,
+    "chapterSlug": slug.current,
     "lesson": *[
       _type == "productionGradeProjects" && references(^._id) && order == 1
     ][0]{
       "lessonSlug": slug.current
     }
   }
 }
 `;

Also applies to lines 36–51 (PRODUCTION_STATS_QUERY).

🤖 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 `@apps/platform/lib/learning-dashboard-stats.ts` around lines 18 - 33,
ARCHITECTURE_STATS_QUERY and PRODUCTION_STATS_QUERY currently project slug and
chapterSlug as Sanity slug objects but SanityModuleRow and buildHref() expect
string slugs; update both queries so the module slug and chapterSlug use
slug.current (same pattern already used for lessonSlug) so they return string
values instead of objects, ensuring fields named "slug" and "chapterSlug" are
projected as slug.current to avoid `[object Object]` in generated URLs.

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.

1 participant