Skip to content

Commit 50420fc

Browse files
authored
Add CSV export progress for user wallets (#8791)
1 parent a82e633 commit 50420fc

2 files changed

Lines changed: 95 additions & 40 deletions

File tree

apps/dashboard/src/@/components/in-app-wallet-users-content/user-wallets-table.tsx

Lines changed: 84 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ export function UserWalletsTable(
104104
completed: 0,
105105
total: 0,
106106
});
107+
const [csvExportProgress, setCsvExportProgress] = useState<{
108+
fetchedWallets: number;
109+
totalWallets?: number;
110+
}>();
107111

108112
const walletsQuery = useEmbeddedWallets({
109113
authToken: props.authToken,
@@ -113,10 +117,18 @@ export function UserWalletsTable(
113117
page: activePage,
114118
});
115119
const wallets = walletsQuery?.data?.users || [];
116-
const { mutateAsync: getAllEmbeddedWallets, isPending: isLoadingAllWallets } =
117-
useAllEmbeddedWallets({
118-
authToken: props.authToken,
119-
});
120+
const {
121+
mutateAsync: getAllEmbeddedWalletsForBalances,
122+
isPending: isLoadingWalletsForBalances,
123+
} = useAllEmbeddedWallets({
124+
authToken: props.authToken,
125+
});
126+
const {
127+
mutateAsync: getAllEmbeddedWalletsForCsv,
128+
isPending: isLoadingWalletsForCsv,
129+
} = useAllEmbeddedWallets({
130+
authToken: props.authToken,
131+
});
120132

121133
const fetchPortfoliosMutation = useFetchAllPortfolios();
122134

@@ -125,7 +137,7 @@ export function UserWalletsTable(
125137

126138
try {
127139
// First get all wallets
128-
const allWallets = await getAllEmbeddedWallets({
140+
const allWallets = await getAllEmbeddedWalletsForBalances({
129141
clientId: props.projectClientId,
130142
ecosystemSlug: props.ecosystemSlug,
131143
teamId: props.teamId,
@@ -161,7 +173,7 @@ export function UserWalletsTable(
161173
}
162174
}, [
163175
selectedChains,
164-
getAllEmbeddedWallets,
176+
getAllEmbeddedWalletsForBalances,
165177
props.projectClientId,
166178
props.ecosystemSlug,
167179
props.teamId,
@@ -170,7 +182,32 @@ export function UserWalletsTable(
170182
]);
171183

172184
const isFetchingBalances =
173-
isLoadingAllWallets || fetchPortfoliosMutation.isPending;
185+
isLoadingWalletsForBalances || fetchPortfoliosMutation.isPending;
186+
187+
const csvExportLabel = useMemo(() => {
188+
if (!isLoadingWalletsForCsv) {
189+
return "Download as .csv";
190+
}
191+
192+
const fetchedWallets = csvExportProgress?.fetchedWallets ?? 0;
193+
const totalWallets = csvExportProgress?.totalWallets;
194+
195+
if (fetchedWallets === 0) {
196+
return "Preparing export...";
197+
}
198+
199+
if (totalWallets && totalWallets > 0) {
200+
const cappedFetchedWallets = Math.min(fetchedWallets, totalWallets);
201+
const percentage = Math.min(
202+
100,
203+
Math.round((cappedFetchedWallets / totalWallets) * 100),
204+
);
205+
206+
return `Exporting ${percentage}% (${cappedFetchedWallets.toLocaleString()}/${totalWallets.toLocaleString()})`;
207+
}
208+
209+
return `Exporting ${fetchedWallets.toLocaleString()} users...`;
210+
}, [csvExportProgress, isLoadingWalletsForCsv]);
174211

175212
const aggregatedStats = useMemo(() => {
176213
let fundedWallets = 0;
@@ -414,41 +451,48 @@ export function UserWalletsTable(
414451
};
415452

416453
const downloadCSV = useCallback(async () => {
417-
if (wallets.length === 0 || !getAllEmbeddedWallets) {
454+
if (wallets.length === 0) {
418455
return;
419456
}
420-
const usersWallets = await getAllEmbeddedWallets({
421-
clientId: props.projectClientId,
422-
ecosystemSlug: props.ecosystemSlug,
423-
teamId: props.teamId,
424-
});
425-
const csv = Papa.unparse(
426-
usersWallets.map((row) => {
427-
const email = getPrimaryEmail(row.linkedAccounts);
428-
429-
return {
430-
address: row.wallets[0]?.address || "Uninitialized",
431-
created: row.wallets[0]?.createdAt
432-
? new Date(row.wallets[0].createdAt).toISOString()
433-
: "Wallet not created yet",
434-
email: email || "N/A",
435-
login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "),
436-
auth_identifier: getAuthIdentifier(row.linkedAccounts) || "N/A",
437-
user_identifier: row.id || "N/A",
438-
};
439-
}),
440-
);
441-
const csvUrl = URL.createObjectURL(
442-
new Blob([csv], { type: "text/csv;charset=utf-8;" }),
443-
);
444-
const tempLink = document.createElement("a");
445-
tempLink.href = csvUrl;
446-
tempLink.setAttribute("download", "download.csv");
447-
tempLink.click();
457+
setCsvExportProgress({ fetchedWallets: 0 });
458+
459+
try {
460+
const usersWallets = await getAllEmbeddedWalletsForCsv({
461+
clientId: props.projectClientId,
462+
ecosystemSlug: props.ecosystemSlug,
463+
teamId: props.teamId,
464+
onProgress: setCsvExportProgress,
465+
});
466+
const csv = Papa.unparse(
467+
usersWallets.map((row) => {
468+
const email = getPrimaryEmail(row.linkedAccounts);
469+
470+
return {
471+
address: row.wallets[0]?.address || "Uninitialized",
472+
created: row.wallets[0]?.createdAt
473+
? new Date(row.wallets[0].createdAt).toISOString()
474+
: "Wallet not created yet",
475+
email: email || "N/A",
476+
login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "),
477+
auth_identifier: getAuthIdentifier(row.linkedAccounts) || "N/A",
478+
user_identifier: row.id || "N/A",
479+
};
480+
}),
481+
);
482+
const csvUrl = URL.createObjectURL(
483+
new Blob([csv], { type: "text/csv;charset=utf-8;" }),
484+
);
485+
const tempLink = document.createElement("a");
486+
tempLink.href = csvUrl;
487+
tempLink.setAttribute("download", "download.csv");
488+
tempLink.click();
489+
} finally {
490+
setCsvExportProgress(undefined);
491+
}
448492
}, [
449493
wallets,
450494
props.projectClientId,
451-
getAllEmbeddedWallets,
495+
getAllEmbeddedWalletsForCsv,
452496
props.teamId,
453497
props.ecosystemSlug,
454498
]);
@@ -481,12 +525,12 @@ export function UserWalletsTable(
481525
</div>
482526
<Button
483527
className="gap-2 bg-background rounded-full"
484-
disabled={wallets.length === 0 || isLoadingAllWallets}
528+
disabled={wallets.length === 0 || isLoadingWalletsForCsv}
485529
onClick={downloadCSV}
486530
variant="outline"
487531
>
488-
{isLoadingAllWallets && <Spinner className="size-4" />}
489-
Download as .csv
532+
{isLoadingWalletsForCsv && <Spinner className="size-4" />}
533+
{csvExportLabel}
490534
</Button>
491535
</div>
492536
</div>

apps/dashboard/src/@/hooks/useEmbeddedWallets.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const fetchAccountList = ({
6868
return {
6969
users: response.data.result.wallets.map(transformToWalletUser),
7070
hasMore: response.data.result.pagination.hasMore ?? false,
71+
totalCount: response.data.result.pagination.totalCount,
7172
};
7273
} catch (error) {
7374
console.error("Failed to fetch wallets:", error);
@@ -170,10 +171,15 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
170171
clientId,
171172
ecosystemSlug,
172173
teamId,
174+
onProgress,
173175
}: {
174176
clientId?: string;
175177
ecosystemSlug?: string;
176178
teamId: string;
179+
onProgress?: (progress: {
180+
fetchedWallets: number;
181+
totalWallets?: number;
182+
}) => void;
177183
}) => {
178184
const responses: WalletUser[] = [];
179185
let page = 1;
@@ -185,6 +191,7 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
185191
const res = await queryClient.fetchQuery<{
186192
users: WalletUser[];
187193
hasMore: boolean;
194+
totalCount?: number;
188195
}>({
189196
queryFn: fetchAccountList({
190197
clientId,
@@ -204,6 +211,10 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
204211
});
205212

206213
responses.push(...res.users);
214+
onProgress?.({
215+
fetchedWallets: responses.length,
216+
totalWallets: res.totalCount,
217+
});
207218
consecutiveFailures = 0; // Reset on success
208219

209220
if (!res.hasMore) {

0 commit comments

Comments
 (0)