@medialane/ui 0.295.0 → 0.297.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/asset-page.cjs +10 -0
  2. package/dist/components/asset-page.cjs.map +1 -1
  3. package/dist/components/asset-page.js +10 -0
  4. package/dist/components/asset-page.js.map +1 -1
  5. package/dist/components/fast-mint/asset-form-parts.cjs +194 -0
  6. package/dist/components/fast-mint/asset-form-parts.cjs.map +1 -0
  7. package/dist/components/fast-mint/asset-form-parts.d.cts +33 -0
  8. package/dist/components/fast-mint/asset-form-parts.d.ts +33 -0
  9. package/dist/components/fast-mint/asset-form-parts.js +158 -0
  10. package/dist/components/fast-mint/asset-form-parts.js.map +1 -0
  11. package/dist/components/fast-mint/collection-section.cjs +211 -0
  12. package/dist/components/fast-mint/collection-section.cjs.map +1 -0
  13. package/dist/components/fast-mint/collection-section.d.cts +27 -0
  14. package/dist/components/fast-mint/collection-section.d.ts +27 -0
  15. package/dist/components/fast-mint/collection-section.js +176 -0
  16. package/dist/components/fast-mint/collection-section.js.map +1 -0
  17. package/dist/components/fast-mint/fast-mint.cjs +116 -653
  18. package/dist/components/fast-mint/fast-mint.cjs.map +1 -1
  19. package/dist/components/fast-mint/fast-mint.js +115 -661
  20. package/dist/components/fast-mint/fast-mint.js.map +1 -1
  21. package/dist/components/fast-mint/form.cjs +40 -0
  22. package/dist/components/fast-mint/form.cjs.map +1 -0
  23. package/dist/components/fast-mint/form.d.cts +15 -0
  24. package/dist/components/fast-mint/form.d.ts +15 -0
  25. package/dist/components/fast-mint/form.js +16 -0
  26. package/dist/components/fast-mint/form.js.map +1 -0
  27. package/dist/components/fast-mint/ip-type-section.cjs +76 -0
  28. package/dist/components/fast-mint/ip-type-section.cjs.map +1 -0
  29. package/dist/components/fast-mint/ip-type-section.d.cts +17 -0
  30. package/dist/components/fast-mint/ip-type-section.d.ts +17 -0
  31. package/dist/components/fast-mint/ip-type-section.js +52 -0
  32. package/dist/components/fast-mint/ip-type-section.js.map +1 -0
  33. package/dist/components/fast-mint/licensing-section.cjs +124 -0
  34. package/dist/components/fast-mint/licensing-section.cjs.map +1 -0
  35. package/dist/components/fast-mint/licensing-section.d.cts +14 -0
  36. package/dist/components/fast-mint/licensing-section.d.ts +14 -0
  37. package/dist/components/fast-mint/licensing-section.js +99 -0
  38. package/dist/components/fast-mint/licensing-section.js.map +1 -0
  39. package/dist/components/fast-mint/media-kind.cjs +48 -0
  40. package/dist/components/fast-mint/media-kind.cjs.map +1 -0
  41. package/dist/components/fast-mint/media-kind.d.cts +12 -0
  42. package/dist/components/fast-mint/media-kind.d.ts +12 -0
  43. package/dist/components/fast-mint/media-kind.js +22 -0
  44. package/dist/components/fast-mint/media-kind.js.map +1 -0
  45. package/dist/components/fast-mint/mint-asset.cjs +124 -0
  46. package/dist/components/fast-mint/mint-asset.cjs.map +1 -0
  47. package/dist/components/fast-mint/mint-asset.d.cts +41 -0
  48. package/dist/components/fast-mint/mint-asset.d.ts +41 -0
  49. package/dist/components/fast-mint/mint-asset.js +100 -0
  50. package/dist/components/fast-mint/mint-asset.js.map +1 -0
  51. package/dist/components/fast-mint/use-ipfs-upload.cjs +64 -0
  52. package/dist/components/fast-mint/use-ipfs-upload.cjs.map +1 -0
  53. package/dist/components/fast-mint/use-ipfs-upload.d.cts +9 -0
  54. package/dist/components/fast-mint/use-ipfs-upload.d.ts +9 -0
  55. package/dist/components/fast-mint/use-ipfs-upload.js +40 -0
  56. package/dist/components/fast-mint/use-ipfs-upload.js.map +1 -0
  57. package/dist/components/fast-mint/use-new-collection-draft.cjs +66 -0
  58. package/dist/components/fast-mint/use-new-collection-draft.cjs.map +1 -0
  59. package/dist/components/fast-mint/use-new-collection-draft.d.cts +13 -0
  60. package/dist/components/fast-mint/use-new-collection-draft.d.ts +13 -0
  61. package/dist/components/fast-mint/use-new-collection-draft.js +42 -0
  62. package/dist/components/fast-mint/use-new-collection-draft.js.map +1 -0
  63. package/dist/components/soulbound-card.cjs +70 -0
  64. package/dist/components/soulbound-card.cjs.map +1 -0
  65. package/dist/components/soulbound-card.d.cts +13 -0
  66. package/dist/components/soulbound-card.d.ts +13 -0
  67. package/dist/components/soulbound-card.js +35 -0
  68. package/dist/components/soulbound-card.js.map +1 -0
  69. package/dist/index.cjs +5 -0
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +1 -0
  72. package/dist/index.d.ts +1 -0
  73. package/dist/index.js +3 -0
  74. package/dist/index.js.map +1 -1
  75. package/package.json +1 -1
@@ -1,156 +1,29 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
- import Image from "next/image";
5
4
  import { useForm } from "react-hook-form";
6
- import { buildAssetMetadata } from "@medialane/sdk";
5
+ import { Loader2 } from "lucide-react";
6
+ import { profileHref } from "@medialane/sdk";
7
7
  import { describeError } from "../../utils/describe-error.js";
