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:
co-authored by
name-user1
Serkan Reis
name-user1
parent
00960f429e
commit
9dc19a99ab
@@ -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',
|
||||
'batch_burn',
|
||||
'shuffle',
|
||||
'airdrop',
|
||||
] as const
|
||||
|
||||
export interface ActionListItem {
|
||||
@@ -87,6 +88,11 @@ export const ACTION_LIST: ActionListItem[] = [
|
||||
name: 'Shuffle Tokens',
|
||||
description: 'Shuffle the token IDs',
|
||||
},
|
||||
{
|
||||
id: 'airdrop',
|
||||
name: 'Airdrop Tokens',
|
||||
description: 'Airdrop tokens to given addresses',
|
||||
},
|
||||
]
|
||||
|
||||
export interface DispatchExecuteProps {
|
||||
@@ -117,6 +123,7 @@ export type DispatchExecuteArgs = {
|
||||
| { type: Select<'batch_transfer'>; recipient: string; tokenIds: string }
|
||||
| { type: Select<'burn'>; tokenId: number }
|
||||
| { type: Select<'batch_burn'>; tokenIds: string }
|
||||
| { type: Select<'airdrop'>; recipients: string[] }
|
||||
)
|
||||
|
||||
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
@@ -161,6 +168,9 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
|
||||
case 'batch_burn': {
|
||||
return sg721Messages.batchBurn(args.tokenIds)
|
||||
}
|
||||
case 'airdrop': {
|
||||
return minterMessages.airdrop(txSigner, args.recipients)
|
||||
}
|
||||
default: {
|
||||
throw new Error('Unknown action')
|
||||
}
|
||||
@@ -210,6 +220,9 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
|
||||
case 'batch_burn': {
|
||||
return sg721Messages(sg721Contract)?.batchBurn(args.tokenIds)
|
||||
}
|
||||
case 'airdrop': {
|
||||
return minterMessages(minterContract)?.airdrop(args.recipients)
|
||||
}
|
||||
default: {
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ interface CollectionDetailsProps {
|
||||
export interface CollectionDetailsDataProps {
|
||||
name: string
|
||||
description: string
|
||||
symbol: string
|
||||
imageFile: File[]
|
||||
externalLink?: string
|
||||
}
|
||||
@@ -44,10 +45,17 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
||||
placeholder: 'My Awesome Collection Description',
|
||||
})
|
||||
|
||||
const symbolState = useInputState({
|
||||
id: 'symbol',
|
||||
name: 'symbol',
|
||||
title: 'Symbol',
|
||||
placeholder: 'SYMBOL',
|
||||
})
|
||||
|
||||
const externalLinkState = useInputState({
|
||||
id: 'external-link',
|
||||
name: 'externalLink',
|
||||
title: 'External Link',
|
||||
title: 'External Link (optional)',
|
||||
placeholder: 'https://my-collection...',
|
||||
})
|
||||
|
||||
@@ -56,6 +64,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
||||
const data: CollectionDetailsDataProps = {
|
||||
name: nameState.value,
|
||||
description: descriptionState.value,
|
||||
symbol: symbolState.value,
|
||||
imageFile: coverImage ? [coverImage] : [],
|
||||
externalLink: externalLinkState.value,
|
||||
}
|
||||
@@ -88,6 +97,7 @@ export const CollectionDetails = ({ onChange, uploadMethod, coverImageUrl }: Col
|
||||
<FormGroup subtitle="Information about your collection" title="Collection Details">
|
||||
<TextInput {...nameState} isRequired />
|
||||
<TextInput {...descriptionState} isRequired />
|
||||
<TextInput {...symbolState} isRequired />
|
||||
|
||||
<FormControl isRequired={uploadMethod === 'new'} title="Cover Image">
|
||||
{uploadMethod === 'new' && (
|
||||
|
||||
@@ -35,8 +35,8 @@ export const MintingDetails = ({ onChange, numberOfTokens, uploadMethod }: Minti
|
||||
id: 'unitPrice',
|
||||
name: 'unitPrice',
|
||||
title: 'Unit Price',
|
||||
subtitle: '',
|
||||
placeholder: '100',
|
||||
subtitle: 'Price of each token (min. 50 STARS)',
|
||||
placeholder: '50',
|
||||
})
|
||||
|
||||
const perAddressLimitState = useNumberInputState({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Conditional } from 'components/Conditional'
|
||||
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 { NumberInput, TextInput } from '../../forms/FormInput'
|
||||
@@ -28,19 +28,19 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
placeholder: 'stars1234567890abcdefghijklmnopqrstuvwxyz...',
|
||||
})
|
||||
|
||||
const royaltyShareState = useNumberInputState({
|
||||
const royaltyShareState = useInputState({
|
||||
id: 'royalty-share',
|
||||
name: 'royaltyShare',
|
||||
title: 'Share Percentage',
|
||||
subtitle: 'Percentage of royalties to be paid',
|
||||
placeholder: '8',
|
||||
placeholder: '8%',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const data: RoyaltyDetailsDataProps = {
|
||||
royaltyType: royaltyState,
|
||||
paymentAddress: royaltyPaymentAddressState.value,
|
||||
share: royaltyShareState.value,
|
||||
share: Number(royaltyShareState.value),
|
||||
}
|
||||
onChange(data)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -84,8 +84,8 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
</div>
|
||||
<Conditional test={royaltyState === 'new'}>
|
||||
<FormGroup subtitle="Information about royalty" title="Royalty Details">
|
||||
<TextInput {...royaltyPaymentAddressState} />
|
||||
<NumberInput {...royaltyShareState} />
|
||||
<TextInput {...royaltyPaymentAddressState} isRequired />
|
||||
<NumberInput {...royaltyShareState} isRequired />
|
||||
</FormGroup>
|
||||
</Conditional>
|
||||
</div>
|
||||
|
||||
@@ -78,11 +78,23 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
})
|
||||
|
||||
const selectAssets = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const files: File[] = []
|
||||
let reader: FileReader
|
||||
if (event.target.files === null) return
|
||||
setAssetFilesArray([])
|
||||
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++) {
|
||||
reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
@@ -102,10 +114,26 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
}
|
||||
|
||||
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[] = []
|
||||
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++) {
|
||||
reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
|
||||
@@ -43,15 +43,15 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
id: 'unit-price',
|
||||
name: 'unitPrice',
|
||||
title: 'Unit Price',
|
||||
subtitle: 'Price of each tokens in collection',
|
||||
placeholder: '500',
|
||||
subtitle: 'Token price for whitelisted addresses \n (min. 25 STARS)',
|
||||
placeholder: '25',
|
||||
})
|
||||
|
||||
const memberLimitState = useNumberInputState({
|
||||
id: 'member-limit',
|
||||
name: 'memberLimit',
|
||||
title: 'Member Limit',
|
||||
subtitle: 'Limit of the whitelisted members',
|
||||
subtitle: 'Maximum number of whitelisted addresses',
|
||||
placeholder: '1000',
|
||||
})
|
||||
|
||||
@@ -59,7 +59,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
id: 'per-address-limit',
|
||||
name: 'perAddressLimit',
|
||||
title: 'Per Address Limit',
|
||||
subtitle: 'Limit of tokens per address',
|
||||
subtitle: 'Maximum number of tokens per whitelisted address',
|
||||
placeholder: '5',
|
||||
})
|
||||
|
||||
@@ -145,11 +145,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
</div>
|
||||
|
||||
<Conditional test={whitelistState === 'existing'}>
|
||||
<AddressInput
|
||||
{...whitelistAddressState}
|
||||
className="pb-5"
|
||||
onChange={(e) => whitelistAddressState.onChange(e.target.value)}
|
||||
/>
|
||||
<AddressInput {...whitelistAddressState} className="pb-5" isRequired />
|
||||
</Conditional>
|
||||
|
||||
<Conditional test={whitelistState === 'new'}>
|
||||
@@ -158,10 +154,20 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
<NumberInput isRequired {...uniPriceState} />
|
||||
<NumberInput isRequired {...memberLimitState} />
|
||||
<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} />
|
||||
</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} />
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
|
||||
Reference in New Issue
Block a user