Skip to content

Commit 7fbf87b

Browse files
committed
BatchMetadataModule fixes and UI adjustments
1 parent c2c0e71 commit 7fbf87b

File tree

5 files changed

+93
-89
lines changed

5 files changed

+93
-89
lines changed

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,25 @@ import {
1717
FormMessage,
1818
} from "@/components/ui/form";
1919
import { Input } from "@/components/ui/input";
20-
import { Separator } from "@/components/ui/separator";
2120
import { Textarea } from "@/components/ui/textarea";
2221
import { useMutation } from "@tanstack/react-query";
2322
import { CircleAlertIcon } from "lucide-react";
2423
import { useCallback } from "react";
2524
import { useForm } from "react-hook-form";
26-
import { toast } from "sonner";
2725
import { sendAndConfirmTransaction } from "thirdweb";
2826
import { BatchMetadataERC721 } from "thirdweb/modules";
2927
import type { NFTMetadataInputLimited } from "types/modified-types";
3028
import { parseAttributes } from "utils/parseAttributes";
29+
import { useTxNotifications } from "../../../../../../../hooks/useTxNotifications";
3130
import { ModuleCardUI, type ModuleCardUIProps } from "./module-card";
3231
import type { ModuleInstanceProps } from "./module-instance";
3332
import { AdvancedNFTMetadataFormGroup } from "./nft/AdvancedNFTMetadataFormGroup";
3433
import { NFTMediaFormGroup } from "./nft/NFTMediaFormGroup";
3534
import { PropertiesFormControl } from "./nft/PropertiesFormControl";
3635

