Use existing base token URI for collection creation (#36)

* Use existing base token URI for collection creation

* Cover image preview when using existing base token URI option

* Fix cover image preview border for non-square images

* check baseTokenURI and coverImageUrl validity

* Fix typo

* Cover image validity condition update

* Remove unnecessary conditionals

* Display correct collection creation results when using an existing base token URI

* Fix: base token URI not being displayed correctly for new uploads

* Post-review update

* Remove extra props

Co-authored-by: findolor <anakisci@gmail.com>
This commit is contained in:
Serkan Reis
2022-08-16 10:04:33 +03:00
committed by GitHub
co-authored by findolor
parent c7098cc40c
commit 1624f0c332
4 changed files with 174 additions and 102 deletions
@@ -1,4 +1,5 @@
/* 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 */
@@ -11,9 +12,12 @@ import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
import { TextInput } from '../../forms/FormInput'
import type { UploadMethod } from './UploadDetails'
interface CollectionDetailsProps {
onChange: (data: CollectionDetailsDataProps) => void
uploadMethod: UploadMethod
coverImageUrl: string
}
export interface CollectionDetailsDataProps {
@@ -23,7 +27,7 @@ export interface CollectionDetailsDataProps {
externalLink?: string
}
export const CollectionDetails = ({ onChange }: CollectionDetailsProps) => {
export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: CollectionDetailsProps) => {
const [coverImage, setCoverImage] = useState<File | null>(null)
const nameState = useInputState({
@@ -84,23 +88,44 @@ export const CollectionDetails = ({ onChange }: CollectionDetailsProps) => {
<FormGroup subtitle="Information about your collection" title="Collection Details">
<TextInput {...nameState} isRequired />
<TextInput {...descriptionState} isRequired />
<FormControl isRequired title="Cover Image">
<input
accept="image/*"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:hover:bg-white/5 before:transition',
)}
id="cover-image"
onChange={selectCoverImage}
type="file"
/>
{coverImage !== null && (
<div className="w-[200px] h-[200px] rounded border-2">
<img alt="cover-preview" src={URL.createObjectURL(coverImage)} />
<FormControl isRequired={uploadMethod === 'new'} title="Cover Image">
{uploadMethod === 'new' && (
<input
accept="image/*"
className={clsx(
'file:py-2 file:px-4 file:mr-4 file:bg-plumbus-light file:rounded file:border-0 cursor-pointer',
'before:hover:bg-white/5 before:transition',
)}
id="cover-image"
onChange={selectCoverImage}
type="file"
/>
)}
{coverImage !== null && uploadMethod === 'new' && (
<div className="max-w-[200px] max-h-[200px] rounded border-2">
<img alt="no-preview-available" src={URL.createObjectURL(coverImage)} />
</div>
)}
{uploadMethod === 'existing' && coverImageUrl?.includes('ipfs://') && (
<div className="max-w-[200px] max-h-[200px] rounded border-2">
<img
alt="no-preview-available"
src={`https://ipfs.io/ipfs/${coverImageUrl.substring(coverImageUrl.lastIndexOf('ipfs://') + 7)}`}
/>
</div>
)}
{uploadMethod === 'existing' && coverImageUrl && !coverImageUrl?.includes('ipfs://') && (
<div className="max-w-[200px] max-h-[200px] rounded border-2">
<img alt="no-preview-available" src={coverImageUrl} />
</div>
)}
{uploadMethod === 'existing' && !coverImageUrl && (
<span className="italic font-light ">Waiting for cover image URL to be specified.</span>
)}
</FormControl>
<TextInput {...externalLinkState} />
</FormGroup>
</div>
@@ -5,10 +5,12 @@ import { InputDateTime } from 'components/InputDateTime'
import React, { useEffect, useState } from 'react'
import { NumberInput } from '../../forms/FormInput'
import type { UploadMethod } from './UploadDetails'
interface MintingDetailsProps {
onChange: (data: MintingDetailsDataProps) => void
numberOfTokens: number | undefined
uploadMethod: UploadMethod
}
export interface MintingDetailsDataProps {
@@ -18,7 +20,7 @@ export interface MintingDetailsDataProps {
startTime: string
}
export const MintingDetails = ({ onChange, numberOfTokens }: MintingDetailsProps) => {
export const MintingDetails = ({ onChange, numberOfTokens, uploadMethod }: MintingDetailsProps) => {
const [timestamp, setTimestamp] = useState<Date | undefined>()
const numberOfTokensState = useNumberInputState({
@@ -60,7 +62,12 @@ export const MintingDetails = ({ onChange, numberOfTokens }: MintingDetailsProps
return (
<div>
<FormGroup subtitle="Information about your minting settings" title="Minting Details">
<NumberInput {...numberOfTokensState} disabled isRequired value={numberOfTokens} />
<NumberInput
{...numberOfTokensState}
disabled={uploadMethod === 'new'}
isRequired
value={uploadMethod === 'new' ? numberOfTokens : numberOfTokensState.value}
/>
<NumberInput {...unitPriceState} isRequired />
<NumberInput {...perAddressLimitState} isRequired />
<FormControl htmlId="timestamp" isRequired subtitle="Start time for the minting" title="Start Time">
@@ -6,14 +6,13 @@ 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, useState } from 'react'
import { toast } from 'react-hot-toast'
import type { UploadServiceType } from 'services/upload'
import { naturalCompare } from 'utils/sort'
type UploadMethod = 'new' | 'existing'
export type UploadMethod = 'new' | 'existing'
interface UploadDetailsProps {
onChange: (value: UploadDetailsDataProps) => void
@@ -26,10 +25,12 @@ export interface UploadDetailsDataProps {
nftStorageApiKey?: string
pinataApiKey?: string
pinataSecretKey?: string
uploadMethod: UploadMethod
baseTokenURI?: string
imageUrl?: string
}
export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
const baseTokenURI = useCollectionStore().base_token_uri
const [assetFilesArray, setAssetFilesArray] = useState<File[]>([])
const [metadataFilesArray, setMetadataFilesArray] = useState<File[]>([])
const [uploadMethod, setUploadMethod] = useState<UploadMethod>('new')
@@ -60,13 +61,21 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
defaultValue: '9d6f42dc01eaab15f52eac8f36cc4f0ee4184944cb3cdbcda229d06ecf877ee7',
})
const handleChangeBaseTokenUri = (event: { target: { value: React.SetStateAction<string> } }) => {
setBaseTokenUri(event.target.value.toString())
}
const baseTokenUriState = useInputState({
id: 'baseTokenUri',
name: 'baseTokenUri',
title: 'Base Token URI',
placeholder: 'ipfs://',
defaultValue: '',
})
const handleChangeImage = (event: { target: { value: React.SetStateAction<string> } }) => {
setImage(event.target.value.toString())
}
const coverImageUrlState = useInputState({
id: 'coverImageUrl',
name: 'coverImageUrl',
title: 'Cover Image URL',
placeholder: 'ipfs://',
defaultValue: '',
})
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
const files: File[] = []
@@ -135,6 +144,9 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
nftStorageApiKey: nftStorageApiKeyState.value,
pinataApiKey: pinataApiKeyState.value,
pinataSecretKey: pinataSecretKeyState.value,
uploadMethod,
baseTokenURI: baseTokenUriState.value,
imageUrl: coverImageUrlState.value,
}
onChange(data)
} catch (error: any) {
@@ -147,11 +159,16 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
nftStorageApiKeyState.value,
pinataApiKeyState.value,
pinataSecretKeyState.value,
uploadMethod,
baseTokenUriState.value,
coverImageUrlState.value,
])
useEffect(() => {
setAssetFilesArray([])
setMetadataFilesArray([])
baseTokenUriState.onChange('')
coverImageUrlState.onChange('')
}, [uploadMethod])
return (
@@ -192,14 +209,6 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
</div>
</div>
{baseTokenURI && (
<Alert className="mt-5" type="info">
<a href={baseTokenURI} rel="noreferrer" target="_blank">
Base Token URI: {baseTokenURI}
</a>
</Alert>
)}
<div className="p-3 py-5 pb-8">
<Conditional test={uploadMethod === 'existing'}>
<div className="ml-3 flex-column">
@@ -216,33 +225,10 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
and upload your assets & metadata manually to get a base URI for your collection.
</p>
<div>
<label className="block mr-1 mb-1 ml-5 font-bold text-white dark:text-gray-300" htmlFor="coverImage">
Collection Cover Image
</label>
<input
className="py-2 px-1 mx-5 mt-2 mb-2 w-1/2 bg-white/10 rounded border-2 border-white/20 focus:ring
focus:ring-plumbus-20
form-input, placeholder:text-white/50,"
id="coverImage"
onChange={handleChangeImage}
placeholder="ipfs://bafybeigi3bwpvyvsmnbj46ra4hyffcxdeaj6ntfk5jpic5mx27x6ih2qvq/images/1.png"
/>
<TextInput {...baseTokenUriState} className="w-1/2" />
</div>
<div>
<label
className="block mt-3 mr-1 mb-1 ml-5 font-bold text-white dark:text-gray-300"
htmlFor="baseTokenURI"
>
Base Token URI
</label>
<input
className="py-2 px-1 mx-5 mt-2 mb-2 w-1/2 bg-white/10 rounded border-2 border-white/20 focus:ring
focus:ring-plumbus-20
form-input, placeholder:text-white/50,"
id="baseTokenURI"
onChange={handleChangeBaseTokenUri}
placeholder="ipfs://..."
/>
<TextInput {...coverImageUrlState} className="mt-2 w-1/2" />
</div>
</div>
</Conditional>