Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions components/bounty-detail/bounty-detail-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { RefundStatusTracker } from "../bounty/refund-status";
import { FeeCalculator } from "../bounty/fee-calculator";
import { useEscrowPool } from "@/hooks/use-escrow";
import { authClient } from "@/lib/auth-client";
import { useWalletAddress } from "@/hooks/use-wallet-address";
import { useDeadlinePassed } from "@/hooks/use-deadline-passed";
import type { CancellationRecord } from "@/types/escrow";
import { MilestoneFunnel } from "@/components/bounty/milestone-funnel";
Expand Down Expand Up @@ -87,6 +88,8 @@ export function BountyDetailClient({ bountyId }: { bountyId: string }) {
}, []);

const pastDeadline = useDeadlinePassed(bounty?.bountyWindow?.endDate);
// walletAddress is required for contract actions. Do NOT fallback to user.id.
const walletAddress = useWalletAddress() ?? "";

if (isPending) return <BountyDetailSkeleton />;

Expand Down Expand Up @@ -145,9 +148,6 @@ export function BountyDetailClient({ bountyId }: { bountyId: string }) {
const isCreator =
(session?.user as { id?: string } | undefined)?.id === bounty.createdBy;
const isFinalized = bounty.status === "COMPLETED";
// walletAddress is required for contract actions. Do NOT fallback to user.id.
const walletAddress =
(session?.user as { walletAddress?: string })?.walletAddress || "";

// Identify if the current user is the assigned contributor
// using a fallback check on submissions or assumed backend field.
Expand Down
11 changes: 2 additions & 9 deletions components/bounty/competition-judging.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState } from "react";
import { Loader2, Trophy, Award, CheckCircle2, Lock } from "lucide-react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { useWalletAddress } from "@/hooks/use-wallet-address";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
Expand Down Expand Up @@ -45,7 +45,7 @@ export function CompetitionJudging({
totalReward,
currency,
}: CompetitionJudgingProps) {
const { data: session } = authClient.useSession();
const walletAddress = useWalletAddress();
const approveMutation = useApproveContestWinner();
const finalizeMutation = useFinalizeContest();

Expand All @@ -55,13 +55,6 @@ export function CompetitionJudging({
// On next query invalidation the backend status takes over.
const [localApproved, setLocalApproved] = useState<Set<string>>(new Set());

const walletAddress =
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.walletAddress ||
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.address ||
null;

const handleApprove = async (sub: Submission) => {
if (!walletAddress) {
toast.error("Connect your wallet to approve.");
Expand Down
11 changes: 2 additions & 9 deletions components/bounty/competition-submission.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { useState, useEffect } from "react";
import { Loader2, Lock, Send, Clock } from "lucide-react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { useWalletAddress } from "@/hooks/use-wallet-address";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
Expand Down Expand Up @@ -40,7 +40,7 @@ export function CompetitionSubmission({
deadline,
hasJoined,
}: CompetitionSubmissionProps) {
const { data: session } = authClient.useSession();
const walletAddress = useWalletAddress();
const [workCid, setWorkCid] = useState("");
const submitMutation = useSubmitContestWork();
const isPastDeadline = useDeadlinePassed(deadline);
Expand All @@ -55,13 +55,6 @@ export function CompetitionSubmission({
return () => clearInterval(id);
}, [deadline]);

const walletAddress =
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.walletAddress ||
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.address ||
null;

if (!hasJoined) return null;

const trimmed = workCid.trim();
Expand Down
8 changes: 2 additions & 6 deletions components/bounty/fcfs-approval-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Link from "next/link";
import { Loader2 } from "lucide-react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { useWalletAddress } from "@/hooks/use-wallet-address";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
Expand All @@ -29,12 +30,7 @@ export function FcfsApprovalPanel({ bounty }: { bounty: FcfsApprovalBounty }) {
const [points, setPoints] = useState(10);

const currentUserId = (session?.user as { id?: string } | undefined)?.id;
const walletAddress =
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.walletAddress ||
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.address ||
null;
const walletAddress = useWalletAddress();

const isCreator = Boolean(
currentUserId && currentUserId === bounty.createdBy,
Expand Down
8 changes: 2 additions & 6 deletions components/bounty/fcfs-claim-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react";
import { AlertTriangle, Clock3, Loader2 } from "lucide-react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { useWalletAddress } from "@/hooks/use-wallet-address";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -73,12 +74,7 @@ export function FcfsClaimButton({ bounty }: { bounty: FcfsBounty }) {
}, []);

const currentUserId = (session?.user as { id?: string } | undefined)?.id;
const walletAddress =
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.walletAddress ||
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.address ||
null;
const walletAddress = useWalletAddress();

const isFcfs = bounty.type === "FIXED_PRICE";
const isOpen = bounty.status === "OPEN";
Expand Down
49 changes: 49 additions & 0 deletions hooks/__tests__/use-wallet-address.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { renderHook } from "@testing-library/react";
import { useWalletAddress } from "../use-wallet-address";
import { authClient } from "@/lib/auth-client";

jest.mock("@/lib/auth-client", () => ({
authClient: {
useSession: jest.fn(),
},
}));

describe("useWalletAddress", () => {
it("returns null when there is no session", () => {
(authClient.useSession as jest.Mock).mockReturnValue({ data: null });
const { result } = renderHook(() => useWalletAddress());
expect(result.current).toBeNull();
});

it("returns null when session has no user", () => {
(authClient.useSession as jest.Mock).mockReturnValue({
data: { user: undefined },
});
const { result } = renderHook(() => useWalletAddress());
expect(result.current).toBeNull();
});

it("returns walletAddress when available", () => {
(authClient.useSession as jest.Mock).mockReturnValue({
data: { user: { walletAddress: "0xABC", address: "0xDEF" } },
});
const { result } = renderHook(() => useWalletAddress());
expect(result.current).toBe("0xABC");
});

it("falls back to address when walletAddress is missing", () => {
(authClient.useSession as jest.Mock).mockReturnValue({
data: { user: { address: "0xDEF" } },
});
const { result } = renderHook(() => useWalletAddress());
expect(result.current).toBe("0xDEF");
});

it("returns null when neither walletAddress nor address exists", () => {
(authClient.useSession as jest.Mock).mockReturnValue({
data: { user: { name: "test" } },
});
const { result } = renderHook(() => useWalletAddress());
expect(result.current).toBeNull();
});
});
11 changes: 2 additions & 9 deletions hooks/use-competition-join-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useState } from "react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import { useWalletAddress } from "@/hooks/use-wallet-address";
import {
useJoinCompetition,
ContestError,
Expand All @@ -22,17 +22,10 @@ interface CompetitionJoinState {
export function useCompetitionJoinState(
bounty: BountyFieldsFragment & Partial<Bounty>,
): CompetitionJoinState {
const { data: session } = authClient.useSession();
const walletAddress = useWalletAddress();
const joinMutation = useJoinCompetition();
const [localJoined, setLocalJoined] = useState(false);

const walletAddress =
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.walletAddress ||
(session?.user as { walletAddress?: string; address?: string } | undefined)
?.address ||
null;

const deadline = bounty.bountyWindow?.endDate ?? null;
const isPastDeadline = useDeadlinePassed(deadline);

Expand Down
15 changes: 15 additions & 0 deletions hooks/use-wallet-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client";

import { authClient } from "@/lib/auth-client";

/**
* Returns the connected wallet address from the current session, or null
* if the user is not signed in or has no wallet attached.
*/
export function useWalletAddress(): string | null {
const { data: session } = authClient.useSession();
const user = session?.user as
| { walletAddress?: string; address?: string }
| undefined;
return user?.walletAddress || user?.address || null;
}
Loading