Skip to content

Split bounty-detail-sidebar-cta into three focused files - #241

Merged
Benjtalkshow merged 9 commits into
boundlessfi:mainfrom
Biokes:main
May 28, 2026
Merged

Benjtalkshow merged 9 commits into
boundlessfi:mainfrom
Biokes:main

Conversation

@Biokes

@Biokes Biokes commented May 28, 2026

Copy link
Copy Markdown
Contributor

Refactor: Split bounty-detail-sidebar-cta into three focused files

Problem

The bounty-detail-sidebar-cta.tsx file had grown to 680+ lines and exported both SidebarCTA and MobileCTA components from the same file. This created:

  • Duplicated state — cancel dialog, dispute dialog, application dialog, copy logic duplicated in both components
  • Duplicated event handlershandleApply, handleJoin, handleCancel etc. implemented twice
  • Merge conflict surface — Any change to apply/dispute flow required updating both places; past merges have dropped handleApply from SidebarCTA and broken the build
  • Poor readability — 680 lines mixing desktop and mobile layouts

Solution

Split into three focused files:

  1. use-bounty-cta-state.ts (new - ~115 lines)

    • Shared custom hook
    • Owns all state: walletAddress, hasJoined, isPastDeadline, cancel/copy dialog state
    • Owns all handlers: handleJoin, handleApply, handleCancel, handleCopy, ctaLabel
    • Owns all computed flags: canAct, isFcfs, isCompetition, isCreator, canRaiseDispute, canCancel
    • Owns all display data: claimCount, deadline, isFinalized, etc.
  2. bounty-detail-sidebar-cta.tsx (refactored - ~250 lines)

    • Exports only SidebarCTA
    • Uses useBountyCTAState hook
    • Focuses on desktop sidebar rendering: full card, separators, competition panels, detailed cancel dialog
  3. bounty-detail-mobile-cta.tsx (new - ~150 lines)

    • Exports only MobileCTA
    • Uses useBountyCTAState hook
    • Focuses on mobile rendering: fixed-bottom sticky bar, compact buttons, simplified cancel dialog
  4. bounty-detail-client.tsx (updated)

    • Updated import: MobileCTA now imported from ./bounty-detail-mobile-cta
    • No behavioral changes

Acceptance Criteria ✅

  • ✅ Both components render exactly as before (identical logic, same state paths)
  • ✅ No duplicated state or event handlers between component files
  • ✅ Each file under 300 lines (Sidebar: ~250, Mobile: ~150, Hook: ~115)
  • ✅ Shared hook owns all data both components consume
  • ✅ Future apply/dispute flow changes made in one place (hook) instead of two

Testing

  • Visual regression: Desktop and mobile CTAs render identically to before
  • State flow: All handlers (join, apply, cancel, copy) work in both contexts
  • No merge conflicts: All shared logic now in single source

Commits

  • Create shared hook for bounty CTA state
  • Create new MobileCTA component using shared hook
  • Refactor SidebarCTA to use shared hook
  • Update client to import MobileCTA from new file

Closes #209

Summary by CodeRabbit

  • New Features

    • Added a mobile-specific CTA with context-aware actions (claim, join/submit, milestone application) and a mobile cancel confirmation flow.
    • Shows a “Raise a Dispute (Coming Soon)” indicator when applicable.
  • Refactor

    • Centralized CTA state and actions to provide consistent behavior across sidebar and mobile, improving reliability of action labels and enabled/disabled states.

Review Change Stack

@vercel

vercel Bot commented May 28, 2026

Copy link
Copy Markdown

@Biokes 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 May 28, 2026

Copy link
Copy Markdown

@Biokes 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 May 28, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Biokes, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 27 minutes and 53 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fcbd9c20-941c-49db-9c2e-89f6d06d467d

📥 Commits

Reviewing files that changed from the base of the PR and between caf9105 and 86c523d.

📒 Files selected for processing (4)
  • components/bounty-detail/bounty-detail-client.tsx
  • components/bounty-detail/bounty-detail-mobile-cta.tsx
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty-detail/use-bounty-cta-state.ts
📝 Walkthrough

Walkthrough

Extracts CTA state/handlers into a new client hook useBountyCTAState, moves MobileCTA to its own file, refactors SidebarCTA to consume the hook, and updates client imports to reference the separated MobileCTA component.

Changes

Bounty CTA State and Component Extraction

