Implement Base Minter Contract dashboard

This commit is contained in:
Serkan Reis
2022-12-09 11:27:50 +03:00
parent b690022655
commit a8c2548554
44 changed files with 1641 additions and 195 deletions
+32 -9
View File
@@ -18,26 +18,49 @@ export const sg721LinkTabs: LinkTabProps[] = [
},
]
export const minterLinkTabs: LinkTabProps[] = [
export const vendingMinterLinkTabs: LinkTabProps[] = [
{
title: 'Instantiate',
description: `Initialize a new Minter contract`,
href: '/contracts/minter/instantiate',
description: `Initialize a new Vending Minter contract`,
href: '/contracts/vendingMinter/instantiate',
},
{
title: 'Query',
description: `Dispatch queries with your Minter contract`,
href: '/contracts/minter/query',
description: `Dispatch queries with your Vending Minter contract`,
href: '/contracts/vendingMinter/query',
},
{
title: 'Execute',
description: `Execute Minter contract actions`,
href: '/contracts/minter/execute',
description: `Execute Vending Minter contract actions`,
href: '/contracts/vendingMinter/execute',
},
{
title: 'Migrate',
description: `Migrate Minter contract`,
href: '/contracts/minter/migrate',
description: `Migrate Vending Minter contract`,
href: '/contracts/vendingMinter/migrate',
},
]
export const baseMinterLinkTabs: LinkTabProps[] = [
{
title: 'Instantiate',
description: `Initialize a new Base Minter contract`,
href: '/contracts/baseMinter/instantiate',
},
{
title: 'Query',
description: `Dispatch queries with your Base Minter contract`,
href: '/contracts/baseMinter/query',
},
{
title: 'Execute',
description: `Execute Base Minter contract actions`,
href: '/contracts/baseMinter/execute',
},
{
title: 'Migrate',
description: `Migrate Base Minter contract`,
href: '/contracts/baseMinter/migrate',
},
]
+4 -3
View File
@@ -14,7 +14,8 @@ const routes = [
{ text: 'My Collections', href: `/collections/myCollections/`, isChild: true },
{ text: 'Collection Actions', href: `/collections/actions/`, isChild: true },
{ text: 'Contract Dashboards', href: `/contracts/`, isChild: false },
{ text: 'Minter Contract', href: `/contracts/minter/`, isChild: true },
{ text: 'Base Minter Contract', href: `/contracts/baseMinter/`, isChild: true },
{ text: 'Vending Minter Contract', href: `/contracts/vendingMinter/`, isChild: true },
{ text: 'SG721 Contract', href: `/contracts/sg721/`, isChild: true },
{ text: 'Whitelist Contract', href: `/contracts/whitelist/`, isChild: true },
]
@@ -37,9 +38,9 @@ export const Sidebar = () => {
<Anchor
key={href}
className={clsx(
'px-4 -mx-5 font-extrabold uppercase rounded-lg', // styling
'px-2 -mx-5 font-extrabold uppercase rounded-lg', // styling
'hover:bg-white/5 transition-colors', // hover styling
{ 'py-0 ml-2 text-sm font-bold': isChild },
{ 'py-0 -ml-2 text-sm font-bold': isChild },
{
'text-gray hover:text-white':
!router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1).includes(href) && isChild,
+4 -4
View File
@@ -13,8 +13,8 @@ import { InputDateTime } from 'components/InputDateTime'
import { JsonPreview } from 'components/JsonPreview'
import { TransactionHash } from 'components/TransactionHash'
import { useWallet } from 'contexts/wallet'
import type { MinterInstance } from 'contracts/minter'
import type { SG721Instance } from 'contracts/sg721'
import type { VendingMinterInstance } from 'contracts/vendingMinter'
import type { FormEvent } from 'react'
import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
@@ -29,7 +29,7 @@ interface CollectionActionsProps {
minterContractAddress: string
sg721ContractAddress: string
sg721Messages: SG721Instance | undefined
minterMessages: MinterInstance | undefined
vendingMinterMessages: VendingMinterInstance | undefined
}
type ExplicitContentType = true | false | undefined
@@ -38,7 +38,7 @@ export const CollectionActions = ({
sg721ContractAddress,
sg721Messages,
minterContractAddress,
minterMessages,
vendingMinterMessages,
}: CollectionActionsProps) => {
const wallet = useWallet()
const [lastTx, setLastTx] = useState('')
@@ -169,7 +169,7 @@ export const CollectionActions = ({
tokenId: tokenIdState.value,
tokenIds: tokenIdListState.value,
batchNumber: batchNumberState.value,
minterMessages,
vendingMinterMessages,
sg721Messages,
recipient: recipientState.value,
recipients: airdropArray,
+37 -37
View File
@@ -1,7 +1,7 @@
import type { MinterInstance } from 'contracts/minter'
import { useMinterContract } from 'contracts/minter'
import type { CollectionInfo, SG721Instance } from 'contracts/sg721'
import { useSG721Contract } from 'contracts/sg721'
import type { VendingMinterInstance } from 'contracts/vendingMinter'
import { useVendingMinterContract } from 'contracts/vendingMinter'
export type ActionType = typeof ACTION_TYPES[number]
@@ -150,11 +150,11 @@ export interface DispatchExecuteProps {
type Select<T extends ActionType> = T
/** @see {@link MinterInstance} */
/** @see {@link VendingMinterInstance} */
export type DispatchExecuteArgs = {
minterContract: string
sg721Contract: string
minterMessages?: MinterInstance
vendingMinterMessages?: VendingMinterInstance
sg721Messages?: SG721Instance
txSigner: string
} & (
@@ -183,40 +183,40 @@ export type DispatchExecuteArgs = {
)
export const dispatchExecute = async (args: DispatchExecuteArgs) => {
const { minterMessages, sg721Messages, txSigner } = args
if (!minterMessages || !sg721Messages) {
const { vendingMinterMessages, sg721Messages, txSigner } = args
if (!vendingMinterMessages || !sg721Messages) {
throw new Error('Cannot execute actions')
}
switch (args.type) {
case 'mint': {
return minterMessages.mint(txSigner)
return vendingMinterMessages.mint(txSigner)
}
case 'purge': {
return minterMessages.purge(txSigner)
return vendingMinterMessages.purge(txSigner)
}
case 'update_mint_price': {
return minterMessages.updateMintPrice(txSigner, args.price)
return vendingMinterMessages.updateMintPrice(txSigner, args.price)
}
case 'mint_to': {
return minterMessages.mintTo(txSigner, args.recipient)
return vendingMinterMessages.mintTo(txSigner, args.recipient)
}
case 'mint_for': {
return minterMessages.mintFor(txSigner, args.recipient, args.tokenId)
return vendingMinterMessages.mintFor(txSigner, args.recipient, args.tokenId)
}
case 'batch_mint': {
return minterMessages.batchMint(txSigner, args.recipient, args.batchNumber)
return vendingMinterMessages.batchMint(txSigner, args.recipient, args.batchNumber)
}
case 'set_whitelist': {
return minterMessages.setWhitelist(txSigner, args.whitelist)
return vendingMinterMessages.setWhitelist(txSigner, args.whitelist)
}
case 'update_start_time': {
return minterMessages.updateStartTime(txSigner, args.startTime)
return vendingMinterMessages.updateStartTime(txSigner, args.startTime)
}
case 'update_start_trading_time': {
return minterMessages.updateStartTradingTime(txSigner, args.startTime)
return vendingMinterMessages.updateStartTradingTime(txSigner, args.startTime)
}
case 'update_per_address_limit': {
return minterMessages.updatePerAddressLimit(txSigner, args.limit)
return vendingMinterMessages.updatePerAddressLimit(txSigner, args.limit)
}
case 'update_collection_info': {
return sg721Messages.updateCollectionInfo(args.collectionInfo as CollectionInfo)
@@ -225,10 +225,10 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
return sg721Messages.freezeCollectionInfo()
}
case 'shuffle': {
return minterMessages.shuffle(txSigner)
return vendingMinterMessages.shuffle(txSigner)
}
case 'withdraw': {
return minterMessages.withdraw(txSigner)
return vendingMinterMessages.withdraw(txSigner)
}
case 'transfer': {
return sg721Messages.transferNft(args.recipient, args.tokenId.toString())
@@ -243,13 +243,13 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
return sg721Messages.batchBurn(args.tokenIds)
}
case 'batch_mint_for': {
return minterMessages.batchMintFor(txSigner, args.recipient, args.tokenIds)
return vendingMinterMessages.batchMintFor(txSigner, args.recipient, args.tokenIds)
}
case 'airdrop': {
return minterMessages.airdrop(txSigner, args.recipients)
return vendingMinterMessages.airdrop(txSigner, args.recipients)
}
case 'burn_remaining': {
return minterMessages.burnRemaining(txSigner)
return vendingMinterMessages.burnRemaining(txSigner)
}
default: {
throw new Error('Unknown action')
@@ -259,40 +259,40 @@ export const dispatchExecute = async (args: DispatchExecuteArgs) => {
export const previewExecutePayload = (args: DispatchExecuteArgs) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages: minterMessages } = useMinterContract()
const { messages: vendingMinterMessages } = useVendingMinterContract()
// eslint-disable-next-line react-hooks/rules-of-hooks
const { messages: sg721Messages } = useSG721Contract()
const { minterContract, sg721Contract } = args
switch (args.type) {
case 'mint': {
return minterMessages(minterContract)?.mint()
return vendingMinterMessages(minterContract)?.mint()
}
case 'purge': {
return minterMessages(minterContract)?.purge()
return vendingMinterMessages(minterContract)?.purge()
}
case 'update_mint_price': {
return minterMessages(minterContract)?.updateMintPrice(args.price)
return vendingMinterMessages(minterContract)?.updateMintPrice(args.price)
}
case 'mint_to': {
return minterMessages(minterContract)?.mintTo(args.recipient)
return vendingMinterMessages(minterContract)?.mintTo(args.recipient)
}
case 'mint_for': {
return minterMessages(minterContract)?.mintFor(args.recipient, args.tokenId)
return vendingMinterMessages(minterContract)?.mintFor(args.recipient, args.tokenId)
}
case 'batch_mint': {
return minterMessages(minterContract)?.batchMint(args.recipient, args.batchNumber)
return vendingMinterMessages(minterContract)?.batchMint(args.recipient, args.batchNumber)
}
case 'set_whitelist': {
return minterMessages(minterContract)?.setWhitelist(args.whitelist)
return vendingMinterMessages(minterContract)?.setWhitelist(args.whitelist)
}
case 'update_start_time': {
return minterMessages(minterContract)?.updateStartTime(args.startTime)
return vendingMinterMessages(minterContract)?.updateStartTime(args.startTime)
}
case 'update_start_trading_time': {
return minterMessages(minterContract)?.updateStartTradingTime(args.startTime as string)
return vendingMinterMessages(minterContract)?.updateStartTradingTime(args.startTime as string)
}
case 'update_per_address_limit': {
return minterMessages(minterContract)?.updatePerAddressLimit(args.limit)
return vendingMinterMessages(minterContract)?.updatePerAddressLimit(args.limit)
}
case 'update_collection_info': {
return sg721Messages(sg721Contract)?.updateCollectionInfo(args.collectionInfo as CollectionInfo)
@@ -301,10 +301,10 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
return sg721Messages(sg721Contract)?.freezeCollectionInfo()
}
case 'shuffle': {
return minterMessages(minterContract)?.shuffle()
return vendingMinterMessages(minterContract)?.shuffle()
}
case 'withdraw': {
return minterMessages(minterContract)?.withdraw()
return vendingMinterMessages(minterContract)?.withdraw()
}
case 'transfer': {
return sg721Messages(sg721Contract)?.transferNft(args.recipient, args.tokenId.toString())
@@ -319,13 +319,13 @@ export const previewExecutePayload = (args: DispatchExecuteArgs) => {
return sg721Messages(sg721Contract)?.batchBurn(args.tokenIds)
}
case 'batch_mint_for': {
return minterMessages(minterContract)?.batchMintFor(args.recipient, args.tokenIds)
return vendingMinterMessages(minterContract)?.batchMintFor(args.recipient, args.tokenIds)
}
case 'airdrop': {
return minterMessages(minterContract)?.airdrop(args.recipients)
return vendingMinterMessages(minterContract)?.airdrop(args.recipients)
}
case 'burn_remaining': {
return minterMessages(minterContract)?.burnRemaining()
return vendingMinterMessages(minterContract)?.burnRemaining()
}
default: {
return {}
+1 -1
View File
@@ -5,8 +5,8 @@ import { FormControl } from 'components/FormControl'
import { AddressInput, TextInput } from 'components/forms/FormInput'
import { useInputState } from 'components/forms/FormInput.hooks'
import { JsonPreview } from 'components/JsonPreview'
import type { MinterInstance } from 'contracts/minter'
import type { SG721Instance } from 'contracts/sg721'
import type { MinterInstance } from 'contracts/vendingMinter'
import { toast } from 'react-hot-toast'
import { useQuery } from 'react-query'
+8 -8
View File
@@ -1,5 +1,5 @@
import type { MinterInstance } from 'contracts/minter'
import type { SG721Instance } from 'contracts/sg721'
import type { VendingMinterInstance } from 'contracts/vendingMinter'
export type QueryType = typeof QUERY_TYPES[number]
@@ -59,7 +59,7 @@ export interface DispatchExecuteProps {
type Select<T extends QueryType> = T
export type DispatchQueryArgs = {
minterMessages?: MinterInstance
vendingMinterMessages?: VendingMinterInstance
sg721Messages?: SG721Instance
} & (
| { type: undefined }
@@ -72,8 +72,8 @@ export type DispatchQueryArgs = {
)
export const dispatchQuery = async (args: DispatchQueryArgs) => {
const { minterMessages, sg721Messages } = args
if (!minterMessages || !sg721Messages) {
const { vendingMinterMessages, sg721Messages } = args
if (!vendingMinterMessages || !sg721Messages) {
throw new Error('Cannot execute actions')
}
switch (args.type) {
@@ -81,16 +81,16 @@ export const dispatchQuery = async (args: DispatchQueryArgs) => {
return sg721Messages.collectionInfo()
}
case 'mint_price': {
return minterMessages.getMintPrice()
return vendingMinterMessages.getMintPrice()
}
case 'num_tokens': {
return minterMessages.getMintableNumTokens()
return vendingMinterMessages.getMintableNumTokens()
}
case 'tokens_minted_to_user': {
return minterMessages.getMintCount(args.address)
return vendingMinterMessages.getMintCount(args.address)
}
// case 'token_owners': {
// return minterMessages.updateStartTime(txSigner, args.startTime)
// return vendingMinterMessages.updateStartTime(txSigner, args.startTime)
// }
case 'token_info': {
if (!args.tokenId) return
@@ -1,4 +1,4 @@
import type { ExecuteListItem } from 'contracts/minter/messages/execute'
import type { ExecuteListItem } from 'contracts/baseMinter/messages/execute'
import { useState } from 'react'
export const useExecuteComboboxState = () => {
@@ -1,8 +1,8 @@
import { Combobox, Transition } from '@headlessui/react'
import clsx from 'clsx'
import { FormControl } from 'components/FormControl'
import type { ExecuteListItem } from 'contracts/minter/messages/execute'
import { EXECUTE_LIST } from 'contracts/minter/messages/execute'
import type { ExecuteListItem } from 'contracts/baseMinter/messages/execute'
import { EXECUTE_LIST } from 'contracts/baseMinter/messages/execute'
import { matchSorter } from 'match-sorter'
import { Fragment, useState } from 'react'
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
@@ -0,0 +1,7 @@
import type { ExecuteListItem } from 'contracts/vendingMinter/messages/execute'
import { useState } from 'react'
export const useExecuteComboboxState = () => {
const [value, setValue] = useState<ExecuteListItem | null>(null)
return { value, onChange: (item: ExecuteListItem) => setValue(item) }
}
@@ -0,0 +1,92 @@
import { Combobox, Transition } from '@headlessui/react'
import clsx from 'clsx'
import { FormControl } from 'components/FormControl'
import type { ExecuteListItem } from 'contracts/vendingMinter/messages/execute'
import { EXECUTE_LIST } from 'contracts/vendingMinter/messages/execute'
import { matchSorter } from 'match-sorter'
import { Fragment, useState } from 'react'
import { FaChevronDown, FaInfoCircle } from 'react-icons/fa'
export interface ExecuteComboboxProps {
value: ExecuteListItem | null
onChange: (item: ExecuteListItem) => void
}
export const ExecuteCombobox = ({ value, onChange }: ExecuteComboboxProps) => {
const [search, setSearch] = useState('')
const filtered =
search === '' ? EXECUTE_LIST : matchSorter(EXECUTE_LIST, search, { keys: ['id', 'name', 'description'] })
return (
<Combobox
as={FormControl}
htmlId="message-type"
labelAs={Combobox.Label}
onChange={onChange}
subtitle="Contract execute message type"
title="Message Type"
value={value}
>
<div className="relative">
<Combobox.Input
className={clsx(
'w-full bg-white/10 rounded border-2 border-white/20 form-input',
'placeholder:text-white/50',
'focus:ring focus:ring-plumbus-20',
)}
displayValue={(val?: ExecuteListItem) => val?.name ?? ''}
id="message-type"
onChange={(event) => setSearch(event.target.value)}
placeholder="Select message type"
/>
<Combobox.Button
className={clsx(
'flex absolute inset-y-0 right-0 items-center p-4',
'opacity-50 hover:opacity-100 active:opacity-100',
)}
>
{({ open }) => <FaChevronDown aria-hidden="true" className={clsx('w-4 h-4', { 'rotate-180': open })} />}
</Combobox.Button>
<Transition afterLeave={() => setSearch('')} as={Fragment}>
<Combobox.Options
className={clsx(
'overflow-auto absolute z-10 mt-2 w-full max-h-[30vh]',
'bg-stone-800/80 rounded shadow-lg backdrop-blur-sm',
'divide-y divide-stone-500/50',
)}
>
{filtered.length < 1 && (
<span className="flex flex-col justify-center items-center p-4 text-sm text-center text-white/50">
Message type not found.
</span>
)}
{filtered.map((entry) => (
<Combobox.Option
key={entry.id}
className={({ active }) =>
clsx('flex relative flex-col py-2 px-4 space-y-1 cursor-pointer', { 'bg-plumbus-70': active })
}
value={entry}
>
<span className="font-bold">{entry.name}</span>
<span className="max-w-md text-sm">{entry.description}</span>
</Combobox.Option>
))}
</Combobox.Options>
</Transition>
</div>
{value && (
<div className="flex space-x-2 text-white/50">
<div className="mt-1">
<FaInfoCircle className="w-3 h-3" />
</div>
<span className="text-sm">{value.description}</span>
</div>
)}
</Combobox>
)
}