From 1c65516a6e5fb37bd47f35ded83e801739959388 Mon Sep 17 00:00:00 2001 From: The Joel Date: Mon, 29 Jun 2026 11:23:29 +0100 Subject: [PATCH] refactor: split bounty CTA branch logic into per-bounty-type components --- .../bounty-detail-sidebar-cta.tsx | 149 ++++-------------- .../bounty-detail/cta/competition-cta.tsx | 74 +++++++++ components/bounty-detail/cta/default-cta.tsx | 39 +++++ components/bounty-detail/cta/fcfs-cta.tsx | 28 ++++ .../bounty-detail/cta/milestone-based-cta.tsx | 35 ++++ .../bounty-detail/cta/multi-winner-cta.tsx | 52 ++++++ 6 files changed, 262 insertions(+), 115 deletions(-) create mode 100644 components/bounty-detail/cta/competition-cta.tsx create mode 100644 components/bounty-detail/cta/default-cta.tsx create mode 100644 components/bounty-detail/cta/fcfs-cta.tsx create mode 100644 components/bounty-detail/cta/milestone-based-cta.tsx create mode 100644 components/bounty-detail/cta/multi-winner-cta.tsx diff --git a/components/bounty-detail/bounty-detail-sidebar-cta.tsx b/components/bounty-detail/bounty-detail-sidebar-cta.tsx index bc0537a2..02286452 100644 --- a/components/bounty-detail/bounty-detail-sidebar-cta.tsx +++ b/components/bounty-detail/bounty-detail-sidebar-cta.tsx @@ -5,11 +5,9 @@ import { Github, Copy, Check, - AlertCircle, XCircle, Loader2, Users, - Clock, Gavel, } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -28,17 +26,46 @@ import { import { BountyFieldsFragment } from "@/lib/graphql/generated"; import { StatusBadge, TypeBadge } from "./bounty-badges"; -import { FcfsClaimButton } from "@/components/bounty/fcfs-claim-button"; import { CompetitionSubmission } from "@/components/bounty/competition-submission"; import { CompetitionStatus } from "@/components/bounty/competition-status"; import type { CancellationRecord } from "@/types/escrow"; import type { Bounty } from "@/types/bounty"; -import { ApplicationDialog } from "@/components/bounty/application-dialog"; import { useBountyCTAState } from "./use-bounty-cta-state"; import { RaiseDisputeDialog } from "./raise-dispute-dialog"; +import { FcfsCta } from "./cta/fcfs-cta"; +import { CompetitionCta } from "./cta/competition-cta"; +import { MultiWinnerCta } from "./cta/multi-winner-cta"; +import { MilestoneBasedCta } from "./cta/milestone-based-cta"; +import { DefaultCta } from "./cta/default-cta"; + type SidebarBounty = BountyFieldsFragment & Partial; +interface BountyCtaProps { + bounty: SidebarBounty; + state: ReturnType; +} + +function BountyCta({ bounty, state }: BountyCtaProps) { + if (state.isFcfs) { + return ; + } + if (state.isCompetition) { + return ; + } + if ( + bounty.type === "MULTI_WINNER_MILESTONE" && + state.canAct && + !state.isCreator + ) { + return ; + } + if (bounty.type === "MILESTONE_BASED" && state.canAct && !state.isCreator) { + return ; + } + return ; +} + interface SidebarCTAProps { bounty: SidebarBounty; onCancelled?: (record: CancellationRecord) => void; @@ -47,13 +74,9 @@ interface SidebarCTAProps { export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) { const [disputeDialogOpen, setDisputeDialogOpen] = useState(false); + const state = useBountyCTAState({ bounty, onCancelled }); const { - walletAddress, hasJoined, - isPastDeadline, - joinMutation, - handleJoin, - handleApply, copied, handleCopy, cancelDialogOpen, @@ -62,8 +85,6 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) { setCancelReason, isCancelling, handleCancel, - canAct, - isFcfs, isCompetition, canRaiseDispute, canCancel, @@ -72,14 +93,7 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) { deadline, isFinalized, submissionCount, - ctaLabel, - isCreator, - applyForSlotMutation, - handleApplyForSlot, - isSlotsFull, - isAlreadyJoined, - applyForSlotButtonLabel, - } = useBountyCTAState({ bounty, onCancelled }); + } = state; return (
@@ -149,102 +163,7 @@ export function SidebarCTA({ bounty, onCancelled }: SidebarCTAProps) { {isCompetition && } {/* CTA */} - {isFcfs ? ( - - ) : isCompetition ? ( - hasJoined ? ( - - ) : ( - - ) - ) : bounty.type === "MULTI_WINNER_MILESTONE" && canAct && !isCreator ? ( - - ) : bounty.type === "MILESTONE_BASED" && canAct && !isCreator ? ( - - Apply for Bounty - - } - /> - ) : ( - - )} - - {/* Helper text when locked out */} - {isCompetition && !hasJoined && isPastDeadline && ( -

- - Submission deadline has passed. -

- )} - {!canAct && !isCompetition && ( -

- - This bounty is no longer accepting new submissions. -

- )} + {/* Raise Dispute */} {canRaiseDispute && ( diff --git a/components/bounty-detail/cta/competition-cta.tsx b/components/bounty-detail/cta/competition-cta.tsx new file mode 100644 index 00000000..3d1710ab --- /dev/null +++ b/components/bounty-detail/cta/competition-cta.tsx @@ -0,0 +1,74 @@ +import { Users, Loader2, Clock } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import type { BountyFieldsFragment } from "@/lib/graphql/generated"; +import type { Bounty } from "@/types/bounty"; +import type { useBountyCTAState } from "../use-bounty-cta-state"; + +type SidebarBounty = BountyFieldsFragment & Partial; + +interface CompetitionCtaProps { + bounty: SidebarBounty; + state: Pick< + ReturnType, + | "hasJoined" + | "canAct" + | "isPastDeadline" + | "joinMutation" + | "walletAddress" + | "handleJoin" + | "ctaLabel" + >; +} + +export function CompetitionCta({ state }: CompetitionCtaProps) { + const { + hasJoined, + canAct, + isPastDeadline, + joinMutation, + walletAddress, + handleJoin, + ctaLabel, + } = state; + + return ( + <> + {hasJoined ? ( + + ) : ( + + )} + + {!hasJoined && isPastDeadline && ( +

+ + Submission deadline has passed. +

+ )} + + ); +} diff --git a/components/bounty-detail/cta/default-cta.tsx b/components/bounty-detail/cta/default-cta.tsx new file mode 100644 index 00000000..69346516 --- /dev/null +++ b/components/bounty-detail/cta/default-cta.tsx @@ -0,0 +1,39 @@ +import { AlertCircle } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import type { BountyFieldsFragment } from "@/lib/graphql/generated"; +import type { Bounty } from "@/types/bounty"; +import type { useBountyCTAState } from "../use-bounty-cta-state"; + +type SidebarBounty = BountyFieldsFragment & Partial; + +interface DefaultCtaProps { + bounty: SidebarBounty; + state: Pick, "canAct" | "ctaLabel">; +} + +export function DefaultCta({ bounty, state }: DefaultCtaProps) { + const { canAct, ctaLabel } = state; + + return ( + <> + + + {!canAct && ( +

+ + This bounty is no longer accepting new submissions. +

+ )} + + ); +} diff --git a/components/bounty-detail/cta/fcfs-cta.tsx b/components/bounty-detail/cta/fcfs-cta.tsx new file mode 100644 index 00000000..bb8529f0 --- /dev/null +++ b/components/bounty-detail/cta/fcfs-cta.tsx @@ -0,0 +1,28 @@ +import { FcfsClaimButton } from "@/components/bounty/fcfs-claim-button"; +import { AlertCircle } from "lucide-react"; +import type { BountyFieldsFragment } from "@/lib/graphql/generated"; +import type { Bounty } from "@/types/bounty"; +import type { useBountyCTAState } from "../use-bounty-cta-state"; + +type SidebarBounty = BountyFieldsFragment & Partial; + +interface FcfsCtaProps { + bounty: SidebarBounty; + state: Pick, "canAct">; +} + +export function FcfsCta({ bounty, state }: FcfsCtaProps) { + const { canAct } = state; + + return ( + <> + + {!canAct && ( +

+ + This bounty is no longer accepting new submissions. +

+ )} + + ); +} diff --git a/components/bounty-detail/cta/milestone-based-cta.tsx b/components/bounty-detail/cta/milestone-based-cta.tsx new file mode 100644 index 00000000..c4c66df2 --- /dev/null +++ b/components/bounty-detail/cta/milestone-based-cta.tsx @@ -0,0 +1,35 @@ +import { Button } from "@/components/ui/button"; +import { ApplicationDialog } from "@/components/bounty/application-dialog"; +import type { BountyFieldsFragment } from "@/lib/graphql/generated"; +import type { Bounty } from "@/types/bounty"; +import type { useBountyCTAState } from "../use-bounty-cta-state"; + +type SidebarBounty = BountyFieldsFragment & Partial; + +interface MilestoneBasedCtaProps { + bounty: SidebarBounty; + state: Pick< + ReturnType, + "walletAddress" | "handleApply" + >; +} + +export function MilestoneBasedCta({ bounty, state }: MilestoneBasedCtaProps) { + const { walletAddress, handleApply } = state; + + return ( + + Apply for Bounty + + } + /> + ); +} diff --git a/components/bounty-detail/cta/multi-winner-cta.tsx b/components/bounty-detail/cta/multi-winner-cta.tsx new file mode 100644 index 00000000..df2182a8 --- /dev/null +++ b/components/bounty-detail/cta/multi-winner-cta.tsx @@ -0,0 +1,52 @@ +import { Users, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import type { BountyFieldsFragment } from "@/lib/graphql/generated"; +import type { Bounty } from "@/types/bounty"; +import type { useBountyCTAState } from "../use-bounty-cta-state"; + +type SidebarBounty = BountyFieldsFragment & Partial; + +interface MultiWinnerCtaProps { + bounty: SidebarBounty; + state: Pick< + ReturnType, + | "isSlotsFull" + | "isAlreadyJoined" + | "walletAddress" + | "applyForSlotMutation" + | "handleApplyForSlot" + | "applyForSlotButtonLabel" + >; +} + +export function MultiWinnerCta({ state }: MultiWinnerCtaProps) { + const { + isSlotsFull, + isAlreadyJoined, + walletAddress, + applyForSlotMutation, + handleApplyForSlot, + applyForSlotButtonLabel, + } = state; + + return ( + + ); +}