feat:implement sponsor review dashboard.#90
Conversation
Add sponsor review dashboard for project owners to manage submissions. - Approve, reject, and request revision actions - Optimistic UI updates for instant feedback - Support multiple submissions with contributor, milestone, and timestamp display - Deterministic date formatting for consistent rendering - Responsive layout with proper mobile button wrapping
|
@Ekene001 is attempting to deploy a commit to the Threadflow Team on Vercel. A member of the Team first needs to authorize it. |
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdds a new client-side SponsorReviewDashboard React component with optimistic action handling, introduces ReviewStatus and ReviewSubmission types, and updates dependency versions in package.json. Changes
Sequence DiagramsequenceDiagram
actor User
participant Dashboard as SponsorReviewDashboard
participant State as InternalState
participant Handler as onAction
User->>Dashboard: Click Approve/Reject/Request Revision
Dashboard->>State: save previous item, set optimistic status & add loadingId
Dashboard->>Dashboard: re-render optimistic UI
Dashboard->>Handler: call onAction(submissionId, action)
alt onAction resolves
Handler-->>Dashboard: success
Dashboard->>State: clear loadingId (keep new status)
else onAction rejects
Handler-->>Dashboard: error
Dashboard->>State: restore previous item, clear loadingId
Dashboard->>Dashboard: re-render rollback UI
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 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: 8
🧹 Nitpick comments (2)
components/bounty/sponsor-review-dashboard.tsx (1)
26-26: Extract the action→status mapping to reduce nesting.The inline triple-ternary inside
setItemsis hard to read and maintain.♻️ Proposed refactor
+ const ACTION_STATUS_MAP: Record<Action, ReviewStatus> = { + approve: 'approved', + reject: 'rejected', + request_revision: 'pending', + } + const handleAction = async (id: string, action: Action) => { setLoadingIds(s => ({ ...s, [id]: true })) - const prev = items - setItems(curr => curr.map(it => (it.submissionId === id ? { ...it, status: action === 'approve' ? 'approved' : action === 'reject' ? 'rejected' : 'pending' } : it))) + const nextStatus = ACTION_STATUS_MAP[action] + setItems(curr => curr.map(it => it.submissionId === id ? { ...it, status: nextStatus } : it))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty/sponsor-review-dashboard.tsx` at line 26, Replace the inline triple-ternary in the setItems update with a simple action→status mapping: create a small helper (e.g., mapActionToStatus(action): string) or a const mapping object that returns 'approved' for 'approve', 'rejected' for 'reject' and 'pending' as default, then use it inside the setItems callback so the update becomes setItems(curr => curr.map(it => it.submissionId === id ? { ...it, status: mapActionToStatus(action) } : it)); reference the setItems call, the submissionId/id comparison, and the action/status fields when implementing the helper to locate the change.types/participation.ts (1)
30-41:ReviewStatusis structurally identical toApplicationStatus.Both resolve to
'pending' | 'approved' | 'rejected'. Consider aliasing rather than duplicating:-export type ReviewStatus = 'pending' | 'approved' | 'rejected' +export type ReviewStatus = ApplicationStatusAlso note that
SubmissionStatus(Line 16) uses'accepted'instead of'approved', creating a naming inconsistency across three sibling status types in the same file. It's worth aligning the vocabulary or adding a comment justifying the distinction.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@types/participation.ts` around lines 30 - 41, ReviewStatus duplicates ApplicationStatus; replace the duplicate union with a type alias (e.g., export type ReviewStatus = ApplicationStatus) instead of repeating `'pending' | 'approved' | 'rejected'`, and then reconcile the inconsistent vocabulary between SubmissionStatus (which uses 'accepted') and the other status types by either renaming SubmissionStatus's literal to use 'approved' or adding a clarifying comment above SubmissionStatus explaining why 'accepted' is intentionally different; reference the ReviewStatus, ApplicationStatus, and SubmissionStatus type names when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Line 26: The optimistic update currently maps the action 'request_revision' to
the same 'pending' status, so change the update in the setItems callback (the
map that checks it.submissionId === id) to set status to 'revision_requested'
when action === 'request_revision', and add a corresponding 'revision_requested'
member to the ReviewStatus type/enum and to the badge rendering logic (the
component that maps ReviewStatus to badge styles) so the UI shows a distinct
"Revision Requested" badge; alternatively ensure a transient confirmation is
shown after dispatch if you prefer not to extend ReviewStatus.
- Line 60: format(new Date(sub.submittedAt), 'Pp') is locale-sensitive and using
new Date(...) on a date-only ISO can cause timezone-based SSR hydration
mismatches; update the rendering in the component to parse and format
deterministically by using parseISO(sub.submittedAt) (from date-fns) and an
explicit, locale-stable format string like 'MM/dd/yyyy, hh:mm aa' when calling
format, and validate/ensure sub.submittedAt carries an ISO timestamp with
timezone offset (or normalize it to UTC before formatting) so the output is
identical between server and client.
- Around line 51-57: The Avatar currently conditionally renders only AvatarImage
when sub.contributor.avatarUrl exists, which causes a blank avatar on image load
errors; update the block around Avatar (components: Avatar, AvatarImage,
AvatarFallback) to always render both AvatarImage and AvatarFallback
together—pass src={sub.contributor.avatarUrl} to AvatarImage and keep
AvatarFallback using sub.contributor.username?.charAt(0).toUpperCase() ?? "?" so
the fallback shows if the image fails to load.
- Around line 24-33: The rollback uses a stale snapshot (const prev = items)
which clobbers other in-flight optimistic updates; change the update/rollback to
use functional updaters on setItems and only revert the specific submission id:
when applying the optimistic change, call setItems(curr => curr.map(...)) (as
already done) and on error call setItems(curr => curr.map(it => it.submissionId
=== id ? prevStateForThatItem : it)), deriving prevStateForThatItem from the
item that was replaced (capture it in the functional update or compute it before
awaiting), so you only restore the failed submission (refer to setItems and the
optimistic update expression and the onAction await flow in
sponsor-review-dashboard.tsx).
- Line 20: The current useEffect React.useEffect(() => setItems(submissions),
[submissions]) blindly replaces local state and discards optimistic updates;
change it to either make the component fully controlled (remove local items
state and use submissions directly everywhere) or implement a
merge-on-structural-change strategy: in the effect for submissions, compute
additions/removals by id/key and call setItems to add new items and remove
missing ones while preserving existing items' local properties (e.g., optimistic
status) instead of overwriting; reference the setItems updater and the
submissions prop (and item id/key) when implementing the merge logic so
in-flight optimistic changes are not lost.
In `@package.json`:
- Line 87: Replace the incorrect dependency version for eslint-config-next in
package.json (the "eslint-config-next" entry) from "^0.2.4" to the released
Next.js tracking version "16.1.6"; after updating the package.json, regenerate
the lockfile (npm install / yarn install) so the change is persisted and run the
linter/CI to confirm compatibility with Next.js 16 and ESLint flat config.
- Around line 88-89: The package.json pins "jest" and "jest-environment-jsdom"
to ^25.x which mismatches the declared "@types/jest": "^30.0.0"; update the
runtime packages to match the types (e.g., set "jest" and
"jest-environment-jsdom" to the current stable ^30.2.0) or alternatively
downgrade "@types/jest" to the matching ^25.x — modify the "jest" and
"jest-environment-jsdom" entries in package.json to the chosen matching
versions, run npm/yarn install, and re-run TypeScript build/tests to verify no
type/runtime mismatches remain.
- Line 77: The project currently depends on
"@graphql-codegen/typescript-react-apollo" (withHooks: true in your codegen.ts)
which generates hooks incompatible with `@apollo/client` ^4.1.2; remove that
plugin and either (A) replace it with "@graphql-codegen/client-preset" in both
package.json and codegen.ts and update codegen.ts to output typed documents
(TypedDocumentNode) instead of hooks, or (B) stop generating hooks (remove
typescript-react-apollo/withHooks) and configure codegen to emit operations as
TypedDocumentNode so you can use Apollo Client 4's built-in useQuery/useMutation
hooks; also remove the obsolete devDependency
"@graphql-codegen/typescript-react-apollo" from package.json if you choose the
migration.
---
Nitpick comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Line 26: Replace the inline triple-ternary in the setItems update with a
simple action→status mapping: create a small helper (e.g.,
mapActionToStatus(action): string) or a const mapping object that returns
'approved' for 'approve', 'rejected' for 'reject' and 'pending' as default, then
use it inside the setItems callback so the update becomes setItems(curr =>
curr.map(it => it.submissionId === id ? { ...it, status:
mapActionToStatus(action) } : it)); reference the setItems call, the
submissionId/id comparison, and the action/status fields when implementing the
helper to locate the change.
In `@types/participation.ts`:
- Around line 30-41: ReviewStatus duplicates ApplicationStatus; replace the
duplicate union with a type alias (e.g., export type ReviewStatus =
ApplicationStatus) instead of repeating `'pending' | 'approved' | 'rejected'`,
and then reconcile the inconsistent vocabulary between SubmissionStatus (which
uses 'accepted') and the other status types by either renaming
SubmissionStatus's literal to use 'approved' or adding a clarifying comment
above SubmissionStatus explaining why 'accepted' is intentionally different;
reference the ReviewStatus, ApplicationStatus, and SubmissionStatus type names
when making the change.
- Create SponsorReviewDashboard component with optimistic updates and merge-on-change strategy - Use parseISO for deterministic date formatting to prevent hydration mismatches - Make dashboard fully responsive for mobile, tablet, and desktop - Fix package.json: remove typescript-react-apollo, update eslint-config-next to 16.1.6, align jest versions to ^30
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
components/bounty/sponsor-review-dashboard.tsx (1)
29-40: Side effect inside a state updater is fragile — extractprevItembefore callingsetItems.
prevItemis assigned inside thesetItemsupdater callback. React state updater functions are called lazily during reconciliation, not synchronously whensetStateis invoked. While in practice React 18's synchronous batch flush (before the firstawait) makes this work today, coupling a rollback snapshot to a side effect in an updater is non-idiomatic and fragile against future concurrent-mode scheduling changes.♻️ Proposed fix: capture `prevItem` from current state before `setItems`
- let prevItem: ReviewSubmission | undefined - - setItems(curr => curr.map(it => { - if (it.submissionId === id) { - prevItem = it - return { - ...it, - status: action === 'approve' ? 'approved' : action === 'reject' ? 'rejected' : 'revision_requested' - } - } - return it - })) + const prevItem = items.find(it => it.submissionId === id) + const nextStatus = action === 'approve' ? 'approved' : action === 'reject' ? 'rejected' : 'revision_requested' + + setItems(curr => curr.map(it => + it.submissionId === id ? { ...it, status: nextStatus } : it + ))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@components/bounty/sponsor-review-dashboard.tsx` around lines 29 - 40, The updater is mutating prevItem inside the setItems state updater (prevItem, setItems, ReviewSubmission, submissionId, id), which is fragile because React may call the updater lazily; capture the existing item before calling setItems instead: read the current items array, find the item with submissionId === id and assign it to prevItem, then call setItems with a pure updater that maps and returns the updated item status ('approved'|'rejected'|'revision_requested'); ensure no external side effects occur inside the map callback so rollback logic can safely reference the previously captured prevItem.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Around line 45-48: The catch block that rolls back optimistic updates
(referencing prevItem, setItems and ReviewSubmission) currently swallows the
error; modify the catch to surface the failure by logging or forwarding the
error—e.g., call console.error(err) and/or invoke an onError callback or toast
notification (if available) with the caught err so the user and developers see
the failure when the optimistic update is reverted.
---
Duplicate comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Line 75: The SSR/client timezone mismatch comes from calling
format(parseISO(sub.submittedAt), ...) directly in the render; update the
component (sponsor-review-dashboard.tsx) to use a mount-guard pattern: render a
stable server-safe fallback (e.g., the ISO string from sub.submittedAt) for the
server render, add a client-only isMounted flag (useState + useEffect) and when
mounted compute the local formatted string for display (the current
format(parseISO(...)) usage) so the server and initial HTML stay stable and the
local-time replacement happens only after hydration.
- Around line 67-72: The Avatar currently conditionally renders either
AvatarImage or AvatarFallback which means when an image URL exists but fails to
load (e.g., 404) the fallback isn't present; update the rendering of Avatar
(where AvatarImage and AvatarFallback are used with sub.contributor.avatarUrl
and sub.contributor.username) so that Avatar always contains both AvatarImage
and AvatarFallback: always render AvatarImage with
src={sub.contributor.avatarUrl} (only if avatarUrl exists pass src, else omit)
and always render AvatarFallback with the username initial; this leverages Radix
UI's built-in fallback behavior to show initials when the image fails to load.
---
Nitpick comments:
In `@components/bounty/sponsor-review-dashboard.tsx`:
- Around line 29-40: The updater is mutating prevItem inside the setItems state
updater (prevItem, setItems, ReviewSubmission, submissionId, id), which is
fragile because React may call the updater lazily; capture the existing item
before calling setItems instead: read the current items array, find the item
with submissionId === id and assign it to prevItem, then call setItems with a
pure updater that maps and returns the updated item status
('approved'|'rejected'|'revision_requested'); ensure no external side effects
occur inside the map callback so rollback logic can safely reference the
previously captured prevItem.
There was a problem hiding this comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@package.json`:
- Around line 85-86: The package.json change aligns eslint-config-next with next
(both "^16.1.6") and the eslint entry is semantically equivalent ("^9" vs
"^9.0.0"); no functional code changes needed—keep "eslint-config-next":
"^16.1.6" and "eslint": "^9.0.0" (or normalize to a single caret form like
"^9.0.0") in the dependencies block, ensuring the keys "eslint" and
"eslint-config-next" remain updated and consistent with the "next" version.
- Around line 87-88: package.json currently lists both "jest" and "vitest" in
devDependencies while the "test" npm script only invokes jest; determine whether
vitest is actually used (look for vitest.config*, imports from "vitest" or tests
using vitest APIs) and then either remove vitest from devDependencies and delete
any vitest config/files and docs if unused, or explicitly add/adjust npm scripts
to run vitest (e.g., "test:vitest") and document which runner owns which tests
if you intend to keep both; after changes, update lockfile (npm/yarn/pnpm
install) so dependencies reflect the removal or addition.
…nt-Sponsor-Review-Dashboard
Benjtalkshow
left a comment
There was a problem hiding this comment.
Hi @Ekene001,
Thanks for the modification. There are just a few final things to take care of:
- Please make sure your code builds successfully and is lint-error free (I’ve updated the codebase, so it now compiles fully) so that the CI/CD checks pass.
- Pull the latest changes from main before pushing your updates.
Thanks again for your contribution — I really appreciate your efforts!
|
@Benjtalkshow Okay, Ive made the updates |
closes #85
Add sponsor review dashboard for project owners to manage submissions.
Summary by CodeRabbit
New Features
Chores