feat: add project favorites with star icon and sort-to-top#147
feat: add project favorites with star icon and sort-to-top#147R-Hart80 wants to merge 2 commits into
Conversation
📝 WalkthroughWalkthroughThis PR implements project favorites for authenticated users. The backend API provides favorite/unfavorite endpoints; ChangesProject Favorites Feature
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- Project interface: add is_favorited?: boolean field
- projectApi: add favorite() and unfavorite() methods calling
POST/DELETE /api/v1/projects/{id}/favorite
- ProjectCard: star button visible to authenticated users only;
optimistic toggle with rollback on error; accessible with
aria-label and aria-pressed attributes
- HomePage: track favorited IDs in local state, sort favorited
projects to the top within any active filter/tab;
pass accessToken and onFavoriteChange to ProjectCard
Closes CatholicOS#78
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…overrides Map Previously favoritedIds was a Set initialized once from the first page load, causing newly paginated or refetched projects to miss their is_favorited values from the server. Replace it with a favoriteOverrides Map that only stores changes made this session. The sort and display logic merges server data (source of truth) with the override map, so all pages stay correct. On API error, the rollback now calls onFavoriteChange back to the previous value, keeping the override map as the single state location. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
29cfe12 to
bad9a80
Compare
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/projects/project-card.tsx (1)
55-98:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFix invalid interactive nesting:
<button>inside<Link>
Incomponents/projects/project-card.tsx, the favorite<button>(lines 78-97) is rendered inside the clickable<Link>wrapper (lines 55-64), which results in a<button>inside an<a>—invalid HTML and can break keyboard/screen-reader behavior. Move the favorite control outside theLinkclickable area (e.g., sibling/overlay in the card wrapper) and keep only the card body as the link.🤖 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 `@components/projects/project-card.tsx` around lines 55 - 98, The favorite button is nested inside the Link (invalid <button> inside <a>); refactor the JSX so the outer card wrapper (a div) contains the Link for the card body and the favorite control as a sibling element (not a child). Specifically, wrap the existing Link props/content (href, aria-label={`Open project ${project.name}`}, the block classes and the project title/description JSX) in a Link that only covers the left/main area, then move the conditional favorite button (accessToken, onClick={handleFavoriteClick}, disabled={isToggling}, aria-label, aria-pressed, className and <Star/> usage) out of the Link as a sibling inside the same card container and preserve the visual classes/positioning (adjust container flex/gap/shrink-0 classes if needed) so behavior and accessibility remain identical.
🤖 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 `@app/page.tsx`:
- Around line 69-80: favoriteOverrides persists across sessions and needs to be
cleared when the authenticated identity changes; add an effect that watches the
auth identity (e.g., accessToken or userId from your auth context/props) and
calls setFavoriteOverrides(new Map()) when that identity changes, so stale
overrides aren't applied to a different user. Locate the state favoriteOverrides
and setter setFavoriteOverrides and the handler handleFavoriteChange, then
implement a useEffect that depends on the chosen identity value and resets
favoriteOverrides on change. Ensure the dependency uses the exact auth value
available in this component (accessToken or userId).
---
Outside diff comments:
In `@components/projects/project-card.tsx`:
- Around line 55-98: The favorite button is nested inside the Link (invalid
<button> inside <a>); refactor the JSX so the outer card wrapper (a div)
contains the Link for the card body and the favorite control as a sibling
element (not a child). Specifically, wrap the existing Link props/content (href,
aria-label={`Open project ${project.name}`}, the block classes and the project
title/description JSX) in a Link that only covers the left/main area, then move
the conditional favorite button (accessToken, onClick={handleFavoriteClick},
disabled={isToggling}, aria-label, aria-pressed, className and <Star/> usage)
out of the Link as a sibling inside the same card container and preserve the
visual classes/positioning (adjust container flex/gap/shrink-0 classes if
needed) so behavior and accessibility remain identical.
🪄 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: 43f1997b-d699-4b70-a1c0-168d943644dd
📒 Files selected for processing (3)
app/page.tsxcomponents/projects/project-card.tsxlib/api/projects.ts
| const [favoriteOverrides, setFavoriteOverrides] = useState<Map<string, boolean>>(new Map()); | ||
|
|
||
| const handleFavoriteChange = (projectId: string, isFavorited: boolean) => { | ||
| setFavoriteOverrides((prev) => new Map(prev).set(projectId, isFavorited)); | ||
| }; | ||
|
|
||
| const rawProjects = data?.pages.flatMap((page) => page.items) ?? []; | ||
| const projects = [...rawProjects].sort((a, b) => { | ||
| const aFav = favoriteOverrides.has(a.id) ? favoriteOverrides.get(a.id)! : (a.is_favorited ?? false); | ||
| const bFav = favoriteOverrides.has(b.id) ? favoriteOverrides.get(b.id)! : (b.is_favorited ?? false); | ||
| return (bFav ? 1 : 0) - (aFav ? 1 : 0); | ||
| }); |
There was a problem hiding this comment.
Reset favoriteOverrides when auth identity changes.
favoriteOverrides persists across session changes, so signing out/in as another user can apply stale overrides to the wrong account’s list ordering/state. Clear overrides on access-token (or user-id) change.
Suggested fix
const [favoriteOverrides, setFavoriteOverrides] = useState<Map<string, boolean>>(new Map());
+
+ useEffect(() => {
+ setFavoriteOverrides(new Map());
+ }, [session?.accessToken]);🤖 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 `@app/page.tsx` around lines 69 - 80, favoriteOverrides persists across
sessions and needs to be cleared when the authenticated identity changes; add an
effect that watches the auth identity (e.g., accessToken or userId from your
auth context/props) and calls setFavoriteOverrides(new Map()) when that identity
changes, so stale overrides aren't applied to a different user. Locate the state
favoriteOverrides and setter setFavoriteOverrides and the handler
handleFavoriteChange, then implement a useEffect that depends on the chosen
identity value and resets favoriteOverrides on change. Ensure the dependency
uses the exact auth value available in this component (accessToken or userId).
Summary
Projectinterface: addsis_favorited?: booleanfieldprojectApi: addsfavorite()andunfavorite()callingPOST/DELETE /api/v1/projects/{id}/favoriteProjectCard: star button shown only to authenticated users; clicking toggles the favorite state with optimistic UI (immediate visual feedback, rollback on API error); fully accessible witharia-labelandaria-pressedHomePage: tracks favorited IDs in local state and sorts favorited projects to the top within any active filter tab, maintaining existing creation-date order within each groupBehavior
Test plan
is_favorited)Closes #78
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features