feat: add top long-standing GSoC participants strip to organizations page#790
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a backend /top-orgs endpoint and seed script, a React Query key and fetch, a horizontal TopOrgsStrip UI integrated into GSoCReposPage, small ProjectsSection button/UX tweaks, a refactor of useInterviewCountdown, and a CI step to generate the Prisma client. ChangesTop Organizations Feature and Supporting Improvements
Sequence DiagramsequenceDiagram
participant Client as Browser Client
participant Page as GSoCReposPage
participant Query as React Query
participant API as /gsoc/top-orgs
participant Strip as TopOrgsStrip
participant Modal as Org Detail Modal
Client->>Page: Load GSoCReposPage
Page->>Query: useQuery("/gsoc/top-orgs")
Query->>API: GET /gsoc/top-orgs
API-->>Query: { organizations: [...top 6 orgs] }
Query-->>Page: topOrgs array
Page->>Strip: Render TopOrgsStrip (if !hasFilters && topOrgs)
Client->>Strip: Click org card
Strip->>Page: onSelect(orgSlug)
Page->>Modal: Set selectedOrg and open modal
Modal-->>Client: Show org details
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 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: 1
🧹 Nitpick comments (3)
client/src/module/student/opensource/GSoCReposPage.tsx (2)
126-141: ⚡ Quick winUse the shared
Buttoncomponent for the scroll controls.The new left/right chevron controls are raw
<button>elements. Per the design system, new buttons should use the reusableButton(mode="icon", e.g.variant="ghost") fromclient/src/components/ui/button.tsx, which also keeps the focus/hover states consistent.As per coding guidelines: "Use the reusable
Buttoncomponent fromclient/src/components/ui/button.tsxfor all new buttons with variants (primary, secondary, mono, ghost, danger), modes (button, icon, link), and sizes (sm, md, lg)".🤖 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 `@client/src/module/student/opensource/GSoCReposPage.tsx` around lines 126 - 141, Replace the raw <button> chevron controls in GSoCReposPage.tsx with the shared Button component from client/src/components/ui/button.tsx: import Button and swap the two onClick handlers that call scroll("left") and scroll("right") into <Button mode="icon" variant="ghost" aria-label="Scroll left|right" onClick={...}> containing the <ChevronLeft> / <ChevronRight> icons; ensure you preserve existing className/size semantics by using the Button's props (e.g., size or className) so focus/hover states and accessibility are handled by the shared Button component rather than raw elements.
102-108: 💤 Low valueExtract the top-org item type to avoid duplication.
The minimal org shape
{ id; name; slug; imageUrl?; yearsParticipated }[]is declared inline both here and at theuseQuerycall (line 599). Hoist a shared type (or reusePick<GSoCOrganization, ...>) so the strip props and the query stay in sync.As per coding guidelines: "Apply DRY principle: no duplicate helpers, shared animation variants per file".
Also applies to: 599-604
🤖 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 `@client/src/module/student/opensource/GSoCReposPage.tsx` around lines 102 - 108, The org shape used inline in TopOrgsStrip props is duplicated elsewhere (e.g., the useQuery result), so extract a shared type alias (e.g., TopOrg or reuse Pick<GSoCOrganization, "id"|"name"|"slug"|"imageUrl"|"yearsParticipated">) at the top of the file and replace the inline object type in the TopOrgsStrip props and the useQuery return type with that alias to keep both in sync and avoid duplication; update any references to TopOrgsStrip and the query (search for TopOrgsStrip and the useQuery call around line ~599) to use the new shared type.server/src/module/gsoc/gsoc.routes.ts (1)
113-127: 💤 Low valueConsider a deterministic tiebreaker and bounded fetch.
findManyreads the full table into memory and sorts in JS. This mirrors the existing/statshandler so it's acceptable, but two small points:
- Orgs with the same
yearsParticipated.lengthcome back in an arbitrary order, so the displayed top-6 can shift between requests. Add a secondary sort key (e.g.totalProjectsorname) for stable results.- If the org table grows large, sorting by array length in Postgres (
ORDER BY array_length("yearsParticipated", 1) DESC) via a raw query would avoid loading every row.♻️ Deterministic tiebreaker
const topOrgs = orgs - .sort((a, b) => b.yearsParticipated.length - a.yearsParticipated.length) + .sort( + (a, b) => + b.yearsParticipated.length - a.yearsParticipated.length || + a.name.localeCompare(b.name) + ) .slice(0, 6);🤖 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 `@server/src/module/gsoc/gsoc.routes.ts` around lines 113 - 127, The current code uses prisma.gsocOrganization.findMany to load all orgs and then sorts in JS to produce topOrgs by yearsParticipated.length, which yields non-deterministic ties and loads the entire table; fix by making the tiebreaker deterministic in the in-memory sort (update the comparator used for topOrgs to compare b.yearsParticipated.length - a.yearsParticipated.length and, on equality, compare a.name.localeCompare(b.name) or a.totalProjects if available) and, for scalability, replace the fetch with a DB-side ordered, bounded query (use prisma.$queryRaw to SELECT id,name,slug,imageUrl,imageBgColor,category,yearsParticipated ORDER BY array_length(yearsParticipated, 1) DESC, name ASC LIMIT 6) so you only fetch the top 6 rows instead of the whole table.
🤖 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 `@client/src/module/student/opensource/GSoCReposPage.tsx`:
- Around line 730-738: The TopOrgsStrip click handler casts lightweight topOrgs
items into a full GSoCOrganization via setSelectedOrg(found as unknown as
GSoCOrganization), causing the modal to crash when the component reads missing
fields (detailOrg / detailData). Fix by mapping the found org into a safe
"partial filled" object in the click handler (in GSoCReposPage) that preserves
id/name/slug/imageUrl/yearsParticipated and adds safe defaults for all missing
fields the modal expects (e.g. technologies: [], topics: [], description: '',
links: [], mentors: [], any booleans/numbers set to sensible defaults) before
calling setSelectedOrg; keep the same type by constructing a
GSoCOrganization-shaped object rather than using as unknown as, so the modal can
render until the full detail query resolves.
---
Nitpick comments:
In `@client/src/module/student/opensource/GSoCReposPage.tsx`:
- Around line 126-141: Replace the raw <button> chevron controls in
GSoCReposPage.tsx with the shared Button component from
client/src/components/ui/button.tsx: import Button and swap the two onClick
handlers that call scroll("left") and scroll("right") into <Button mode="icon"
variant="ghost" aria-label="Scroll left|right" onClick={...}> containing the
<ChevronLeft> / <ChevronRight> icons; ensure you preserve existing
className/size semantics by using the Button's props (e.g., size or className)
so focus/hover states and accessibility are handled by the shared Button
component rather than raw elements.
- Around line 102-108: The org shape used inline in TopOrgsStrip props is
duplicated elsewhere (e.g., the useQuery result), so extract a shared type alias
(e.g., TopOrg or reuse Pick<GSoCOrganization,
"id"|"name"|"slug"|"imageUrl"|"yearsParticipated">) at the top of the file and
replace the inline object type in the TopOrgsStrip props and the useQuery return
type with that alias to keep both in sync and avoid duplication; update any
references to TopOrgsStrip and the query (search for TopOrgsStrip and the
useQuery call around line ~599) to use the new shared type.
In `@server/src/module/gsoc/gsoc.routes.ts`:
- Around line 113-127: The current code uses prisma.gsocOrganization.findMany to
load all orgs and then sorts in JS to produce topOrgs by
yearsParticipated.length, which yields non-deterministic ties and loads the
entire table; fix by making the tiebreaker deterministic in the in-memory sort
(update the comparator used for topOrgs to compare b.yearsParticipated.length -
a.yearsParticipated.length and, on equality, compare
a.name.localeCompare(b.name) or a.totalProjects if available) and, for
scalability, replace the fetch with a DB-side ordered, bounded query (use
prisma.$queryRaw to SELECT
id,name,slug,imageUrl,imageBgColor,category,yearsParticipated ORDER BY
array_length(yearsParticipated, 1) DESC, name ASC LIMIT 6) so you only fetch the
top 6 rows instead of the whole table.
🪄 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: fdeab902-0b04-4a9a-a4bb-f48601f249fc
📒 Files selected for processing (5)
client/src/hooks/useInterviewCountdown.tsclient/src/lib/query-keys.tsclient/src/module/student/opensource/GSoCReposPage.tsxclient/src/module/student/profile/components/ProjectsSection.tsxserver/src/module/gsoc/gsoc.routes.ts
|
completed! |
Sachinchaurasiya360
left a comment
There was a problem hiding this comment.
Review: Changes Requested
The "Top long-standing participants" strip is a genuinely useful feature — surfacing orgs with multi-year participation helps students identify established communities. However the PR includes several unrelated changes that need to be addressed.
1. Unexplained ProjectsSection.tsx behavior change (required)
The diff removes mode="icon" from a Button in ProjectsSection.tsx. Per CLAUDE.md, mode: button is the default (not icon). Removing mode="icon" changes the button from an icon-only button to a text button, which would visually break the project editor UI. Please:
- Confirm this is intentional and explain what visual change is expected, OR
- Revert this change if it was accidental
2. useInterviewCountdown.ts changes are out of scope
The reformatting + adding clearInterval when the countdown hits zero are legitimate improvements (the missing clearInterval is a memory leak), but they don't belong in a PR titled "featured projects." Please either:
- Open a separate PR for the countdown fix, OR
- Add these changes to the PR description explicitly
3. PR title is too generic
"Feature/gssoc featured projects" should be more descriptive, e.g., "feat: add top long-standing GSoC participants strip to organizations page"
Please address item 1 (the behavior change in ProjectsSection) and update the description for items 2-3.
|
ok ,I will fix that |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
client/src/hooks/useInterviewCountdown.ts (1)
25-25: ⚡ Quick winUse lazy initialization to avoid redundant computation on re-renders.
useState(calculate())evaluatescalculate()on every render, even though the result is only used during mount. Use the lazy initializer form to run the calculation once.♻️ Suggested fix
- const [timeLeft, setTimeLeft] = useState(calculate()); + const [timeLeft, setTimeLeft] = useState(() => calculate());🤖 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 `@client/src/hooks/useInterviewCountdown.ts` at line 25, The initial state for timeLeft currently calls calculate() eagerly on every render; change the useState invocation in useInterviewCountdown (the timeLeft state) to use the lazy initializer form so calculate is executed only once on mount (i.e., replace useState(calculate()) with useState(() => calculate()) to defer computation).
🤖 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.
Nitpick comments:
In `@client/src/hooks/useInterviewCountdown.ts`:
- Line 25: The initial state for timeLeft currently calls calculate() eagerly on
every render; change the useState invocation in useInterviewCountdown (the
timeLeft state) to use the lazy initializer form so calculate is executed only
once on mount (i.e., replace useState(calculate()) with useState(() =>
calculate()) to defer computation).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a74731b2-4495-4afd-9d26-a5e0b68a2728
📒 Files selected for processing (2)
client/src/hooks/useInterviewCountdown.tsclient/src/module/student/profile/components/ProjectsSection.tsx
|
Rebase it, Solve the conflict |
…x seed-gsoc types, fix modal crash on TopOrgsStrip click - CI (test-server): add missing 'prisma generate' step before tests - vitest.config.ts: revert unnecessary alias hack (prisma generate is the proper fix) - seed-gsoc.ts: fix Record<string,unknown> -> JSON.parse for Prisma InputJsonValue - GSoCReposPage.tsx: seed all required GSoCOrganization fields when clicking a TopOrgsStrip card to prevent modal crash on .length access
072a21c to
f5555fd
Compare
Pull Request
Description
This pull request adds the "Most Active Organizations" section (labeled as "Long-standing participants") to the GSoC Organizations page (
GSoCReposPage.tsx).Key implementations:
GET /api/gsoc/top-orgsendpoint in the GSoC routes to find, sort, and slice the top 6 organizations by the number of years they have participated (yearsParticipated.length).topOrgs()under thegsocquery key namespace.TopOrgsStrip, a horizontal scrollable container that renders cards containing the org logo/initials, organization name, and a customrounded-mdbadge showing the years participated count. Clicking any card triggers the organization details modal.lucide-react, matching the premium design system.Related Issue
Fixes #714
Type of Change
Testing
/api/gsoc/top-orgsendpoint correctly sorts and returns the top 6 organizations by participation length.TopOrgsStripcomponent retrieves data correctly using the React Query hook.Screenshots
Checklist
.env, credentials, ornode_modulescommittedSummary by CodeRabbit
New Features
Backend
UI/UX Improvements