Skip to content

[feat]: homepage upcoming course rounds#1770

Merged
alyssia-x merged 6 commits into
masterfrom
alyssia-homepage-courses-schedule
Dec 8, 2025
Merged

[feat]: homepage upcoming course rounds#1770
alyssia-x merged 6 commits into
masterfrom
alyssia-homepage-courses-schedule

Conversation

@alyssia-x

@alyssia-x alyssia-x commented Dec 7, 2025

Copy link
Copy Markdown
Collaborator

Description

New Upcoming Rounds UpcomingRounds.tsx section on the homepage, displaying upcoming intensive and part-time course rounds fetched from the database. When enough rounds are available, the component will display:

  • The 3 earliest courses for both intensive and part time from 3 different courses (We want to show the user that BlueDot offers a variety of courses when available)
  • If one course always has the 2-3 earliest rounds, pick the earliest one and prioritize displaying the next-earliest round from a different course, and so on
  • Self-paced courses never show up on the course rounds homepage

Otherwise, round display follows the same sorting logic mentioned in #1758's PR description.

Some notes on design:

  • For the homepage only, an interpunct was requested for formatting course dates, e.g "AGI Strategy · 15 Dec – 19 Dec". It isn't added for implementations on course landers or the /courses page.
  • For mobile, INTENSIVE and PART-TIME headers have different alignment, this is not intentional, the intent was for them to be centered so they are centered in this implementation.

Other Notes/TODOs
I didn't want to focus on abstracting for this PR because the code for courses is going to change soon in a follow-up PR (e.g #1775). Since this is a P1, just want to get this out and will de-duplicate later :)

Issue

Closes #1730

Developer checklist

Screenshot

Desktop / Tablet

upcoming-rounds-desktop

upcoming-rounds-680

Mobile (320)

upcoming-rounds-320

@coderabbitai

coderabbitai Bot commented Dec 7, 2025

Copy link
Copy Markdown
Contributor

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new UpcomingRounds homepage section: a React component that queries a new TRPC endpoint to retrieve, partition, and render upcoming course rounds (intensive vs part-time), plus Storybook stories and server-side aggregation logic in the course-rounds router.

Changes

Cohort / File(s) Change Summary
Frontend component & story
apps/website/src/components/homepage/UpcomingRounds.tsx, apps/website/src/components/homepage/UpcomingRounds.stories.tsx
New UpcomingRounds React component with TRPC data fetching, partitioning into intensive/part-time, helper selection logic (selectDiverseRounds), responsive rendering, accessible links, and a Storybook story with MSW mocks.
Homepage integration
apps/website/src/pages/index.tsx
Imports and renders <UpcomingRounds /> on the homepage (inserted after CourseSection).
Server API (router)
apps/website/src/server/routers/course-rounds.ts
Adds getAllUpcomingRounds TRPC procedure that queries active courses and rounds, filters by deadline, computes/enriches date ranges and metadata, sorts and groups rounds into intense and partTime buckets.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser (Homepage)
  participant UI as UpcomingRounds Component
  participant TRPC as trpc.courseRounds.getAllUpcomingRounds
  participant DB as Database (Prisma)
  Note over UI,TRPC: User loads homepage → component mounts
  Browser->>UI: render
  UI->>TRPC: query getAllUpcomingRounds()
  TRPC->>DB: fetch active courses & rounds (apply filters)
  DB-->>TRPC: course & round records
  TRPC->>TRPC: compute date ranges, enrich metadata, sort, group
  TRPC-->>UI: return { intense, partTime }
  UI->>Browser: render grouped round cards / links
  Note right of Browser: Storybook MSW intercepts the TRPC call in stories
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing careful review:
    • getAllUpcomingRounds logic: filtering window, date calculations, grouping and sorting correctness.
    • selectDiverseRounds / fallback behavior to ensure intended diversity and ordering.
    • URL generation and accessibility attributes in UpcomingRounds.tsx.
    • Storybook MSW handler to ensure it faithfully mirrors server shape.

Possibly related PRs

Suggested reviewers

  • marn-in-prod

Poem

