Skip to content

Comments

feat:implement sponsor review dashboard.#90

Merged
Benjtalkshow merged 5 commits intoboundlessfi:mainfrom
Ekene001:Feat/Implement-Sponsor-Review-Dashboard
Feb 20, 2026
Merged

feat:implement sponsor review dashboard.#90
Benjtalkshow merged 5 commits intoboundlessfi:mainfrom
Ekene001:Feat/Implement-Sponsor-Review-Dashboard

Conversation

@Ekene001
Copy link
Contributor

@Ekene001 Ekene001 commented Feb 20, 2026

closes #85

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
image

Summary by CodeRabbit

  • New Features

    • Added a sponsor review dashboard showing submissions with contributor avatar, name, time, optional milestone, status badges, and actions to Approve, Reject, or Request Revision; actions update the UI optimistically per item and roll back if an action fails.
  • Chores

    • Updated project dependencies and development tooling versions to keep builds and tests up to date.

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
@vercel
Copy link

vercel bot commented Feb 20, 2026

@Ekene001 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

Warning

Rate limit exceeded

@Ekene001 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📝 Walkthrough

Walkthrough

Adds a new client-side SponsorReviewDashboard React component with optimistic action handling, introduces ReviewStatus and ReviewSubmission types, and updates dependency versions in package.json.

Changes

Cohort / File(s) Summary
Sponsor Review Component
components/bounty/sponsor-review-dashboard.tsx
New client-side React component rendering review submissions with contributor info, status badges, and actions (Approve, Reject, Request revision). Implements optimistic UI updates, per-submission loading state, rollback on error, and optional onAction callback.
Type Definitions
types/participation.ts
Added ReviewStatus (`'pending'
Dependency / Manifest
package.json
Adjusted dependency/devDependency versions: "next" changed to ^16.1.6; removed @graphql-codegen/typescript-react-apollo; updated eslint, eslint-config-next, jest, and jest-environment-jsdom version specifiers.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hop through lists with nimble cheer,

Click approve, request change, or steer,
Optimistic leaps, then steady paws,
Rollback ready if code gives pause,
Reviews aligned — a happy ear!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Package.json updates are dependency maintenance (unrelated to dashboard feature) and should be in a separate PR, but types/participation.ts additions directly support the dashboard implementation and are in-scope. Move package.json dependency updates to a separate maintenance PR to keep this PR focused on implementing the sponsor review dashboard feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat:implement sponsor review dashboard' directly describes the main change: introducing a new sponsor review dashboard component with all required functionality.
Linked Issues check ✅ Passed All coding requirements from issue #85 are met: ReviewSubmission type defined with required fields, all three actions (Approve, Reject, Request revision) implemented, optimistic UI updates in place, and multiple submissions support included.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 setItems is 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: ReviewStatus is structurally identical to ApplicationStatus.

Both resolve to 'pending' | 'approved' | 'rejected'. Consider aliasing rather than duplicating:

-export type ReviewStatus = 'pending' | 'approved' | 'rejected'
+export type ReviewStatus = ApplicationStatus

Also 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
Copy link

@coderabbitai coderabbitai bot left a comment

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 (1)
components/bounty/sponsor-review-dashboard.tsx (1)

29-40: Side effect inside a state updater is fragile — extract prevItem before calling setItems.

prevItem is assigned inside the setItems updater callback. React state updater functions are called lazily during reconciliation, not synchronously when setState is invoked. While in practice React 18's synchronous batch flush (before the first await) 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 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.

Copy link
Contributor

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

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

Hi @Ekene001,

Thanks for the modification. There are just a few final things to take care of:

  1. 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.
  2. Pull the latest changes from main before pushing your updates.

Thanks again for your contribution — I really appreciate your efforts!

@Ekene001
Copy link
Contributor Author

@Benjtalkshow Okay, Ive made the updates

Copy link
Contributor

@Benjtalkshow Benjtalkshow left a comment

Choose a reason for hiding this comment

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

LGTM

@Benjtalkshow Benjtalkshow merged commit a00499d into boundlessfi:main Feb 20, 2026
2 of 3 checks passed
This was referenced Feb 20, 2026
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.

Implement Sponsor Review Dashboard

2 participants