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
4 changes: 2 additions & 2 deletions src/app/dashboard/contributor/ContributorDashboardClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { apiRequest, fetchBounties } from "@/lib/api";
import {
mockReputationProfiles,
mockBounties,
recentActivity,
getRecentActivity,
contributorEarningsHistory,
contributorSparkline,
} from "@/lib/mock-data";
Expand Down Expand Up @@ -277,7 +277,7 @@ export default function ContributorDashboardClient() {
{isLive && <SampleDataChip />}
</div>
<div className="mt-4">
<ActivityList events={recentActivity.slice(0, 4)} />
<ActivityList events={getRecentActivity().slice(0, 4)} />
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/app/dashboard/maintainer/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from "next/link";
import { Layers, Clock, Coins, FolderGit2, CheckCircle2 } from "lucide-react";
import { fetchBounties } from "@/lib/api";
import { mockBounties, recentActivity, bountiesCompletedSparkline } from "@/lib/mock-data";
import { mockBounties, getRecentActivity, bountiesCompletedSparkline } from "@/lib/mock-data";
import { DashboardShell } from "@/components/dashboard/DashboardShell";
import { ActivityList } from "@/components/dashboard/ActivityList";
import { StatCard } from "@/components/ui/StatCard";
Expand Down Expand Up @@ -167,7 +167,7 @@ export default async function MaintainerDashboardPage() {
{source === "mock" && <SampleDataChip />}
</div>
<div className="mt-4">
<ActivityList events={recentActivity.slice(0, 5)} />
<ActivityList events={getRecentActivity().slice(0, 5)} />
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/app/dashboard/sponsor/SponsorDashboardClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { adaptBounty, type RawBounty, type RawMilestone } from "@/lib/adapters";
import {
mockSponsorSummary,
mockBounties,
recentActivity,
getRecentActivity,
sponsorSpendHistory,
sponsorSpendByRepo,
budgetSparkline,
Expand Down Expand Up @@ -284,7 +284,7 @@ export default function SponsorDashboardClient() {
{isLive && <SampleDataChip />}
</div>
<div className="mt-4">
<ActivityList events={recentActivity.slice(0, 5)} />
<ActivityList events={getRecentActivity().slice(0, 5)} />
</div>

{/* Dev-only: visual kitchen sink — renders all four StatCard states
Expand Down
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
platformStats,
trustedOrgs,
topContributors,
recentActivity,
getRecentActivity,
faqs,
} from "@/lib/mock-data";
import { formatCurrency, formatInteger } from "@/lib/utils";
Expand Down Expand Up @@ -270,7 +270,7 @@ export default function HomePage() {
</h2>
</div>
<div className="mt-6">
<ActivityList events={recentActivity} />
<ActivityList events={getRecentActivity()} />
</div>
</section>

Expand Down
20 changes: 17 additions & 3 deletions src/lib/mock-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
mockBounties,
mockMaintenancePools,
mockMilestones,
recentActivity,
getRecentActivity,
} from "./mock-data";
import { formatCurrency, isPlausibleAmount } from "./utils";

Expand All @@ -13,7 +13,7 @@ describe("mock-data asset coverage", () => {
mockBounties,
mockMilestones,
mockMaintenancePools,
recentActivity: recentActivity.filter((e) => e.asset),
recentActivity: getRecentActivity().filter((e) => e.asset),
};

it.each(Object.entries(collections))("%s contains both USDC and XLM entries", (_, items) => {
Expand All @@ -26,13 +26,27 @@ describe("mock-data asset coverage", () => {
...mockBounties.map((b) => b.reward),
...mockMilestones.flatMap((m) => [m.budget, m.distributed]),
...mockMaintenancePools.flatMap((p) => [p.balance, p.monthlyDeposit]),
...recentActivity.flatMap((e) => (e.amount === undefined ? [] : [e.amount])),
...getRecentActivity().flatMap((e) => (e.amount === undefined ? [] : [e.amount])),
];
for (const amount of amounts) {
expect(isPlausibleAmount(amount)).toBe(true);
}
});

it("anchors activity timestamps to the current call time, not module load (#707)", () => {
jest.useFakeTimers().setSystemTime(new Date("2026-01-01T00:00:00.000Z"));
const first = getRecentActivity()[0].occurredAt;

// Simulate a long-running server: real time passes well after the
// module was first evaluated.
jest.setSystemTime(new Date("2026-01-03T00:00:00.000Z"));
const twoDaysLater = getRecentActivity()[0].occurredAt;

jest.useRealTimers();
expect(twoDaysLater).not.toBe(first);
expect(new Date(twoDaysLater).getTime()).toBeGreaterThan(new Date(first).getTime());
});

it("XLM mock amounts render with XLM precision and no sanity warning", () => {
const pool = mockMaintenancePools.find((p) => p.asset === "XLM")!;
expect(formatCurrency(pool.balance, pool.asset)).toBe("8,412.1234567 XLM");
Expand Down
126 changes: 67 additions & 59 deletions src/lib/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,65 +275,73 @@ function minutesAgo(n: number): string {
return new Date(Date.now() - n * 60_000).toISOString();
}

export const recentActivity: ActivityEvent[] = [
{
id: "a1",
handle: "devrel_ana",
action: "was paid",
target: "core-indexer#288",
amount: 40,
asset: "USDC",
occurredAt: minutesAgo(6),
},
{
id: "a2",
handle: "0xkoda",
action: "claimed",
target: "soroban-escrow-sdk#17",
occurredAt: minutesAgo(22),
},
{
id: "a3",
handle: "linh_dev",
action: "opened a pull request for",
target: "stellar-wallet-kit#475",
occurredAt: minutesAgo(48),
},
{
id: "a4",
handle: "Stellar Development Foundation",
action: "funded",
target: "v2.0: Multi-asset escrow",
amount: 2500,
asset: "USDC",
occurredAt: minutesAgo(71),
},
{
id: "a4b",
handle: "Stellar Community Fund",
action: "funded",
target: "v1.4: Indexer performance pass",
amount: 15000,
asset: "XLM",
occurredAt: minutesAgo(95),
},
{
id: "a5",
handle: "priyaeth",
action: "was paid",
target: "core-indexer#301",
amount: 480,
asset: "USDC",
occurredAt: minutesAgo(130),
},
{
id: "a6",
handle: "qa_marcus",
action: "joined as a",
target: "contributor",
occurredAt: minutesAgo(210),
},
];
// A function, not a module-level constant: minutesAgo(n) anchors each event
// n minutes before whenever it's *called*. A constant array only calls
// minutesAgo once, when the module first loads -- on a long-running server
// that's once at deploy, so the "live" feed goes stale for the rest of the
// process's uptime (#707). Calling getRecentActivity() at render/request
// time instead keeps every event's anchor fresh relative to now.
export function getRecentActivity(): ActivityEvent[] {
return [
{
id: "a1",
handle: "devrel_ana",
action: "was paid",
target: "core-indexer#288",
amount: 40,
asset: "USDC",
occurredAt: minutesAgo(6),
},
{
id: "a2",
handle: "0xkoda",
action: "claimed",
target: "soroban-escrow-sdk#17",
occurredAt: minutesAgo(22),
},
{
id: "a3",
handle: "linh_dev",
action: "opened a pull request for",
target: "stellar-wallet-kit#475",
occurredAt: minutesAgo(48),
},
{
id: "a4",
handle: "Stellar Development Foundation",
action: "funded",
target: "v2.0: Multi-asset escrow",
amount: 2500,
asset: "USDC",
occurredAt: minutesAgo(71),
},
{
id: "a4b",
handle: "Stellar Community Fund",
action: "funded",
target: "v1.4: Indexer performance pass",
amount: 15000,
asset: "XLM",
occurredAt: minutesAgo(95),
},
{
id: "a5",
handle: "priyaeth",
action: "was paid",
target: "core-indexer#301",
amount: 480,
asset: "USDC",
occurredAt: minutesAgo(130),
},
{
id: "a6",
handle: "qa_marcus",
action: "joined as a",
target: "contributor",
occurredAt: minutesAgo(210),
},
];
}


// Weekly earnings/spend series (8 weeks) for dashboard charts.
Expand Down