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
55 changes: 40 additions & 15 deletions src/app/connect/ConnectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,46 @@ export function ConnectPanel() {
</p>
</div>
</div>
{address ? (
<div className="mt-4 flex items-center gap-2 rounded-lg bg-emerald-50 px-4 py-3 text-sm text-emerald-700 ring-1 ring-inset ring-emerald-200">
<CheckCircle2 className="h-4 w-4" />
Connected: {address.slice(0, 4)}...{address.slice(-4)} ({network})
</div>
) : (
<Button
className="mt-4 w-full"
variant="outline"
onClick={connect}
disabled={connecting}
>
{connecting ? "Connecting..." : "Connect Freighter"}
</Button>
)}

<div className="mt-4 space-y-2">
{address ? (
<div className="flex items-center gap-2 rounded-lg bg-emerald-50 px-4 py-3 text-sm text-emerald-700 ring-1 ring-inset ring-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-300 dark:ring-emerald-500/30">
<CheckCircle2 className="h-4 w-4" />
Connected: {address.slice(0, 4)}...{address.slice(-4)} ({network})
</div>
) : (
<Button
className="w-full"
variant="outline"
onClick={connect}
disabled={connecting}
>
{connecting ? "Connecting..." : "Connect Freighter"}
</Button>
)}

{user ? (
user.stellarAddress && user.stellarAddress === address ? (
<div className="flex items-center gap-2 rounded-lg bg-emerald-50 px-4 py-3 text-sm text-emerald-700 ring-1 ring-inset ring-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-300 dark:ring-emerald-500/30">
<CheckCircle2 className="h-4 w-4" />
Linked to your account
</div>
) : address ? (
<div className="rounded-lg bg-amber-50 px-4 py-3 text-sm text-amber-800 ring-1 ring-inset ring-amber-200 dark:bg-amber-500/10 dark:text-amber-300 dark:ring-amber-500/30">
This wallet is connected locally but not linked to your GitHub account.
{user.stellarAddress ? ` Your account is currently linked to ${user.stellarAddress.slice(0, 4)}...${user.stellarAddress.slice(-4)}.` : ""}
</div>
) : user.stellarAddress ? (
<div className="rounded-lg bg-amber-50 px-4 py-3 text-sm text-amber-800 ring-1 ring-inset ring-amber-200 dark:bg-amber-500/10 dark:text-amber-300 dark:ring-amber-500/30">
Your account is linked to {user.stellarAddress.slice(0, 4)}...{user.stellarAddress.slice(-4)}, but it is not currently active in Freighter.
</div>
) : null
) : address ? (
<div className="rounded-lg bg-amber-50 px-4 py-3 text-sm text-amber-800 ring-1 ring-inset ring-amber-200 dark:bg-amber-500/10 dark:text-amber-300 dark:ring-amber-500/30">
Wallet connected locally. Sign in with GitHub above to link this wallet to your account for payouts.
</div>
) : null}
</div>
{error && <p className="mt-3 text-sm text-rose-600">{error}</p>}
</div>
</div>
Expand Down
11 changes: 8 additions & 3 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,19 +156,24 @@ export default function HomePage() {
<section className="grid grid-cols-2 gap-4 md:grid-cols-4">
<StatCard
label="Total paid out"
value={formatCurrency(platformStats.totalPaidOut)}
value={platformStats.totalPaidOut}
format="currency"
/>
<StatCard
label="Bounties completed"
value={platformStats.bountiesCompleted.toLocaleString()}
value={platformStats.bountiesCompleted}
format="count"
/>
<StatCard
label="Active contributors"
value={platformStats.activeContributors.toLocaleString()}
value={platformStats.activeContributors}
format="count"
/>
{/* Duration doesn't fit currency/count/percent/raw formats; keep pre-formatted string */}
<StatCard
label="Avg. payout time"
value={`${platformStats.avgPayoutTimeMinutes} min`}
format="raw"
/>
</section>

Expand Down
27 changes: 20 additions & 7 deletions src/app/reputation/[handle]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { notFound } from "next/navigation";
import { fetchReputationByUsername } from "@/lib/api";
import { fetchReputationWithStatus } from "@/lib/api";
import { mockReputationProfiles } from "@/lib/mock-data";
import { StatCard } from "@/components/ui/StatCard";
import { Badge } from "@/components/ui/Badge";
import { Avatar } from "@/components/ui/Avatar";
import { formatCurrency, formatPercent } from "@/lib/utils";
// formatCurrency/formatPercent no longer needed — StatCard handles numeric formatting

export default async function ReputationPage({
params,
}: {
params: Promise<{ handle: string }>;
}) {
const { handle } = await params;
const profile = await fetchReputationByUsername(
const { profile, isLive } = await fetchReputationWithStatus(
handle,
mockReputationProfiles[handle] ?? null,
);

if (!profile) notFound();

const cardStatus = isLive ? "loaded" as const : "error" as const;

return (
<div className="mx-auto max-w-4xl px-6 py-12">
<div className="flex items-center gap-4">
Expand All @@ -43,16 +45,27 @@ export default async function ReputationPage({
<div className="mt-8 grid grid-cols-2 gap-4 md:grid-cols-4">
<StatCard
label="Lifetime earnings"
value={formatCurrency(profile.lifetimeEarnings)}
value={profile.lifetimeEarnings}
format="currency"
status={cardStatus}
/>
<StatCard
label="Merged PRs"
value={profile.mergedPRs}
format="count"
status={cardStatus}
/>
<StatCard label="Merged PRs" value={String(profile.mergedPRs)} />
<StatCard
label="Completion rate"
value={formatPercent(profile.completionRate)}
value={profile.completionRate}
format="percent"
status={cardStatus}
/>
<StatCard
label="On-time delivery"
value={formatPercent(profile.onTimeDeliveryRate)}
value={profile.onTimeDeliveryRate}
format="percent"
status={cardStatus}
/>
</div>

Expand Down
18 changes: 18 additions & 0 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,22 @@ export async function fetchReputationByUsername(
}
}


export async function fetchReputationWithStatus(
username: string,
fallback: ReputationProfile | null,
): Promise<{ profile: ReputationProfile | null; isLive: boolean }> {
try {
const users = await request<(RawUserProfile & { id: string })[]>("/users");
const user = users.find((u) => u.username === username);
if (!user) return { profile: fallback, isLive: false };
const snapshot = await request<RawReputationSnapshot | null>(
`/reputation/${user.id}`,
);
return { profile: adaptReputation(user, snapshot), isLive: true };
} catch {
return { profile: fallback, isLive: false };
}
}

export { request };