Layer / File(s) Summary
Shared CTA state hook
components/bounty-detail/use-bounty-cta-state.ts
New client hook useBountyCTAState composes apply/join/cancel/dispute hooks, computes permission/status flags, provides clipboard copy behavior, and returns handlers/values used by both CTAs.
Mobile CTA component
components/bounty-detail/bounty-detail-mobile-cta.tsx
New MobileCTA component uses useBountyCTAState to render mobile-optimized actions: FCFS claim, competition join/submit, milestone application dialog, generic submit (opens githubIssueUrl), disabled dispute button, and a cancel confirmation dialog.
Sidebar CTA refactoring
components/bounty-detail/bounty-detail-sidebar-cta.tsx
SidebarCTA now consumes useBountyCTAState instead of managing local state/handlers; imports updated to include types and ApplicationDialog. Render logic remains driven by the hook's returned flags/handlers.
Client-side import updates
components/bounty-detail/bounty-detail-client.tsx
Splits the combined import so MobileCTA is imported from bounty-detail-mobile-cta and SidebarCTA remains imported from bounty-detail-sidebar-cta.

Sequence Diagram

sequenceDiagram
  participant Client as bounty-detail-client
  participant SidebarCTA
  participant MobileCTA
  participant Hook as useBountyCTAState
  participant Composed as ComposedHooks

  Client->>SidebarCTA: render with bounty
  Client->>MobileCTA: render with bounty

  SidebarCTA->>Hook: useBountyCTAState({bounty, onCancelled})
  MobileCTA->>Hook: useBountyCTAState({bounty, onCancelled})

  Hook->>Composed: compose useApplyToBounty, useCompetitionJoinState, useCancelBountyDialog, useCanRaiseDispute
  Composed-->>Hook: handlers + state
  Hook-->>SidebarCTA: {walletAddress, handleApply, handleJoin, handleCancel, ctaLabel, flags...}
  Hook-->>MobileCTA: {walletAddress, handleApply, handleJoin, handleCancel, ctaLabel, flags...}

  SidebarCTA-->>Client: sidebar UI
  MobileCTA-->>Client: mobile UI
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • boundlessfi/bounties#170: Refactors SidebarCTA/MobileCTA and introduces useBountyCTAState to centralize cancellation/refund CTA behavior.
  • boundlessfi/bounties#200: Adds milestone-based application dialog logic that overlaps the CTA apply flow now centralized in the hook.
  • boundlessfi/bounties#161: Adds FCFS-specific claim rendering used by CTA components affected by this refactor.

Suggested reviewers

  • Benjtalkshow

Poem

🐰 I hopped through code both near and far,
Split one file into two like splitting a star,
A hook holds the strings, the CTAs dance light,
Mobile hops separate, Sidebar stays bright,
Celebrate with carrots — refactor done right!

🚥 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 accurately describes the main change: splitting a large file into three focused files with no misleading or vague phrasing.
Linked Issues check ✅ Passed The PR fully satisfies issue #209: created shared hook, split components into separate files, updated imports, and maintained identical rendering behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to the refactoring objective; no unrelated modifications to behavior, styling, or other components were introduced.

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

✨ 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 and usage tips.

@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: 4

