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
11 changes: 11 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,14 @@ body {
background-clip: text;
color: transparent;
}

@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
17 changes: 14 additions & 3 deletions src/app/issues/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,20 @@ import { fetchBounty } from "@/lib/api";
import { mockBounties } from "@/lib/mock-data";
import { StatusBadge, DifficultyBadge, Badge } from "@/components/ui/Badge";
import { BountyDescription } from "@/components/bounty/BountyDescription";
import { formatCurrency, daysUntil } from "@/lib/utils";
import { formatCurrency, formatDaysUntil, daysUntil } from "@/lib/utils";
import { IssueActions } from "./IssueActions";

const escrowStatusLabel: Record<string, string> = {
open: "Awaiting funding",
funded: "Funds locked",
claimed: "Funds locked",
in_review: "Funds locked",
merged: "Funds locked",
paid: "Paid out",
refunded: "Refunded to sponsor",
expired: "Expired, unclaimed",
};

export default async function IssueDetailPage({
params,
}: {
Expand Down Expand Up @@ -50,7 +61,7 @@ export default async function IssueDetailPage({
<span className="text-sm">Escrow status</span>
</div>
<p className="mt-2 font-medium text-slate-900 dark:text-white">
{bounty.status === "open" ? "Awaiting funding" : "Funds locked"}
{escrowStatusLabel[bounty.status] ?? "Funds locked"}
</p>
</div>
<div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900">
Expand All @@ -59,7 +70,7 @@ export default async function IssueDetailPage({
<span className="text-sm">Deadline</span>
</div>
<p className="mt-2 font-medium text-slate-900 dark:text-white">
{days > 0 ? `${days} days left` : "Passed"}
{formatDaysUntil(days)}
</p>
</div>
<div className="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900">
Expand Down
4 changes: 2 additions & 2 deletions src/components/bounty/BountyCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Link from "next/link";
import { Clock, GitPullRequest } from "lucide-react";
import { StatusBadge, DifficultyBadge, Badge } from "@/components/ui/Badge";
import { Avatar } from "@/components/ui/Avatar";
import { formatCurrency, daysUntil } from "@/lib/utils";
import { formatCurrency, formatDaysUntil, daysUntil } from "@/lib/utils";
import { stripMarkdownToPlainText } from "@/lib/markdown";
import type { Bounty } from "@/types";

Expand Down Expand Up @@ -49,7 +49,7 @@ export function BountyCard({ bounty }: { bounty: Bounty }) {
<div className="mt-4 flex items-center gap-4 pl-2 text-xs text-slate-400 dark:text-slate-500">
<span className="flex items-center gap-1">
<Clock className="h-3.5 w-3.5" />
{days > 0 ? `${days}d left` : "Deadline passed"}
{formatDaysUntil(days)}
</span>
{bounty.claimedBy && (
<span className="flex items-center gap-1">
Expand Down
24 changes: 24 additions & 0 deletions src/components/bounty/BountyDescription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ const markdownComponents: Components = {
{children}
</blockquote>
),
table: ({ children }) => (
<div className="my-4 overflow-x-auto">
<table className="w-full border-collapse text-left text-sm text-slate-600 dark:text-slate-300">
{children}
</table>
</div>
),
thead: ({ children }) => (
<thead className="border-b border-slate-200 bg-slate-50 text-xs font-semibold uppercase text-slate-700 dark:border-slate-700 dark:bg-slate-800/50 dark:text-slate-300">
{children}
</thead>
),
tbody: ({ children }) => (
<tbody className="divide-y divide-slate-200 dark:divide-slate-800">
{children}
</tbody>
),
tr: ({ children }) => <tr>{children}</tr>,
th: ({ children }) => (
<th className="px-4 py-2 font-semibold text-slate-900 dark:text-white">
{children}
</th>
),
td: ({ children }) => <td className="px-4 py-2">{children}</td>,
};

/**
Expand Down
7 changes: 2 additions & 5 deletions src/components/ui/StatCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* always available via the title attribute (keyboard-navigable, hover tooltip).
*/

import { cn } from "@/lib/utils";
import { cn, formatCurrency } from "@/lib/utils";
import { ArrowUpRight, ArrowDownRight, AlertCircle } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import { Sparkline } from "./Sparkline";
Expand Down Expand Up @@ -78,10 +78,7 @@ function formatValue(
): { display: string; exact: string } {
switch (format) {
case "currency": {
const formatted = value.toLocaleString("en-US", {
maximumFractionDigits: 2,
});
const display = `${formatted} ${asset}`;
const display = formatCurrency(value, asset);
// Exact value for tooltip always shows full precision
const exact = `${value.toLocaleString("en-US", { maximumFractionDigits: 6 })} ${asset}`;
return { display, exact };
Expand Down
16 changes: 15 additions & 1 deletion src/lib/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import {
formatCurrency,
formatDaysUntil,
formatPercent,
coerceDecimal,
coerceNonNegative,
Expand Down Expand Up @@ -92,7 +93,20 @@ describe("formatCurrency", () => {
});
});

// ─── formatPercent ───────────────────────────────────────────────────────────
// ─── formatDaysUntil ────────────────────────────────────────────────────────

describe("formatDaysUntil", () => {
it("formats positive days correctly", () => {
expect(formatDaysUntil(5)).toBe("5 days left");
expect(formatDaysUntil(1)).toBe("1 day left");
});

it("formats 0 or negative days as 'Deadline passed'", () => {
expect(formatDaysUntil(0)).toBe("Deadline passed");
expect(formatDaysUntil(-2)).toBe("Deadline passed");
});
});


describe("formatPercent", () => {
it("converts a fraction to a percentage", () => {
Expand Down
5 changes: 5 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ export function daysUntil(dateIso: string) {
return Math.ceil(diffMs / (1000 * 60 * 60 * 24));
}

export function formatDaysUntil(days: number): string {
if (days <= 0) return "Deadline passed";
return `${days} ${days === 1 ? "day" : "days"} left`;
}

export function validateTeamSplits(
splits: Array<{ percentage: string | number }>,
tolerance = 0.01
Expand Down