🐰 I hopped in code to fetch the days,
Rounds sorted neat in cheerful arrays.
Intensive, part-time — all in view,
Cards that click and links that woo.
Homepage hops — a tiny code hooray! 🌱

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[feat]: homepage upcoming course rounds' clearly summarizes the main change: adding a new UpcomingRounds component to the homepage.
Description check ✅ Passed The description comprehensively covers the feature, design notes, implementation rationale, and completes all developer checklist items with screenshots.
Linked Issues check ✅ Passed The PR implementation matches issue #1730 requirements: displays upcoming intensive/part-time rounds with diverse course selection, excludes self-paced courses, and includes design-consistent visual formatting.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the UpcomingRounds feature: the new component, Storybook story, TRPC router procedure, and homepage integration.
✨ 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 alyssia-homepage-courses-schedule

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.

@alyssia-x alyssia-x marked this pull request as ready for review December 8, 2025 20:09

@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: 1

🧹 Nitpick comments (4)
apps/website/src/server/routers/course-rounds.ts (2)

182-226: Consider extracting shared helper functions to reduce duplication.

The formatDate and formatDateRange functions are duplicated verbatim from getRoundsForCourse (lines 52-100). Additionally, the sorting logic (lines 261-276) and grouping logic (lines 279-282) are also duplicated.

Extract these into shared helper functions defined once at the module level or in a utility file.

+// Move to top of file, before router definition
+const formatDate = (isoDate: string) => {
+  const date = new Date(isoDate);
+  const day = String(date.getUTCDate()).padStart(2, '0');
+  const month = date.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' });
+  return `${day} ${month}`;
+};
+
+const formatDateRange = (
+  firstDate: string | null,
+  lastDate: string | null,
+  numberOfUnits: number | null,
+  intensity: string | null,
+  separator = ' - ',
+) => {
+  if (!firstDate) return null;
+  const first = new Date(firstDate);
+  let computedLast: Date | null = null;
+  if (numberOfUnits && numberOfUnits > 0) {
+    const isPartTime = intensity?.toLowerCase() === 'part-time';
+    const daysToAdd = isPartTime ? (numberOfUnits * 7) - 1 : numberOfUnits - 1;
+    computedLast = new Date(first.getTime() + daysToAdd * 24 * 60 * 60 * 1000);
+  } else if (lastDate) {
+    computedLast = new Date(lastDate);
+  } else {
+    return null;
+  }
+  const firstDay = String(first.getUTCDate()).padStart(2, '0');
+  const lastDay = String(computedLast.getUTCDate()).padStart(2, '0');
+  const firstMonth = first.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' });
+  const lastMonth = computedLast.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' });
+  return `${firstDay} ${firstMonth}${separator}${lastDay} ${lastMonth}`;
+};

Then call with formatDateRange(..., ' - ') for course pages and formatDateRange(..., ' – ') for homepage (en-dash).


162-165: Hardcoded slug exclusion is fragile.

The 'future-of-ai' slug is a magic string. If this course slug changes in the CMS/database, this filter will silently break.

Consider defining this as a named constant or adding a database field (e.g., excludeFromHomepage) to make the exclusion explicit and maintainable.

+const EXCLUDED_COURSE_SLUGS = ['future-of-ai'] as const;
+
 // Filter out self-paced courses (cadence = 'self-paced' or slug = 'future-of-ai')
 const courses = allCourses.filter(
-  (course) => course.cadence?.toLowerCase() !== 'self-paced' && course.slug !== 'future-of-ai',
+  (course) => course.cadence?.toLowerCase() !== 'self-paced' && !EXCLUDED_COURSE_SLUGS.includes(course.slug as typeof EXCLUDED_COURSE_SLUGS[number]),
 );
apps/website/src/components/homepage/UpcomingRounds.tsx (1)

188-196: Fallback to round.id for courseId defeats diversity logic.

If round.courseId is nullish, using round.id as the fallback means every such round would be treated as unique, potentially allowing multiple rounds from the same course to be selected (if courseId is missing from all).

