import clsx from 'clsx' import { Alert } from 'components/Alert' import Anchor from 'components/Anchor' import { Conditional } from 'components/Conditional' import { TextInput } from 'components/forms/FormInput' import { useInputState } from 'components/forms/FormInput.hooks' import { MetadataModal } from 'components/MetadataModal' import { setBaseTokenUri, setImage, useCollectionStore } from 'contexts/collection' import type { ChangeEvent } from 'react' import { useEffect, useMemo, 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 nftStorageApiKeyState = useInputState({ id: 'nft-storage-api-key', name: 'nftStorageApiKey', title: 'NFT Storage API Key', placeholder: '...', defaultValue: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDJBODk5OGI4ZkE2YTM1NzMyYmMxQTRDQzNhOUU2M0Y2NUM3ZjA1RWIiLCJpc3MiOiJuZnQtc3RvcmFnZSIsImlhdCI6MTY1NTE5MTcwNDQ2MiwibmFtZSI6IlRlc3QifQ.IbdV_26bkPHSdd81sxox5AoG-5a4CCEY4aCrdbCXwAE', }) const pinataApiKeyState = useInputState({ id: 'pinata-api-key', name: 'pinataApiKey', title: 'Pinata API Key', placeholder: '...', defaultValue: 'c8c2ea440c09ee8fa639', }) const pinataSecretKeyState = useInputState({ id: 'pinata-secret-key', name: 'pinataSecretKey', title: 'Pinata Secret Key', placeholder: '...', defaultValue: '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.') } } const videoPreviewElements = useMemo(() => { const tempArray: JSX.Element[] = [] assetFilesArray.forEach((assetFile) => { if (getAssetType(assetFile.name) === 'video') { tempArray.push(