diff --git a/components/openEdition/CollectionDetails.tsx b/components/openEdition/CollectionDetails.tsx new file mode 100644 index 0000000..75973f5 --- /dev/null +++ b/components/openEdition/CollectionDetails.tsx @@ -0,0 +1,277 @@ +/* eslint-disable eslint-comments/disable-enable-pair */ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ + +import clsx from 'clsx' +import { Conditional } from 'components/Conditional' +import { FormControl } from 'components/FormControl' +import { FormGroup } from 'components/FormGroup' +import { useInputState } from 'components/forms/FormInput.hooks' +import { InputDateTime } from 'components/InputDateTime' +import { Tooltip } from 'components/Tooltip' +import { addLogItem } from 'contexts/log' +import type { ChangeEvent } from 'react' +import { useEffect, useRef, useState } from 'react' +import { toast } from 'react-hot-toast' +import { SG721_UPDATABLE_CODE_ID } from 'utils/constants' +import { uid } from 'utils/random' + +import { TextInput } from '../forms/FormInput' +import type { UploadMethod } from './OffChainMetadataUploadDetails' + +interface CollectionDetailsProps { + onChange: (data: CollectionDetailsDataProps) => void + uploadMethod: UploadMethod + coverImageUrl: string +} + +export interface CollectionDetailsDataProps { + name: string + description: string + symbol: string + imageFile: File[] + externalLink?: string + startTradingTime?: string + explicit: boolean + updatable: boolean +} + +export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: CollectionDetailsProps) => { + const [coverImage, setCoverImage] = useState(null) + const [timestamp, setTimestamp] = useState() + const [explicit, setExplicit] = useState(false) + const [updatable, setUpdatable] = useState(false) + + const initialRender = useRef(true) + + 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 symbolState = useInputState({ + id: 'symbol', + name: 'symbol', + title: 'Symbol', + placeholder: 'SYMBOL', + }) + + const externalLinkState = useInputState({ + id: 'external-link', + name: 'externalLink', + title: 'External Link (optional)', + placeholder: 'https://my-collection...', + }) + + useEffect(() => { + try { + const data: CollectionDetailsDataProps = { + name: nameState.value, + description: descriptionState.value, + symbol: symbolState.value, + imageFile: coverImage ? [coverImage] : [], + externalLink: externalLinkState.value || undefined, + startTradingTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '', + explicit, + updatable, + } + onChange(data) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + toast.error(error.message, { style: { maxWidth: 'none' } }) + addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + nameState.value, + descriptionState.value, + symbolState.value, + externalLinkState.value, + coverImage, + timestamp, + explicit, + updatable, + ]) + + const selectCoverImage = (event: ChangeEvent) => { + if (event.target.files === null) return toast.error('Error selecting cover image') + if (event.target.files.length === 0) { + setCoverImage(null) + return toast.error('No files selected.') + } + const reader = new FileReader() + reader.onload = (e) => { + if (!e.target?.result) return toast.error('Error parsing file.') + if (!event.target.files) return toast.error('No files selected.') + const imageFile = new File([e.target.result], event.target.files[0].name, { type: 'image/jpg' }) + setCoverImage(imageFile) + } + reader.readAsArrayBuffer(event.target.files[0]) + } + + useEffect(() => { + if (initialRender.current) { + initialRender.current = false + } else if (updatable) { + toast.success('Token metadata will be updatable upon collection creation.', { + style: { maxWidth: 'none' }, + icon: '✅📝', + }) + } else { + toast.error('Token metadata will not be updatable upon collection creation.', { + style: { maxWidth: 'none' }, + icon: '⛔🔏', + }) + } + }, [updatable]) + + return ( +
+ +
+
+ + + +
+
+ + {/* Currently trading starts immediately for 1/1 Collections */} + + + setTimestamp(date)} value={timestamp} /> + +
+
+ + + {uploadMethod === 'new' && ( + + )} + + {coverImage !== null && uploadMethod === 'new' && ( +
+ no-preview-available +
+ )} + {uploadMethod === 'existing' && coverImageUrl?.includes('ipfs://') && ( +
+ no-preview-available +
+ )} + {uploadMethod === 'existing' && coverImageUrl && !coverImageUrl?.includes('ipfs://') && ( +
+ no-preview-available +
+ )} + {uploadMethod === 'existing' && !coverImageUrl && ( + Waiting for cover image URL to be specified. + )} +
+ +
+
+
+ + Does the collection contain explicit content? + +
+ { + setExplicit(true) + }} + type="radio" + /> + +
+
+ { + setExplicit(false) + }} + type="radio" + /> + +
+
+
+
+ 0}> + + + â„šī¸ When enabled, the metadata for tokens can be updated after the collection is created until the + collection is frozen by the creator. + +
+ } + placement="bottom" + > +
+ +
+ + + + + ) +}