Skip to content

Commit bb20bf8

Browse files
authored
Merge branch 'main' into yash/ocr-contracts-integration
2 parents b6f8fc8 + a3b2f8f commit bb20bf8

File tree

42 files changed

+2886
-568
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2886
-568
lines changed

.changeset/shaky-candles-rule.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.changeset/slick-cups-joke.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

.changeset/warm-ways-decide.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

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/@/components/ui/DynamicHeight.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ export function DynamicHeight(props: {
1717
boxSizing: "border-box",
1818
height: height ? `${height}px` : "auto",
1919
overflow: "hidden",
20-
transition:
21-
props.transition ||
22-
"height 210ms cubic-bezier(0.175, 0.885, 0.32, 1.1)",
20+
transition: props.transition || "height 250ms ease",
2321
}}
2422
>
2523
<div

apps/dashboard/src/@/components/ui/checkbox.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

33
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
4-
import { CheckIcon } from "lucide-react";
4+
import { CheckIcon, MinusIcon } from "lucide-react";
55
import * as React from "react";
66

77
import { cn } from "@/lib/utils";
@@ -21,7 +21,11 @@ const Checkbox = React.forwardRef<
2121
<CheckboxPrimitive.Indicator
2222
className={cn("flex items-center justify-center text-current")}
2323
>
24-
<CheckIcon className="size-4" />
24+
{props.checked === "indeterminate" ? (
25+
<MinusIcon className="size-3" />
26+
) : (
27+
<CheckIcon className="size-4" />
28+
)}
2529
</CheckboxPrimitive.Indicator>
2630
</CheckboxPrimitive.Root>
2731
));

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/@/hooks/useSplit.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,19 @@ import {
77
import { toast } from "sonner";
88
import {
99
type Chain,
10+
NATIVE_TOKEN_ADDRESS,
1011
sendAndConfirmTransaction,
1112
type ThirdwebClient,
1213
type ThirdwebContract,
1314
} from "thirdweb";
15+
import type { GetBalanceResult } from "thirdweb/extensions/erc20";
1416
import { distribute, distributeByToken } from "thirdweb/extensions/split";
1517
import { getOwnedTokens } from "thirdweb/insight";
1618
import { useActiveAccount } from "thirdweb/react";
19+
import { getWalletBalance } from "thirdweb/wallets";
1720
import invariant from "tiny-invariant";
1821
import { parseError } from "../utils/errorParser";
22+
import { tryCatch } from "../utils/try-catch";
1923

2024
function getTokenBalancesQuery(params: {
2125
ownerAddress: string;
@@ -24,14 +28,39 @@ function getTokenBalancesQuery(params: {
2428
}) {
2529
return queryOptions({
2630
queryFn: async () => {
27-
return getOwnedTokens({
31+
const ownedTokenBalancePromise = getOwnedTokens({
2832
client: params.client,
2933
chains: [params.chain],
3034
ownerAddress: params.ownerAddress,
3135
queryOptions: {
3236
include_native: "true",
3337
},
3438
});
39+
40+
const result = await tryCatch(ownedTokenBalancePromise);
41+
42+
// fallback to fetch native token balance with rpc
43+
if (result.error) {
44+
const walletBalance = await getWalletBalance({
45+
address: params.ownerAddress,
46+
client: params.client,
47+
chain: params.chain,
48+
});
49+
50+
const nativeTokenBalance: GetBalanceResult = {
51+
name: walletBalance.name,
52+
value: walletBalance.value,
53+
decimals: walletBalance.decimals,
54+
displayValue: walletBalance.displayValue,
55+
symbol: walletBalance.symbol,
56+
chainId: params.chain.id,
57+
tokenAddress: NATIVE_TOKEN_ADDRESS,
58+
};
59+
60+
return [nativeTokenBalance];
61+
}
62+
63+
return result.data;
3564
},
3665
queryKey: ["getOwnedTokens", params.chain.id, params.ownerAddress],
3766
retry: false,

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)