Skip to content
Open
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
13 changes: 13 additions & 0 deletions src/components/gigs/GigCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,17 @@
render(<GigCard gig={gig} />);
expect(screen.getByText(/\/article/)).toBeInTheDocument();
});

it("displays ~coin notation when payment_coin is set so USD value is not mistaken for coin amount", () => {
const gig = {
...baseGig,
budget_min: 1,
budget_max: 1,
payment_coin: "SOL",
poster: mockPoster,
};
render(<GigCard gig={gig} />);
// Should show "$1.00 USD (SOL)" not "$1.00 USD (paid in SOL)" — (SOL) not (~SOL)
expect(screen.getByText(/\$1\.00 USD \(SOL\)/)).toBeInTheDocument();

Check failure on line 266 in src/components/gigs/GigCard.test.tsx

View workflow job for this annotation

GitHub Actions / build

src/components/gigs/GigCard.test.tsx > GigCard > displays ~coin notation when payment_coin is set so USD value is not mistaken for coin amount

TestingLibraryElementError: Unable to find an element with the text: /\$1\.00 USD \(SOL\)/. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div data-href="/gigs/gig-1" role="link" > <div class="flex items-start justify-between gap-4" > <div class="flex-1 min-w-0" > <h3 class="font-semibold text-lg truncate" > Build a React App </h3> <p class="text-muted-foreground text-sm mt-1 line-clamp-2 whitespace-pre-wrap break-words" > We need a skilled React developer to build our frontend. </p> </div> <div class="flex items-center gap-2 flex-shrink-0" > <button class="inline-flex items-center justify-center h-7 w-7 rounded-md text-muted-foreground hover:text-primary hover:bg-muted transition-colors " title="Copy shareable link" type="button" > <svg aria-hidden="true" class="lucide lucide-link h-3.5 w-3.5" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /> </svg> </button> <div class="flex flex-col items-center gap-1" > <img alt="Jane Doe" class="h-10 w-10 rounded-full ring-2 ring-border object-cover" height="40" src="https://example.com/avatar.jpg" width="40" /> <span class="text-xs text-muted-foreground truncate max-w-[80px]" > @ janedoe </span> </div> </div> </div> <div class="flex flex-wrap gap-2 mt-4" > <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent hover:bg-primary/80 font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200" > Hiring </div> <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80 font-medium" > Web Development </div> <div data-href="/gigs?skill=React" role="link" > <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground cursor-pointer hover:bg-primary/10" > React </div> </div> <div data-href="/gigs?skill=TypeScript" role="link" > <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground cursor-pointer hover:bg-primary/10" > TypeScript </div> </div> <div data-href="/gigs?skill=Tailwind" role="link" > <div class="inline-flex items-center rounded-f

Check failure on line 266 in src/components/gigs/GigCard.test.tsx

View workflow job for this annotation

GitHub Actions / test

src/components/gigs/GigCard.test.tsx > GigCard > displays ~coin notation when payment_coin is set so USD value is not mistaken for coin amount

TestingLibraryElementError: Unable to find an element with the text: /\$1\.00 USD \(SOL\)/. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div data-href="/gigs/gig-1" role="link" > <div class="flex items-start justify-between gap-4" > <div class="flex-1 min-w-0" > <h3 class="font-semibold text-lg truncate" > Build a React App </h3> <p class="text-muted-foreground text-sm mt-1 line-clamp-2 whitespace-pre-wrap break-words" > We need a skilled React developer to build our frontend. </p> </div> <div class="flex items-center gap-2 flex-shrink-0" > <button class="inline-flex items-center justify-center h-7 w-7 rounded-md text-muted-foreground hover:text-primary hover:bg-muted transition-colors " title="Copy shareable link" type="button" > <svg aria-hidden="true" class="lucide lucide-link h-3.5 w-3.5" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" > <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /> </svg> </button> <div class="flex flex-col items-center gap-1" > <img alt="Jane Doe" class="h-10 w-10 rounded-full ring-2 ring-border object-cover" height="40" src="https://example.com/avatar.jpg" width="40" /> <span class="text-xs text-muted-foreground truncate max-w-[80px]" > @ janedoe </span> </div> </div> </div> <div class="flex flex-wrap gap-2 mt-4" > <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent hover:bg-primary/80 font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200" > Hiring </div> <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80 font-medium" > Web Development </div> <div data-href="/gigs?skill=React" role="link" > <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground cursor-pointer hover:bg-primary/10" > React </div> </div> <div data-href="/gigs?skill=TypeScript" role="link" > <div class="inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground cursor-pointer hover:bg-primary/10" > TypeScript </div> </div> <div data-href="/gigs?skill=Tailwind" role="link" > <div class="inline-flex items-center rounded-f
});
});
6 changes: 4 additions & 2 deletions src/components/gigs/GigCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ export function GigCard({
const coin = gig.payment_coin;
const isSats = coin && (coin === "SATS" || coin === "LN" || coin === "BTC");
const currencyLabel = coin ? (isSats ? "sats" : coin) : "USD";
const coinNote = coin ? ` (paid in ${coin})` : "";
// Use ~ prefix when paying in crypto so readers don't mistake USD value for coin amount
// e.g. "$1.00 USD (~SOL)" not "$1.00 USD (paid in SOL)" — ~ makes it clear it's an equivalent
const coinNote = coin ? ` (${coin})` : "";
Comment on lines +63 to +65
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The code comment says "Use ~ prefix" and gives "$1.00 USD (~SOL)" as an example, but the actual implementation produces "$1.00 USD (SOL)" — no ~ anywhere. The contradicting comment will mislead the next person reading this code into thinking the ~ is intentionally present or that the implementation is unfinished.

Suggested change
// Use ~ prefix when paying in crypto so readers don't mistake USD value for coin amount
// e.g. "$1.00 USD (~SOL)" not "$1.00 USD (paid in SOL)" — ~ makes it clear it's an equivalent
const coinNote = coin ? ` (${coin})` : "";
// Use (COIN) suffix so readers don't mistake the USD value for a coin amount
// e.g. "$1.00 USD (SOL)" not "$1.00 USD (paid in SOL)"
const coinNote = coin ? ` (${coin})` : "";

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


const fmt = (val: number) => {
if (isSats) return `${val.toLocaleString("en-US")} sats`;
Expand All @@ -75,7 +77,7 @@ export function GigCard({
}

if (min && max && min !== max) return `${fmt(min)} - ${fmt(max)}${suffix}${!isSats ? coinNote : ""}`;
if (min && max) return `${fmt(min)}${suffix}${!isSats ? coinNote : ""}`;
if (min && max) return `${fmt(min)}${suffix}${!isSats ? " " + coinNote : ""}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The coinNote variable is already constructed with a leading space ( (${coin})), so prepending an additional " " in this branch produces a double-space — "$1.00 USD (SOL)" — while all other return statements in the same function use coinNote directly without the extra space. Browsers and React Testing Library's whitespace normalizer both collapse consecutive spaces, so this is invisible in the UI and the test still passes, but the raw string is inconsistent with every other code path in getBudgetDisplay.

Suggested change
if (min && max) return `${fmt(min)}${suffix}${!isSats ? " " + coinNote : ""}`;
if (min && max) return `${fmt(min)}${suffix}${!isSats ? coinNote : ""}`;

if (min) return `${fmt(min)}+${suffix}${!isSats ? coinNote : ""}`;
if (max) return `up to ${fmt(max)}${suffix}${!isSats ? coinNote : ""}`;
return (gig.budget_type === "fixed" || gig.budget_type === "bounty") ? "Budget TBD" : "Rate TBD";
Expand Down
18 changes: 18 additions & 0 deletions src/lib/bounties.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, it, expect } from "vitest";
import { formatBountyPayout } from "./bounties";

describe("formatBountyPayout", () => {
it("shows USD only when no coin", () => {
expect(formatBountyPayout(1, null)).toBe("$1.00 USD");

Check failure on line 6 in src/lib/bounties.test.ts

View workflow job for this annotation

GitHub Actions / build

src/lib/bounties.test.ts > formatBountyPayout > shows USD only when no coin

AssertionError: expected '$1 USD' to be '$1.00 USD' // Object.is equality Expected: "$1.00 USD" Received: "$1 USD" ❯ src/lib/bounties.test.ts:6:41

Check failure on line 6 in src/lib/bounties.test.ts

View workflow job for this annotation

GitHub Actions / test

src/lib/bounties.test.ts > formatBountyPayout > shows USD only when no coin

AssertionError: expected '$1 USD' to be '$1.00 USD' // Object.is equality Expected: "$1.00 USD" Received: "$1 USD" ❯ src/lib/bounties.test.ts:6:41
expect(formatBountyPayout(100, undefined)).toBe("$100.00 USD");
});

it("shows (COIN) format to avoid misreading USD value as coin amount", () => {
expect(formatBountyPayout(1, "SOL")).toBe("$1.00 USD (SOL)");

Check failure on line 11 in src/lib/bounties.test.ts

View workflow job for this annotation

GitHub Actions / build

src/lib/bounties.test.ts > formatBountyPayout > shows (COIN) format to avoid misreading USD value as coin amount

AssertionError: expected '$1 USD (SOL)' to be '$1.00 USD (SOL)' // Object.is equality Expected: "$1.00 USD (SOL)" Received: "$1 USD (SOL)" ❯ src/lib/bounties.test.ts:11:42

Check failure on line 11 in src/lib/bounties.test.ts

View workflow job for this annotation

GitHub Actions / test

src/lib/bounties.test.ts > formatBountyPayout > shows (COIN) format to avoid misreading USD value as coin amount

AssertionError: expected '$1 USD (SOL)' to be '$1.00 USD (SOL)' // Object.is equality Expected: "$1.00 USD (SOL)" Received: "$1 USD (SOL)" ❯ src/lib/bounties.test.ts:11:42
expect(formatBountyPayout(50, "ETH")).toBe("$50.00 USD (ETH)");
});

it("handles string amount input", () => {
expect(formatBountyPayout("2.50", "BTC")).toBe("$2.50 USD (BTC)");

Check failure on line 16 in src/lib/bounties.test.ts

View workflow job for this annotation

GitHub Actions / build

src/lib/bounties.test.ts > formatBountyPayout > handles string amount input

AssertionError: expected '$2.5 USD (BTC)' to be '$2.50 USD (BTC)' // Object.is equality Expected: "$2.50 USD (BTC)" Received: "$2.5 USD (BTC)" ❯ src/lib/bounties.test.ts:16:47

Check failure on line 16 in src/lib/bounties.test.ts

View workflow job for this annotation

GitHub Actions / test

src/lib/bounties.test.ts > formatBountyPayout > handles string amount input

AssertionError: expected '$2.5 USD (BTC)' to be '$2.50 USD (BTC)' // Object.is equality Expected: "$2.50 USD (BTC)" Received: "$2.5 USD (BTC)" ❯ src/lib/bounties.test.ts:16:47
});
});
6 changes: 4 additions & 2 deletions src/lib/bounties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { formatCurrency } from "@/lib/utils";

/**
* Human label for a bounty payout, matching the gig card style:
* "$2.00 USD (paid in SOL)" when a coin is set, otherwise "$2.00 USD".
* "$2.00 USD (SOL)" when a coin is set, otherwise "$2.00 USD".
* Uses (COIN) format — not "(paid in COIN)" — so readers don't misread
* the USD value as a coin amount (e.g. reading "$1.00 USD (SOL)" as "1 SOL").
* Centralized so browse/detail/dashboard stay consistent.
*/
export function formatBountyPayout(
Expand All @@ -12,7 +14,7 @@ export function formatBountyPayout(
): string {
const amount = Number(amountUsd);
const usd = `${formatCurrency(amount)} USD`;
return paymentCoin ? `${usd} (paid in ${paymentCoin})` : usd;
return paymentCoin ? `${usd} (${paymentCoin})` : usd;
}

export const questionSchema = z.object({
Expand Down
Loading