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
24 changes: 13 additions & 11 deletions apps/dashboard/src/@/components/analytics/empty-chart-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function LoadingChartState({ className }: { className?: string }) {
}

export function EmptyChartStateGetStartedCTA(props: {
link: {
link?: {
label: string;
href: string;
};
Expand All @@ -59,16 +59,18 @@ export function EmptyChartStateGetStartedCTA(props: {
)}
</div>

<Button
asChild
className="rounded-full gap-2"
variant="default"
size="sm"
>
<Link href={props.link.href}>
{props.link.label} <ArrowUpRightIcon className="size-4" />
</Link>
</Button>
{props.link && (
<Button
asChild
className="rounded-full gap-2"
variant="default"
size="sm"
>
<Link href={props.link.href}>
{props.link.label} <ArrowUpRightIcon className="size-4" />
</Link>
</Button>
)}
</div>
);
}
24 changes: 4 additions & 20 deletions apps/dashboard/src/@/types/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,36 +101,20 @@ export interface X402SettlementsOverall {
totalValueUSD: number;
}

export interface X402SettlementsByChainId {
date: string;
export interface X402SettlementsByChainId extends X402SettlementsOverall {
chainId: string;
totalRequests: number;
totalValue: number;
totalValueUSD: number;
}

export interface X402SettlementsByPayer {
date: string;
export interface X402SettlementsByPayer extends X402SettlementsOverall {
payer: string;
totalRequests: number;
totalValue: number;
totalValueUSD: number;
}

interface X402SettlementsByReceiver {
date: string;
interface X402SettlementsByReceiver extends X402SettlementsOverall {
receiver: string;
totalRequests: number;
totalValue: number;
totalValueUSD: number;
}

export interface X402SettlementsByResource {
date: string;
export interface X402SettlementsByResource extends X402SettlementsOverall {
resource: string;
totalRequests: number;
totalValue: number;
totalValueUSD: number;
}

interface X402SettlementsByAsset {
Expand Down
10 changes: 5 additions & 5 deletions apps/dashboard/src/@/utils/number.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const usdCurrencyFormatter = new Intl.NumberFormat("en-US", {
currency: "USD",
maximumFractionDigits: 6, // prefix with $
minimumFractionDigits: 0, // don't show decimal places if value is a whole number
notation: "compact", // at max 2 decimal places
roundingMode: "halfEven", // round to nearest even number, standard practice for financial calculations
style: "currency", // shows 1.2M instead of 1,200,000, 1.2B instead of 1,200,000,000
maximumFractionDigits: 2,
minimumFractionDigits: 0,
notation: "compact",
roundingMode: "halfEven",
style: "currency",
});

export const toUSD = (value: number) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import type { Meta } from "@storybook/nextjs";
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
import type {
InAppWalletStats,
UniversalBridgeStats,
X402SettlementsOverall,
} from "@/types/analytics";
import { ProjectHighlightsCard } from "./highlights-card-ui";

const meta = {
component: ProjectHighlightsCard,
decorators: [
(Story) => (
<ResponsiveSearchParamsProvider value={{}}>
<div className="container max-w-6xl py-10">
<Story />
</div>
</ResponsiveSearchParamsProvider>
),
],
title: "Analytics/ProjectHighlightsCard",
parameters: {
nextjs: {
appDirectory: true,
},
},
} satisfies Meta<typeof ProjectHighlightsCard>;

export default meta;

function generateDateSeries(days: number) {
const dates: string[] = [];
const today = new Date();
for (let i = days - 1; i >= 0; i--) {
const d = new Date(today);
d.setDate(today.getDate() - i);
dates.push(d.toISOString());
}
return dates;
}

function inAppWalletStub(days: number): InAppWalletStats[] {
const dates = generateDateSeries(days);
return dates.map((date) => {
return {
// main data
newUsers: randomValue(50, 100),
uniqueWalletsConnected: randomValue(500, 1200),
// others
authenticationMethod: "email",
date,
};
});
}

// a few aggregated entries that will be summed for the "Active Users" stat
function aggregatedInAppWalletsStub(): InAppWalletStats[] {
return Array.from({ length: 3 }).map(() => {
return {
// main data
newUsers: randomValue(50, 100),
uniqueWalletsConnected: randomValue(500, 1200),
// others
date: new Date().toISOString(),
authenticationMethod: "email",
};
});
}

function bridgeVolumeStub(days: number): UniversalBridgeStats[] {
const dates = generateDateSeries(days);
return dates.map((date) => {
return {
// main data
developerFeeUsdCents: randomValue(500 * 100, 2500 * 100),
status: "completed",
// others
chainId: 0,
count: 0,
date,
amountUsdCents: 0,
type: "onchain",
};
});
}

function randomValue(min: number, max: number) {
return Math.max(0, Math.round(min + Math.random() * (max - min)));
}

function generateX402Settlements(days: number): X402SettlementsOverall[] {
const dates = generateDateSeries(days);
return dates.map((date) => {
return {
date,
totalRequests: randomValue(10, 100),
totalValue: randomValue(500, 2500),
totalValueUSD: randomValue(500, 2500),
};
});
}

export function ThirtyDays() {
const days = 30;
const data = {
aggregatedUserStats: aggregatedInAppWalletsStub(),
userStats: inAppWalletStub(days),
bridgeVolumeStats: bridgeVolumeStub(days),
x402Settlements: generateX402Settlements(days),
};

return (
<ProjectHighlightsCard
aggregatedUserStats={data.aggregatedUserStats}
userStats={data.userStats}
volumeStats={data.bridgeVolumeStats}
x402Settlements={data.x402Settlements}
/>
);
}

export function SingleDayRevenue() {
const days = 1;
const data = {
aggregatedUserStats: [],
userStats: [],
bridgeVolumeStats: bridgeVolumeStub(days),
x402Settlements: generateX402Settlements(days),
};

return (
<ProjectHighlightsCard
aggregatedUserStats={data.aggregatedUserStats}
userStats={data.userStats}
volumeStats={data.bridgeVolumeStats}
x402Settlements={data.x402Settlements}
/>
);
}

export function SingleDayRevenueNoX402() {
const days = 1;
const data = {
aggregatedUserStats: [],
userStats: [],
bridgeVolumeStats: bridgeVolumeStub(days),
x402Settlements: [],
};

return (
<ProjectHighlightsCard
aggregatedUserStats={data.aggregatedUserStats}
userStats={data.userStats}
volumeStats={data.bridgeVolumeStats}
x402Settlements={data.x402Settlements}
/>
);
}
Loading
Loading