8
- import { UPLOAD_LIMITS, acceptFor, uploadError } from "../../utils/upload-limits.js";
9
- import { executeIntent, deployedCollectionFromReceipt, mintedTokenIdFromReceipt } from "@medialane/sdk/starknet";
10
- import { mintableCollections, collectionKey } from "./mintable-collections.js";
11
- import {
12
- ImagePlus,
13
- Music,
14
- Video,
15
- FileText,
16
- Loader2,
17
- Layers,
18
- ImagePlus as SingleIcon,
19
- ChevronDown,
20
- Boxes,
21
- Plus,
22
- Check,
23
- ShieldCheck
24
- } from "lucide-react";
8
+ import { uploadError } from "../../utils/upload-limits.js";
9
+ import { uploadFileToIpfs } from "../../utils/ipfs-upload.js";
10
+ import { cn } from "../../utils/cn.js";
25
11
  import { Input } from "../input.js";
26
12
  import { Textarea } from "../textarea.js";
27
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select.js";
28
- import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../collapsible.js";
29
- import { Popover, PopoverContent, PopoverTrigger } from "../popover.js";
30
13
  import { MedialaneCollectionCard } from "../medialane-collection-card.js";
31
14
  import { Dialog, DialogContent, DialogDescription, DialogTitle } from "../dialog.js";
32
- import { IPTypeFields } from "../ip-type-fields.js";
33
- import {
34
- IP_TYPES,
35
- LICENSE_TYPES,
36
- GEOGRAPHIC_SCOPES,
37
- AI_POLICIES,
38
- DERIVATIVES_OPTIONS
39
- } from "../../data/ip.js";
40
- import { IP_TEMPLATES } from "../../data/ip-templates.js";
41
- import { suggestLaunchpadSymbol } from "../../utils/launchpad-defaults.js";
42
- import { uploadFileToIpfs, uploadJsonToIpfs, uploadFailureToast } from "../../utils/ipfs-upload.js";
43
- import { cn } from "../../utils/cn.js";
44
15
  import { Dropzone } from "./dropzone.js";
45
16
  import { SuccessView } from "./success-view.js";
