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:
Arda Nakışçı
2022-09-01 09:27:23 +03:00
committed by GitHub
co-authored by name-user1 Serkan Reis name-user1
parent 00960f429e
commit 9dc19a99ab
12 changed files with 234 additions and 77 deletions
+41
View 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>
)
}
+13
View File
@@ -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>