36+
// TODO: add form validation on the upload form
37+
// TODO: this module currently shows the UI for doing a single upload, but it should be batch upload UI
38+
3739
export type UploadMetadataFormValues = NFTMetadataInputLimited & {
3840
supply: number;
3941
customImage: string;
@@ -82,14 +84,12 @@ export function BatchMetadataModuleUI(
8284
) {
8385
return (
8486
<ModuleCardUI {...props}>
85-
<div className="h-1" />
86-
8787
<div className="flex flex-col gap-4">
88-
{/* uploadMetadata NFT */}
8988
<Accordion type="single" collapsible className="-mx-1">
89+
{/* uploadMetadata */}
9090
<AccordionItem value="metadata" className="border-none">
9191
<AccordionTrigger className="border-border border-t px-1">
92-
uploadMetadata NFT
92+
Upload NFT Metadata
9393
</AccordionTrigger>
9494
<AccordionContent className="px-1">
9595
{props.isOwnerAccount && (
@@ -117,6 +117,7 @@ export function BatchMetadataModuleUI(
117117
function UploadMetadataNFTSection(props: {
118118
uploadMetadata: (values: UploadMetadataFormValues) => Promise<void>;
119119
}) {
120+
// TODO: add form validation here
120121
const form = useForm<UploadMetadataFormValues>({
121122
values: {
122123
supply: 1,
@@ -127,16 +128,19 @@ function UploadMetadataNFTSection(props: {
127128
reValidateMode: "onChange",
128129
});
129130

131+
const uploadNotifications = useTxNotifications(
132+
"NFT metadata uploaded successfully",
133+
"Failed to uploadMetadata NFT metadata",
134+
);
135+
130136
const uploadMetadataMutation = useMutation({
131137
mutationFn: props.uploadMetadata,
138+
onSuccess: uploadNotifications.onSuccess,
139+
onError: uploadNotifications.onError,
132140
});
133141

134142
const onSubmit = async () => {
135-
const promise = uploadMetadataMutation.mutateAsync(form.getValues());
136-
toast.promise(promise, {
137-
success: "Successfully uploadMetadataed NFT",
138-
error: (error) => `Failed to uploadMetadata NFT: ${error}`,
139-
});
143+
uploadMetadataMutation.mutateAsync(form.getValues());
140144
};
141145

142146
return (
@@ -197,7 +201,7 @@ function UploadMetadataNFTSection(props: {
197201
>
198202
<AccordionItem
199203
value="advanced-options"
200-
className="-mx-1 border-t border-b-0"
204+
className="-mx-1 border-y"
201205
>
202206
<AccordionTrigger className="px-1">
203207
Advanced Options
@@ -210,8 +214,6 @@ function UploadMetadataNFTSection(props: {
210214
</div>
211215
</div>
212216

213-
<Separator />
214-
215217
<div className="flex justify-end">
216218
<Button
217219
size="sm"
@@ -222,7 +224,7 @@ function UploadMetadataNFTSection(props: {
222224
{uploadMetadataMutation.isPending && (
223225
<Spinner className="size-4" />
224226
)}
225-
uploadMetadata
227+
Upload
226228
</Button>
227229
</div>
228230
</div>

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

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { useMutation } from "@tanstack/react-query";
44
import { useState } from "react";
55
import { Toaster, toast } from "sonner";
66
import { BadgeContainer, mobileViewport } from "stories/utils";
7+
import { ZERO_ADDRESS } from "thirdweb";
8+
import { ThirdwebProvider } from "thirdweb/react";
9+
import {
10+
ErrorProvider,
11+
type TransactionError,
12+
} from "../../../../../../../contexts/error-handler";
713
import {
814
BatchMetadataModuleUI,
915
type UploadMetadataFormValues,
@@ -37,6 +43,17 @@ function Component() {
3743
async function uploadMetadataStub(values: UploadMetadataFormValues) {
3844
console.log("submitting", values);
3945
await new Promise((resolve) => setTimeout(resolve, 1000));
46+
const error = new Error("Upload failed");
47+
(error as TransactionError).reason = "This is a test reason error";
48+
(error as TransactionError).info = {
49+
from: ZERO_ADDRESS,
50+
to: ZERO_ADDRESS,
51+
network: {
52+
name: "test",
53+
chainId: 1,
54+
},
55+
};
56+
throw error;
4057
}
4158

4259
const removeMutation = useMutation({
@@ -58,30 +75,34 @@ function Component() {
5875

5976
// TODO - remove ChakraProviderSetup after converting the chakra components used in BatchMetadataModuleUI
6077
return (
61-
<div className="container flex max-w-[1150px] flex-col gap-10 py-10">
62-
<div className="flex items-center gap-5">
63-
<CheckboxWithLabel
64-
value={isOwner}
65-
onChange={setIsOwner}
66-
id="isOwner"
67-
label="Is Owner"
68-
/>
69-
</div>
78+
<ThirdwebProvider>
79+
<ErrorProvider>
80+
<div className="container flex max-w-[1150px] flex-col gap-10 py-10">
81+
<div className="flex items-center gap-5">
82+
<CheckboxWithLabel
83+
value={isOwner}
84+
onChange={setIsOwner}
85+
id="isOwner"
86+
label="Is Owner"
87+
/>
88+
</div>
7089

71-
<BadgeContainer label="Default">
72-
<BatchMetadataModuleUI
73-
contractInfo={contractInfo}
74-
moduleAddress="0x0000000000000000000000000000000000000000"
75-
uploadMetadata={uploadMetadataStub}
76-
uninstallButton={{
77-
onClick: async () => removeMutation.mutateAsync(),
78-
isPending: removeMutation.isPending,
79-
}}
80-
isOwnerAccount={isOwner}
81-
/>
82-
</BadgeContainer>
83-
<Toaster richColors />
84-
</div>
90+
<BadgeContainer label="Default">
91+
<BatchMetadataModuleUI
92+
contractInfo={contractInfo}
93+
moduleAddress="0x0000000000000000000000000000000000000000"
94+
uploadMetadata={uploadMetadataStub}
95+
uninstallButton={{
96+
onClick: async () => removeMutation.mutateAsync(),
97+
isPending: removeMutation.isPending,
98+
}}
99+
isOwnerAccount={isOwner}
100+
/>
101+
</BadgeContainer>
102+
<Toaster richColors />
103+
</div>
104+
</ErrorProvider>
105+
</ThirdwebProvider>
85106
);
86107
}
87108

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export function NFTMediaFormGroup<T extends NFTMediaFormGroupValues>(props: {
100100
value={mediaFileUrl as File | string}
101101
showPreview={true}
102102
setValue={setFile}
103-
className="rounded border border-border transition-all duration-200"
103+
className="max-sm:!max-w-full rounded border border-border transition-all duration-200"
104104
selectOrUpload="Upload"
105105
helperText="Media"
106106
/>

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

Lines changed: 29 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,70 +6,42 @@ import {
66
FormMessage,
77
} from "@/components/ui/form";
88
import { Input } from "@/components/ui/input";
9-
import { BanIcon, PlusIcon, Trash2Icon } from "lucide-react";
10-
import {
11-
type ArrayPath,
12-
type FieldValues,
13-
type Path,
14-
type PathValue,
15-
type UseFormReturn,
16-
useFieldArray,
17-
} from "react-hook-form";
9+
import { PlusIcon, RotateCcwIcon, Trash2Icon } from "lucide-react";
10+
import { type UseFormReturn, useFieldArray } from "react-hook-form";
1811

19-
interface IPropertyFieldValues extends FieldValues {
12+
type PropertiesFormValues = {
2013
attributes?: {
2114
trait_type: string;
2215
value: string;
2316
}[];
24-
}
17+
};
2518

26-
export function PropertiesFormControl<
27-
TFieldValues extends IPropertyFieldValues,
28-
>({
29-
form,
30-
}: {
31-
form: UseFormReturn<TFieldValues>;
19+
export function PropertiesFormControl<T extends PropertiesFormValues>(props: {
20+
form: UseFormReturn<T>;
3221
}) {
22+
// T contains all properties of PropertiesFormValues, so this correct
23+
const form = props.form as unknown as UseFormReturn<PropertiesFormValues>;
24+
3325
const { fields, append, remove } = useFieldArray({
3426
control: form.control,
35-
name: "attributes" as ArrayPath<TFieldValues>,
27+
name: "attributes",
3628
});
3729

3830
return (
3931
<div className="flex flex-col gap-4">
40-
<div className="flex items-center justify-between gap-2">
41-
<p>Attributes</p>
42-
<Button
43-
className="flex items-center gap-2"
44-
variant="destructive"
45-
size="sm"
46-
onClick={() =>
47-
form.setValue(
48-
"attributes" as Path<TFieldValues>,
49-
// biome-ignore lint/suspicious/noExplicitAny: FIXME
50-
[{ trait_type: "", value: "" } as any] as PathValue<
51-
TFieldValues,
52-
Path<TFieldValues>
53-
>,
54-
)
55-
}
56-
>
57-
Reset
58-
<BanIcon className="size-4" />
59-
</Button>
60-
</div>
32+
<h4>Attributes</h4>
6133

6234
<div className="flex flex-col gap-3">
6335
{/* Addresses */}
6436
{fields.map((fieldItem, index) => (
6537
<div className="flex items-start gap-3" key={fieldItem.id}>
6638
<FormField
6739
control={form.control}
68-
name={`attributes.${index}.trait_type` as Path<TFieldValues>}
40+
name={`attributes.${index}.trait_type`}
6941
render={({ field }) => (
7042
<FormItem className="grow">
7143
<FormControl>
72-
<Input {...field} />
44+
<Input {...field} placeholder="Trait Type" />
7345
</FormControl>
7446
<FormMessage />
7547
</FormItem>
@@ -78,11 +50,11 @@ export function PropertiesFormControl<
7850

7951
<FormField
8052
control={form.control}
81-
name={`attributes.${index}.value` as Path<TFieldValues>}
53+
name={`attributes.${index}.value`}
8254
render={({ field }) => (
8355
<FormItem className="grow">
8456
<FormControl>
85-
<Input {...field} />
57+
<Input {...field} placeholder="Value" />
8658
</FormControl>
8759
<FormMessage />
8860
</FormItem>
@@ -100,18 +72,27 @@ export function PropertiesFormControl<
10072
))}
10173
</div>
10274

103-
<div className="flex flex-row gap-2">
75+
<div className="flex flex-row gap-3">
10476
<Button
10577
size="sm"
10678
className="flex items-center gap-2"
107-
onClick={() =>
108-
// biome-ignore lint/suspicious/noExplicitAny: FIXME
109-
append({ trait_type: undefined, value: undefined } as any)
110-
}
79+
onClick={() => append({ trait_type: "", value: "" })}
11180
>
11281
<PlusIcon className="size-5" />
11382
Add Row
11483
</Button>
84+
85+
<Button
86+
className="flex items-center gap-2"
87+
variant="outline"
88+
size="sm"
89+
onClick={() =>
90+
form.setValue("attributes", [{ trait_type: "", value: "" }])
91+
}
92+
>
93+
Reset
94+
<RotateCcwIcon className="size-4" />
95+
</Button>
11596
</div>
11697
</div>
11798
);

apps/dashboard/src/contexts/error-handler.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const ErrorContext = createContext<ErrorContext>({
3131
// We have decided to not export this class from v5 because that area need to be reworked
3232
// so this type is created as a workaround
3333
// @internal
34-
type TransactionError = {
34+
export type TransactionError = {
3535
message: string;
3636
info?: {
3737
from: string;
@@ -86,7 +86,7 @@ export const ErrorProvider: ComponentWithChildren = ({ children }) => {
8686
{/* Header */}
8787
<DialogHeader>
8888
<div className="mb-1 flex justify-start">
89-
<div className="rounded-xl bg-destructive p-2 text-destructive-text">
89+
<div className="rounded-xl border border-border p-2 text-destructive-text">
9090
<CircleAlertIcon className="size-6" />
9191
</div>
9292
</div>

0 commit comments

Comments
 (0)