46
- import { profileHref } from "@medialane/sdk";
47
- import { useImageUrl } from "../../utils/use-image-url.js";
48
- const MEDIA_ROUTE_MIME_TYPES = new Set(UPLOAD_LIMITS.media.mimeTypes);
49
- const DOCUMENT_MIME_TYPES = new Set(UPLOAD_LIMITS.document.mimeTypes.filter((type) => type !== "application/octet-stream"));
50
- function detectMediaKind(mime) {
51
- if (mime.startsWith("image/")) return MEDIA_ROUTE_MIME_TYPES.has(mime) ? "image" : null;
52
- if (mime.startsWith("video/")) return MEDIA_ROUTE_MIME_TYPES.has(mime) ? "video" : null;
53
- if (mime.startsWith("audio/")) return MEDIA_ROUTE_MIME_TYPES.has(mime) ? "audio" : null;
54
- if (DOCUMENT_MIME_TYPES.has(mime)) return "document";
55
- return null;
56
- }
57
- const IP_TYPE_BY_KIND = {
58
- image: "NFT",
59
- audio: "Audio",
60
- video: "Video",
61
- document: "Documents"
62
- };
63
- const MEDIA_KIND_ICON = {
64
- image: ImagePlus,
65
- audio: Music,
66
- video: Video,
67
- document: FileText
68
- };
69
- function ToggleGroup({
70
- value,
71
- options,
72
- onChange
73
- }) {
74
- return /* @__PURE__ */ jsx("div", { className: "flex rounded-lg border border-border overflow-hidden w-full", children: options.map((opt, i) => /* @__PURE__ */ jsx(
75
- "button",
76
- {
77
- type: "button",
78
- onClick: () => onChange(opt),
79
- className: cn(
80
- "flex-1 px-3 py-2 text-sm transition-colors",
81
- i > 0 && "border-l border-border",
82
- value === opt ? "bg-primary text-primary-foreground font-medium" : "bg-background hover:bg-muted text-muted-foreground"
83
- ),
84
- children: opt
85
- },
86
- opt
87
- )) });
88
- }
89
- function collectionDisplayLabel(col) {
90
- return col.name || col.symbol || `Collection #${col.collectionId}`;
91
- }
92
- function CollectionThumb({ image }) {
93
- const resolveImage = useImageUrl();
94
- const imageUrl = resolveImage(image);
95
- return /* @__PURE__ */ jsx("div", { className: "relative h-10 w-10 rounded-lg overflow-hidden bg-muted shrink-0", children: imageUrl ? /* @__PURE__ */ jsx(Image, { src: imageUrl, alt: "", fill: true, className: "object-cover", unoptimized: true }) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(Boxes, { className: "h-4 w-4 text-muted-foreground/40" }) }) });
96
- }
97
- function CollectionPicker({
98
- collections,
99
- value,
100
- onChange,
101
- valueOf
102
- }) {
103
- const [open, setOpen] = useState(false);
104
- const selected = value ? collections.find((c) => valueOf(c) === value) ?? null : null;
105
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
106
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
107
- "button",
108
- {
109
- type: "button",
110
- "aria-expanded": open,
111
- className: "flex w-full items-center gap-3 rounded-xl border border-border p-3 text-left transition-colors hover:bg-muted/40",
112
- children: [
113
- /* @__PURE__ */ jsx(CollectionThumb, { image: selected?.image }),
114
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
115
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold truncate", children: selected ? collectionDisplayLabel(selected) : collections.length ? "Choose a collection" : "No collections yet" }),
116
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: selected ? `${selected.totalSupply ?? 0} work${selected.totalSupply === 1 ? "" : "s"}` : "Where this work will live" })
117
- ] }),
118
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" })
119
- ]
120
- }
121
- ) }),
122
- /* @__PURE__ */ jsx(PopoverContent, { className: "w-[var(--radix-popover-trigger-width)] p-0", align: "start", children: /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto p-1", children: collections.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: "No collections yet \u2014 create one instead." }) : collections.map((col) => {
123
- const id = valueOf(col);
124
- const isSelected = !!id && value === id;
125
- return /* @__PURE__ */ jsxs(
126
- "button",
127
- {
128
- type: "button",
129
- disabled: !id,
130
- onClick: () => {
131
- if (!id) return;
132
- onChange(id);
133
- setOpen(false);
134
- },
135
- className: "flex w-full items-center gap-2.5 rounded-lg px-2 py-2 text-left transition-colors hover:bg-muted/60",
136
- children: [
137
- /* @__PURE__ */ jsx(CollectionThumb, { image: col.image }),
138
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
139
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: collectionDisplayLabel(col) }),
140
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
141
- col.totalSupply ?? 0,
142
- " work",
143
- col.totalSupply === 1 ? "" : "s"
144
- ] })
145
- ] }),
146
- isSelected && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-brand-blue shrink-0" })
147
- ]
148
- },
149
- id ?? col.contractAddress ?? col.name
150
- );
151
- }) }) })
152
- ] });
153
- }
17
+ import { AssetTypePicker, CoverUpload, MediaRow } from "./asset-form-parts.js";
18
+ import { CollectionSection } from "./collection-section.js";
19
+ import { LicensingSection } from "./licensing-section.js";
20
+ import { IpTypeSection } from "./ip-type-section.js";
21
+ import { DEFAULT_VALUES } from "./form.js";
22
+ import { IP_TYPE_BY_KIND, MEDIA_ROUTE_MIME_TYPES, detectMediaKind } from "./media-kind.js";
23
+ import { mintAsset } from "./mint-asset.js";
24
+ import { mintableCollections } from "./mintable-collections.js";
25
+ import { useIpfsUpload } from "./use-ipfs-upload.js";
26
+ import { useNewCollectionDraft } from "./use-new-collection-draft.js";
154
27
  function FastMint(props) {
155
28
  const {
156
29
  presentation = "inline",
@@ -171,79 +44,36 @@ function FastMint(props) {
171
44
  } = props;
172
45
  const [status, setStatus] = useState("idle");
173
46
  const [errorMsg, setErrorMsg] = useState(null);
47
+ const media = useIpfsUpload();
48
+ const feature = useIpfsUpload();
174
49
  const [mediaFile, setMediaFile] = useState(null);
175
- const [mediaPreview, setMediaPreview] = useState(null);
176
- const [mediaUri, setMediaUri] = useState(null);
177
50
  const [mediaKind, setMediaKind] = useState("image");
178
- const [mediaUploading, setMediaUploading] = useState(false);
179
- const previewUrlRef = useRef(null);
180
51
  const mediaInputRef = useRef(null);
181
- const [featurePreview, setFeaturePreview] = useState(null);
182
- const [featureUri, setFeatureUri] = useState(null);
183
- const [featureUploading, setFeatureUploading] = useState(false);
184
- const featurePreviewRef = useRef(null);
185
52
  const featureInputRef = useRef(null);
186
53
  const [assetType, setAssetType] = useState("single");
187
54
  const [editionCount, setEditionCount] = useState("10");
188
55
  const [collectionMode, setCollectionMode] = useState("existing");
189
56
  const [existingCollectionId, setExistingCollectionId] = useState("");
190
57
  const [existingCollectionContract, setExistingCollectionContract] = useState("");
191
- const [newCollectionName, setNewCollectionName] = useState("");
192
- const [newCollectionSymbol, setNewCollectionSymbol] = useState("");
193
- const [newCollectionDescription, setNewCollectionDescription] = useState("");
194
- const [autoSymbol, setAutoSymbol] = useState("");
195
- const [autoCollectionName, setAutoCollectionName] = useState("");
196
- const [autoCollectionDescription, setAutoCollectionDescription] = useState("");
197
58
  const [ipType, setIpType] = useState("NFT");
198
- const [advancedOpen, setAdvancedOpen] = useState(false);
199
- const [ipTypeOpen, setIpTypeOpen] = useState(false);
200
59
  const templateFieldsRef = useRef([]);
201
60
  const [mintedAsset, setMintedAsset] = useState(null);
202
- useEffect(() => () => {
203
- if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
204
- if (featurePreviewRef.current) URL.revokeObjectURL(featurePreviewRef.current);
205
- }, []);
206
- const form = useForm({
207
- defaultValues: {
208
- name: "",
209
- description: "",
210
- external_url: "",
211
- licenseType: "CC BY-SA",
212
- commercialUse: "Yes",
213
- derivatives: "Share-Alike",
214
- attribution: "Required",
215
- geographicScope: "Worldwide",
216
- aiPolicy: "Allowed",
217
- royalty: 0
218
- }
219
- });
61
+ const form = useForm({ defaultValues: DEFAULT_VALUES });
220
62
  const name = form.watch("name");
63
+ const description = form.watch("description");
64
+ const draft = useNewCollectionDraft(name, description);
221
65
  useEffect(() => {
222
66
  if (walletAddress && !form.getValues("external_url")) {
223
67
  form.setValue("external_url", `https://medialane.io/account/${walletAddress}`);
224
68
  }
225
69
  }, [walletAddress, form]);
226
- useEffect(() => {
227
- const s = suggestLaunchpadSymbol(name);
228
- if (!s) return;
229
- if (!newCollectionSymbol || newCollectionSymbol === autoSymbol) {
230
- setNewCollectionSymbol(s);
231
- setAutoSymbol(s);
232
- }
233
- if (!newCollectionName || newCollectionName === autoCollectionName) {
234
- setNewCollectionName(name);
235
- setAutoCollectionName(name);
236
- }
237
- }, [name, autoSymbol, autoCollectionName, newCollectionSymbol, newCollectionName]);
238
- const description = form.watch("description");
239
- useEffect(() => {
240
- if (!newCollectionDescription || newCollectionDescription === autoCollectionDescription) {
241
- setNewCollectionDescription(description);
242
- setAutoCollectionDescription(description);
243
- }
244
- }, [description, autoCollectionDescription, newCollectionDescription]);
245
70
  const erc721Collections = mintableCollections("single", collections);
246
71
  const erc1155Collections = mintableCollections("editions", collections);
72
+ const uploadFile = async (file, kind) => {
73
+ const token = await getUploadToken();
74
+ if (!token) throw new Error("Sign in first");
75
+ return (await uploadFileToIpfs(client.api, file, kind)).uri;
76
+ };
247
77
  const handleMediaSelect = async (file) => {
248
78
  const kind = detectMediaKind(file.type);
249
79
  if (!kind) {
@@ -254,8 +84,8 @@ function FastMint(props) {
254
84
  setErrorMsg(`Please upload a${mediaKindLock === "image" ? "n" : ""} ${mediaKindLock}.`);
255
85
  return;
256
86
  }
257
- const viaMediaRoute = MEDIA_ROUTE_MIME_TYPES.has(file.type);
258
- const fileError = uploadError(file, viaMediaRoute ? "media" : "document");
87
+ const route = MEDIA_ROUTE_MIME_TYPES.has(file.type) ? "media" : "document";
88
+ const fileError = uploadError(file, route);
259
89
  if (fileError) {
260
90
  setErrorMsg(fileError);
261
91
  return;
@@ -263,74 +93,20 @@ function FastMint(props) {
263
93
  setMediaFile(file);
264
94
  setMediaKind(kind);
265
95
  setIpType(IP_TYPE_BY_KIND[kind]);
266
- if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
267
- const objectUrl = URL.createObjectURL(file);
268
- previewUrlRef.current = objectUrl;
269
- setMediaPreview(objectUrl);
270
- setMediaUri(null);
271
- setMediaUploading(true);
272
- try {
273
- const token = await getUploadToken();
274
- if (!token) throw new Error("Sign in first");
275
- const { uri } = await uploadFileToIpfs(client.api, file, viaMediaRoute ? "media" : "document");
276
- setMediaUri(uri);
277
- } catch (err) {
278
- const t = uploadFailureToast(err);
279
- setErrorMsg(t.description ?? t.title);
280
- } finally {
281
- setMediaUploading(false);
282
- }
96
+ await media.start(file, (f) => uploadFile(f, route), setErrorMsg);
283
97
  };
284
98
  const clearMedia = () => {
285
99
  setMediaFile(null);
286
- setMediaPreview(null);
287
- setMediaUri(null);
100
+ media.clear();
288
101
  if (mediaInputRef.current) mediaInputRef.current.value = "";
289
102
  };
290
- const handleFeatureSelect = async (file) => {
291
- if (featurePreviewRef.current) URL.revokeObjectURL(featurePreviewRef.current);
292
- const objectUrl = URL.createObjectURL(file);
293
- featurePreviewRef.current = objectUrl;
294
- setFeaturePreview(objectUrl);
295
- setFeatureUri(null);
296
- setFeatureUploading(true);
297
- try {
298
- const token = await getUploadToken();
299
- if (!token) throw new Error("Sign in first");
300
- const { uri } = await uploadFileToIpfs(client.api, file, "image");
301
- setFeatureUri(uri);
302
- } catch (err) {
303
- const t = uploadFailureToast(err);
304
- setErrorMsg(t.description ?? t.title);
305
- } finally {
306
- setFeatureUploading(false);
307
- }
308
- };
309
- const handleLicenseChange = (value) => {
310
- form.setValue("licenseType", value);
311
- const def = LICENSE_TYPES.find((l) => l.value === value);
312
- if (def) {
313
- form.setValue("commercialUse", def.commercialUse);
314
- form.setValue("derivatives", def.derivatives);
315
- form.setValue("attribution", def.attribution);
316
- }
317
- };
318
- const ready = !!mediaUri && !mediaUploading && name.trim().length > 0 && (mediaKind === "image" || !!featureUri) && (collectionMode === "existing" ? assetType === "single" ? !!existingCollectionId : !!existingCollectionContract : newCollectionName.trim().length > 0 && newCollectionSymbol.trim().length > 0);
319
- async function pollForNewCollection(name2, symbol) {
320
- for (let attempt = 0; attempt < 8; attempt++) {
321
- const list = await refetchCollections();
322
- const match = list.find((c) => c.name === name2 && c.symbol === symbol);
323
- if (match?.collectionId) return match;
324
- await new Promise((r) => setTimeout(r, 1500));
325
- }
326
- return null;
327
- }
103
+ const ready = !!media.uri && !media.uploading && name.trim().length > 0 && (mediaKind === "image" || !!feature.uri) && (collectionMode === "existing" ? assetType === "single" ? !!existingCollectionId : !!existingCollectionContract : draft.name.trim().length > 0 && draft.symbol.trim().length > 0);
328
104
  const onSubmit = async (values) => {
329
105
  if (!hasWallet) {
330
106
  onRequireWallet();
331
107
  return;
332
108
  }
333
- if (!walletAddress || !mediaUri) return;
109
+ if (!walletAddress || !media.uri) return;
334
110
  if (values.external_url && !values.external_url.startsWith("http://") && !values.external_url.startsWith("https://")) {
335
111
  setErrorMsg("External link must start with http:// or https://");
336
112
  return;
@@ -338,93 +114,24 @@ function FastMint(props) {
338
114
  setStatus("minting");
339
115
  setErrorMsg(null);
340
116
  try {
341
- const signer = await getSigner();
342
- const image = mediaKind === "image" ? mediaUri : featureUri;
343
- const animationUrl = mediaKind === "image" ? void 0 : mediaUri;
344
- const built = buildAssetMetadata({
345
- name: values.name,
346
- description: values.description || "",
347
- externalUrl: values.external_url || "",
348
- imageUri: image,
349
- creator: walletAddress,
117
+ const asset = await mintAsset({
118
+ client,
119
+ signer: await getSigner(),
120
+ walletAddress,
121
+ values,
350
122
  ipType,
351
- licenseType: values.licenseType,
352
- commercialUse: values.commercialUse,
353
- derivatives: values.derivatives,
354
- attribution: values.attribution,
355
- geographicScope: values.geographicScope,
356
- aiPolicy: values.aiPolicy,
357
- royalty: String(values.royalty),
358
- templateTraits: templateFieldsRef.current.filter(({ traitType, value }) => traitType.trim() && value.trim()).map(({ traitType, value }) => ({ traitType, value }))
123
+ templateFields: templateFieldsRef.current,
124
+ mediaKind,
125
+ mediaUri: media.uri,
126
+ featureUri: feature.uri,
127
+ assetType,
128
+ editionCount,
129
+ destination: collectionMode === "new" ? { mode: "new", name: draft.name, symbol: draft.symbol, description: draft.description } : { mode: "existing", collectionId: existingCollectionId, collectionContract: existingCollectionContract },
130
+ singleCollections: erc721Collections,
131
+ refetchCollections,
132
+ getUploadToken
359
133
  });
360
- const metadata = { ...built };
361
- if (animationUrl) metadata.animation_url = animationUrl;
362
- const uploadToken = await getUploadToken();
363
- if (!uploadToken) throw new Error("Sign in first");
364
- const tokenUri = await uploadJsonToIpfs(client.api, metadata);
365
- let result;
366
- let finalContractAddress;
367
- let finalTokenId = null;
368
- if (assetType === "single") {
369
- let collectionId = existingCollectionId;
370
- let contractAddress = erc721Collections.find((c) => c.collectionId === existingCollectionId)?.contractAddress ?? null;
371
- if (collectionMode === "new") {
372
- const intentRes2 = await client.api.intents.create("create-collection", {
373
- owner: walletAddress,
374
- name: newCollectionName,
375
- symbol: newCollectionSymbol,
376
- description: newCollectionDescription || void 0,
377
- image
378
- });
379
- await executeIntent(signer, client, intentRes2);
380
- const found = await pollForNewCollection(newCollectionName, newCollectionSymbol);
381
- if (!found) throw new Error("Collection created, but it's still indexing \u2014 try minting again in a moment from My Collections.");
382
- collectionId = found.collectionId;
383
- contractAddress = found.contractAddress ?? null;
384
- }
385
- const intentRes = await client.api.intents.create("mint", {
386
- owner: walletAddress,
387
- collectionId,
388
- recipient: walletAddress,
389
- tokenUri,
390
- royaltyBps: Math.round(values.royalty * 100)
391
- });
392
- result = await executeIntent(signer, client, intentRes);
393
- finalContractAddress = contractAddress;
394
- if (finalContractAddress) {
395
- finalTokenId = mintedTokenIdFromReceipt(result.receipt, finalContractAddress);
396
- }
397
- } else {
398
- let collectionContract = existingCollectionContract;
399
- if (collectionMode === "new") {
400
- const intentRes2 = await client.api.intents.create("create-collection", {
401
- owner: walletAddress,
402
- name: newCollectionName,
403
- symbol: newCollectionSymbol,
404
- description: newCollectionDescription || void 0,
405
- image,
406
- baseUri: "",
407
- service: "mip-erc1155"
408
- });
409
- const deployResult = await executeIntent(signer, client, intentRes2);
410
- const deployed = deployedCollectionFromReceipt(deployResult.receipt, "mip-erc1155");
411
- if (!deployed) throw new Error("Collection deployed, but its address couldn't be read \u2014 check My Collections to mint into it.");
412
- collectionContract = deployed;
413
- }
414
- const intentRes = await client.api.intents.create("mint", {
415
- owner: walletAddress,
416
- recipient: walletAddress,
417
- collectionContract,
418
- tokenUri,
419
- value: editionCount,
420
- royaltyBps: Math.round(values.royalty * 100)
421
- });
422
- result = await executeIntent(signer, client, intentRes);
423
- finalContractAddress = collectionContract;
424
- finalTokenId = mintedTokenIdFromReceipt(result.receipt, collectionContract);
425
- }
426
- if (finalContractAddress && finalTokenId) {
427
- const asset = { contract: finalContractAddress, tokenId: finalTokenId, image };
134
+ if (asset) {
428
135
  setMintedAsset(asset);
429
136
  onMinted?.(asset);
430
137
  }
@@ -440,25 +147,19 @@ function FastMint(props) {
440
147
  setMintedAsset(null);
441
148
  form.reset();
442
149
  clearMedia();
443
- setFeaturePreview(null);
444
- setFeatureUri(null);
150
+ feature.clear();
445
151
  setAssetType("single");
446
152
  setEditionCount("10");
447
153
  setCollectionMode("existing");
448
154
  setExistingCollectionId("");
449
155
  setExistingCollectionContract("");
450
- setNewCollectionName("");
451
- setNewCollectionSymbol("");
452
- setNewCollectionDescription("");
453
- setAutoSymbol("");
454
- setAutoCollectionName("");
455
- setAutoCollectionDescription("");
156
+ draft.reset();
456
157
  templateFieldsRef.current = [];
457
158
  };
458
159
  const busy = status === "minting";
459
- const KindIcon = MEDIA_KIND_ICON[mediaKind];
160
+ const previewImage = mediaKind === "image" ? media.preview : feature.preview;
460
161
  const selectedExistingCollection = assetType === "single" ? erc721Collections.find((c) => c.collectionId === existingCollectionId) : erc1155Collections.find((c) => c.contractAddress === existingCollectionContract);
461
- const collectionLabel = mediaUri ? collectionMode === "new" ? newCollectionName || "New collection" : selectedExistingCollection?.name || "IP Asset" : void 0;
162
+ const collectionLabel = media.uri ? collectionMode === "new" ? draft.name || "New collection" : selectedExistingCollection?.name || "IP Asset" : void 0;
462
163
  const wrap = (node) => {
463
164
  if (presentation !== "dialog") return node;
464
165
  return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => {
@@ -476,7 +177,7 @@ function FastMint(props) {
476
177
  {
477
178
  footer: successFooter,
478
179
  presentation,
479
- previewImage: mediaKind === "image" ? mediaPreview : featurePreview,
180
+ previewImage,
480
181
  name,
481
182
  collectionLabel,
482
183
  mintedAsset,
@@ -500,53 +201,23 @@ function FastMint(props) {
500
201
  )
501
202
  );
502
203
  }
204
+ const submitBlocked = !ready || busy || !hasWallet;
503
205
  return wrap(
504
206
  /* @__PURE__ */ jsxs("section", { className: "space-y-6", children: [
505
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 rounded-xl border border-border p-3", children: [
506
- /* @__PURE__ */ jsxs("div", { className: "relative h-14 w-14 rounded-lg bg-muted overflow-hidden shrink-0", children: [
507
- mediaKind === "image" && mediaPreview ? /* @__PURE__ */ jsx(Image, { src: mediaPreview, alt: "", fill: true, className: "object-cover", unoptimized: true }) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(KindIcon, { className: "h-6 w-6 text-muted-foreground" }) }),
508
- mediaUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/70", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) })
509
- ] }),
510
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
511
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold truncate", children: mediaFile.name }),
512
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
513
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: mediaUploading ? "Uploading\u2026" : mediaUri ? "Uploaded" : "Upload failed" }),
514
- mediaUri && (() => {
515
- const template = IP_TEMPLATES[ipType];
516
- const TemplateIcon = template.icon;
517
- return /* @__PURE__ */ jsxs(
518
- "span",
519
- {
520
- className: cn(
521
- "inline-flex shrink-0 items-center gap-1 rounded-full border px-2 py-0.5 text-2xs font-medium",
522
- template.color.bg,
523
- template.color.text,
524
- template.color.border
525
- ),
526
- children: [
527
- /* @__PURE__ */ jsx(TemplateIcon, { className: "h-3 w-3" }),
528
- template.label
529
- ]
530
- }
531
- );
532
- })()
533
- ] })
534
- ] }),
535
- /* @__PURE__ */ jsx(
536
- "button",
537
- {
538
- type: "button",
539
- onClick: () => mediaInputRef.current?.click(),
540
- className: "text-xs font-semibold text-brand-blue hover:underline shrink-0",
541
- children: "Change"
542
- }
543
- ),
544
- /* @__PURE__ */ jsx("input", { ref: mediaInputRef, type: "file", className: "hidden", onChange: (e) => {
545
- const f = e.target.files?.[0];
546
- if (f) handleMediaSelect(f);
547
- } })
548
- ] }),
549
- mediaUri && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-8 items-start", children: [
207
+ /* @__PURE__ */ jsx(
208
+ MediaRow,
209
+ {
210
+ file: mediaFile,
211
+ kind: mediaKind,
212
+ preview: media.preview,
213
+ uri: media.uri,
214
+ uploading: media.uploading,
215
+ ipType,
216
+ inputRef: mediaInputRef,
217
+ onSelect: handleMediaSelect
218
+ }
219
+ ),
220
+ media.uri && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-8 items-start", children: [
550
221
  /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
551
222
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
552
223
  /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Title *" }),
@@ -564,7 +235,7 @@ function FastMint(props) {
564
235
  /* @__PURE__ */ jsx(
565
236
  Textarea,
566
237
  {
567
- value: form.watch("description"),
238
+ value: description,
568
239
  onChange: (e) => form.setValue("description", e.target.value),
569
240
  placeholder: "Describe your work, its story, and any context for buyers\u2026",
570
241
  rows: 3
@@ -585,277 +256,60 @@ function FastMint(props) {
585
256
  }
586
257
  )
587
258
  ] }),
588
- mediaKind !== "image" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
589
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Upload a cover image for your asset *" }),
590
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
591
- /* @__PURE__ */ jsxs(
592
- "div",
593
- {
594
- role: "button",
595
- tabIndex: 0,
596
- onClick: () => !featureUploading && featureInputRef.current?.click(),
597
- onKeyDown: (e) => {
598
- if ((e.key === "Enter" || e.key === " ") && !featureUploading) {
599
- e.preventDefault();
600
- featureInputRef.current?.click();
601
- }
602
- },
603
- className: "relative h-20 w-20 rounded-xl border-2 border-dashed border-border bg-muted flex items-center justify-center overflow-hidden shrink-0 cursor-pointer hover:border-primary/50 transition-colors",
604
- children: [
605
- featurePreview ? /* @__PURE__ */ jsx(Image, { src: featurePreview, alt: "", fill: true, className: "object-cover", unoptimized: true }) : /* @__PURE__ */ jsx(ImagePlus, { className: "h-6 w-6 text-muted-foreground" }),
606
- featureUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-background/70", children: /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) }),
607
- /* @__PURE__ */ jsx("input", { ref: featureInputRef, type: "file", accept: acceptFor("image"), className: "hidden", onChange: (e) => {
608
- const f = e.target.files?.[0];
609
- if (f) handleFeatureSelect(f);
610
- } })
611
- ]
612
- }
613
- ),
614
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Cover art shown wherever the work is previewed. JPG, PNG, GIF, SVG or WebP." })
615
- ] })
616
- ] }),
617
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
618
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Asset Type" }),
619
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
620
- /* @__PURE__ */ jsxs(
621
- "button",
622
- {
623
- type: "button",
624
- onClick: () => setAssetType("single"),
625
- className: cn(
626
- "flex items-start gap-3 rounded-xl border p-4 text-left transition-colors",
627
- assetType === "single" ? "border-brand-blue bg-brand-blue/5" : "border-border hover:bg-muted/40"
628
- ),
629
- children: [
630
- /* @__PURE__ */ jsx(SingleIcon, { className: cn("h-5 w-5 shrink-0 mt-0.5", assetType === "single" ? "text-brand-blue" : "text-muted-foreground") }),
631
- /* @__PURE__ */ jsxs("span", { children: [
632
- /* @__PURE__ */ jsx("span", { className: "block text-sm font-semibold", children: "Single Edition NFT" }),
633
- /* @__PURE__ */ jsx("span", { className: "block text-xs mt-0.5 text-muted-foreground", children: "Minted once" })
634
- ] })
635
- ]
636
- }
637
- ),
638
- /* @__PURE__ */ jsxs(
639
- "button",
640
- {
641
- type: "button",
642
- onClick: () => setAssetType("editions"),
643
- className: cn(
644
- "flex items-start gap-3 rounded-xl border p-4 text-left transition-colors",
645
- assetType === "editions" ? "border-brand-blue bg-brand-blue/5" : "border-border hover:bg-muted/40"
646
- ),
647
- children: [
648
- /* @__PURE__ */ jsx(Layers, { className: cn("h-5 w-5 shrink-0 mt-0.5", assetType === "editions" ? "text-brand-blue" : "text-muted-foreground") }),
649
- /* @__PURE__ */ jsxs("span", { children: [
650
- /* @__PURE__ */ jsx("span", { className: "block text-sm font-semibold", children: "Limited Editions NFT" }),
651
- /* @__PURE__ */ jsx("span", { className: "block text-xs mt-0.5 text-muted-foreground", children: "Several numbered copies" })
652
- ] })
653
- ]
654
- }
655
- )
656
- ] })
657
- ] }),
658
- assetType === "editions" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
659
- /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Number of copies" }),
660
- /* @__PURE__ */ jsx(
661
- Input,
662
- {
663
- type: "number",
664
- min: 1,
665
- value: editionCount,
666
- onChange: (e) => setEditionCount(e.target.value),
667
- className: "max-w-[140px]"
668
- }
669
- )
670
- ] }),
671
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
672
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
673
- /* @__PURE__ */ jsxs("label", { className: "text-sm font-medium flex items-center gap-1.5", children: [
674
- /* @__PURE__ */ jsx(Boxes, { className: "h-4 w-4" }),
675
- "Collection *"
676
- ] }),
677
- /* @__PURE__ */ jsxs("div", { className: "flex rounded-lg border border-border overflow-hidden", children: [
678
- /* @__PURE__ */ jsx(
679
- "button",
680
- {
681
- type: "button",
682
- onClick: () => setCollectionMode("existing"),
683
- className: cn(
684
- "px-3 h-7 text-xs font-medium transition-colors",
685
- collectionMode === "existing" ? "bg-primary text-primary-foreground" : "bg-background hover:bg-muted text-muted-foreground"
686
- ),
687
- children: "Existing"
688
- }
689
- ),
690
- /* @__PURE__ */ jsxs(
691
- "button",
692
- {
693
- type: "button",
694
- onClick: () => setCollectionMode("new"),
695
- className: cn(
696
- "px-3 h-7 text-xs font-medium border-l border-border transition-colors flex items-center gap-1",
697
- collectionMode === "new" ? "bg-primary text-primary-foreground" : "bg-background hover:bg-muted text-muted-foreground"
698
- ),
699
- children: [
700
- /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
701
- "New"
702
- ]
703
- }
704
- )
705
- ] })
706
- ] }),
707
- collectionMode === "existing" ? /* @__PURE__ */ jsx(
708
- CollectionPicker,
709
- {
710
- collections: assetType === "single" ? erc721Collections : erc1155Collections,
711
- value: assetType === "single" ? existingCollectionId : existingCollectionContract,
712
- onChange: assetType === "single" ? setExistingCollectionId : setExistingCollectionContract,
713
- valueOf: collectionKey(assetType === "single" ? "single" : "editions")
714
- }
715
- ) : /* @__PURE__ */ jsxs("div", { className: "flex gap-3 rounded-xl border border-border p-3", children: [
716
- /* @__PURE__ */ jsx("div", { className: "relative h-16 w-16 rounded-lg overflow-hidden bg-muted shrink-0", children: (mediaKind === "image" ? mediaPreview : featurePreview) ? /* @__PURE__ */ jsx(
717
- Image,
718
- {
719
- src: mediaKind === "image" ? mediaPreview : featurePreview,
720
- alt: "",
721
- fill: true,
722
- className: "object-cover",
723
- unoptimized: true
724
- }
725
- ) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(Boxes, { className: "h-5 w-5 text-muted-foreground/40" }) }) }),
726
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-2", children: [
727
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[1fr_100px] gap-2", children: [
728
- /* @__PURE__ */ jsx(
729
- Input,
730
- {
731
- value: newCollectionName,
732
- onChange: (e) => setNewCollectionName(e.target.value),
733
- placeholder: "Collection name",
734
- className: "h-9 text-sm"
735
- }
736
- ),
737
- /* @__PURE__ */ jsx(
738
- Input,
739
- {
740
- value: newCollectionSymbol,
741
- onChange: (e) => setNewCollectionSymbol(e.target.value.toUpperCase()),
742
- placeholder: "SYMBOL",
743
- className: "h-9 text-sm"
744
- }
745
- )
746
- ] }),
747
- /* @__PURE__ */ jsx(
748
- Textarea,
749
- {
750
- value: newCollectionDescription,
751
- onChange: (e) => setNewCollectionDescription(e.target.value),
752
- placeholder: "Describe this collection\u2026",
753
- rows: 2,
754
- className: "text-sm resize-none"
755
- }
756
- ),
757
- /* @__PURE__ */ jsx("p", { className: "text-2xs text-muted-foreground", children: "Prefilled from your asset \u2014 edit anything above." })
758
- ] })
759
- ] })
760
- ] }),
761
- /* @__PURE__ */ jsx(Collapsible, { open: advancedOpen, onOpenChange: setAdvancedOpen, children: /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border overflow-hidden", children: [
762
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("button", { type: "button", className: "w-full flex items-center justify-between px-4 py-3.5 hover:bg-muted/30 transition-colors", children: [
763
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
764
- /* @__PURE__ */ jsx(ShieldCheck, { className: "h-4 w-4 text-primary" }),
765
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "Licensing Terms" }),
766
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-normal", children: "Optional \xB7 Berne Convention" })
767
- ] }),
768
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-4 w-4 text-muted-foreground transition-transform", advancedOpen && "rotate-180") })
769
- ] }) }),
770
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxs("div", { className: "px-4 pb-4 space-y-4 border-t border-border/60 pt-4", children: [
771
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
772
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "License" }),
773
- /* @__PURE__ */ jsxs(Select, { value: form.watch("licenseType"), onValueChange: handleLicenseChange, children: [
774
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
775
- /* @__PURE__ */ jsx(SelectContent, { children: LICENSE_TYPES.map((l) => /* @__PURE__ */ jsx(SelectItem, { value: l.value, children: l.label }, l.value)) })
776
- ] })
777
- ] }),
778
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
779
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Commercial use" }),
780
- /* @__PURE__ */ jsx(ToggleGroup, { value: form.watch("commercialUse"), options: ["Yes", "No"], onChange: (v) => form.setValue("commercialUse", v) })
781
- ] }),
782
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
783
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Derivatives" }),
784
- /* @__PURE__ */ jsx(ToggleGroup, { value: form.watch("derivatives"), options: DERIVATIVES_OPTIONS, onChange: (v) => form.setValue("derivatives", v) })
785
- ] }),
786
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
787
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Territory" }),
788
- /* @__PURE__ */ jsxs(Select, { value: form.watch("geographicScope"), onValueChange: (v) => form.setValue("geographicScope", v), children: [
789
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
790
- /* @__PURE__ */ jsx(SelectContent, { children: GEOGRAPHIC_SCOPES.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s, children: s }, s)) })
791
- ] })
792
- ] }),
793
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
794
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "AI & data mining" }),
795
- /* @__PURE__ */ jsx(ToggleGroup, { value: form.watch("aiPolicy"), options: AI_POLICIES, onChange: (v) => form.setValue("aiPolicy", v) })
796
- ] }),
797
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
798
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Royalty % (0\u201350)" }),
799
- /* @__PURE__ */ jsx(
800
- Input,
801
- {
802
- type: "number",
803
- min: 0,
804
- max: 50,
805
- step: 0.5,
806
- value: form.watch("royalty"),
807
- onChange: (e) => form.setValue("royalty", parseFloat(e.target.value) || 0),
808
- className: "max-w-[120px]"
809
- }
810
- )
811
- ] })
812
- ] }) })
813
- ] }) }),
814
- /* @__PURE__ */ jsx(Collapsible, { open: ipTypeOpen, onOpenChange: setIpTypeOpen, children: /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border overflow-hidden", children: [
815
- /* @__PURE__ */ jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("button", { type: "button", className: "w-full flex items-center justify-between px-4 py-3.5 hover:bg-muted/30 transition-colors", children: [
816
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
817
- /* @__PURE__ */ jsx(Layers, { className: "h-4 w-4 text-primary" }),
818
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: "IP Type & Metadata" }),
819
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground font-normal", children: "Optional" })
820
- ] }),
821
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-4 w-4 text-muted-foreground transition-transform", ipTypeOpen && "rotate-180") })
822
- ] }) }),
823
- /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxs("div", { className: "px-4 pb-4 space-y-4 border-t border-border/60 pt-4", children: [
824
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
825
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "IP Type" }),
826
- /* @__PURE__ */ jsxs(Select, { value: ipType, onValueChange: (v) => setIpType(v), children: [
827
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
828
- /* @__PURE__ */ jsx(SelectContent, { children: IP_TYPES.map((t) => /* @__PURE__ */ jsx(SelectItem, { value: t, children: t }, t)) })
829
- ] })
830
- ] }),
831
- /* @__PURE__ */ jsx(
832
- IPTypeFields,
833
- {
834
- ipType,
835
- onChange: (fields) => {
836
- templateFieldsRef.current = fields;
837
- },
838
- uploadDocument: async (file) => {
839
- const token = await getUploadToken();
840
- if (!token) throw new Error("Sign in first");
841
- const { uri } = await uploadFileToIpfs(client.api, file, "document");
842
- return uri;
843
- },
844
- existingDocument: mediaKind === "document" && mediaUri && mediaFile ? { uri: mediaUri, name: mediaFile.name } : null
845
- }
846
- )
847
- ] }) })
848
- ] }) }),
259
+ mediaKind !== "image" && /* @__PURE__ */ jsx(
260
+ CoverUpload,
261
+ {
262
+ preview: feature.preview,
263
+ uploading: feature.uploading,
264
+ inputRef: featureInputRef,
265
+ onSelect: (file) => void feature.start(file, (f) => uploadFile(f, "image"), setErrorMsg)
266
+ }
267
+ ),
268
+ /* @__PURE__ */ jsx(
269
+ AssetTypePicker,
270
+ {
271
+ value: assetType,
272
+ onChange: setAssetType,
273
+ editionCount,
274
+ onEditionCount: setEditionCount
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsx(
278
+ CollectionSection,
279
+ {
280
+ assetType,
281
+ mode: collectionMode,
282
+ onModeChange: setCollectionMode,
283
+ collections: assetType === "single" ? erc721Collections : erc1155Collections,
284
+ existingId: existingCollectionId,
285
+ existingContract: existingCollectionContract,
286
+ onExistingId: setExistingCollectionId,
287
+ onExistingContract: setExistingCollectionContract,
288
+ draft,
289
+ previewImage
290
+ }
291
+ ),
292
+ /* @__PURE__ */ jsx(LicensingSection, { form }),
293
+ /* @__PURE__ */ jsx(
294
+ IpTypeSection,
295
+ {
296
+ ipType,
297
+ onIpTypeChange: setIpType,
298
+ fieldsRef: templateFieldsRef,
299
+ uploadDocument: (file) => uploadFile(file, "document"),
300
+ existingDocument: mediaKind === "document" ? { uri: media.uri, name: mediaFile.name } : null
301
+ }
302
+ ),
849
303
  status === "error" && errorMsg && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: errorMsg }),
