Skip to content

refactor: split bounty CTA branch logic into per-bounty-type components#313

Merged
Benjtalkshow merged 1 commit into
boundlessfi:mainfrom
Benedict315:refactor/split-cta-logic
Jun 29, 2026
Merged

refactor: split bounty CTA branch logic into per-bounty-type components#313
Benjtalkshow merged 1 commit into
boundlessfi:mainfrom
Benedict315:refactor/split-cta-logic

Conversation

@Benedict315

@Benedict315 Benedict315 commented Jun 29, 2026

Copy link
Copy Markdown

Description

Resolves #297.

This PR refactors the call-to-action (CTA) rendering logic inside components/bounty-detail/bounty-detail-sidebar-cta.tsx. The previously giant cascading conditional ternary that selected the appropriate CTA button based on the bounty type has been split into dedicated, modular sub-components.

Changes Made

Created a new directory components/bounty-detail/cta/ and extracted the branch rendering logic into five separate components:

  1. fcfs-cta.tsx: Wraps the FcfsClaimButton and handles showing the fallback alert text if the FCFS bounty is no longer active (!canAct).
  2. competition-cta.tsx: Renders competition CTA buttons (e.g. "Joined ✓" state, "Join Competition" with load spinners and deadlines), and the past-deadline lockout warning.
  3. multi-winner-cta.tsx: Renders the apply-for-slot button for multi-winner milestone bounties, with slot fullness and already-joined validations.
  4. milestone-based-cta.tsx: Wraps the <ApplicationDialog> trigger and button component for milestone-based bounties.
  5. default-cta.tsx: Renders the fallback "View on GitHub" link or the closed-bounty message for when no other actions are applicable.

Main Component Simplified

  • bounty-detail-sidebar-cta.tsx: Replaced the 100+ line cascading ternary block with a single <BountyCta bounty={bounty} state={state} /> dispatch component.
  • Reduced unused variables and cleaned up unused imports (AlertCircle, Clock, ApplicationDialog, FcfsClaimButton).
  • Hoisted state from the parent SidebarCTA component and passed the required state properties down to the sub-components using strong TypeScript typing.

Verification

  • TypeScript: pnpm tsc --noEmit compiled successfully with 0 errors.
  • ESLint: pnpm lint and eslint checked and verified with 0 errors/warnings.
  • Unit Tests: Full unit test suite (16 passed, 16 total / 94 tests passed) successfully ran and passed:
    PASS hooks/__tests__/use-cancel-bounty.test.ts
    PASS hooks/__tests__/use-bounty-subscription.test.ts
    PASS hooks/__tests__/use-bounty-filters.test.ts
    ...
    Test Suites: 16 passed, 16 total
    Tests:       94 passed, 94 total
    
    

closes #297

Summary by CodeRabbit

  • New Features
    • Improved bounty detail sidebar actions with clearer, flow-specific call-to-actions for competition, FCFS, multi-winner, milestone-based, and default bounty types.
    • Added clearer status states for joining, applying, and locked submissions, including disabled actions when a bounty is no longer accepting entries.
    • Added deadline and availability messaging to help users understand why an action may be unavailable.

@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

@jotel-dev is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jun 29, 2026

Copy link
Copy Markdown

@Benedict315 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Extracts the inline CTA ternary in bounty-detail-sidebar-cta.tsx into five dedicated components (FcfsCta, CompetitionCta, MultiWinnerCta, MilestoneBasedCta, DefaultCta) under components/bounty-detail/cta/. A new internal BountyCta dispatcher selects the correct component; useBountyCTAState is hoisted to a state variable and passed down.

Changes

CTA Component Extraction

Layer / File(s) Summary
New per-bounty-type CTA components
components/bounty-detail/cta/fcfs-cta.tsx, components/bounty-detail/cta/competition-cta.tsx, components/bounty-detail/cta/multi-winner-cta.tsx, components/bounty-detail/cta/milestone-based-cta.tsx, components/bounty-detail/cta/default-cta.tsx
Adds five CTA components, each receiving a bounty prop and a typed Pick of useBountyCTAState fields. Each component encapsulates its own disabled logic, loading states, and status messaging.
SidebarCTA refactor and BountyCta dispatcher
components/bounty-detail/bounty-detail-sidebar-cta.tsx
Adds BountyCta dispatcher that branches on state.isFcfs, state.isCompetition, and bounty.type. Hoists useBountyCTAState into a state variable; replaces the ~80-line ternary with <BountyCta bounty={bounty} state={state} />.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • boundlessfi/bounties#241: Introduced the useBountyCTAState hook that this PR's BountyCta dispatcher and all five new CTA components consume.
  • boundlessfi/bounties#251: Added the multi-winner slot apply fields (handleApplyForSlot, applyForSlotMutation, applyForSlotButtonLabel) that MultiWinnerCta now directly destructures.
  • boundlessfi/bounties#178: Previously touched the competition CTA join-state logic in bounty-detail-sidebar-cta.tsx that is now extracted into CompetitionCta.

Suggested reviewers

  • Benjtalkshow