Per the router code, rounds without valid courseId are already filtered out (line 234-236 in course-rounds.ts), so this fallback may be unreachable. Consider removing it or adding a comment explaining the defensive nature.

   for (const round of rounds) {
     if (selectedRounds.length >= 3) break;

-    const courseId = round.courseId ?? round.id;
+    // courseId is guaranteed non-null from router filtering
+    const courseId = round.courseId;
     if (!seenCourses.has(courseId)) {
       selectedRounds.push(round);
       seenCourses.add(courseId);
apps/website/src/components/homepage/UpcomingRounds.stories.tsx (1)

5-16: Consider reusing the inferred Round type from the router.

The local Round type duplicates the shape from the router. If the router output changes, this type may drift. You can import and reuse the inferred type for consistency.

+import type { inferRouterOutputs } from '@trpc/server';
+import type { AppRouter } from '../../server/routers/_app';
+
+type RouterOutput = inferRouterOutputs<AppRouter>;
+type Round = RouterOutput['courseRounds']['getAllUpcomingRounds']['intense'][number];
-type Round = {
-  id: string;
-  courseId: string;
-  courseTitle: string;
-  courseSlug: string;
-  intensity: string;
-  applicationDeadline: string;
-  applicationDeadlineRaw: string;
-  firstDiscussionDateRaw: string;
-  dateRange: string;
-  numberOfUnits: number;
-};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93a5995 and e99e6ad.

📒 Files selected for processing (4)
  • apps/website/src/components/homepage/UpcomingRounds.stories.tsx (1 hunks)
  • apps/website/src/components/homepage/UpcomingRounds.tsx (1 hunks)
  • apps/website/src/pages/index.tsx (2 hunks)
  • apps/website/src/server/routers/course-rounds.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:416-418
Timestamp: 2025-12-05T22:16:53.272Z
Learning: In apps/website/src/pages/courses/index.tsx, the CourseCard component fetches rounds data via trpc.courseRounds.getRoundsForCourse.useQuery even though the same data is fetched in useSortedCourses. This is intentional because tRPC (which uses React Query) automatically deduplicates network requests with identical query keys, so only one network request is made and the result is shared across components via the query cache.
📚 Learning: 2025-06-19T08:37:04.853Z
Learnt from: tolgadur
Repo: bluedotimpact/bluedot PR: 1029
File: apps/website/src/components/homepage/CourseSection.test.tsx:37-81
Timestamp: 2025-06-19T08:37:04.853Z
Learning: In the BlueDot codebase, the `mockCourse` helper function in test files provides default values for all Course properties including `isFeatured: false` and `isNew: false`. When creating mock course objects, only properties that differ from the defaults need to be explicitly specified in the overrides object.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.stories.tsx
📚 Learning: 2025-12-05T22:16:53.272Z
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:416-418
Timestamp: 2025-12-05T22:16:53.272Z
Learning: In apps/website/src/pages/courses/index.tsx, the CourseCard component fetches rounds data via trpc.courseRounds.getRoundsForCourse.useQuery even though the same data is fetched in useSortedCourses. This is intentional because tRPC (which uses React Query) automatically deduplicates network requests with identical query keys, so only one network request is made and the result is shared across components via the query cache.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.stories.tsx
  • apps/website/src/components/homepage/UpcomingRounds.tsx
  • apps/website/src/pages/index.tsx
  • apps/website/src/server/routers/course-rounds.ts
📚 Learning: 2025-11-03T01:57:21.418Z
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1554
File: apps/website/src/components/homepage/OurCommunitySection.tsx:11-17
Timestamp: 2025-11-03T01:57:21.418Z
Learning: In apps/website/src/components/homepage/OurCommunitySection.tsx, the CommunityMember type is intentionally duplicated (also exists in apps/website/src/components/lander/components/CommunityMembersSubSection.tsx) to keep the component changeable for potential future refactoring.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.tsx
  • apps/website/src/pages/index.tsx
🧬 Code graph analysis (4)
apps/website/src/components/homepage/UpcomingRounds.stories.tsx (2)
apps/website/src/components/homepage/UpcomingRounds.tsx (1)
  • UpcomingRounds (21-84)
apps/website/src/__tests__/trpcMswSetup.browser.ts (1)
  • trpcStorybookMsw (10-16)
apps/website/src/components/homepage/UpcomingRounds.tsx (5)
apps/website/src/server/routers/_app.ts (1)
  • AppRouter (36-36)
apps/website/src/components/lander/course-content/AgiStrategyContent.tsx (1)
  • AGI_STRATEGY_APPLICATION_URL (15-15)
apps/website/src/components/lander/course-content/BioSecurityContent.tsx (1)
  • BIOSECURITY_APPLICATION_URL (15-15)
apps/website/src/components/lander/course-content/TechnicalAiSafetyContent.tsx (1)
  • TECHNICAL_AI_SAFETY_APPLICATION_URL (16-16)
apps/website/src/components/lander/course-content/AiGovernanceContent.tsx (1)
  • AI_GOVERNANCE_APPLICATION_URL (12-12)
apps/website/src/pages/index.tsx (1)
apps/website/src/components/homepage/UpcomingRounds.tsx (1)
  • UpcomingRounds (21-84)
apps/website/src/server/routers/course-rounds.ts (3)
apps/website/src/server/trpc.ts (1)
  • publicProcedure (126-126)
libraries/db/src/index.ts (2)
  • courseTable (9-9)
  • applicationsRoundTable (23-23)
libraries/db/src/schema.ts (2)
  • courseTable (61-146)
  • applicationsRoundTable (557-591)
🔇 Additional comments (5)
apps/website/src/server/routers/course-rounds.ts (1)

149-285: Approve the new getAllUpcomingRounds endpoint.

The implementation correctly:

  • Filters active courses and excludes self-paced cadence
  • Applies the 12-hour deadline threshold for timezone tolerance
  • Enriches rounds with course metadata and filters orphaned rounds
  • Sorts by start date then duration
  • Groups by intensity type

The logic aligns well with the PR requirements.

apps/website/src/components/homepage/UpcomingRounds.tsx (2)

21-84: Good implementation of the UpcomingRounds component.

The component correctly:

  • Displays a loading skeleton while fetching
  • Returns null when no data or no rounds exist
  • Uses the selectDiverseRounds helper to pick diverse courses
  • Renders separate groups for intensive and part-time rounds
  • Provides accessible links with appropriate aria-label and target="_blank" attributes

14-19: Add fallback URL logic for unmapped courses in UpcomingRounds.

The map COURSE_APPLICATION_URLS has no fallback if a course slug is missing—it defaults to an empty string, breaking the apply link. Unlike apps/website/src/pages/courses/index.tsx, which falls back to course.detailsUrl || course.path, this component offers no safety net. If a course is added to the database and appears in upcoming rounds without being added to the map, users will encounter broken apply links.

Consider either:

  • Adding a fallback to course.detailsUrl or course.path (passed via the round object)
  • Adding a database constraint or validation to prevent courses without application URLs from appearing in rounds
apps/website/src/pages/index.tsx (1)

9-9: LGTM!

The integration correctly imports and positions the UpcomingRounds component after CourseSection and before the divider, as specified in the PR objectives.

Also applies to: 90-90

apps/website/src/components/homepage/UpcomingRounds.stories.tsx (1)

96-121: Story setup looks good.

The Storybook configuration correctly:

  • Uses fullscreen layout for realistic preview
  • Provides descriptive component documentation
  • Sets up MSW handlers to mock the tRPC endpoint
  • Returns properly structured mock data matching the API contract

Comment on lines +118 to +121
const RoundItem = ({ round }: RoundItemProps) => {
const applicationUrl = COURSE_APPLICATION_URLS[round.courseSlug] || '';
const separator = applicationUrl.includes('?') ? '&' : '?';
const applyUrl = `${applicationUrl}${separator}prefill_%5B%3E%5D%20Round=${round.id}`;

@coderabbitai coderabbitai Bot Dec 8, 2025

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

Missing course slug results in broken apply links.

When round.courseSlug isn't in COURSE_APPLICATION_URLS, applicationUrl becomes an empty string, producing a malformed URL like ?prefill_%5B%3E%5D%20Round=.... Users clicking "Apply now" will navigate to an invalid page.

Consider filtering out rounds without valid application URLs, or falling back to a generic courses page.

 const RoundItem = ({ round }: RoundItemProps) => {
   const applicationUrl = COURSE_APPLICATION_URLS[round.courseSlug] || '';
+  
+  // Skip rendering if no valid application URL exists
+  if (!applicationUrl) {
+    console.warn(`Missing application URL for course: ${round.courseSlug}`);
+    return null;
+  }
+  
   const separator = applicationUrl.includes('?') ? '&' : '?';
   const applyUrl = `${applicationUrl}${separator}prefill_%5B%3E%5D%20Round=${round.id}`;

Alternatively, filter in selectDiverseRounds or before rendering.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yep, this is temporary because we're about to sync the application URL from airtable (e.g #1775)

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.

@alyssia-x, thanks for clarifying! That makes sense—syncing the application URLs from Airtable in #1775 will handle this comprehensively. I appreciate the context about the upcoming change. 👍


🧠 Learnings used
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:416-418
Timestamp: 2025-12-05T22:16:53.272Z
Learning: In apps/website/src/pages/courses/index.tsx, the CourseCard component fetches rounds data via trpc.courseRounds.getRoundsForCourse.useQuery even though the same data is fetched in useSortedCourses. This is intentional because tRPC (which uses React Query) automatically deduplicates network requests with identical query keys, so only one network request is made and the result is shared across components via the query cache.

Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:53-53
Timestamp: 2025-12-05T22:16:07.019Z
Learning: In apps/website/src/pages/courses/index.tsx, the `isSelfPacedCourse` function intentionally includes a hardcoded check for `course.slug === 'future-of-ai'` alongside the `course.cadence?.toLowerCase() === 'self-paced'` check. This hardcoded slug check should be retained.

@greptile-apps

greptile-apps Bot commented Dec 8, 2025

Copy link
Copy Markdown
Contributor

Greptile Overview

Greptile Summary

Implements a new UpcomingRounds section on the homepage displaying the 3 earliest intensive and part-time course rounds, prioritizing diversity across different courses. The component includes proper loading states, responsive design, and accessibility features.

Key changes:

  • New getAllUpcomingRounds tRPC endpoint fetches all upcoming rounds across courses
  • Date formatting uses en-dash () for homepage vs hyphen (-) for course landers
  • selectDiverseRounds() function prioritizes showing rounds from different courses
  • Storybook stories added for component testing

Issues found:

  • Governance course application URL missing from /courses page but present on homepage, creating inconsistency
  • Significant code duplication (~120 lines) in router between getRoundsForCourse and getAllUpcomingRounds endpoints
  • Defensive round.courseId ?? round.id fallback uses incorrect type (round ID instead of course ID)

Confidence Score: 3/5

  • Safe to merge with some issues that should be addressed in follow-up
  • The implementation is functionally correct with good structure and testing, but has cross-page inconsistency in the governance URL mapping and significant code duplication in the router that should be refactored. The course diversity logic has a defensive fallback that doesn't make logical sense but won't cause runtime errors since courseId is always present.
  • Pay attention to apps/website/src/server/routers/course-rounds.ts (code duplication) and verify governance course apply links work correctly on both homepage and /courses page

Important Files Changed

File Analysis

Filename Score Overview
apps/website/src/components/homepage/UpcomingRounds.tsx 4/5 New component displaying upcoming course rounds with responsive design, proper loading states, and accessibility features. Missing governance course URL handling on /courses page creates inconsistency.
apps/website/src/components/homepage/UpcomingRounds.stories.tsx 5/5 Well-structured Storybook stories with comprehensive mock data for testing the UpcomingRounds component in different states.
apps/website/src/server/routers/course-rounds.ts 3/5 New getAllUpcomingRounds endpoint with significant code duplication from existing getRoundsForCourse procedure. Date formatting logic differs only by en-dash vs hyphen separator.
apps/website/src/pages/index.tsx 5/5 Simple integration of UpcomingRounds component into homepage with proper placement after CourseSection.

Sequence Diagram

sequenceDiagram
    participant User
    participant HomePage
    participant UpcomingRounds
    participant tRPC
    participant CourseRoundsRouter
    participant Database

    User->>HomePage: Visit homepage
    HomePage->>UpcomingRounds: Render component
    UpcomingRounds->>tRPC: getAllUpcomingRounds.useQuery()
    tRPC->>CourseRoundsRouter: getAllUpcomingRounds()
    
    CourseRoundsRouter->>Database: SELECT active courses
    Database-->>CourseRoundsRouter: Return courses (excl. self-paced)
    
    CourseRoundsRouter->>Database: SELECT upcoming rounds (deadline > now - 12h)
    Database-->>CourseRoundsRouter: Return all rounds
    
    CourseRoundsRouter->>CourseRoundsRouter: Enrich rounds with course info
    CourseRoundsRouter->>CourseRoundsRouter: Format dates with en-dash
    CourseRoundsRouter->>CourseRoundsRouter: Sort by start date, then duration
    CourseRoundsRouter->>CourseRoundsRouter: Group by intensity (intensive/part-time)
    
    CourseRoundsRouter-->>tRPC: Return grouped rounds
    tRPC-->>UpcomingRounds: Return data
    
    UpcomingRounds->>UpcomingRounds: selectDiverseRounds() - pick up to 3 from different courses
    UpcomingRounds->>User: Display rounds with apply links
Loading

@greptile-apps greptile-apps 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.

4 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment thread apps/website/src/server/routers/course-rounds.ts
Comment on lines +14 to +19
const COURSE_APPLICATION_URLS: Record<string, string> = {
'agi-strategy': AGI_STRATEGY_APPLICATION_URL,
biosecurity: BIOSECURITY_APPLICATION_URL,
'technical-ai-safety': TECHNICAL_AI_SAFETY_APPLICATION_URL,
governance: AI_GOVERNANCE_APPLICATION_URL,
};

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.

logic: missing governance key in the /courses page COURSE_APPLICATION_URLS map (only has 3 courses), but present here (has 4 courses). this creates inconsistency - governance rounds will have working apply links on homepage but broken ones on /courses page

suggest adding the missing entry to /courses/index.tsx or extracting this constant to a shared location

Context Used: Context from dashboard - Consider refactoring complex lines of code for better readability and to handle potential edge cases... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/website/src/components/homepage/UpcomingRounds.tsx
Line: 14:19

Comment:
**logic:** missing `governance` key in the `/courses` page `COURSE_APPLICATION_URLS` map (only has 3 courses), but present here (has 4 courses). this creates inconsistency - governance rounds will have working apply links on homepage but broken ones on `/courses` page

suggest adding the missing entry to `/courses/index.tsx` or extracting this constant to a shared location

**Context Used:** Context from `dashboard` - Consider refactoring complex lines of code for better readability and to handle potential edge cases... ([source](https://app.greptile.com/review/custom-context?memory=331d942d-90e3-4857-8dba-26213d1a47a6))

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yeah governance dates / ability to apply was just added so a PR will be made for that too

Comment thread apps/website/src/components/homepage/UpcomingRounds.tsx Outdated

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e99e6ad and 897ad67.

📒 Files selected for processing (1)
  • apps/website/src/components/homepage/UpcomingRounds.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:416-418
Timestamp: 2025-12-05T22:16:53.272Z
Learning: In apps/website/src/pages/courses/index.tsx, the CourseCard component fetches rounds data via trpc.courseRounds.getRoundsForCourse.useQuery even though the same data is fetched in useSortedCourses. This is intentional because tRPC (which uses React Query) automatically deduplicates network requests with identical query keys, so only one network request is made and the result is shared across components via the query cache.
📚 Learning: 2025-12-05T22:16:53.272Z
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:416-418
Timestamp: 2025-12-05T22:16:53.272Z
Learning: In apps/website/src/pages/courses/index.tsx, the CourseCard component fetches rounds data via trpc.courseRounds.getRoundsForCourse.useQuery even though the same data is fetched in useSortedCourses. This is intentional because tRPC (which uses React Query) automatically deduplicates network requests with identical query keys, so only one network request is made and the result is shared across components via the query cache.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.tsx
📚 Learning: 2025-11-03T01:57:21.418Z
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1554
File: apps/website/src/components/homepage/OurCommunitySection.tsx:11-17
Timestamp: 2025-11-03T01:57:21.418Z
Learning: In apps/website/src/components/homepage/OurCommunitySection.tsx, the CommunityMember type is intentionally duplicated (also exists in apps/website/src/components/lander/components/CommunityMembersSubSection.tsx) to keep the component changeable for potential future refactoring.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.tsx
📚 Learning: 2025-12-05T22:16:07.019Z
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:53-53
Timestamp: 2025-12-05T22:16:07.019Z
Learning: In apps/website/src/pages/courses/index.tsx, the `isSelfPacedCourse` function intentionally includes a hardcoded check for `course.slug === 'future-of-ai'` alongside the `course.cadence?.toLowerCase() === 'self-paced'` check. This hardcoded slug check should be retained.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.tsx
📚 Learning: 2025-11-18T09:31:50.904Z
Learnt from: Will-Howard
Repo: bluedotimpact/bluedot PR: 1642
File: apps/website/src/components/courses/GroupDiscussionBanner.tsx:149-189
Timestamp: 2025-11-18T09:31:50.904Z
Learning: In GroupDiscussionBanner (apps/website/src/components/courses/GroupDiscussionBanner.tsx), Will-Howard prefers to keep buttons like "Open discussion doc" and "Message group" visible even when their underlying links (discussionDocLink, slackChannelLink) are empty. This design choice helps catch configuration errors early because users will report non-functional buttons, whereas hidden buttons might mask missing data without anyone noticing.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.tsx
📚 Learning: 2025-10-07T11:28:47.742Z
Learnt from: Will-Howard
Repo: bluedotimpact/bluedot PR: 1428
File: apps/website/src/components/courses/GroupSwitchModal.test.tsx:229-240
Timestamp: 2025-10-07T11:28:47.742Z
Learning: In GroupSwitchModal component (apps/website/src/components/courses/GroupSwitchModal.tsx), when switching groups for one unit (temporary switch), the `oldGroupId` is intentionally set to `undefined` in the request payload. The backend derives the `oldGroupId` from the `oldDiscussionId` instead. This design avoids potential mismatches between the group ID and discussion ID.

Applied to files:

  • apps/website/src/components/homepage/UpcomingRounds.tsx
🧬 Code graph analysis (1)
apps/website/src/components/homepage/UpcomingRounds.tsx (5)
apps/website/src/server/routers/_app.ts (1)
  • AppRouter (36-36)
apps/website/src/components/lander/course-content/AgiStrategyContent.tsx (1)
  • AGI_STRATEGY_APPLICATION_URL (15-15)
apps/website/src/components/lander/course-content/BioSecurityContent.tsx (1)
  • BIOSECURITY_APPLICATION_URL (15-15)
apps/website/src/components/lander/course-content/TechnicalAiSafetyContent.tsx (1)
  • TECHNICAL_AI_SAFETY_APPLICATION_URL (16-16)
apps/website/src/components/lander/course-content/AiGovernanceContent.tsx (1)
  • AI_GOVERNANCE_APPLICATION_URL (12-12)
⏰ 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). (1)
  • GitHub Check: ci
🔇 Additional comments (5)
apps/website/src/components/homepage/UpcomingRounds.tsx (5)

1-12: Type-safe router output inference is well implemented.

Using inferRouterOutputs<AppRouter> to derive types from the tRPC router ensures type safety across the client-server boundary. The type definitions are clean and precise.


86-112: Clean component structure and correct responsive behavior.

The RoundGroup component correctly implements the mobile-centered header requirement from the PR objectives and properly renders dividers only between items. The responsive breakpoint handling is consistent with the rest of the component.


137-177: Excellent accessibility and responsive design implementation.

The component provides appropriate aria-label attributes that describe the action and new-tab behavior. The different interaction patterns for mobile (clickable link only) versus desktop (clickable card) offer good UX tailored to each context.


123-132: Interpunct formatting correctly implements the design spec.

The use of the interpunct separator (·) between course title and date range matches the homepage-specific formatting requirement from the PR objectives.


182-208: Diverse round selection logic correctly implements the requirement.

The two-pass algorithm appropriately prioritizes course diversity (first loop) while ensuring up to 3 rounds are displayed (second loop as fallback). The deduplication check on line 201 using round.id prevents duplicates from being added in the fallback phase.

Comment on lines +22 to +37
const { data: allRounds, isLoading } = trpc.courseRounds.getAllUpcomingRounds.useQuery();

if (isLoading) {
return (
<section className="bg-white pt-[16px] pb-[48px] px-5 min-[680px]:pt-0 min-[680px]:pb-[64px] min-[680px]:px-8 min-[1024px]:pb-[80px] lg:px-12 min-[1280px]:pb-[96px] min-[1440px]:pt-[24px] min-[1920px]:pt-0 xl:px-16 2xl:px-20">
<div className="flex flex-col gap-4 max-w-screen-xl mx-auto">
<div className="h-20 w-full animate-pulse rounded bg-gray-200" />
<div className="h-20 w-full animate-pulse rounded bg-gray-200" />
</div>
</section>
);
}

if (!allRounds) {
return null;
}

@coderabbitai coderabbitai Bot Dec 8, 2025

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 adding error state handling for better UX.

The component only handles isLoading and missing data states. If the tRPC query fails (network error, server error, etc.), the component silently returns null, leaving users without feedback that something went wrong.

For a non-critical homepage section, this might be acceptable, but consider showing a lightweight error state or fallback message to improve user experience when the data fetch fails.

Example approach:

-const { data: allRounds, isLoading } = trpc.courseRounds.getAllUpcomingRounds.useQuery();
+const { data: allRounds, isLoading, isError } = trpc.courseRounds.getAllUpcomingRounds.useQuery();

 if (isLoading) {
   return (
     <section className="...">
       {/* skeleton */}
     </section>
   );
 }

-if (!allRounds) {
+if (isError || !allRounds) {
   return null;
 }

Or optionally display a minimal error message for better transparency.

🤖 Prompt for AI Agents
In apps/website/src/components/homepage/UpcomingRounds.tsx around lines 22 to
37, the component only handles loading and null data but does not handle query
errors; update the component to check
trpc.courseRounds.getAllUpcomingRounds.useQuery's isError (and optionally error)
state and render a lightweight error/fallback UI instead of returning null —
show a brief message like "Unable to load upcoming rounds" (optionally include
error.message), and provide a retry action by calling refetch from the hook so
users can recover from transient failures.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

A previous issue was made that follows under this area #1772

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch details of issue #1772 to understand what it covers
gh issue view 1772 --repo bluedotimpact/bluedot --json title,body,url,state,labels

Length of output: 2697


@alyssia-x Thanks for pointing that out! I can see that issue #1772 covers error and empty state handling for data fetching components including course rounds on the homepage. Since this is already being tracked as part of a broader design system improvement, my concern here is adequately covered.

Looking forward to seeing the standardized error/empty state designs once they're implemented! 👍


🧠 Learnings used
Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1758
File: apps/website/src/pages/courses/index.tsx:416-418
Timestamp: 2025-12-05T22:16:53.272Z
Learning: In apps/website/src/pages/courses/index.tsx, the CourseCard component fetches rounds data via trpc.courseRounds.getRoundsForCourse.useQuery even though the same data is fetched in useSortedCourses. This is intentional because tRPC (which uses React Query) automatically deduplicates network requests with identical query keys, so only one network request is made and the result is shared across components via the query cache.

Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1577
File: apps/website/src/components/homepage/EventsSection.tsx:296-297
Timestamp: 2025-11-05T00:03:33.638Z
Learning: In the bluedotimpact/bluedot repository, when handling loading states for data fetching in components (such as the EventsSection component in apps/website/src/components/homepage/EventsSection.tsx), prefer using the ProgressDots component from the library to display a loading indicator rather than hiding the entire section or returning null during loading states. This provides a better user experience by keeping the layout consistent while data is being fetched.

Learnt from: alyssia-x
Repo: bluedotimpact/bluedot PR: 1554
File: apps/website/src/components/homepage/OurCommunitySection.tsx:11-17
Timestamp: 2025-11-03T01:57:21.418Z
Learning: In apps/website/src/components/homepage/OurCommunitySection.tsx, the CommunityMember type is intentionally duplicated (also exists in apps/website/src/components/lander/components/CommunityMembersSubSection.tsx) to keep the component changeable for potential future refactoring.

@alyssia-x alyssia-x merged commit d18f159 into master Dec 8, 2025
7 checks passed
@alyssia-x alyssia-x deleted the alyssia-homepage-courses-schedule branch December 8, 2025 20:58
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.

[Design] Add "Upcoming Rounds" to Homepage

1 participant