import clsx from 'clsx' import { Alert } from 'components/Alert' import Anchor from 'components/Anchor' import { Conditional } from 'components/Conditional' import { StyledInput } from 'components/forms/StyledInput' import { MetadataModal } from 'components/MetadataModal' import { setBaseTokenUri, setImage, useCollectionStore } from 'contexts/collection' import type { ChangeEvent } from 'react' import { useEffect, useState } from 'react' import { toast } from 'react-hot-toast' import type { UploadServiceType } from 'services/upload' import { getAssetType } from 'utils/getAssetType' import { naturalCompare } from 'utils/sort' type UploadMethod = 'new' | 'existing' interface UploadDetailsProps { onChange: (value: UploadDetailsDataProps) => void } export interface UploadDetailsDataProps { assetFiles: File[] metadataFiles: File[] uploadService: UploadServiceType nftStorageApiKey?: string pinataApiKey?: string pinataSecretKey?: string } export const UploadDetails = ({ onChange }: UploadDetailsProps) => { const baseTokenURI = useCollectionStore().base_token_uri const [assetFilesArray, setAssetFilesArray] = useState([]) const [metadataFilesArray, setMetadataFilesArray] = useState([]) const [updatedMetadataFilesArray, setUpdatedMetadataFilesArray] = useState([]) const [uploadMethod, setUploadMethod] = useState('new') const [uploadService, setUploadService] = useState('nft-storage') const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0) const [refreshMetadata, setRefreshMetadata] = useState(false) const [nftStorageApiKey, setNftStorageApiKey] = useState( 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDJBODk5OGI4ZkE2YTM1NzMyYmMxQTRDQzNhOUU2M0Y2NUM3ZjA1RWIiLCJpc3MiOiJuZnQtc3RvcmFnZSIsImlhdCI6MTY1NTE5MTcwNDQ2MiwibmFtZSI6IlRlc3QifQ.IbdV_26bkPHSdd81sxox5AoG-5a4CCEY4aCrdbCXwAE', ) const [pinataApiKey, setPinataApiKey] = useState('c8c2ea440c09ee8fa639') const [pinataSecretKey, setPinataSecretKey] = useState( '9d6f42dc01eaab15f52eac8f36cc4f0ee4184944cb3cdbcda229d06ecf877ee7', ) const handleChangeBaseTokenUri = (event: { target: { value: React.SetStateAction } }) => { setBaseTokenUri(event.target.value.toString()) } const handleChangeImage = (event: { target: { value: React.SetStateAction } }) => { setImage(event.target.value.toString()) } const selectAssets = (event: ChangeEvent) => { setAssetFilesArray([]) setMetadataFilesArray([]) setUpdatedMetadataFilesArray([]) console.log(event.target.files) let reader: FileReader if (event.target.files === null) return for (let i = 0; i < event.target.files.length; i++) { 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 assetFile = new File([e.target.result], event.target.files[i].name, { type: 'image/jpg' }) setAssetFilesArray((prev) => [...prev, assetFile]) } if (!event.target.files) return toast.error('No file selected.') reader.readAsArrayBuffer(event.target.files[i]) reader.onloadend = (e) => { setAssetFilesArray((prev) => prev.sort((a, b) => naturalCompare(a.name, b.name))) } } } const selectMetadata = (event: ChangeEvent) => { setMetadataFilesArray([]) setUpdatedMetadataFilesArray([]) console.log(assetFilesArray) console.log(event.target.files) let reader: FileReader if (event.target.files === null) return toast.error('No files selected.') for (let i = 0; i < event.target.files.length; i++) { reader = new FileReader() reader.onload = async (e) => { if (!e.target?.result) return toast.error('Error parsing file.') if (!event.target.files) return toast.error('No files selected.') if (!JSON.parse(await event.target.files[i].text()).attributes) return toast.error(`The file with name '${event.target.files[i].name}' doesn't have an attributes list!`) const metadataFile = new File([e.target.result], event.target.files[i].name, { type: 'application/json' }) setMetadataFilesArray((prev) => [...prev, metadataFile]) } if (!event.target.files) return toast.error('No file selected.') reader.readAsText(event.target.files[i], 'utf8') reader.onloadend = (e) => { setMetadataFilesArray((prev) => prev.sort((a, b) => naturalCompare(a.name, b.name))) console.log(metadataFilesArray) } } } const updateMetadataFileIndex = (index: number) => { setMetadataFileArrayIndex(index) setRefreshMetadata((prev) => !prev) } const updateMetadataFileArray = async (updatedMetadataFile: File) => { metadataFilesArray[metadataFileArrayIndex] = updatedMetadataFile console.log('Updated Metadata File:') console.log(JSON.parse(await metadataFilesArray[metadataFileArrayIndex]?.text())) } const checkAssetMetadataMatch = () => { const metadataFileNames = metadataFilesArray.map((file) => file.name) const assetFileNames = assetFilesArray.map((file) => file.name.substring(0, file.name.lastIndexOf('.'))) // Compare the two arrays to make sure they are the same const areArraysEqual = metadataFileNames.every((val, index) => val === assetFileNames[index]) if (!areArraysEqual) { throw new Error('Asset and metadata file names do not match.') } } useEffect(() => { try { checkAssetMetadataMatch() const data: UploadDetailsDataProps = { assetFiles: assetFilesArray, metadataFiles: metadataFilesArray, uploadService, nftStorageApiKey, pinataApiKey, pinataSecretKey, } onChange(data) } catch (error: any) { toast.error(error.message) } }, [assetFilesArray, metadataFilesArray]) return (
{ setUploadMethod('existing') }} type="radio" value="Existing" />
{ setUploadMethod('new') }} type="radio" value="New" />
{baseTokenURI && ( Base Token URI: {baseTokenURI} )}

{uploadMethod === 'existing' && (

Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a decentralized storage solution, such as IPFS.
You may head over to{' '} NFT Storage {' '} or{' '} Pinata {' '} and upload your assets & metadata manually to get a base URI for your collection.

)} {uploadMethod === 'new' && (
{ setUploadService('nft-storage') }} type="radio" value="nft-storage" />
{ setUploadService('pinata') }} type="radio" value="pinata" />
setNftStorageApiKey(e.target.value)} value={nftStorageApiKey} />
setPinataApiKey(e.target.value)} value={pinataApiKey} /> setPinataSecretKey(e.target.value)} value={pinataSecretKey} />
{assetFilesArray.length > 0 && (
)} 0 && metadataFilesArray.length > 0 && assetFilesArray.length !== metadataFilesArray.length } > The number of assets and metadata files should match.
{assetFilesArray.length > 0 && (
{assetFilesArray.map((assetSource, index) => (
4 * index}> 4 * index + 1}> 4 * index + 2}> 4 * index + 3}>
))}
)}
)}
) }