diff --git a/components/bounty-detail/bounty-detail-client.tsx b/components/bounty-detail/bounty-detail-client.tsx index 6d6ed5c..816e5f2 100644 --- a/components/bounty-detail/bounty-detail-client.tsx +++ b/components/bounty-detail/bounty-detail-client.tsx @@ -13,7 +13,6 @@ import { BountyDetailSkeleton } from "@/components/ui/loading"; import { useBountyDetail } from "@/hooks/use-bounty-detail"; import { FcfsApprovalPanel } from "@/components/bounty/fcfs-approval-panel"; import { CompetitionJudging } from "@/components/bounty/competition-judging"; -import type { CompetitionSubmissionEntry } from "@/components/bounty/competition-judging"; import { EscrowDetailPanel } from "../bounty/escrow-detail-panel"; import { RefundStatusTracker } from "../bounty/refund-status"; import { FeeCalculator } from "../bounty/fee-calculator"; @@ -72,10 +71,7 @@ function getFullMilestoneData(bounty: BountyData): { // Backend does not currently provide applications in the response. // Fall back to empty array until the schema supports it. const getApplications = (bounty: BountyData): Application[] => { - return ( - (bounty as BountyData & { applications?: Application[] })?.applications ?? - [] - ); + return bounty?.applications ?? []; }; export function BountyDetailClient({ bountyId }: { bountyId: string }) { @@ -156,17 +152,14 @@ export function BountyDetailClient({ bountyId }: { bountyId: string }) { // Identify if the current user is the assigned contributor // using a fallback check on submissions or assumed backend field. const isAssignedApplicant = - (bounty as BountyData & { assignedContributorId?: string }) - ?.assignedContributorId === session?.user?.id || + bounty.assignedContributorId === session?.user?.id || bounty.submissions?.some((s) => s.submittedBy === session?.user?.id) || (!isCreator && bounty.status === "IN_PROGRESS"); // submissions is present on BountyQuery (single-bounty query) but not on - // BountyFieldsFragment (list query). The cast is safe here because - // useBountyDetail returns BountyFieldsFragment & Partial. - const competitionSubmissions = - (bounty as { submissions?: CompetitionSubmissionEntry[] | null }) - .submissions ?? []; + // BountyFieldsFragment (list query). Accessed via the Partial + // intersection in useBountyDetail's return type. + const competitionSubmissions = bounty.submissions ?? []; return (
diff --git a/hooks/use-competition-join-state.ts b/hooks/use-competition-join-state.ts index 1dde47b..f3c5ee2 100644 --- a/hooks/use-competition-join-state.ts +++ b/hooks/use-competition-join-state.ts @@ -9,6 +9,7 @@ import { } from "@/hooks/use-competition-bounty"; import { useDeadlinePassed } from "@/hooks/use-deadline-passed"; import type { BountyFieldsFragment } from "@/lib/graphql/generated"; +import type { Bounty } from "@/types/bounty"; interface CompetitionJoinState { walletAddress: string | null; @@ -19,7 +20,7 @@ interface CompetitionJoinState { } export function useCompetitionJoinState( - bounty: BountyFieldsFragment, + bounty: BountyFieldsFragment & Partial, ): CompetitionJoinState { const { data: session } = authClient.useSession(); const joinMutation = useJoinCompetition(); @@ -38,9 +39,7 @@ export function useCompetitionJoinState( // Derive from server payload (submissions list on BountyQuery) + local optimism. // BountyFieldsFragment (list queries) doesn't include submissions, so falls // back to false until the detail query resolves. - const bountySubmissions = ( - bounty as { submissions?: Array<{ submittedBy: string }> | null } - ).submissions; + const bountySubmissions = bounty.submissions; const serverHasJoined = walletAddress != null && (bountySubmissions?.some((s) => s.submittedBy === walletAddress) ?? false); diff --git a/types/bounty.ts b/types/bounty.ts index c0c8580..2eda170 100644 --- a/types/bounty.ts +++ b/types/bounty.ts @@ -83,6 +83,24 @@ export interface ContributorProgress { currentMilestoneId: string; } +export interface BountyApplication { + id: string; + applicantAddress: string; + applicantName?: string; + proposal: { + approach: string; + estimatedTimeline: string; + relevantExperience: string; + portfolioUrl?: string; + }; + reputation: { + score: number; + tier: string; + completionStats: string; + }; + createdAt: string; +} + export interface Bounty { id: string; title: string; @@ -105,14 +123,19 @@ export interface Bounty { bountyWindow?: BountyWindowType | null; submissions?: BountySubmission[] | null; + applications?: BountyApplication[] | null; _count?: BountyCount | null; milestones?: Milestone[] | null; contributorProgress?: ContributorProgress[] | null; + claimCount?: number | null; + maxParticipants?: number | null; maxSlots?: number | null; totalSlotsOccupied?: number | null; + assignedContributorId?: string | null; + createdBy: string; createdAt: string; updatedAt: string;