Skip to content

Commit 95f3fff

Browse files
committed
feat: adds payments history
1 parent 2b01daa commit 95f3fff

File tree

12 files changed

+397
-285
lines changed

12 files changed

+397
-285
lines changed

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

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ async function fetchAnalytics(
3232

3333
// create a new URL object for the analytics server
3434
const ANALYTICS_SERVICE_URL = new URL(
35-
"https://analytics-service-dev-ldna.zeet-nftlabs.zeet.app",
36-
); // Production analytics URL (yes I know it says dev)
35+
process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
36+
);
3737
ANALYTICS_SERVICE_URL.pathname = pathname;
3838
for (const param of searchParams?.split("&") || []) {
3939
const [key, value] = param.split("=");
@@ -46,16 +46,16 @@ async function fetchAnalytics(
4646
);
4747
}
4848
// client id DEBUG OVERRIDE
49-
ANALYTICS_SERVICE_URL.searchParams.delete("projectId");
50-
ANALYTICS_SERVICE_URL.searchParams.delete("teamId");
51-
ANALYTICS_SERVICE_URL.searchParams.append(
52-
"teamId",
53-
"team_clmb33q9w00gn1x0u2ri8z0k0",
54-
);
55-
ANALYTICS_SERVICE_URL.searchParams.append(
56-
"projectId",
57-
"prj_clyqwud5y00u1na7nzxnzlz7o",
58-
);
49+
// ANALYTICS_SERVICE_URL.searchParams.delete("projectId");
50+
// ANALYTICS_SERVICE_URL.searchParams.delete("teamId");
51+
// ANALYTICS_SERVICE_URL.searchParams.append(
52+
// "teamId",
53+
// "team_clmb33q9w00gn1x0u2ri8z0k0",
54+
// );
55+
// ANALYTICS_SERVICE_URL.searchParams.append(
56+
// "projectId",
57+
// "prj_clyqwud5y00u1na7nzxnzlz7o",
58+
// );
5959