850
304
  /* @__PURE__ */ jsx(
851
305
  "button",
852
306
  {
853
307
  type: "button",
854
- disabled: !ready || busy || !hasWallet,
308
+ disabled: submitBlocked,
855
309
  onClick: form.handleSubmit(onSubmit),
856
310
  className: cn(
857
311
  "w-full h-12 text-base font-semibold text-white rounded-xl flex items-center justify-center gap-2 transition-all hover:brightness-110 active:scale-[0.98] bg-brand-blue",
858
- (!ready || busy || !hasWallet) && "opacity-40 pointer-events-none"
312
+ submitBlocked && "opacity-40 pointer-events-none"
859
313
  ),
860
314
  children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [
861
315
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
@@ -868,7 +322,7 @@ function FastMint(props) {
868
322
  /* @__PURE__ */ jsx("div", { className: "order-first lg:order-last lg:sticky lg:top-20 max-w-[260px] mx-auto w-full lg:max-w-none lg:mx-0", children: /* @__PURE__ */ jsx(
869
323
  MedialaneCollectionCard,
870
324
  {
871
- image: mediaKind === "image" ? mediaPreview : featurePreview,
325
+ image: previewImage,
872
326
  name,
873
327
  collection: collectionLabel,
874
328
  creator: walletAddress ? `${walletAddress.slice(0, 6)}\u2026${walletAddress.slice(-4)}` : void 0,