Skip to content

Commit 927a4d3

Browse files
committed
claimable module claim conditions form cleanup
1 parent 278c20a commit 927a4d3

File tree

2 files changed

+188
-153
lines changed

2 files changed

+188
-153
lines changed

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/modules/components/Claimable.tsx

Lines changed: 130 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import { Skeleton } from "@/components/ui/skeleton";
2424
import { ToolTipLabel } from "@/components/ui/tooltip";
2525
import { zodResolver } from "@hookform/resolvers/zod";
2626
import { useMutation } from "@tanstack/react-query";
27+
import { addDays, fromUnixTime } from "date-fns";
2728
import { useAllChainsData } from "hooks/chains/allChains";
2829
import { useTxNotifications } from "hooks/useTxNotifications";
2930
import { CircleAlertIcon, PlusIcon, Trash2Icon } from "lucide-react";
3031
import { useCallback } from "react";
3132
import { useFieldArray, useForm } from "react-hook-form";
3233
import {
3334
type PreparedTransaction,
35+
ZERO_ADDRESS,
3436
getContract,
3537
sendAndConfirmTransaction,
3638
toTokens,
@@ -44,7 +46,7 @@ import { CurrencySelector } from "./CurrencySelector";
4446
import { ModuleCardUI, type ModuleCardUIProps } from "./module-card";
4547
import type { ModuleInstanceProps } from "./module-instance";
4648

47-
export type ClaimCondition = {
49+
export type ClaimConditionValue = {
4850
availableSupply: bigint;
4951
allowlistMerkleRoot: `0x${string}`;
5052
pricePerUnit: bigint;
@@ -55,20 +57,34 @@ export type ClaimCondition = {
5557
auxData: string;
5658
};
5759

60+
// TODO - for erc1155 have a UI something like this:
61+
// - show one input initially - user enters tokenId
62+
// - fetch the claim conditions for that tokenId and show the entire claim condition form with those values if they exist, or show empty form state if they don't exist
63+
64+
// TODO - don't compare with ZERO_ADDRESS we are not getting ZERO_ADDRESS as currency address and instead getting 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, (checksummed NATIVE_TOKEN_ADDRESS)
65+
66+
// TODO - fix Currency selector not showing the selected currency
67+
5868
function ClaimableModule(props: ModuleInstanceProps) {
5969
const { contract, ownerAccount } = props;
6070
const account = useActiveAccount();
6171

72+
const isErc721 = props.contractInfo.name === "ClaimableERC721";
73+
6274
const primarySaleRecipientQuery = useReadContract(
63-
ClaimableERC721.getSaleConfig,
75+
isErc721 ? ClaimableERC721.getSaleConfig : ClaimableERC1155.getSaleConfig,
6476
{
6577
contract: contract,
6678
},
6779
);
80+
6881
const claimConditionQuery = useReadContract(
6982
ClaimableERC721.getClaimCondition,
7083
{
7184
contract: contract,
85+
queryOptions: {
86+
enabled: isErc721,
87+
},
7288
},
7389
);
7490

@@ -78,18 +94,17 @@ function ClaimableModule(props: ModuleInstanceProps) {
7894
client: props.contract.client,
7995
});
8096

81-
const tokenDecimals = useReadContract(decimals, {
97+
const shouldFetchTokenDecimals =
98+
isErc721 &&
99+
claimConditionQuery.data &&
100+
claimConditionQuery.data?.currency !== ZERO_ADDRESS;
101+
102+
const tokenDecimalsQuery = useReadContract(decimals, {
82103
contract: currencyContract,
83104
queryOptions: {
84-
enabled:
85-
claimConditionQuery.data &&
86-
claimConditionQuery.data?.currency !==
87-
"0x0000000000000000000000000000000000000000",
105+
enabled: shouldFetchTokenDecimals,
88106
},
89107
});
90-
const tokenDecimalsData = tokenDecimals.data ?? 0;
91-
92-
const isErc721 = props.contractInfo.name === "ClaimableERC721";
93108

94109
const mint = useCallback(
95110
async (values: MintFormValues) => {
@@ -182,41 +197,64 @@ function ClaimableModule(props: ModuleInstanceProps) {
182197
return (
183198
<ClaimableModuleUI
184199
{...props}
185-
isPendingPrimarySaleRecipient={primarySaleRecipientQuery.isPending}
186-
isPendingClaimCondition={
187-
claimConditionQuery.isPending ||
188-
(claimConditionQuery.data?.currency !==
189-
"0x0000000000000000000000000000000000000000" &&
190-
tokenDecimals.isPending)
191-
}
192-
primarySaleRecipient={primarySaleRecipientQuery.data}
193-
claimCondition={claimConditionQuery.data}
194-
setClaimCondition={setClaimCondition}
195-
setPrimarySaleRecipient={setPrimarySaleRecipient}
196-
mint={mint}
200+
primarySaleRecipientSection={{
201+
data: primarySaleRecipientQuery.data
202+
? { primarySaleRecipient: primarySaleRecipientQuery.data }
203+
: undefined,
204+
setPrimarySaleRecipient,
205+
}}
206+
claimConditionSection={{
207+
data:
208+
// claim condition is common for all tokens
209+
isErc721 &&
210+
// claim conditions is fetched
211+
claimConditionQuery.isFetched &&
212+
// token decimals is fetched if it should be fetched
213+
(shouldFetchTokenDecimals ? tokenDecimalsQuery.isFetched : true)
214+
? {
215+
claimCondition: claimConditionQuery.data,
216+
tokenDecimals: tokenDecimalsQuery.data,
217+
}
218+
: undefined,
219+
setClaimCondition,
220+
}}
197221
isOwnerAccount={!!ownerAccount}
198222
isErc721={isErc721}
199223
chainId={props.contract.chain.id}
200-
tokenDecimals={tokenDecimalsData}
224+
mintSection={{
225+
mint,
226+
}}
201227
/>
202228
);
203229
}
204230

205231
export function ClaimableModuleUI(
206232
props: Omit<ModuleCardUIProps, "children" | "updateButton"> & {
207-
primarySaleRecipient: string | undefined;
208-
isPendingPrimarySaleRecipient: boolean;
209-
isPendingClaimCondition: boolean;
210233
isOwnerAccount: boolean;
211-
claimCondition: ClaimCondition | undefined;
212-
setClaimCondition: (values: ClaimConditionFormValues) => Promise<void>;
213-
setPrimarySaleRecipient: (
214-
values: PrimarySaleRecipientFormValues,
215-
) => Promise<void>;
216-
mint: (values: MintFormValues) => Promise<void>;
217234
isErc721: boolean;
218235
chainId: number;
219-
tokenDecimals: number;
236+
primarySaleRecipientSection: {
237+
setPrimarySaleRecipient: (
238+
values: PrimarySaleRecipientFormValues,
239+
) => Promise<void>;
240+
data:
241+
| {
242+
primarySaleRecipient: string;
243+
}
244+
| undefined;
245+
};
246+
mintSection: {
247+
mint: (values: MintFormValues) => Promise<void>;
248+
};
249+
claimConditionSection: {
250+
setClaimCondition: (values: ClaimConditionFormValues) => Promise<void>;
251+
data:
252+
| {
253+
claimCondition: ClaimConditionValue | undefined;
254+
tokenDecimals: number | undefined;
255+
}
256+
| undefined;
257+
};
220258
},
221259
) {
222260
return (
@@ -231,7 +269,10 @@ export function ClaimableModuleUI(
231269
Mint NFT
232270
</AccordionTrigger>
233271
<AccordionContent className="px-1">
234-
<MintNFTSection mint={props.mint} isErc721={props.isErc721} />
272+
<MintNFTSection
273+
mint={props.mintSection.mint}
274+
isErc721={props.isErc721}
275+
/>
235276
</AccordionContent>
236277
</AccordionItem>
237278

@@ -240,18 +281,19 @@ export function ClaimableModuleUI(
240281
Claim Conditions
241282
</AccordionTrigger>
242283
<AccordionContent className="px-1">
243-
{!props.isPendingClaimCondition ? (
284+
{props.claimConditionSection.data ? (
244285
<ClaimConditionSection
245286
isOwnerAccount={props.isOwnerAccount}
246-
primarySaleRecipient={props.primarySaleRecipient}
247-
claimCondition={props.claimCondition}
248-
update={props.setClaimCondition}
287+
claimCondition={
288+
props.claimConditionSection.data.claimCondition
289+
}
290+
update={props.claimConditionSection.setClaimCondition}
249291
isErc721={props.isErc721}
250292
chainId={props.chainId}
251-
tokenDecimals={props.tokenDecimals}
293+
tokenDecimals={props.claimConditionSection.data.tokenDecimals}
252294
/>
253295
) : (
254-
<Skeleton className="h-[74px]" />
296+
<Skeleton className="h-[350px]" />
255297
)}
256298
</AccordionContent>
257299
</AccordionItem>
@@ -264,11 +306,15 @@ export function ClaimableModuleUI(
264306
Primary Sale Recipient
265307
</AccordionTrigger>
266308
<AccordionContent className="px-1">
267-
{!props.isPendingPrimarySaleRecipient ? (
309+
{props.primarySaleRecipientSection.data ? (
268310
<PrimarySaleRecipientSection
269311
isOwnerAccount={props.isOwnerAccount}
270-
primarySaleRecipient={props.primarySaleRecipient}
271-
update={props.setPrimarySaleRecipient}
312+
primarySaleRecipient={
313+
props.primarySaleRecipientSection.data.primarySaleRecipient
314+
}
315+
update={
316+
props.primarySaleRecipientSection.setPrimarySaleRecipient
317+
}
272318
/>
273319
) : (
274320
<Skeleton className="h-[74px]" />
@@ -282,10 +328,9 @@ export function ClaimableModuleUI(
282328
}
283329

284330
const claimConditionFormSchema = z.object({
285-
tokenId: z.string().refine((v) => v.length === 0 || Number(v) >= 0, {
331+
tokenId: z.string().refine((v) => BigInt(v) >= 0n, {
286332
message: "Invalid tokenId",
287333
}),
288-
289334
pricePerToken: z.coerce
290335
.number()
291336
.min(0, { message: "Invalid price per token" })
@@ -303,62 +348,61 @@ const claimConditionFormSchema = z.object({
303348
message: "Invalid max claimable per wallet",
304349
}),
305350

306-
startTime: z.date().optional(),
307-
endTime: z.date().optional(),
308-
351+
startTime: z.date(),
352+
endTime: z.date(),
309353
allowList: z.array(z.object({ address: addressSchema })).optional(),
310354
});
311355

312356
export type ClaimConditionFormValues = z.infer<typeof claimConditionFormSchema>;
313357

314-
// perform this outside else it forces too many re-renders
315-
const now = Date.now() / 1000;
316358
const MAX_UINT_256 =
317359
"115792089237316195423570985008687907853269984665640564039457584007913129639935";
318360

361+
const defaultStartDate = addDays(new Date(), 7);
362+
const defaultEndDate = addDays(new Date(), 14);
363+
319364
function ClaimConditionSection(props: {
320-
primarySaleRecipient: string | undefined;
321-
claimCondition: ClaimCondition | undefined;
365+
claimCondition: ClaimConditionValue | undefined;
322366
update: (values: ClaimConditionFormValues) => Promise<void>;
323367
isOwnerAccount: boolean;
324368
isErc721: boolean;
325369
chainId: number;
326-
tokenDecimals: number;
370+
tokenDecimals: number | undefined;
327371
}) {
328372
const { idToChain } = useAllChainsData();
329373
const chain = idToChain.get(props.chainId);
374+
const { claimCondition } = props;
330375

331376
const form = useForm<ClaimConditionFormValues>({
332377
resolver: zodResolver(claimConditionFormSchema),
333378
values: {
334379
tokenId: "",
335380
currencyAddress:
336-
props.claimCondition?.currency ===
337-
"0x0000000000000000000000000000000000000000"
381+
claimCondition?.currency === ZERO_ADDRESS
338382
? ""
339-
: props.claimCondition?.currency,
383+
: claimCondition?.currency,
340384
pricePerToken:
341-
props.claimCondition?.pricePerUnit &&
342-
props.claimCondition?.currency !==
343-
"0x0000000000000000000000000000000000000000"
344-
? Number(
345-
toTokens(props.claimCondition?.pricePerUnit, props.tokenDecimals),
346-
)
385+
claimCondition?.pricePerUnit &&
386+
claimCondition?.currency !== ZERO_ADDRESS &&
387+
props.tokenDecimals
388+
? Number(toTokens(claimCondition?.pricePerUnit, props.tokenDecimals))
347389
: 0,
348390
maxClaimableSupply:
349-
props.claimCondition?.availableSupply.toString() === "0" ||
350-
props.claimCondition?.availableSupply.toString() === MAX_UINT_256
391+
claimCondition?.availableSupply.toString() === "0" ||
392+
claimCondition?.availableSupply.toString() === MAX_UINT_256
351393
? ""
352-
: props.claimCondition?.availableSupply.toString() || "",
394+
: claimCondition?.availableSupply.toString() || "",
353395
maxClaimablePerWallet:
354-
props.claimCondition?.maxMintPerWallet.toString() === "0" ||
355-
props.claimCondition?.maxMintPerWallet.toString() === MAX_UINT_256
396+
claimCondition?.maxMintPerWallet.toString() === "0" ||
397+
claimCondition?.maxMintPerWallet.toString() === MAX_UINT_256
356398
? ""
357-
: props.claimCondition?.maxMintPerWallet.toString() || "",
358-
startTime: new Date((props.claimCondition?.startTimestamp || now) * 1000),
359-
endTime: new Date(
360-
(props.claimCondition?.endTimestamp || now + 604800) * 1000,
361-
),
399+
: claimCondition?.maxMintPerWallet.toString() || "",
400+
startTime: claimCondition?.startTimestamp
401+
? fromUnixTime(claimCondition?.startTimestamp)
402+
: defaultStartDate,
403+
endTime: claimCondition?.endTimestamp
404+
? fromUnixTime(claimCondition?.endTimestamp)
405+
: defaultEndDate,
362406
allowList: [],
363407
},
364408
});
@@ -466,20 +510,22 @@ function ClaimConditionSection(props: {
466510
</div>
467511

468512
<FormFieldSetup
469-
htmlFor="startTime"
470-
label="Start & End Time"
471-
isRequired={false}
513+
htmlFor="duration"
514+
label="Duration"
515+
isRequired
472516
errorMessage={
473517
form.formState.errors?.startTime?.message ||
474518
form.formState.errors?.endTime?.message
475519
}
476520
>
477-
<DatePickerWithRange
478-
from={startTime || new Date()}
479-
to={endTime || new Date()}
480-
setFrom={(from: Date) => form.setValue("startTime", from)}
481-
setTo={(to: Date) => form.setValue("endTime", to)}
482-
/>
521+
<div>
522+
<DatePickerWithRange
523+
from={startTime}
524+
to={endTime}
525+
setFrom={(from: Date) => form.setValue("startTime", from)}
526+
setTo={(to: Date) => form.setValue("endTime", to)}
527+
/>
528+
</div>
483529
</FormFieldSetup>
484530

485531
<Separator />
@@ -588,15 +634,15 @@ function PrimarySaleRecipientSection(props: {
588634
},
589635
});
590636

591-
const updateNotificaions = useTxNotifications(
637+
const updateNotifications = useTxNotifications(
592638
"Successfully update primary sale recipient",
593639
"Failed to update primary sale recipient",
594640
);
595641

596642
const updateMutation = useMutation({
597643
mutationFn: props.update,
598-
onSuccess: updateNotificaions.onSuccess,
599-
onError: updateNotificaions.onError,
644+
onSuccess: updateNotifications.onSuccess,
645+
onError: updateNotifications.onError,
600646
});
601647

602648
const onSubmit = async () => {

0 commit comments

Comments
 (0)