🐇 Five branches once tangled and wide,
Now each in their own cozy file reside.
BountyCta dispatches with glee,
No more nested ternary to see!
Hopped clean, the sidebar can breathe. 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the refactor from one CTA branch to per-bounty-type components.
Linked Issues check ✅ Passed The PR matches #297 by splitting the CTA branches into the requested components and delegating selection from the parent sidebar.
Out of Scope Changes check ✅ Passed The changes stay within the requested CTA refactor and related cleanup, with no obvious unrelated features or behavior changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@components/bounty-detail/cta/competition-cta.tsx`:
- Around line 45-63: The CTA in competition-cta.tsx is falling back to
ctaLabel() after the deadline, which uses bounty status only and can show the
wrong action text for a past-deadline OPEN competition. Update the Button label
logic in the competition CTA so it uses a competition-specific closed/ended
label when isPastDeadline is true, while keeping the existing “Join Competition”
text only for the active canAct case. Reference the Button rendering in
competition-cta.tsx and the ctaLabel() usage to keep the change localized.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 903b5f6e-fb97-4885-b0e5-b68e79dbca9b

📥 Commits

Reviewing files that changed from the base of the PR and between a7244b5 and 1c65516.

📒 Files selected for processing (6)
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty-detail/cta/competition-cta.tsx
  • components/bounty-detail/cta/default-cta.tsx
  • components/bounty-detail/cta/fcfs-cta.tsx
  • components/bounty-detail/cta/milestone-based-cta.tsx
  • components/bounty-detail/cta/multi-winner-cta.tsx

Comment on lines +45 to +63
<Button
data-testid="apply-to-bounty-btn"
className="w-full h-11 font-bold tracking-wide"
disabled={
!canAct ||
isPastDeadline ||
joinMutation.isPending ||
!walletAddress
}
size="lg"
onClick={() => void handleJoin()}
>
{joinMutation.isPending ? (
<Loader2 className="mr-2 size-4 animate-spin" />
) : (
<Users className="mr-2 size-4" />
)}
{canAct && !isPastDeadline ? "Join Competition" : ctaLabel()}
</Button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a competition-specific closed label after the deadline.

Line 62 falls back to ctaLabel() when isPastDeadline is true, but ctaLabel() only looks at bounty status. For an OPEN competition past its deadline, the disabled button will read "Submit to Bounty", which is the wrong action here.

Suggested fix
-          {canAct && !isPastDeadline ? "Join Competition" : ctaLabel()}
+          {!canAct
+            ? ctaLabel()
+            : isPastDeadline
+              ? "Deadline Passed"
+              : "Join Competition"}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
data-testid="apply-to-bounty-btn"
className="w-full h-11 font-bold tracking-wide"
disabled={
!canAct ||
isPastDeadline ||
joinMutation.isPending ||
!walletAddress
}
size="lg"
onClick={() => void handleJoin()}
>
{joinMutation.isPending ? (
<Loader2 className="mr-2 size-4 animate-spin" />
) : (
<Users className="mr-2 size-4" />
)}
{canAct && !isPastDeadline ? "Join Competition" : ctaLabel()}
</Button>
<Button
data-testid="apply-to-bounty-btn"
className="w-full h-11 font-bold tracking-wide"
disabled={
!canAct ||
isPastDeadline ||
joinMutation.isPending ||
!walletAddress
}
size="lg"
onClick={() => void handleJoin()}
>
{joinMutation.isPending ? (
<Loader2 className="mr-2 size-4 animate-spin" />
) : (
<Users className="mr-2 size-4" />
)}
{!canAct
? ctaLabel()
: isPastDeadline
? "Deadline Passed"
: "Join Competition"}
</Button>
🤖 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/bounty-detail/cta/competition-cta.tsx` around lines 45 - 63, The
CTA in competition-cta.tsx is falling back to ctaLabel() after the deadline,
which uses bounty status only and can show the wrong action text for a
past-deadline OPEN competition. Update the Button label logic in the competition
CTA so it uses a competition-specific closed/ended label when isPastDeadline is
true, while keeping the existing “Join Competition” text only for the active
canAct case. Reference the Button rendering in competition-cta.tsx and the
ctaLabel() usage to keep the change localized.

@Benjtalkshow Benjtalkshow merged commit c5d69b7 into boundlessfi:main Jun 29, 2026
4 of 5 checks passed
Benjtalkshow added a commit to Johnpii1/bounties-fork that referenced this pull request Jun 29, 2026
Reset branch onto current main so this PR no longer deletes the work
from PRs boundlessfi#298, boundlessfi#311, and boundlessfi#313. Only changes left:

- New hooks/__tests__/use-bounty-application.test.tsx with 12 cases
  covering happy and rollback paths for useApplyToBounty,
  useSelectApplicant, useApproveApplicationSubmission, useRequestRevisions,
  useApplyForSlot, useReleasePayment, useRemoveContributor,
  useDeclineApplicant, and useRaiseDispute.
- Validation in useApplyToBounty that throws ApplicationError when
  applicantAddress is missing. Production guard, not test-only.

Verified pnpm lint, pnpm tsc --noEmit, pnpm build, and the new test
suite (12/12 passing) all clean.
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.

Split the CTA branch logic in bounty-detail-sidebar-cta.tsx into per-bounty-type components

3 participants