Merge development to main (#43)
* UI changes & improvements (#41) * Remove show advanced options button, add symbol input * Add checks for minting time constraints * Royalty share input placeholder update * Update input subtitles & error messages * Token price subtitles now include minimum token price * Ensure the files to be uploaded are in numerical order starting from 1 * Add collection creation confirmation modal * Make some changes Co-authored-by: findolor <anakisci@gmail.com> * Airdrop feature added to collection actions (#42) Co-authored-by: name-user1 <eray@deuslabs.fi> Co-authored-by: Serkan Reis <serkanreis@gmail.com> Co-authored-by: name-user1 <101495985+name-user1@users.noreply.github.com> Co-authored-by: name-user1 <eray@deuslabs.fi>
This commit is contained in:
parent
00960f429e
commit
9dc19a99ab
41
components/ConfirmationModal.tsx
Normal file
41
components/ConfirmationModal.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Button } from './Button'
|
||||||
|
|
||||||
|
export interface ConfirmationModalProps {
|
||||||
|
confirm: () => void
|
||||||
|
}
|
||||||
|
export const ConfirmationModal = (props: ConfirmationModalProps) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input className="modal-toggle" id="my-modal-2" type="checkbox" />
|
||||||
|
<label className="cursor-pointer modal" htmlFor="my-modal-2">
|
||||||
|
<label
|
||||||
|
className="absolute top-[40%] bottom-5 left-1/3 max-w-xl max-h-40 border-2 no-scrollbar modal-box"
|
||||||
|
htmlFor="temp"
|
||||||
|
>
|
||||||
|
{/* <Alert type="warning"></Alert> */}
|
||||||
|
<div className="text-xl font-bold">
|
||||||
|
Are you sure to create a collection 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">
|
||||||
|
<label
|
||||||
|
className="w-full h-full text-white bg-gray-600 hover:bg-gray-600 border-0 btn modal-button"
|
||||||
|
htmlFor="my-modal-2"
|
||||||
|
>
|
||||||
|
Go Back
|
||||||
|
</label>
|
||||||
|
</Button>
|
||||||
|
<Button className="px-0 mt-4 mb-4 max-h-12" onClick={props.confirm}>
|
||||||
|
<label
|
||||||
|
className="w-full h-full text-white bg-plumbus-light hover:bg-plumbus-light border-0 btn modal-button"
|
||||||
|
htmlFor="my-modal-2"
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</label>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -18,6 +18,7 @@ export const ACTION_TYPES = [
|
|||||||
'burn',
|
'burn',
|
||||||
'batch_burn',
|
'batch_burn',
|
||||||
'shuffle',
|
'shuffle',
|
||||||
|
'airdrop',
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export interface ActionListItem {
|
export interface ActionListItem {
|
||||||
@ -87,6 +88,11 @@ export const ACTION_LIST: ActionListItem[] = [
|
|||||||
name: 'Shuffle Tokens',
|
name: 'Shuffle Tokens',
|
||||||
description: 'Shuffle the token IDs',
|
description: 'Shuffle the token IDs',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'airdrop',
|
||||||
|
name: 'Airdrop Tokens',
|
||||||
|
description: 'Airdrop tokens to given addresses',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export interface DispatchExecuteProps {
|
export interface DispatchExecuteProps {
|
||||||
@ -117,6 +123,7 @@ export type DispatchExecuteArgs = {
|
|||||||
| { type: Select<'batch_transfer'>; recipient: string; tokenIds: string }
|
| { type: Select<'batch_transfer'>; recipient: string; tokenIds: string }
|
||||||
| { type: Select<'burn'>; tokenId: number }
|
| { type: Select<'burn'>; tokenId: number }
|
||||||
| { type: Select<'batch_burn'>; tokenIds: string }
|
| { type: Select<'batch_burn'>; tokenIds: string }
|
||||||
|
| { type: Select<'airdrop'>; recipients: string[] }
|
||||||
)
|
)
|
||||||
|
|
||||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||||
@ -161,6 +168,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
|||||||
case 'batch_burn': {
|
case 'batch_burn': {
|
||||||
return sg721Messages.batchBurn(args.tokenIds)
|
return sg721Messages.batchBurn(args.tokenIds)
|
||||||
}
|
}
|
||||||
|
case 'airdrop': {
|
||||||
|
return minterMessages.airdrop(txSigner, args.recipients)
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error('Unknown action')
|
throw new Error('Unknown action')
|
||||||
}
|
}
|
||||||
@ -210,6 +220,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
|||||||
case 'batch_burn': {
|
case 'batch_burn': {
|
||||||
return sg721Messages(sg721Contract)?.batchBurn(args.tokenIds)
|
return sg721Messages(sg721Contract)?.batchBurn(args.tokenIds)
|
||||||
}
|
}
|
||||||
|
case 'airdrop': {
|
||||||
|
return minterMessages(minterContract)?.airdrop(args.recipients)
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ interface CollectionDetailsProps {
|
|||||||
export interface CollectionDetailsDataProps {
|
export interface CollectionDetailsDataProps {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
|
symbol: string
|
||||||
imageFile: File[]
|
imageFile: File[]
|
||||||
externalLink?: string
|
externalLink?: string
|
||||||
}
|
}
|
||||||
@ -44,10 +45,17 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
|||||||
placeholder: 'My Awesome Collection Description',
|
placeholder: 'My Awesome Collection Description',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const symbolState = useInputState({
|
||||||
|
id: 'symbol',
|
||||||
|
name: 'symbol',
|
||||||
|
title: 'Symbol',
|
||||||
|
placeholder: 'SYMBOL',
|
||||||
|
})
|
||||||
|
|
||||||
const externalLinkState = useInputState({
|
const externalLinkState = useInputState({
|
||||||
id: 'external-link',
|
id: 'external-link',
|
||||||
name: 'externalLink',
|
name: 'externalLink',
|
||||||
title: 'External Link',
|
title: 'External Link (optional)',
|
||||||
placeholder: 'https://my-collection...',
|
placeholder: 'https://my-collection...',
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -56,6 +64,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
|||||||
const data: CollectionDetailsDataProps = {
|
const data: CollectionDetailsDataProps = {
|
||||||
name: nameState.value,
|
name: nameState.value,
|
||||||
description: descriptionState.value,
|
description: descriptionState.value,
|
||||||
|
symbol: symbolState.value,
|
||||||
imageFile: coverImage ? [coverImage] : [],
|
imageFile: coverImage ? [coverImage] : [],
|
||||||
externalLink: externalLinkState.value,
|
externalLink: externalLinkState.value,
|
||||||
}
|
}
|
||||||
@ -88,6 +97,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
|||||||
<FormGroup subtitle="Information about your collection" title="Collection Details">
|
<FormGroup subtitle="Information about your collection" title="Collection Details">
|
||||||
<TextInput {...nameState} isRequired />
|
<TextInput {...nameState} isRequired />
|
||||||
<TextInput {...descriptionState} isRequired />
|
<TextInput {...descriptionState} isRequired />
|
||||||
|
<TextInput {...symbolState} isRequired />
|
||||||
|
|
||||||
<FormControl isRequired={uploadMethod === 'new'} title="Cover Image">
|
<FormControl isRequired={uploadMethod === 'new'} title="Cover Image">
|
||||||
{uploadMethod === 'new' && (
|
{uploadMethod === 'new' && (
|
||||||
|
@ -35,8 +35,8 @@ export const MintingDetails = ({ onChange, numberOfTokens, uploadMethod }: Minti
|
|||||||
id: 'unitPrice',
|
id: 'unitPrice',
|
||||||
name: 'unitPrice',
|
name: 'unitPrice',
|
||||||
title: 'Unit Price',
|
title: 'Unit Price',
|
||||||
subtitle: '',
|
subtitle: 'Price of each token (min. 50 STARS)',
|
||||||
placeholder: '100',
|
placeholder: '50',
|
||||||
})
|
})
|
||||||
|
|
||||||
const perAddressLimitState = useNumberInputState({
|
const perAddressLimitState = useNumberInputState({
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Conditional } from 'components/Conditional'
|
import { Conditional } from 'components/Conditional'
|
||||||
import { FormGroup } from 'components/FormGroup'
|
import { FormGroup } from 'components/FormGroup'
|
||||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { NumberInput, TextInput } from '../../forms/FormInput'
|
import { NumberInput, TextInput } from '../../forms/FormInput'
|
||||||
@ -28,19 +28,19 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
|||||||
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||||
})
|
})
|
||||||
|
|
||||||
const royaltyShareState = useNumberInputState({
|
const royaltyShareState = useInputState({
|
||||||
id: 'royalty-share',
|
id: 'royalty-share',
|
||||||
name: 'royaltyShare',
|
name: 'royaltyShare',
|
||||||
title: 'Share Percentage',
|
title: 'Share Percentage',
|
||||||
subtitle: 'Percentage of royalties to be paid',
|
subtitle: 'Percentage of royalties to be paid',
|
||||||
placeholder: '8',
|
placeholder: '8%',
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data: RoyaltyDetailsDataProps = {
|
const data: RoyaltyDetailsDataProps = {
|
||||||
royaltyType: royaltyState,
|
royaltyType: royaltyState,
|
||||||
paymentAddress: royaltyPaymentAddressState.value,
|
paymentAddress: royaltyPaymentAddressState.value,
|
||||||
share: royaltyShareState.value,
|
share: Number(royaltyShareState.value),
|
||||||
}
|
}
|
||||||
onChange(data)
|
onChange(data)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -84,8 +84,8 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<Conditional test={royaltyState === 'new'}>
|
<Conditional test={royaltyState === 'new'}>
|
||||||
<FormGroup subtitle="Information about royalty" title="Royalty Details">
|
<FormGroup subtitle="Information about royalty" title="Royalty Details">
|
||||||
<TextInput {...royaltyPaymentAddressState} />
|
<TextInput {...royaltyPaymentAddressState} isRequired />
|
||||||
<NumberInput {...royaltyShareState} />
|
<NumberInput {...royaltyShareState} isRequired />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Conditional>
|
</Conditional>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,11 +78,23 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
|
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
const files: File[] = []
|
|
||||||
let reader: FileReader
|
|
||||||
if (event.target.files === null) return
|
|
||||||
setAssetFilesArray([])
|
setAssetFilesArray([])
|
||||||
setMetadataFilesArray([])
|
setMetadataFilesArray([])
|
||||||
|
if (event.target.files === null) return
|
||||||
|
//sort the files
|
||||||
|
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
|
||||||
|
//check if the sorted file names are in numerical order
|
||||||
|
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
|
||||||
|
for (let i = 0; i < sortedFileNames.length; i++) {
|
||||||
|
if (parseInt(sortedFileNames[i]) !== i + 1) {
|
||||||
|
toast.error('The file names should be in numerical order starting from 1.')
|
||||||
|
//clear the input
|
||||||
|
event.target.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const files: File[] = []
|
||||||
|
let reader: FileReader
|
||||||
for (let i = 0; i < event.target.files.length; i++) {
|
for (let i = 0; i < event.target.files.length; i++) {
|
||||||
reader = new FileReader()
|
reader = new FileReader()
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
@ -102,10 +114,26 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectMetadata = (event: ChangeEvent<HTMLInputElement>) => {
|
const selectMetadata = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setMetadataFilesArray([])
|
||||||
|
if (event.target.files === null) return toast.error('No files selected.')
|
||||||
|
if (event.target.files.length !== assetFilesArray.length) {
|
||||||
|
event.target.value = ''
|
||||||
|
return toast.error('The number of metadata files should be equal to the number of asset files.')
|
||||||
|
}
|
||||||
|
//sort the files
|
||||||
|
const sortedFiles = Array.from(event.target.files).sort((a, b) => naturalCompare(a.name, b.name))
|
||||||
|
//check if the sorted file names are in numerical order
|
||||||
|
const sortedFileNames = sortedFiles.map((file) => file.name.split('.')[0])
|
||||||
|
for (let i = 0; i < sortedFileNames.length; i++) {
|
||||||
|
if (parseInt(sortedFileNames[i]) !== i + 1) {
|
||||||
|
toast.error('The file names should be in numerical order starting from 1.')
|
||||||
|
//clear the input
|
||||||
|
event.target.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
const files: File[] = []
|
const files: File[] = []
|
||||||
let reader: FileReader
|
let reader: FileReader
|
||||||
if (event.target.files === null) return toast.error('No files selected.')
|
|
||||||
setMetadataFilesArray([])
|
|
||||||
for (let i = 0; i < event.target.files.length; i++) {
|
for (let i = 0; i < event.target.files.length; i++) {
|
||||||
reader = new FileReader()
|
reader = new FileReader()
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
|
@ -43,15 +43,15 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
id: 'unit-price',
|
id: 'unit-price',
|
||||||
name: 'unitPrice',
|
name: 'unitPrice',
|
||||||
title: 'Unit Price',
|
title: 'Unit Price',
|
||||||
subtitle: 'Price of each tokens in collection',
|
subtitle: 'Token price for whitelisted addresses \n (min. 25 STARS)',
|
||||||
placeholder: '500',
|
placeholder: '25',
|
||||||
})
|
})
|
||||||
|
|
||||||
const memberLimitState = useNumberInputState({
|
const memberLimitState = useNumberInputState({
|
||||||
id: 'member-limit',
|
id: 'member-limit',
|
||||||
name: 'memberLimit',
|
name: 'memberLimit',
|
||||||
title: 'Member Limit',
|
title: 'Member Limit',
|
||||||
subtitle: 'Limit of the whitelisted members',
|
subtitle: 'Maximum number of whitelisted addresses',
|
||||||
placeholder: '1000',
|
placeholder: '1000',
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
id: 'per-address-limit',
|
id: 'per-address-limit',
|
||||||
name: 'perAddressLimit',
|
name: 'perAddressLimit',
|
||||||
title: 'Per Address Limit',
|
title: 'Per Address Limit',
|
||||||
subtitle: 'Limit of tokens per address',
|
subtitle: 'Maximum number of tokens per whitelisted address',
|
||||||
placeholder: '5',
|
placeholder: '5',
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -145,11 +145,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Conditional test={whitelistState === 'existing'}>
|
<Conditional test={whitelistState === 'existing'}>
|
||||||
<AddressInput
|
<AddressInput {...whitelistAddressState} className="pb-5" isRequired />
|
||||||
{...whitelistAddressState}
|
|
||||||
className="pb-5"
|
|
||||||
onChange={(e) => whitelistAddressState.onChange(e.target.value)}
|
|
||||||
/>
|
|
||||||
</Conditional>
|
</Conditional>
|
||||||
|
|
||||||
<Conditional test={whitelistState === 'new'}>
|
<Conditional test={whitelistState === 'new'}>
|
||||||
@ -158,10 +154,20 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
|||||||
<NumberInput isRequired {...uniPriceState} />
|
<NumberInput isRequired {...uniPriceState} />
|
||||||
<NumberInput isRequired {...memberLimitState} />
|
<NumberInput isRequired {...memberLimitState} />
|
||||||
<NumberInput isRequired {...perAddressLimitState} />
|
<NumberInput isRequired {...perAddressLimitState} />
|
||||||
<FormControl htmlId="start-date" isRequired subtitle="Start time for the minting" title="Start Time">
|
<FormControl
|
||||||
|
htmlId="start-date"
|
||||||
|
isRequired
|
||||||
|
subtitle="Start time for minting tokens to whitelisted addresses"
|
||||||
|
title="Start Time"
|
||||||
|
>
|
||||||
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
<InputDateTime minDate={new Date()} onChange={(date) => setStartDate(date)} value={startDate} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl htmlId="end-date" isRequired subtitle="End time for the minting" title="End Time">
|
<FormControl
|
||||||
|
htmlId="end-date"
|
||||||
|
isRequired
|
||||||
|
subtitle="End time for minting tokens to whitelisted addresses"
|
||||||
|
title="End Time"
|
||||||
|
>
|
||||||
<InputDateTime minDate={new Date()} onChange={(date) => setEndDate(date)} value={endDate} />
|
<InputDateTime minDate={new Date()} onChange={(date) => setEndDate(date)} value={endDate} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
@ -37,6 +37,7 @@ export interface MinterInstance {
|
|||||||
batchMint: (senderAddress: string, recipient: string, batchNumber: number) => Promise<string>
|
batchMint: (senderAddress: string, recipient: string, batchNumber: number) => Promise<string>
|
||||||
shuffle: (senderAddress: string) => Promise<string>
|
shuffle: (senderAddress: string) => Promise<string>
|
||||||
withdraw: (senderAddress: string) => Promise<string>
|
withdraw: (senderAddress: string) => Promise<string>
|
||||||
|
airdrop: (senderAddress: string, recipients: string[]) => Promise<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MinterMessages {
|
export interface MinterMessages {
|
||||||
@ -46,9 +47,10 @@ export interface MinterMessages {
|
|||||||
updatePerAddressLimit: (perAddressLimit: number) => UpdatePerAddressLimitMessage
|
updatePerAddressLimit: (perAddressLimit: number) => UpdatePerAddressLimitMessage
|
||||||
mintTo: (recipient: string) => MintToMessage
|
mintTo: (recipient: string) => MintToMessage
|
||||||
mintFor: (recipient: string, tokenId: number) => MintForMessage
|
mintFor: (recipient: string, tokenId: number) => MintForMessage
|
||||||
batchMint: (recipient: string, batchNumber: number) => BatchMintMessage
|
batchMint: (recipient: string, batchNumber: number) => CustomMessage
|
||||||
shuffle: () => ShuffleMessage
|
shuffle: () => ShuffleMessage
|
||||||
withdraw: () => WithdrawMessage
|
withdraw: () => WithdrawMessage
|
||||||
|
airdrop: (recipients: string[]) => CustomMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MintMessage {
|
export interface MintMessage {
|
||||||
@ -114,7 +116,7 @@ export interface MintForMessage {
|
|||||||
funds: Coin[]
|
funds: Coin[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BatchMintMessage {
|
export interface CustomMessage {
|
||||||
sender: string
|
sender: string
|
||||||
contract: string
|
contract: string
|
||||||
msg: Record<string, unknown>[]
|
msg: Record<string, unknown>[]
|
||||||
@ -301,6 +303,29 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
|
|||||||
return res.transactionHash
|
return res.transactionHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const airdrop = async (senderAddress: string, recipients: string[]): Promise<string> => {
|
||||||
|
const executeContractMsgs: MsgExecuteContractEncodeObject[] = []
|
||||||
|
for (let i = 0; i < recipients.length; i++) {
|
||||||
|
const msg = {
|
||||||
|
mint_to: { recipient: recipients[i] },
|
||||||
|
}
|
||||||
|
const executeContractMsg: MsgExecuteContractEncodeObject = {
|
||||||
|
typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract',
|
||||||
|
value: MsgExecuteContract.fromPartial({
|
||||||
|
sender: senderAddress,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg: toUtf8(JSON.stringify(msg)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
executeContractMsgs.push(executeContractMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await client.signAndBroadcast(senderAddress, executeContractMsgs, 'auto', 'airdrop')
|
||||||
|
|
||||||
|
return res.transactionHash
|
||||||
|
}
|
||||||
|
|
||||||
const shuffle = async (senderAddress: string): Promise<string> => {
|
const shuffle = async (senderAddress: string): Promise<string> => {
|
||||||
const res = await client.execute(
|
const res = await client.execute(
|
||||||
senderAddress,
|
senderAddress,
|
||||||
@ -343,6 +368,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
|
|||||||
mintTo,
|
mintTo,
|
||||||
mintFor,
|
mintFor,
|
||||||
batchMint,
|
batchMint,
|
||||||
|
airdrop,
|
||||||
shuffle,
|
shuffle,
|
||||||
withdraw,
|
withdraw,
|
||||||
}
|
}
|
||||||
@ -441,7 +467,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const batchMint = (recipient: string, batchNumber: number): BatchMintMessage => {
|
const batchMint = (recipient: string, batchNumber: number): CustomMessage => {
|
||||||
const msg: Record<string, unknown>[] = []
|
const msg: Record<string, unknown>[] = []
|
||||||
for (let i = 0; i < batchNumber; i++) {
|
for (let i = 0; i < batchNumber; i++) {
|
||||||
msg.push({ mint_to: { recipient } })
|
msg.push({ mint_to: { recipient } })
|
||||||
@ -454,6 +480,19 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const airdrop = (recipients: string[]): CustomMessage => {
|
||||||
|
const msg: Record<string, unknown>[] = []
|
||||||
|
for (let i = 0; i < recipients.length; i++) {
|
||||||
|
msg.push({ mint_to: { recipient: recipients[i] } })
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
sender: txSigner,
|
||||||
|
contract: contractAddress,
|
||||||
|
msg,
|
||||||
|
funds: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const shuffle = (): ShuffleMessage => {
|
const shuffle = (): ShuffleMessage => {
|
||||||
return {
|
return {
|
||||||
sender: txSigner,
|
sender: txSigner,
|
||||||
@ -484,6 +523,7 @@ export const minter = (client: SigningCosmWasmClient, txSigner: string): MinterC
|
|||||||
mintTo,
|
mintTo,
|
||||||
mintFor,
|
mintFor,
|
||||||
batchMint,
|
batchMint,
|
||||||
|
airdrop,
|
||||||
shuffle,
|
shuffle,
|
||||||
withdraw,
|
withdraw,
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"@fontsource/roboto": "^4",
|
"@fontsource/roboto": "^4",
|
||||||
"@headlessui/react": "^1",
|
"@headlessui/react": "^1",
|
||||||
"@keplr-wallet/cosmos": "^0.9.16",
|
"@keplr-wallet/cosmos": "^0.9.16",
|
||||||
|
"@pinata/sdk": "^1.1.26",
|
||||||
"@popperjs/core": "^2",
|
"@popperjs/core": "^2",
|
||||||
"@svgr/webpack": "^6",
|
"@svgr/webpack": "^6",
|
||||||
"@tailwindcss/forms": "^0",
|
"@tailwindcss/forms": "^0",
|
||||||
@ -34,7 +35,6 @@
|
|||||||
"next": "^12",
|
"next": "^12",
|
||||||
"next-seo": "^4",
|
"next-seo": "^4",
|
||||||
"nft.storage": "^6.3.0",
|
"nft.storage": "^6.3.0",
|
||||||
"@pinata/sdk": "^1.1.26",
|
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-datetime-picker": "^3",
|
"react-datetime-picker": "^3",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
@ -44,7 +44,6 @@
|
|||||||
"react-popper": "^2",
|
"react-popper": "^2",
|
||||||
"react-query": "^3",
|
"react-query": "^3",
|
||||||
"react-tracked": "^1",
|
"react-tracked": "^1",
|
||||||
"react-collapsed": "^3",
|
|
||||||
"scheduler": "^0",
|
"scheduler": "^0",
|
||||||
"zustand": "^3"
|
"zustand": "^3"
|
||||||
},
|
},
|
||||||
|
@ -6,11 +6,13 @@ import { useActionsComboboxState } from 'components/collections/actions/Combobox
|
|||||||
import { Conditional } from 'components/Conditional'
|
import { Conditional } from 'components/Conditional'
|
||||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||||
import { FormControl } from 'components/FormControl'
|
import { FormControl } from 'components/FormControl'
|
||||||
|
import { FormGroup } from 'components/FormGroup'
|
||||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||||
import { InputDateTime } from 'components/InputDateTime'
|
import { InputDateTime } from 'components/InputDateTime'
|
||||||
import { JsonPreview } from 'components/JsonPreview'
|
import { JsonPreview } from 'components/JsonPreview'
|
||||||
import { TransactionHash } from 'components/TransactionHash'
|
import { TransactionHash } from 'components/TransactionHash'
|
||||||
|
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||||
import { useContracts } from 'contexts/contracts'
|
import { useContracts } from 'contexts/contracts'
|
||||||
import { useWallet } from 'contexts/wallet'
|
import { useWallet } from 'contexts/wallet'
|
||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
@ -31,6 +33,7 @@ const CollectionActionsPage: NextPage = () => {
|
|||||||
const [lastTx, setLastTx] = useState('')
|
const [lastTx, setLastTx] = useState('')
|
||||||
|
|
||||||
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||||
|
const [airdropArray, setAirdropArray] = useState<string[]>([])
|
||||||
|
|
||||||
const comboboxState = useActionsComboboxState()
|
const comboboxState = useActionsComboboxState()
|
||||||
const type = comboboxState.value?.id
|
const type = comboboxState.value?.id
|
||||||
@ -99,6 +102,7 @@ const CollectionActionsPage: NextPage = () => {
|
|||||||
const showNumberOfTokensField = type === 'batch_mint'
|
const showNumberOfTokensField = type === 'batch_mint'
|
||||||
const showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer'])
|
const showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer'])
|
||||||
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer'])
|
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer'])
|
||||||
|
const showAirdropFileField = type === 'airdrop'
|
||||||
|
|
||||||
const minterMessages = useMemo(
|
const minterMessages = useMemo(
|
||||||
() => minterContract?.use(minterContractState.value),
|
() => minterContract?.use(minterContractState.value),
|
||||||
@ -120,6 +124,7 @@ const CollectionActionsPage: NextPage = () => {
|
|||||||
minterMessages,
|
minterMessages,
|
||||||
sg721Messages,
|
sg721Messages,
|
||||||
recipient: recipientState.value,
|
recipient: recipientState.value,
|
||||||
|
recipients: airdropArray,
|
||||||
txSigner: wallet.address,
|
txSigner: wallet.address,
|
||||||
type,
|
type,
|
||||||
}
|
}
|
||||||
@ -148,6 +153,10 @@ const CollectionActionsPage: NextPage = () => {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const airdropFileOnChange = (data: string[]) => {
|
||||||
|
setAirdropArray(data)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-6 px-12 space-y-4">
|
<section className="py-6 px-12 space-y-4">
|
||||||
<NextSeo title="Collection Actions" />
|
<NextSeo title="Collection Actions" />
|
||||||
@ -168,6 +177,11 @@ const CollectionActionsPage: NextPage = () => {
|
|||||||
{showTokenIdField && <NumberInput {...tokenIdState} />}
|
{showTokenIdField && <NumberInput {...tokenIdState} />}
|
||||||
{showTokenIdListField && <TextInput {...tokenIdListState} />}
|
{showTokenIdListField && <TextInput {...tokenIdListState} />}
|
||||||
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
|
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
|
||||||
|
{showAirdropFileField && (
|
||||||
|
<FormGroup subtitle="TXT file that contains the airdrop addresses" title="Airdrop File">
|
||||||
|
<WhitelistUpload onChange={airdropFileOnChange} />
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
<Conditional test={showDateField}>
|
<Conditional test={showDateField}>
|
||||||
<FormControl htmlId="start-date" subtitle="Start time for the minting" title="Start Time">
|
<FormControl htmlId="start-date" subtitle="Start time for the minting" title="Start Time">
|
||||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||||
|
@ -26,7 +26,6 @@ import { useWallet } from 'contexts/wallet'
|
|||||||
import type { NextPage } from 'next'
|
import type { NextPage } from 'next'
|
||||||
import { NextSeo } from 'next-seo'
|
import { NextSeo } from 'next-seo'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import useCollapse from 'react-collapsed'
|
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { upload } from 'services/upload'
|
import { upload } from 'services/upload'
|
||||||
import { compareFileArrays } from 'utils/compareFileArrays'
|
import { compareFileArrays } from 'utils/compareFileArrays'
|
||||||
@ -35,15 +34,12 @@ import { withMetadata } from 'utils/layout'
|
|||||||
import { links } from 'utils/links'
|
import { links } from 'utils/links'
|
||||||
|
|
||||||
import type { UploadMethod } from '../../components/collections/creation/UploadDetails'
|
import type { UploadMethod } from '../../components/collections/creation/UploadDetails'
|
||||||
|
import { ConfirmationModal } from '../../components/ConfirmationModal'
|
||||||
import { getAssetType } from '../../utils/getAssetType'
|
import { getAssetType } from '../../utils/getAssetType'
|
||||||
|
|
||||||
const CollectionCreationPage: NextPage = () => {
|
const CollectionCreationPage: NextPage = () => {
|
||||||
const wallet = useWallet()
|
const wallet = useWallet()
|
||||||
const { minter: minterContract, whitelist: whitelistContract } = useContracts()
|
const { minter: minterContract, whitelist: whitelistContract } = useContracts()
|
||||||
|
|
||||||
const { getCollapseProps, getToggleProps, isExpanded } = useCollapse()
|
|
||||||
const toggleProps = getToggleProps()
|
|
||||||
const collapseProps = getCollapseProps()
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null)
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const [uploadDetails, setUploadDetails] = useState<UploadDetailsDataProps | null>(null)
|
const [uploadDetails, setUploadDetails] = useState<UploadDetailsDataProps | null>(null)
|
||||||
@ -53,12 +49,29 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
|
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
|
||||||
|
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
|
const [readyToCreate, setReadyToCreate] = useState(false)
|
||||||
const [minterContractAddress, setMinterContractAddress] = useState<string | null>(null)
|
const [minterContractAddress, setMinterContractAddress] = useState<string | null>(null)
|
||||||
const [sg721ContractAddress, setSg721ContractAddress] = useState<string | null>(null)
|
const [sg721ContractAddress, setSg721ContractAddress] = useState<string | null>(null)
|
||||||
const [baseTokenUri, setBaseTokenUri] = useState<string | null>(null)
|
const [baseTokenUri, setBaseTokenUri] = useState<string | null>(null)
|
||||||
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
|
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
|
||||||
const [transactionHash, setTransactionHash] = useState<string | null>(null)
|
const [transactionHash, setTransactionHash] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const performChecks = () => {
|
||||||
|
try {
|
||||||
|
// setReadyToCreate(false)
|
||||||
|
// checkUploadDetails()
|
||||||
|
// checkCollectionDetails()
|
||||||
|
// checkMintingDetails()
|
||||||
|
// checkWhitelistDetails()
|
||||||
|
// checkRoyaltyDetails()
|
||||||
|
setReadyToCreate(true)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
} catch (error: any) {
|
||||||
|
toast.error(error.message)
|
||||||
|
setUploading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const createCollection = async () => {
|
const createCollection = async () => {
|
||||||
try {
|
try {
|
||||||
setBaseTokenUri(null)
|
setBaseTokenUri(null)
|
||||||
@ -66,11 +79,6 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
setMinterContractAddress(null)
|
setMinterContractAddress(null)
|
||||||
setSg721ContractAddress(null)
|
setSg721ContractAddress(null)
|
||||||
setTransactionHash(null)
|
setTransactionHash(null)
|
||||||
checkUploadDetails()
|
|
||||||
checkCollectionDetails()
|
|
||||||
checkMintingDetails()
|
|
||||||
checkWhitelistDetails()
|
|
||||||
checkRoyaltyDetails()
|
|
||||||
if (uploadDetails?.uploadMethod === 'new') {
|
if (uploadDetails?.uploadMethod === 'new') {
|
||||||
setUploading(true)
|
setUploading(true)
|
||||||
|
|
||||||
@ -84,22 +92,27 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
uploadDetails.pinataApiKey as string,
|
uploadDetails.pinataApiKey as string,
|
||||||
uploadDetails.pinataSecretKey as string,
|
uploadDetails.pinataSecretKey as string,
|
||||||
)
|
)
|
||||||
|
|
||||||
setUploading(false)
|
setUploading(false)
|
||||||
|
|
||||||
setBaseTokenUri(baseUri)
|
setBaseTokenUri(baseUri)
|
||||||
setCoverImageUrl(coverImageUri)
|
setCoverImageUrl(coverImageUri)
|
||||||
|
|
||||||
let whitelist: string | undefined
|
let whitelist: string | undefined
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||||
|
|
||||||
await instantiate(baseUri, coverImageUri, whitelist)
|
await instantiate(baseUri, coverImageUri, whitelist)
|
||||||
} else {
|
} else {
|
||||||
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
setBaseTokenUri(uploadDetails?.baseTokenURI as string)
|
||||||
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
setCoverImageUrl(uploadDetails?.imageUrl as string)
|
||||||
|
|
||||||
let whitelist: string | undefined
|
let whitelist: string | undefined
|
||||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
||||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||||
|
|
||||||
await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
|
await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(error.message)
|
toast.error(error.message)
|
||||||
@ -141,13 +154,14 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
share: (Number(royaltyDetails.share) / 100).toString(),
|
share: (Number(royaltyDetails.share) / 100).toString(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}/` : `${baseUri}`}`,
|
base_token_uri: `${uploadDetails?.uploadMethod === 'new' ? `ipfs://${baseUri}/` : `${baseUri}`}`,
|
||||||
num_tokens: mintingDetails?.numTokens,
|
num_tokens: mintingDetails?.numTokens,
|
||||||
sg721_code_id: SG721_CODE_ID,
|
sg721_code_id: SG721_CODE_ID,
|
||||||
sg721_instantiate_msg: {
|
sg721_instantiate_msg: {
|
||||||
name: collectionDetails?.name,
|
name: collectionDetails?.name,
|
||||||
symbol: 'SYMBOL',
|
symbol: collectionDetails?.symbol,
|
||||||
minter: wallet.address,
|
minter: wallet.address,
|
||||||
collection_info: {
|
collection_info: {
|
||||||
creator: wallet.address,
|
creator: wallet.address,
|
||||||
@ -187,20 +201,25 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
.then((assetUri: string) => {
|
.then((assetUri: string) => {
|
||||||
const fileArray: File[] = []
|
const fileArray: File[] = []
|
||||||
let reader: FileReader
|
let reader: FileReader
|
||||||
|
|
||||||
for (let i = 0; i < uploadDetails.metadataFiles.length; i++) {
|
for (let i = 0; i < uploadDetails.metadataFiles.length; i++) {
|
||||||
reader = new FileReader()
|
reader = new FileReader()
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
const data: any = JSON.parse(e.target?.result as string)
|
const data: any = JSON.parse(e.target?.result as string)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
getAssetType(uploadDetails.assetFiles[i].name) === 'audio' ||
|
getAssetType(uploadDetails.assetFiles[i].name) === 'audio' ||
|
||||||
getAssetType(uploadDetails.assetFiles[i].name) === 'video'
|
getAssetType(uploadDetails.assetFiles[i].name) === 'video'
|
||||||
) {
|
) {
|
||||||
data.animation_url = `ipfs://${assetUri}/${uploadDetails.assetFiles[i].name}`
|
data.animation_url = `ipfs://${assetUri}/${uploadDetails.assetFiles[i].name}`
|
||||||
}
|
}
|
||||||
|
|
||||||
data.image = `ipfs://${assetUri}/${uploadDetails.assetFiles[i].name}`
|
data.image = `ipfs://${assetUri}/${uploadDetails.assetFiles[i].name}`
|
||||||
|
|
||||||
const metadataFileBlob = new Blob([JSON.stringify(data)], {
|
const metadataFileBlob = new Blob([JSON.stringify(data)], {
|
||||||
type: 'application/json',
|
type: 'application/json',
|
||||||
})
|
})
|
||||||
|
|
||||||
const updatedMetadataFile = new File(
|
const updatedMetadataFile = new File(
|
||||||
[metadataFileBlob],
|
[metadataFileBlob],
|
||||||
uploadDetails.metadataFiles[i].name.substring(0, uploadDetails.metadataFiles[i].name.lastIndexOf('.')),
|
uploadDetails.metadataFiles[i].name.substring(0, uploadDetails.metadataFiles[i].name.lastIndexOf('.')),
|
||||||
@ -208,6 +227,7 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
type: 'application/json',
|
type: 'application/json',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
fileArray.push(updatedMetadataFile)
|
fileArray.push(updatedMetadataFile)
|
||||||
}
|
}
|
||||||
reader.onloadend = () => {
|
reader.onloadend = () => {
|
||||||
@ -286,6 +306,7 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
)
|
)
|
||||||
throw new Error('Invalid limit for tokens per address')
|
throw new Error('Invalid limit for tokens per address')
|
||||||
if (mintingDetails.startTime === '') throw new Error('Start time is required')
|
if (mintingDetails.startTime === '') throw new Error('Start time is required')
|
||||||
|
if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time')
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkWhitelistDetails = () => {
|
const checkWhitelistDetails = () => {
|
||||||
@ -301,14 +322,18 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
if (whitelistDetails.endTime === '') throw new Error('End time is required')
|
if (whitelistDetails.endTime === '') throw new Error('End time is required')
|
||||||
if (whitelistDetails.perAddressLimit === 0) throw new Error('Per address limit is required')
|
if (whitelistDetails.perAddressLimit === 0) throw new Error('Per address limit is required')
|
||||||
if (whitelistDetails.memberLimit === 0) throw new Error('Member limit is required')
|
if (whitelistDetails.memberLimit === 0) throw new Error('Member limit is required')
|
||||||
|
if (Number(whitelistDetails.startTime) > Number(whitelistDetails.endTime))
|
||||||
|
throw new Error('Whitelist start time cannot be later than whitelist end time')
|
||||||
|
if (Number(whitelistDetails.endTime) > Number(mintingDetails?.startTime))
|
||||||
|
throw new Error('Whitelist end time cannot be later than public start time')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkRoyaltyDetails = () => {
|
const checkRoyaltyDetails = () => {
|
||||||
if (!royaltyDetails) throw new Error('Please fill out the royalty details')
|
if (!royaltyDetails) throw new Error('Please fill out the royalty details')
|
||||||
if (royaltyDetails.royaltyType === 'new') {
|
if (royaltyDetails.royaltyType === 'new') {
|
||||||
if (royaltyDetails.share === 0) throw new Error('Royalty share is required')
|
if (royaltyDetails.share === 0) throw new Error('Royalty share percentage is required')
|
||||||
if (royaltyDetails.share > 100 || royaltyDetails.share < 0) throw new Error('Invalid royalty share')
|
if (royaltyDetails.share > 100 || royaltyDetails.share < 0) throw new Error('Invalid royalty share percentage')
|
||||||
if (royaltyDetails.paymentAddress === '') throw new Error('Royalty payment address is required')
|
if (royaltyDetails.paymentAddress === '') throw new Error('Royalty payment address is required')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,21 +436,22 @@ const CollectionCreationPage: NextPage = () => {
|
|||||||
uploadMethod={uploadDetails?.uploadMethod as UploadMethod}
|
uploadMethod={uploadDetails?.uploadMethod as UploadMethod}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="my-6">
|
||||||
<div className="flex justify-between my-6">
|
|
||||||
<Button {...toggleProps} isWide type="button" variant="outline">
|
|
||||||
{isExpanded ? 'Hide' : 'Show'} Advanced Details
|
|
||||||
</Button>
|
|
||||||
<Button isWide onClick={createCollection} variant="solid">
|
|
||||||
Create Collection
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<section {...collapseProps} className="mb-10">
|
|
||||||
<WhitelistDetails onChange={setWhitelistDetails} />
|
<WhitelistDetails onChange={setWhitelistDetails} />
|
||||||
<div className="my-6" />
|
<div className="my-6" />
|
||||||
<RoyaltyDetails onChange={setRoyaltyDetails} />
|
<RoyaltyDetails onChange={setRoyaltyDetails} />
|
||||||
</section>
|
</div>
|
||||||
|
{readyToCreate && <ConfirmationModal confirm={createCollection} />}
|
||||||
|
<div className="flex justify-end w-full">
|
||||||
|
<Button className="px-0 mb-6 max-h-12" onClick={performChecks} variant="solid">
|
||||||
|
<label
|
||||||
|
className="relative justify-end w-full h-full text-white bg-plumbus-light hover:bg-plumbus-light border-0 btn modal-button"
|
||||||
|
htmlFor="my-modal-2"
|
||||||
|
>
|
||||||
|
Create Collection
|
||||||
|
</label>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
22
yarn.lock
22
yarn.lock
@ -6541,11 +6541,6 @@ pbkdf2@^3.0.16, pbkdf2@^3.0.9, pbkdf2@^3.1.1:
|
|||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
sha.js "^2.4.8"
|
sha.js "^2.4.8"
|
||||||
|
|
||||||
performance-now@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
|
||||||
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
|
|
||||||
|
|
||||||
picocolors@^1.0.0:
|
picocolors@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
||||||
@ -6725,13 +6720,6 @@ rabin-wasm@^0.1.4:
|
|||||||
node-fetch "^2.6.1"
|
node-fetch "^2.6.1"
|
||||||
readable-stream "^3.6.0"
|
readable-stream "^3.6.0"
|
||||||
|
|
||||||
raf@^3.4.1:
|
|
||||||
version "3.4.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
|
|
||||||
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==
|
|
||||||
dependencies:
|
|
||||||
performance-now "^2.1.0"
|
|
||||||
|
|
||||||
randombytes@^2.0.1:
|
randombytes@^2.0.1:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz"
|
||||||
@ -6759,14 +6747,6 @@ react-clock@^3.0.0:
|
|||||||
merge-class-names "^1.1.1"
|
merge-class-names "^1.1.1"
|
||||||
prop-types "^15.6.0"
|
prop-types "^15.6.0"
|
||||||
|
|
||||||
react-collapsed@^3:
|
|
||||||
version "3.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-collapsed/-/react-collapsed-3.3.2.tgz#b04dd13db5370ef553feac6556c911bb41dc63e2"
|
|
||||||
integrity sha512-P3YmP0k3Z7LaELQP2CbXjWSjiaNEIBvvxFUAJToJwztMFrTdHe9v7vQMz3FtMTyxLDJc9eGdNA8qMxCe9Aj3tg==
|
|
||||||
dependencies:
|
|
||||||
raf "^3.4.1"
|
|
||||||
tiny-warning "^1.0.3"
|
|
||||||
|
|
||||||
react-date-picker@^8.4.0:
|
react-date-picker@^8.4.0:
|
||||||
version "8.4.0"
|
version "8.4.0"
|
||||||
resolved "https://registry.npmjs.org/react-date-picker/-/react-date-picker-8.4.0.tgz"
|
resolved "https://registry.npmjs.org/react-date-picker/-/react-date-picker-8.4.0.tgz"
|
||||||
@ -7585,7 +7565,7 @@ tiny-secp256k1@^1.1.3:
|
|||||||
elliptic "^6.4.0"
|
elliptic "^6.4.0"
|
||||||
nan "^2.13.2"
|
nan "^2.13.2"
|
||||||
|
|
||||||
tiny-warning@^1.0.0, tiny-warning@^1.0.3:
|
tiny-warning@^1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
|
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
|
||||||
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
|
||||||
|
Loading…
Reference in New Issue
Block a user