Skip to content

Commit 9856ee4

Browse files
committed
[PRO-138] Dashboard: Update Project highlights card revenue tab (#8515)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the analytics components by adding new functionalities and refining existing code for better performance and readability. It introduces new data handling for `x402Settlements`, updates chart configurations, and improves conditional rendering in various components. ### Detailed summary - Added `x402Settlements` data handling in `AsyncAppHighlightsCard`. - Updated `CombinedBarChartConfig` to include `stackedKeys`. - Improved conditional rendering for the `EmptyChartStateGetStartedCTA`. - Refined the `BarChart` component for better data processing. - Enhanced `ProjectHighlightsCard` to include `bridgeRevenue` and `x402Revenue`. - Introduced utility functions for generating mock data in stories. - Updated types in various components for better type safety. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added stacked data visualization support to charts. * Integrated x402 revenue tracking into analytics highlights. * Added ability to hide specific chart tabs from display. * **Improvements** * Refined USD currency formatting to display 2 decimal places for cleaner presentation. * Made analytics CTA buttons conditionally displayed based on configuration. * **Refactor** * Simplified settlement data model with inheritance to reduce redundancy. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 3c768a0 commit 9856ee4

File tree

10 files changed

+374
-167
lines changed

10 files changed

+374
-167
lines changed

apps/dashboard/src/@/components/analytics/empty-chart-state.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export function LoadingChartState({ className }: { className?: string }) {
3636
}
3737

3838
export function EmptyChartStateGetStartedCTA(props: {
39-
link: {
39+
link?: {
4040
label: string;
4141
href: string;
4242
};
@@ -59,16 +59,18 @@ export function EmptyChartStateGetStartedCTA(props: {
5959
)}
6060
</div>
6161

62-
<Button
63-
asChild
64-
className="rounded-full gap-2"
65-
variant="default"
66-
size="sm"
67-
>
68-
<Link href={props.link.href}>
69-
{props.link.label} <ArrowUpRightIcon className="size-4" />
70-
</Link>
71-
</Button>
62+
{props.link && (
63+
<Button
64+
asChild
65+
className="rounded-full gap-2"
66+
variant="default"
67+
size="sm"
68+
>
69+
<Link href={props.link.href}>
70+
{props.link.label} <ArrowUpRightIcon className="size-4" />
71+
</Link>
72+
</Button>
73+
)}
7274
</div>
7375
);
7476
}

apps/dashboard/src/@/types/analytics.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -101,36 +101,20 @@ export interface X402SettlementsOverall {
101101
totalValueUSD: number;
102102
}
103103

104-
export interface X402SettlementsByChainId {
105-
date: string;
104+
export interface X402SettlementsByChainId extends X402SettlementsOverall {
106105
chainId: string;
107-
totalRequests: number;
108-
totalValue: number;
109-
totalValueUSD: number;
110106
}
111107

112-
export interface X402SettlementsByPayer {
113-
date: string;
108+
export interface X402SettlementsByPayer extends X402SettlementsOverall {
114109
payer: string;
115-
totalRequests: number;
116-
totalValue: number;
117-
totalValueUSD: number;
118110
}
119111

120-
interface X402SettlementsByReceiver {
121-
date: string;
112+
interface X402SettlementsByReceiver extends X402SettlementsOverall {
122113
receiver: string;
123-
totalRequests: number;
124-
totalValue: number;
125-
totalValueUSD: number;
126114
}
127115

128-
export interface X402SettlementsByResource {
129-
date: string;
116+
export interface X402SettlementsByResource extends X402SettlementsOverall {
130117
resource: string;
131-
totalRequests: number;
132-
totalValue: number;
133-
totalValueUSD: number;
134118
}
135119

136120
interface X402SettlementsByAsset {

apps/dashboard/src/@/utils/number.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
const usdCurrencyFormatter = new Intl.NumberFormat("en-US", {
22
currency: "USD",
3-
maximumFractionDigits: 6, // prefix with $
4-
minimumFractionDigits: 0, // don't show decimal places if value is a whole number
5-
notation: "compact", // at max 2 decimal places
6-
roundingMode: "halfEven", // round to nearest even number, standard practice for financial calculations
7-
style: "currency", // shows 1.2M instead of 1,200,000, 1.2B instead of 1,200,000,000
3+
maximumFractionDigits: 2,
4+
minimumFractionDigits: 0,
5+
notation: "compact",
6+
roundingMode: "halfEven",
7+
style: "currency",
88
});
99

1010
export const toUSD = (value: number) => {
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import type { Meta } from "@storybook/nextjs";
2+
import { ResponsiveSearchParamsProvider } from "responsive-rsc";
3+
import type {
4+
InAppWalletStats,
5+
UniversalBridgeStats,
6+
X402SettlementsOverall,
7+
} from "@/types/analytics";
8+
import { ProjectHighlightsCard } from "./highlights-card-ui";
9+
10+
const meta = {
11+
component: ProjectHighlightsCard,
12+
decorators: [
13+
(Story) => (
14+
<ResponsiveSearchParamsProvider value={{}}>
15+
<div className="container max-w-6xl py-10">
16+
<Story />
17+
</div>
18+
</ResponsiveSearchParamsProvider>
19+
),
20+
],
21+
title: "Analytics/ProjectHighlightsCard",
22+
parameters: {
23+
nextjs: {
24+
appDirectory: true,
25+
},
26+
},
27+
} satisfies Meta<typeof ProjectHighlightsCard>;
28+
29+
export default meta;
30+
31+
function generateDateSeries(days: number) {
32+
const dates: string[] = [];
33+
const today = new Date();
34+
for (let i = days - 1; i >= 0; i--) {
35+
const d = new Date(today);
36+
d.setDate(today.getDate() - i);
37+
dates.push(d.toISOString());
38+
}
39+
return dates;
40+
}
41+
42+
function inAppWalletStub(days: number): InAppWalletStats[] {
43+
const dates = generateDateSeries(days);
44+
return dates.map((date) => {
45+
return {
46+
// main data
47+
newUsers: randomValue(50, 100),
48+
uniqueWalletsConnected: randomValue(500, 1200),
49+
// others
50+
authenticationMethod: "email",
51+
date,
52+
};
53+
});
54+
}
55+
56+
// a few aggregated entries that will be summed for the "Active Users" stat
57+
function aggregatedInAppWalletsStub(): InAppWalletStats[] {
58+
return Array.from({ length: 3 }).map(() => {
59+
return {
60+
// main data
61+
newUsers: randomValue(50, 100),
62+
uniqueWalletsConnected: randomValue(500, 1200),
63+
// others
64+
date: new Date().toISOString(),
65+
authenticationMethod: "email",
66+
};
67+
});
68+
}
69+
70+
function bridgeVolumeStub(days: number): UniversalBridgeStats[] {
71+
const dates = generateDateSeries(days);
72+
return dates.map((date) => {
73+
return {
74+
// main data
75+
developerFeeUsdCents: randomValue(500 * 100, 2500 * 100),
76+
status: "completed",
77+
// others
78+
chainId: 0,
79+
count: 0,
80+
date,
81+
amountUsdCents: 0,
82+
type: "onchain",
83+
};
84+
});
85+
}
86+
87+
function randomValue(min: number, max: number) {
88+
return Math.max(0, Math.round(min + Math.random() * (max - min)));
89+
}
90+
91+
function generateX402Settlements(days: number): X402SettlementsOverall[] {
92+
const dates = generateDateSeries(days);
93+
return dates.map((date) => {
94+
return {
95+
date,
96+
totalRequests: randomValue(10, 100),
97+
totalValue: randomValue(500, 2500),
98+
totalValueUSD: randomValue(500, 2500),
99+
};
100+
});
101+
}
102+
103+
export function ThirtyDays() {
104+
const days = 30;
105+
const data = {
106+
aggregatedUserStats: aggregatedInAppWalletsStub(),
107+
userStats: inAppWalletStub(days),
108+
bridgeVolumeStats: bridgeVolumeStub(days),
109+
x402Settlements: generateX402Settlements(days),
110+
};
111+
112+
return (
113+
<ProjectHighlightsCard
114+
aggregatedUserStats={data.aggregatedUserStats}
115+
userStats={data.userStats}
116+
volumeStats={data.bridgeVolumeStats}
117+
x402Settlements={data.x402Settlements}
118+
/>
119+
);
120+
}
121+
122+
export function SingleDayRevenue() {
123+
const days = 1;
124+
const data = {
125+
aggregatedUserStats: [],
126+
userStats: [],
127+
bridgeVolumeStats: bridgeVolumeStub(days),
128+
x402Settlements: generateX402Settlements(days),
129+
};
130+
131+
return (
132+
<ProjectHighlightsCard
133+
aggregatedUserStats={data.aggregatedUserStats}
134+
userStats={data.userStats}
135+
volumeStats={data.bridgeVolumeStats}
136+
x402Settlements={data.x402Settlements}
137+
/>
138+
);
139+
}
140+
141+
export function SingleDayRevenueNoX402() {
142+
const days = 1;
143+
const data = {
144+
aggregatedUserStats: [],
145+
userStats: [],
146+
bridgeVolumeStats: bridgeVolumeStub(days),
147+
x402Settlements: [],
148+
};
149+
150+
return (
151+
<ProjectHighlightsCard
152+
aggregatedUserStats={data.aggregatedUserStats}
153+
userStats={data.userStats}
154+
volumeStats={data.bridgeVolumeStats}
155+
x402Settlements={data.x402Settlements}
156+
/>
157+
);
158+
}

0 commit comments

Comments
 (0)