diff --git a/components/openEdition/OnChainMetadataInputDetails.tsx b/components/openEdition/OnChainMetadataInputDetails.tsx new file mode 100644 index 0000000..6141a56 --- /dev/null +++ b/components/openEdition/OnChainMetadataInputDetails.tsx @@ -0,0 +1,253 @@ +/* eslint-disable eslint-comments/disable-enable-pair */ + +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +import clsx from 'clsx' +import { useInputState } from 'components/forms/FormInput.hooks' +import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks' +import { useWallet } from 'contexts/wallet' +import type { Trait } from 'contracts/badgeHub' +import type { ChangeEvent } from 'react' +import { useEffect, useRef, useState } from 'react' +import { toast } from 'react-hot-toast' + +import { TextInput } from '../forms/FormInput' +import { MetadataAttributes } from '../forms/MetadataAttributes' +import { Tooltip } from '../Tooltip' +import type { UploadMethod } from './ImageUploadDetails' + +interface OnChainMetadataInputDetailsProps { + onChange: (data: OnChainMetadataInputDetailsDataProps) => void + uploadMethod: UploadMethod | undefined +} + +export interface OnChainMetadataInputDetailsDataProps { + name?: string + description?: string + attributes?: Trait[] + image_data?: string + external_url?: string + background_color?: string + animation_url?: string + youtube_url?: string +} + +export const OnChainMetadataInputDetails = ({ onChange }: OnChainMetadataInputDetailsProps) => { + const wallet = useWallet() + const [timestamp, setTimestamp] = useState(undefined) + const [metadataFile, setMetadataFile] = useState() + const [metadataFeeRate, setMetadataFeeRate] = useState(0) + + const metadataFileRef = useRef(null) + + const nameState = useInputState({ + id: 'name', + name: 'name', + title: 'Name', + placeholder: 'My Awesome Collection', + }) + + const descriptionState = useInputState({ + id: 'description', + name: 'description', + title: 'Description', + placeholder: 'My Awesome Collection Description', + }) + + const imageDataState = useInputState({ + id: 'metadata-image-data', + name: 'metadata-image-data', + title: 'Image Data', + subtitle: 'Raw SVG image data', + }) + + const externalUrlState = useInputState({ + id: 'metadata-external-url', + name: 'metadata-external-url', + title: 'External URL', + subtitle: 'External URL for the badge', + }) + + const attributesState = useMetadataAttributesState() + + const animationUrlState = useInputState({ + id: 'metadata-animation-url', + name: 'metadata-animation-url', + title: 'Animation URL', + subtitle: 'Animation URL for the badge', + }) + + const youtubeUrlState = useInputState({ + id: 'metadata-youtube-url', + name: 'metadata-youtube-url', + title: 'YouTube URL', + subtitle: 'YouTube URL for the badge', + }) + + const parseMetadata = async () => { + try { + let parsedMetadata: any + if (metadataFile) { + attributesState.reset() + parsedMetadata = JSON.parse(await metadataFile.text()) + + if (!parsedMetadata.attributes || parsedMetadata.attributes.length === 0) { + attributesState.add({ + trait_type: '', + value: '', + }) + } else { + for (let i = 0; i < parsedMetadata.attributes.length; i++) { + attributesState.add({ + trait_type: parsedMetadata.attributes[i].trait_type, + value: parsedMetadata.attributes[i].value, + }) + } + } + nameState.onChange(parsedMetadata.name ? parsedMetadata.name : '') + descriptionState.onChange(parsedMetadata.description ? parsedMetadata.description : '') + externalUrlState.onChange(parsedMetadata.external_url ? parsedMetadata.external_url : '') + youtubeUrlState.onChange(parsedMetadata.youtube_url ? parsedMetadata.youtube_url : '') + animationUrlState.onChange(parsedMetadata.animation_url ? parsedMetadata.animation_url : '') + imageDataState.onChange(parsedMetadata.image_data ? parsedMetadata.image_data : '') + } else { + attributesState.reset() + nameState.onChange('') + descriptionState.onChange('') + externalUrlState.onChange('') + youtubeUrlState.onChange('') + animationUrlState.onChange('') + imageDataState.onChange('') + } + } catch (error) { + toast.error('Error parsing metadata file: Invalid JSON format.') + if (metadataFileRef.current) metadataFileRef.current.value = '' + setMetadataFile(undefined) + } + } + + const selectMetadata = (event: ChangeEvent) => { + setMetadataFile(undefined) + if (event.target.files === null) return + + let selectedFile: File + const reader = new FileReader() + reader.onload = (e) => { + if (!event.target.files) return toast.error('No file selected.') + if (!e.target?.result) return toast.error('Error parsing file.') + selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'application/json' }) + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0]) + else return toast.error('No file selected.') + reader.onloadend = () => { + if (!event.target.files) return toast.error('No file selected.') + setMetadataFile(selectedFile) + } + } + + useEffect(() => { + void parseMetadata() + if (!metadataFile) + attributesState.add({ + trait_type: '', + value: '', + }) + }, [metadataFile]) + + useEffect(() => { + try { + const data: OnChainMetadataInputDetailsDataProps = { + name: nameState.value || undefined, + description: descriptionState.value || undefined, + attributes: + attributesState.values[0]?.trait_type && attributesState.values[0]?.value + ? attributesState.values + .map((attr) => ({ + trait_type: attr.trait_type, + value: attr.value, + })) + .filter((attr) => attr.trait_type && attr.value) + : undefined, + image_data: imageDataState.value || undefined, + external_url: externalUrlState.value || undefined, + animation_url: animationUrlState.value || undefined, + youtube_url: youtubeUrlState.value || undefined, + } + onChange(data) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + toast.error(error.message, { style: { maxWidth: 'none' } }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + nameState.value, + descriptionState.value, + timestamp, + imageDataState.value, + externalUrlState.value, + attributesState.values, + animationUrlState.value, + youtubeUrlState.value, + ]) + + return ( +
+
+
+ + + + + +
+
+
+ +
+
+ +
+ +
+ +
+
+
+
+
+
+
+ ) +}