Implement 1/1 minting UI
This commit is contained in:
@@ -2,14 +2,18 @@ import clsx from 'clsx'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { getAssetType } from 'utils/getAssetType'
|
||||
|
||||
import type { MinterType } from './collections/actions/Combobox'
|
||||
import { Conditional } from './Conditional'
|
||||
|
||||
interface AssetsPreviewProps {
|
||||
assetFilesArray: File[]
|
||||
updateMetadataFileIndex: (index: number) => void
|
||||
minterType: MinterType
|
||||
}
|
||||
|
||||
const ITEM_NUMBER = 12
|
||||
|
||||
export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: AssetsPreviewProps) => {
|
||||
export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex, minterType }: AssetsPreviewProps) => {
|
||||
const [page, setPage] = useState(1)
|
||||
|
||||
const totalPages = useMemo(() => Math.ceil(assetFilesArray.length / ITEM_NUMBER), [assetFilesArray])
|
||||
@@ -116,23 +120,25 @@ export const AssetsPreview = ({ assetFilesArray, updateMetadataFileIndex }: Asse
|
||||
return (
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="mt-2 w-[400px] h-[300px]">{renderImages()}</div>
|
||||
<div className="mt-5 btn-group">
|
||||
<button className="text-white bg-plumbus-light btn" onClick={multiplePrevPage} type="button">
|
||||
««
|
||||
</button>
|
||||
<button className="text-white bg-plumbus-light btn" onClick={prevPage} type="button">
|
||||
«
|
||||
</button>
|
||||
<button className="text-white btn" type="button">
|
||||
Page {page}/{totalPages}
|
||||
</button>
|
||||
<button className="text-white bg-plumbus-light btn" onClick={nextPage} type="button">
|
||||
»
|
||||
</button>
|
||||
<button className="text-white bg-plumbus-light btn" onClick={multipleNextPage} type="button">
|
||||
»»
|
||||
</button>
|
||||
</div>
|
||||
<Conditional test={minterType === 'vending'}>
|
||||
<div className="mt-5 btn-group">
|
||||
<button className="text-white bg-plumbus-light btn" onClick={multiplePrevPage} type="button">
|
||||
««
|
||||
</button>
|
||||
<button className="text-white bg-plumbus-light btn" onClick={prevPage} type="button">
|
||||
«
|
||||
</button>
|
||||
<button className="text-white btn" type="button">
|
||||
Page {page}/{totalPages}
|
||||
</button>
|
||||
<button className="text-white bg-plumbus-light btn" onClick={nextPage} type="button">
|
||||
»
|
||||
</button>
|
||||
<button className="text-white bg-plumbus-light btn" onClick={multipleNextPage} type="button">
|
||||
»»
|
||||
</button>
|
||||
</div>
|
||||
</Conditional>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const ConfirmationModal = (props: ConfirmationModalProps) => {
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
Are you sure to create a collection with the specified assets, metadata and parameters?
|
||||
Are you sure to proceed with the specified assets, metadata and parameters?
|
||||
</div>
|
||||
<div className="flex justify-end w-full">
|
||||
<Button className="px-0 mt-4 mr-5 mb-4 max-h-12 bg-gray-600 hover:bg-gray-600">
|
||||
|
||||
@@ -35,7 +35,7 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
|
||||
const wallet = useWallet()
|
||||
|
||||
const [myBaseMinterContracts, setMyBaseMinterContracts] = useState<MinterInfo[]>([])
|
||||
const [minterAcquisitionMethod, setMinterAcquisitionMethod] = useState<MinterAcquisitionMethod>('existing')
|
||||
const [minterAcquisitionMethod, setMinterAcquisitionMethod] = useState<MinterAcquisitionMethod>('new')
|
||||
|
||||
const existingMinterState = useInputState({
|
||||
id: 'existingMinter',
|
||||
@@ -79,7 +79,7 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
|
||||
minterContracts.map(async (minterContract: any) => {
|
||||
await getMinterContractType(minterContract.minter)
|
||||
.then((contractType) => {
|
||||
if (contractType?.includes('sg-minter')) {
|
||||
if (contractType?.includes('sg-base-minter')) {
|
||||
setMyBaseMinterContracts((prevState) => [...prevState, minterContract])
|
||||
}
|
||||
})
|
||||
@@ -181,12 +181,12 @@ export const MinterDetails = ({ onChange, minterType }: MinterDetailsProps) => {
|
||||
<select
|
||||
className="mt-8 w-full max-w-lg text-sm bg-white/10 select select-bordered"
|
||||
onChange={(e) => {
|
||||
existingMinterState.onChange(e.target.value.slice(e.target.value.indexOf('-') + 2))
|
||||
existingMinterState.onChange(e.target.value.slice(e.target.value.indexOf('stars1')))
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<option className="mt-2 text-lg bg-[#1A1A1A]" disabled selected>
|
||||
Select a Base Minter Contract
|
||||
Select one of your existing Base Minter Contracts
|
||||
</option>
|
||||
{renderMinterContracts()}
|
||||
</select>
|
||||
|
||||
@@ -11,15 +11,20 @@ import { TextInput } from 'components/forms/FormInput'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { MetadataModal } from 'components/MetadataModal'
|
||||
import type { ChangeEvent } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import type { UploadServiceType } from 'services/upload'
|
||||
import { naturalCompare } from 'utils/sort'
|
||||
|
||||
import type { MinterType } from '../actions/Combobox'
|
||||
import type { MinterAcquisitionMethod } from './MinterDetails'
|
||||
|
||||
export type UploadMethod = 'new' | 'existing'
|
||||
|
||||
interface UploadDetailsProps {
|
||||
onChange: (value: UploadDetailsDataProps) => void
|
||||
minterType: MinterType
|
||||
minterAcquisitionMethod?: MinterAcquisitionMethod
|
||||
}
|
||||
|
||||
export interface UploadDetailsDataProps {
|
||||
@@ -34,7 +39,7 @@ export interface UploadDetailsDataProps {
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
export const UploadDetails = ({ onChange, minterType, minterAcquisitionMethod }: UploadDetailsProps) => {
|
||||
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
|
||||
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
|
||||
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
|
||||
@@ -42,6 +47,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
const [metadataFileArrayIndex, setMetadataFileArrayIndex] = useState(0)
|
||||
const [refreshMetadata, setRefreshMetadata] = useState(false)
|
||||
|
||||
const assetFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
const metadataFilesRef = useRef<HTMLInputElement | null>(null)
|
||||
|
||||
const nftStorageApiKeyState = useInputState({
|
||||
id: 'nft-storage-api-key',
|
||||
name: 'nftStorageApiKey',
|
||||
@@ -67,7 +75,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
const baseTokenUriState = useInputState({
|
||||
id: 'baseTokenUri',
|
||||
name: 'baseTokenUri',
|
||||
title: 'Base Token URI',
|
||||
title: minterType === 'vending' ? 'Base Token URI' : 'Token URI',
|
||||
placeholder: 'ipfs://',
|
||||
defaultValue: '',
|
||||
})
|
||||
@@ -226,11 +234,13 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (assetFilesRef.current) assetFilesRef.current.value = ''
|
||||
if (assetFilesRef.current) assetFilesRef.current.value = ''
|
||||
setAssetFilesArray([])
|
||||
setMetadataFilesArray([])
|
||||
baseTokenUriState.onChange('')
|
||||
coverImageUrlState.onChange('')
|
||||
}, [uploadMethod])
|
||||
}, [uploadMethod, minterType, minterAcquisitionMethod])
|
||||
|
||||
return (
|
||||
<div className="justify-items-start mb-3 rounded border-2 border-white/20 flex-column">
|
||||
@@ -251,7 +261,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio2"
|
||||
>
|
||||
Upload assets & metadata
|
||||
{minterType === 'base' ? 'Upload asset & metadata' : 'Upload assets & metadata'}
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-3 ml-2 font-bold form-check form-check-inline">
|
||||
@@ -270,13 +280,13 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio1"
|
||||
>
|
||||
Use an existing base URI
|
||||
{minterType === 'base' ? 'Use an existing Token URI' : 'Use an existing base URI'}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 py-5 pb-8">
|
||||
<Conditional test={uploadMethod === 'existing'}>
|
||||
<Conditional test={uploadMethod === 'existing' && minterType === 'vending'}>
|
||||
<div className="ml-3 flex-column">
|
||||
<p className="mb-5 ml-5">
|
||||
Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a
|
||||
@@ -293,9 +303,35 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
<div>
|
||||
<TextInput {...baseTokenUriState} className="w-1/2" />
|
||||
</div>
|
||||
<Conditional test={minterType !== 'base'}>
|
||||
<div>
|
||||
<TextInput {...coverImageUrlState} className="mt-2 w-1/2" />
|
||||
</div>
|
||||
</Conditional>
|
||||
</div>
|
||||
</Conditional>
|
||||
<Conditional test={uploadMethod === 'existing' && minterType === 'base'}>
|
||||
<div className="ml-3 flex-column">
|
||||
<p className="mb-5 ml-5">
|
||||
Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a
|
||||
decentralized storage solution, such as IPFS. <br /> You may head over to{' '}
|
||||
<Anchor className="font-bold text-plumbus hover:underline" href="https://nft.storage">
|
||||
NFT.Storage
|
||||
</Anchor>{' '}
|
||||
or{' '}
|
||||
<Anchor className="font-bold text-plumbus hover:underline" href="https://www.pinata.cloud/">
|
||||
Pinata
|
||||
</Anchor>{' '}
|
||||
and upload your asset & metadata manually to get a URI for your token before minting.
|
||||
</p>
|
||||
<div>
|
||||
<TextInput {...coverImageUrlState} className="mt-2 w-1/2" />
|
||||
<TextInput {...baseTokenUriState} className="w-1/2" />
|
||||
</div>
|
||||
<Conditional test={minterType !== 'base' || (minterType === 'base' && minterAcquisitionMethod === 'new')}>
|
||||
<div>
|
||||
<TextInput {...coverImageUrlState} className="mt-2 w-1/2" />
|
||||
</div>
|
||||
</Conditional>
|
||||
</div>
|
||||
</Conditional>
|
||||
<Conditional test={uploadMethod === 'new'}>
|
||||
@@ -390,8 +426,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
)}
|
||||
id="assetFiles"
|
||||
multiple
|
||||
multiple={minterType === 'vending'}
|
||||
onChange={selectAssets}
|
||||
ref={assetFilesRef}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
@@ -418,8 +455,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
'before:absolute before:inset-0 before:hover:bg-white/5 before:transition',
|
||||
)}
|
||||
id="metadataFiles"
|
||||
multiple
|
||||
multiple={minterType === 'vending'}
|
||||
onChange={selectMetadata}
|
||||
ref={metadataFilesRef}
|
||||
type="file"
|
||||
/>
|
||||
</div>
|
||||
@@ -435,7 +473,11 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
</div>
|
||||
|
||||
<Conditional test={assetFilesArray.length > 0}>
|
||||
<AssetsPreview assetFilesArray={assetFilesArray} updateMetadataFileIndex={updateMetadataFileIndex} />
|
||||
<AssetsPreview
|
||||
assetFilesArray={assetFilesArray}
|
||||
minterType={minterType}
|
||||
updateMetadataFileIndex={updateMetadataFileIndex}
|
||||
/>
|
||||
</Conditional>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user