🤖 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/bounty-detail-mobile-cta.tsx`:
- Around line 77-87: The mobile CTA currently duplicates conditional label logic
(using hasJoined, canAct, isPastDeadline, bounty.status) and omits the explicit
"Cancelled" label; replace the inline ternary block in
bounty-detail-mobile-cta.tsx with a single call to the existing ctaLabel()
hook/function to ensure consistent labels across mobile/desktop and include
cancelled handling; update both occurrences (the one at the shown block and the
other at the later occurrence) to use ctaLabel() and remove the duplicated
ternary logic so the component reads its label only from ctaLabel().
- Around line 105-113: The icon-only cancel Buttons (the Button wrapping
<XCircle /> that calls setCancelDialogOpen(true)) lack accessible names; add an
explicit accessible label (e.g., aria-label="Cancel" or aria-label="Close") to
those Button components (both occurrences, including the other icon-only cancel
Button around lines 133-140) so assistive tech can announce them; update the
Button props to include aria-label (or aria-labelledby pointing to a
visually-hidden text node) on the Button elements containing only the XCircle
icon.

In `@components/bounty-detail/use-bounty-cta-state.ts`:
- Around line 65-67: Replace the hardcoded/ignored values so count fields from
the backend are used when present: set claimCount to use bounty.claimCount
first, falling back to bounty._count?.submissions then 0 (e.g., claimCount =
bounty.claimCount ?? bounty._count?.submissions ?? 0), and set maxParticipants
to read bounty.maxParticipants (e.g., maxParticipants: number | null =
bounty.maxParticipants ?? null) instead of forcing null; keep deadline as-is.
- Around line 72-79: The handleApply function currently returns early when
walletAddress is missing, causing the dialog to treat it as success; change it
to reject the submission by throwing an Error (or invoking the existing
toast/failure path) when walletAddress is falsy so the caller knows the apply
failed. Specifically, in handleApply (which calls applyToBounty with bounty.id
and walletAddress), replace the silent return with throwing a descriptive error
(or calling the toast failure helper) so the dialog stays open and shows the
failure state instead of closing silently.
🪄 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: 924c74e2-f473-4a47-b85d-380bd1f99423

📥 Commits

Reviewing files that changed from the base of the PR and between ede3b20 and caf9105.

📒 Files selected for processing (4)
  • components/bounty-detail/bounty-detail-client.tsx
  • components/bounty-detail/bounty-detail-mobile-cta.tsx
  • components/bounty-detail/bounty-detail-sidebar-cta.tsx
  • components/bounty-detail/use-bounty-cta-state.ts

Comment thread components/bounty-detail/bounty-detail-mobile-cta.tsx
Comment thread components/bounty-detail/bounty-detail-mobile-cta.tsx
Comment thread components/bounty-detail/use-bounty-cta-state.ts
Comment thread components/bounty-detail/use-bounty-cta-state.ts

@Benjtalkshow Benjtalkshow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good structure. The shared hook is well-organized and both components consume from it cleanly. This is the right split.

Three things to fix before merge.

TypeScript fails with 4 import errors. CancellationRecord is imported from @/types/bounty in all three files, but it actually lives at @/types/escrow. And ApplicationFormValues is imported from @/lib/graphql/generated in use-bounty-cta-state.ts, but it's exported from @/components/bounty/application-dialog. Fix the import paths and run pnpm tsc --noEmit locally before pushing.

The branch has merge conflicts against main. Please rebase.

SidebarCTA is 365 lines, over the 300-line target from the issue. The cancel dialog AlertDialog block is about 60 lines of JSX that could be extracted into a small CancelBountyDialog component to bring it under 300.

@Biokes Biokes left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

update:
fixed the issue

@Benjtalkshow

Copy link
Copy Markdown
Contributor

update: fixed the issue

The merge made things worse, not better. bounty-detail-sidebar-cta.tsx now contains a duplicated MobileCTA function starting at line 372 that the merge re-introduced from main. So MobileCTA is defined twice (once in your new file, once back in sidebar-cta.tsx), and the duplicated version is missing all its imports.

pnpm tsc --noEmit now produces 10 errors instead of the 4 from last review:

The original CancellationRecord and ApplicationFormValues import path errors from the previous review are still unfixed. CancellationRecord lives at @/types/escrow, not @/types/bounty. ApplicationFormValues is exported from @/components/bounty/application-dialog, not @/lib/graphql/generated.

The merge then added 6 more errors inside the accidentally duplicated MobileCTA block (lines 373-400) which references authClient, useCancelBountyDialog, useCanRaiseDispute, useCompetitionJoinState, useApplyToBounty, and ApplicationFormValues without importing them.

bounty-detail-sidebar-cta.tsx is now 565 lines, nearly back to the original 680-line problem the issue was created to solve.

Please:

  1. Delete the entire duplicated MobileCTA block from bounty-detail-sidebar-cta.tsx (line 372 onward). It belongs only in bounty-detail-mobile-cta.tsx now.
  2. Fix the CancellationRecord and ApplicationFormValues import paths.
  3. Run pnpm tsc --noEmit locally before pushing. The errors are visible immediately.
  4. Verify the SidebarCTA file lands under 300 lines after the duplicate removal.

The shared hook itself is well-structured. Once the merge cleanup is done correctly this will be ready.

@Biokes

Biokes commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

Latest comments fixed

@Benjtalkshow Benjtalkshow left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Big turnaround. All the TS errors are gone, the duplicated MobileCTA is out of the sidebar file, the import paths are fixed, and the conflicts are resolved. Both components consume from the shared hook cleanly.

The sidebar file is at 366 lines, down from the original 680 but still over the 300-line target from the issue. Most of the remaining weight is the Cancel AlertDialog JSX (about 60 lines). I'm OK merging this as-is and opening a small follow-up to extract CancelBountyDialog into its own file, since the main duplication problem the issue was filed to solve is gone.

Merging this in. Thanks for sticking with it through the rounds.

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 bounty-detail-sidebar-cta.tsx into SidebarCTA and MobileCTA files

2 participants