Skip to content

Commit 713b607

Browse files
committed
[BLD-108] Dashboard: Update Claim conditions UI to handle very large snapshots
1 parent 827819b commit 713b607

File tree

9 files changed

+394
-257
lines changed

9 files changed

+394
-257
lines changed

apps/dashboard/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
"next-themes": "^0.4.6",
4444
"nextjs-toploader": "^1.6.12",
4545
"nuqs": "^2.4.3",
46-
"p-limit": "^6.2.0",
4746
"papaparse": "^5.5.3",
4847
"pluralize": "^8.0.0",
4948
"posthog-js": "1.256.1",

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

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { useQuery } from "@tanstack/react-query";
2-
import pLimit from "p-limit";
1+
import { useQuery, useQueryClient } from "@tanstack/react-query";
32
import Papa from "papaparse";
43
import { useCallback, useState } from "react";
54
import { isAddress, type ThirdwebClient, ZERO_ADDRESS } from "thirdweb";
@@ -95,6 +94,7 @@ export function useCsvUpload<
9594
// Always gonna need the wallet address
9695
T extends { address: string },
9796
>(props: Props<T>) {
97+
const queryClient = useQueryClient();
9898
const [rawData, setRawData] = useState<
9999
T[] | Array<T & { [key in string]: unknown }>
100100
>(props.defaultRawData || []);
@@ -132,16 +132,29 @@ export function useCsvUpload<
132132
[props.csvParser],
133133
);
134134

135+
const [normalizeProgress, setNormalizeProgress] = useState({
136+
total: 0,
137+
current: 0,
138+
});
139+
135140
const normalizeQuery = useQuery({
136141
queryFn: async () => {
137-
const limit = pLimit(50);
138-
const results = await Promise.all(
139-
rawData.map((item) => {
140-
return limit(() =>
142+
const batchSize = 50;
143+
const results = [];
144+
for (let i = 0; i < rawData.length; i += batchSize) {
145+
const batch = rawData.slice(i, i + batchSize);
146+
setNormalizeProgress({
147+
total: rawData.length,
148+
current: i,
149+
});
150+
const batchResults = await Promise.all(
151+
batch.map((item) =>
141152
checkIsAddress({ item: item, thirdwebClient: props.client }),
142-
);
143-
}),
144-
);
153+
),
154+
);
155+
results.push(...batchResults);
156+
}
157+
145158
return {
146159
invalidFound: !!results.find((item) => !item?.isValid),
147160
result: processAirdropData(results),
@@ -153,12 +166,18 @@ export function useCsvUpload<
153166

154167
const removeInvalid = useCallback(() => {
155168
const filteredData = normalizeQuery.data?.result.filter(
156-
({ isValid }) => isValid,
169+
(d) => d.isValid && d.resolvedAddress !== ZERO_ADDRESS,
157170
);
158-
// double type assertion is save here because we don't really use this variable (only check for its length)
159-
// Also filteredData's type is the superset of T[]
160-
setRawData(filteredData as unknown as T[]);
161-
}, [normalizeQuery.data?.result]);
171+
172+
if (filteredData && normalizeQuery.data) {
173+
// Directly update the query result instead of setting new state to avoid triggering refetch
174+
queryClient.setQueryData(["snapshot-check-isAddress", rawData], {
175+
...normalizeQuery.data,
176+
result: filteredData,
177+
invalidFound: false, // Since we removed all invalid items
178+
});
179+
}
180+
}, [normalizeQuery.data, queryClient, rawData]);
162181

163182
const processData = useCallback(
164183
(data: T[]) => {
@@ -181,5 +200,6 @@ export function useCsvUpload<
181200
removeInvalid,
182201
reset,
183202
setFiles,
203+
normalizeProgress,
184204
};
185205
}

apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_components/claim-conditions/claim-conditions-form/Inputs/ClaimerSelection.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,31 @@ export const ClaimerSelection = () => {
2727
setOpenSnapshotIndex: setOpenIndex,
2828
isAdmin,
2929
claimConditionType,
30+
phaseSnapshots,
31+
setPhaseSnapshot,
3032
} = useClaimConditionsFormContext();
3133

3234
const handleClaimerChange = (value: string) => {
3335
const val = value as "any" | "specific" | "overrides";
3436

3537
if (val === "any") {
36-
form.setValue(`phases.${phaseIndex}.snapshot`, undefined);
38+
setPhaseSnapshot(phaseIndex, undefined);
3739
} else {
3840
if (val === "specific") {
3941
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 0);
4042
}
4143
if (val === "overrides" && field.maxClaimablePerWallet !== 1) {
4244
form.setValue(`phases.${phaseIndex}.maxClaimablePerWallet`, 1);
4345
}
44-
form.setValue(`phases.${phaseIndex}.snapshot`, []);
46+
setPhaseSnapshot(phaseIndex, []);
4547
setOpenIndex(phaseIndex);
4648
}
4749
};
4850

4951
let helperText: React.ReactNode;
5052

5153
const disabledSnapshotButton = isAdmin && formDisabled;
54+
const snapshot = phaseSnapshots[phaseIndex];
5255

5356
if (dropType === "specific") {
5457
helperText = (
@@ -87,10 +90,7 @@ export const ClaimerSelection = () => {
8790

8891
return (
8992
<FormFieldSetup
90-
errorMessage={
91-
form.getFieldState(`phases.${phaseIndex}.snapshot`, form.formState)
92-
?.error?.message
93-
}
93+
errorMessage={undefined}
9494
helperText={helperText}
9595
label={label}
9696
isRequired={false}
@@ -117,7 +117,7 @@ export const ClaimerSelection = () => {
117117
)}
118118

119119
{/* Edit or See Snapshot */}
120-
{field.snapshot ? (
120+
{snapshot ? (
121121
<div className="flex items-center gap-3">
122122
{/* disable the "Edit" button when form is disabled, but not when it's a "See" button */}
123123
<Button
@@ -133,17 +133,16 @@ export const ClaimerSelection = () => {
133133
<div
134134
className={cn(
135135
"flex gap-2 items-center",
136-
field.snapshot?.length === 0
136+
snapshot?.length === 0
137137
? "text-muted-foreground"
138138
: "text-green-600 dark:text-green-500",
139139
disabledSnapshotButton ? "opacity-50" : "",
140140
)}
141141
>
142142
<div className="size-2 bg-current rounded-full" />
143143
<span className="text-sm">
144-
{field.snapshot?.length}{" "}
145-
{field.snapshot?.length === 1 ? "address" : "addresses"} in
146-
snapshot
144+
{snapshot?.length}{" "}
145+
{snapshot?.length === 1 ? "address" : "addresses"} in snapshot
147146
</span>
148147
</div>
149148
</div>

0 commit comments

Comments
 (0)