6060
return fetch(ANALYTICS_SERVICE_URL, {
6161
...init,
@@ -428,7 +428,6 @@ export async function getUniversalBridgeWalletUsage(args: {
428428
}
429429

430430
const json = await res.json();
431-
console.log(json);
432431

433432
return json.data as UniversalBridgeWalletStats[];
434433
}

apps/dashboard/src/@/api/universal-bridge/developer.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,86 @@ export async function updateFee(props: {
150150

151151
return;
152152
}
153+
154+
export type PaymentsResponse = {
155+
data: Payment[];
156+
meta: {
157+
totalCount: number;
158+
};
159+
};
160+
export type Payment = {
161+
id: string;
162+
blockNumber?: bigint;
163+
transactionId: string;
164+
clientId: string;
165+
sender: string;
166+
receiver: string;
167+
developerFeeRecipient: string;
168+
developerFeeBps: number;
169+
transactions: Array<{
170+
chainId: number;
171+
transactionHash: string;
172+
}>;
173+
status: "PENDING" | "COMPLETED" | "FAILED" | "NOT_FOUND";
174+
type: "buy" | "sell" | "transfer";
175+
originAmount: bigint;
176+
destinationAmount: bigint;
177+
purchaseData: unknown;
178+
originToken: {
179+
address: string;
180+
symbol: string;
181+
decimals: number;
182+
chainId: number;
183+
};
184+
destinationToken: {
185+
address: string;
186+
symbol: string;
187+
decimals: number;
188+
chainId: number;
189+
};
190+
createdAt: string;
191+
};
192+
193+
export async function getPayments(props: {
194+
clientId: string;
195+
limit?: number;
196+
offset?: number;
197+
}) {
198+
const authToken = await getAuthToken();
199+
200+
// Build URL with query parameters if provided
201+
let url = `${UB_BASE_URL}/v1/developer/payments`;
202+
const queryParams = new URLSearchParams();
203+
204+
if (props.limit) {
205+
queryParams.append("limit", props.limit.toString());
206+
}
207+
208+
if (props.offset) {
209+
queryParams.append("offset", props.offset.toString());
210+
}
211+
212+
// Append query params to URL if any exist
213+
const queryString = queryParams.toString();
214+
if (queryString) {
215+
url = `${url}?${queryString}`;
216+
}
217+
218+
const res = await fetch(url, {
219+
method: "GET",
220+
headers: {
221+
"Content-Type": "application/json",
222+
"x-client-id-override": props.clientId,
223+
Authorization: `Bearer ${authToken}`,
224+
},
225+
});
226+
227+
if (!res.ok) {
228+
const text = await res.text();
229+
throw new Error(text);
230+
}
231+
232+
const json = await res.json();
233+
console.log("json", json);
234+
return json as PaymentsResponse;
235+
}
Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
import { getProject } from "@/api/projects";
22
import { PayAnalytics } from "components/pay/PayAnalytics/PayAnalytics";
33
import { redirect } from "next/navigation";
4+
import {
5+
ResponsiveSearchParamsProvider,
6+
ResponsiveSuspense,
7+
} from "responsive-rsc";
8+
import { Spinner } from "../../../../../../../@/components/ui/Spinner/Spinner";
9+
import { PayAnalyticsFilter } from "../../../../../../../components/pay/PayAnalytics/components/PayAnalyticsFilter";
10+
import { getUniversalBridgeFiltersFromSearchParams } from "../../../../../../../lib/time";
411

512
export default async function Page(props: {
613
params: Promise<{
714
team_slug: string;
815
project_slug: string;
916
}>;
17+
searchParams: {
18+
from?: string | undefined | string[];
19+
to?: string | undefined | string[];
20+
interval?: string | undefined | string[];
21+
};
1022
}) {
1123
const params = await props.params;
1224
const project = await getProject(params.team_slug, params.project_slug);
@@ -15,11 +27,36 @@ export default async function Page(props: {
1527
redirect(`/team/${params.team_slug}`);
1628
}
1729

30+
const searchParams = await props.searchParams;
31+
const { range, interval } = getUniversalBridgeFiltersFromSearchParams({
32+
from: searchParams.from,
33+
to: searchParams.to,
34+
interval: searchParams.interval,
35+
});
36+
1837
return (
19-
<PayAnalytics
20-
clientId={project.publishableKey}
21-
projectId={project.id}
22-
teamId={project.teamId}
23-
/>
38+
<ResponsiveSearchParamsProvider value={props.searchParams}>
39+
<div>
40+
<div className="mb-4 flex justify-end">
41+
<PayAnalyticsFilter />
42+
</div>
43+
<ResponsiveSuspense
44+
searchParamsUsed={["from", "to", "interval"]}
45+
fallback={
46+
<div className="flex w-full items-center justify-center py-24">
47+
<Spinner className="size-8" />
48+
</div>
49+
}
50+
>
51+
<PayAnalytics
52+
clientId={project.publishableKey}
53+
projectId={project.id}
54+
teamId={project.teamId}
55+
range={range}
56+
interval={interval}
57+
/>
58+
</ResponsiveSuspense>
59+
</div>
60+
</ResponsiveSearchParamsProvider>
2461
);
2562
}

apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx

Lines changed: 48 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,40 @@
1-
import { getLastNDaysRange } from "../../analytics/date-range-selector";
2-
import { Payouts } from "./components/Payouts";
3-
import { TotalPayVolume } from "./components/TotalPayVolume";
4-
import { TotalVolumePieChart } from "./components/TotalVolumePieChart";
5-
import { PaymentsSuccessRate } from "./components/PaymentsSuccessRate";
6-
import { PayNewCustomers } from "./components/PayNewCustomers";
7-
import { PayCustomersTable } from "./components/PayCustomersTable";
81
import {
92
getUniversalBridgeUsage,
103
getUniversalBridgeWalletUsage,
114
} from "@/api/analytics";
12-
import { useMemo } from "react";
5+
import type { Range } from "../../analytics/date-range-selector";
6+
import { PayCustomersTable } from "./components/PayCustomersTable";
7+
import { PayNewCustomers } from "./components/PayNewCustomers";
8+
import { PaymentHistory } from "./components/PaymentHistory";
9+
import { PaymentsSuccessRate } from "./components/PaymentsSuccessRate";
10+
import { Payouts } from "./components/Payouts";
11+
import { TotalPayVolume } from "./components/TotalPayVolume";
12+
import { TotalVolumePieChart } from "./components/TotalVolumePieChart";
1313

1414
export async function PayAnalytics(props: {
15+
clientId: string;
1516
// switching to projectId for lookup, but have to send both during migration
1617
projectId: string;
1718
teamId: string;
19+
range: Range;
20+
interval: "day" | "week";
1821
}) {
19-
const projectId = props.projectId;
20-
const teamId = props.teamId;
21-
const range = getLastNDaysRange("last-120");
22-
const numberOfDays = Math.round(
23-
(range.to.getTime() - range.from.getTime()) / (1000 * 60 * 60 * 24),
24-
);
25-
const [period, dateFormat]: [
26-
"day" | "week" | "month",
27-
{
28-
month: "short" | "long";
29-
day?: "numeric" | "2-digit";
30-
},
31-
] = useMemo(() => {
32-
if (numberOfDays > 90) {
33-
return ["month", { month: "short" }];
34-
}
35-
if (numberOfDays > 30) {
36-
return ["week", { month: "short", day: "numeric" }];
37-
}
38-
return ["day", { month: "short", day: "numeric" }];
39-
}, [numberOfDays]);
22+
const { projectId, teamId, range, interval } = props;
23+
24+
const dateFormat =
25+
interval === "day"
26+
? { month: "short" as const, day: "numeric" as const }
27+
: {
28+
month: "short" as const,
29+
day: "numeric" as const,
30+
};
4031

4132
const volumeData = await getUniversalBridgeUsage({
4233
teamId: teamId,
4334
projectId: projectId,
4435
from: range.from,
4536
to: range.to,
46-
period,
37+
period: interval,
4738
}).catch((error) => {
4839
console.error(error);
4940
return [];
@@ -53,61 +44,47 @@ export async function PayAnalytics(props: {
5344
projectId: projectId,
5445
from: range.from,
5546
to: range.to,
56-
period,
47+
period: interval,
5748
}).catch((error) => {
5849
console.error(error);
5950
return [];
6051
});
61-
console.log(walletData);
6252

6353
return (
64-
<div>
65-
<div className="mb-4 flex">
66-
{/* <DateRangeSelector range={range} setRange={() => {}} /> */}
67-
</div>
68-
<div className="flex flex-col gap-10 lg:gap-6">
69-
<GridWithSeparator>
70-
<div className="flex items-center border-border border-b pb-6 xl:border-none xl:pb-0">
71-
<TotalVolumePieChart
72-
data={volumeData?.filter((x) => x.status === "completed") || []}
73-
/>
74-
</div>
75-
<TotalPayVolume
54+
<div className="flex flex-col gap-10 lg:gap-6">
55+
<GridWithSeparator>
56+
<div className="flex items-center border-border border-b pb-6 xl:border-none xl:pb-0">
57+
<TotalVolumePieChart
7658
data={volumeData?.filter((x) => x.status === "completed") || []}
77-
dateFormat={dateFormat}
7859
/>
79-
</GridWithSeparator>
80-
81-
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
82-
<CardContainer>
83-
<Payouts
84-
data={volumeData?.filter((x) => x.status === "completed") || []}
85-
dateFormat={dateFormat}
86-
/>
87-
</CardContainer>
88-
<CardContainer>
89-
<PaymentsSuccessRate data={volumeData || []} />
90-
</CardContainer>
9160
</div>
61+
<TotalPayVolume
62+
data={volumeData?.filter((x) => x.status === "completed") || []}
63+
dateFormat={dateFormat}
64+
/>
65+
</GridWithSeparator>
9266

93-
<GridWithSeparator>
94-
<div className="border-border border-b pb-6 xl:border-none xl:pb-0">
95-
<PayNewCustomers data={walletData || []} dateFormat={dateFormat} />
96-
</div>
97-
<PayCustomersTable data={walletData || []} />
98-
</GridWithSeparator>
99-
{/*
67+
<div className="grid grid-cols-1 gap-6 xl:grid-cols-2">
10068
<CardContainer>
101-
<PaymentHistory
102-
clientId={clientId}
103-
projectId={projectId}
104-
teamId={teamId}
105-
from={range.from}
106-
to={range.to}
69+
<Payouts
70+
data={volumeData?.filter((x) => x.status === "completed") || []}
71+
dateFormat={dateFormat}
10772
/>
10873
</CardContainer>
109-
*/}
74+
<CardContainer>
75+
<PaymentsSuccessRate data={volumeData || []} />
76+
</CardContainer>
11077
</div>
78+
79+
<GridWithSeparator>
80+
<div className="border-border border-b pb-6 xl:border-none xl:pb-0">
81+
<PayNewCustomers data={walletData || []} dateFormat={dateFormat} />
82+
</div>
83+
<PayCustomersTable data={walletData || []} />
84+
</GridWithSeparator>
85+
<CardContainer>
86+
<PaymentHistory clientId={props.clientId} />
87+
</CardContainer>
11188
</div>
11289
);
11390
}

0 commit comments

Comments
 (0)