commit
9c664a4eb6
@ -8,6 +8,7 @@ NEXT_PUBLIC_SG721_CODE_ID=1
|
||||
NEXT_PUBLIC_API_URL=https://
|
||||
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze
|
||||
NEXT_PUBLIC_NETWORK=testnet
|
||||
NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev
|
||||
NEXT_PUBLIC_WEBSITE_URL=https://
|
||||
|
||||
NEXT_PUBLIC_S3_BUCKET= # TODO
|
||||
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -1 +1 @@
|
||||
* @orkunkl @findolor @kaymakf
|
||||
* @findolor @MightOfOaks @name-user1
|
||||
|
@ -25,7 +25,7 @@ export const Button = (props: ButtonProps) => {
|
||||
'bg-plumbus hover:bg-plumbus-light rounded ': variant === 'solid',
|
||||
'bg-plumbus hover:bg-plumbus-light rounded border border-plumbus-dark': variant === 'outline',
|
||||
'opacity-50 cursor-not-allowed pointer-events-none': isDisabled,
|
||||
'animate-pulse cursor-wait pointer-events-none': isLoading,
|
||||
'pl-2 animate-pulse cursor-wait pointer-events-none': isLoading,
|
||||
},
|
||||
className,
|
||||
)}
|
||||
|
@ -28,7 +28,7 @@ export const Layout = ({ children, metadata = {} }: LayoutProps) => {
|
||||
</div> */}
|
||||
|
||||
{/* actual layout container */}
|
||||
<div className="hidden sm:flex">
|
||||
<div className="hidden bg-black sm:flex">
|
||||
<Sidebar />
|
||||
<div className="overflow-auto relative flex-grow h-screen no-scrollbar">
|
||||
<main
|
||||
|
@ -9,9 +9,14 @@ import { SidebarLayout } from './SidebarLayout'
|
||||
import { WalletLoader } from './WalletLoader'
|
||||
|
||||
const routes = [
|
||||
{ text: 'Create Collection', href: `/collections/create/` },
|
||||
{ text: 'Collections', href: `/collections/` },
|
||||
{ text: 'Contract Dashboards', href: `/contracts/` },
|
||||
{ text: 'Collections', href: `/collections/`, isChild: false },
|
||||
{ text: 'Create a Collection', href: `/collections/create/`, isChild: true },
|
||||
{ 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: 'SG721 Contract', href: `/contracts/sg721/`, isChild: true },
|
||||
{ text: 'Whitelist Contract', href: `/contracts/whitelist/`, isChild: true },
|
||||
]
|
||||
|
||||
export const Sidebar = () => {
|
||||
@ -22,20 +27,24 @@ export const Sidebar = () => {
|
||||
<SidebarLayout>
|
||||
{/* Stargaze brand as home button */}
|
||||
<Anchor href="/" onContextMenu={(e) => [e.preventDefault(), router.push('/brand')]}>
|
||||
<img alt="Brand Text" className="w-full" src="/stargaze-text.png" />
|
||||
<img alt="Brand Text" className="w-full" src="/stargaze_logo_800.svg" />
|
||||
</Anchor>
|
||||
|
||||
{/* wallet button */}
|
||||
<WalletLoader />
|
||||
|
||||
{/* main navigation routes */}
|
||||
{routes.map(({ text, href }) => (
|
||||
{routes.map(({ text, href, isChild }) => (
|
||||
<Anchor
|
||||
key={href}
|
||||
className={clsx(
|
||||
'py-2 px-4 -mx-4 uppercase', // styling
|
||||
'px-4 -mx-5 font-extrabold uppercase rounded-lg', // styling
|
||||
'hover:bg-white/5 transition-colors', // hover styling
|
||||
{ 'font-bold text-plumbus': router.asPath === href }, // active route styling
|
||||
{ 'py-0 ml-2 text-sm font-bold': isChild },
|
||||
{
|
||||
'text-gray hover:text-white':
|
||||
router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1) !== href && isChild,
|
||||
},
|
||||
{ 'text-plumbus': router.asPath.substring(0, router.asPath.lastIndexOf('/') + 1) === href && isChild }, // active route styling
|
||||
// { 'text-gray-500 pointer-events-none': disabled }, // disabled route styling
|
||||
)}
|
||||
href={href}
|
||||
|
188
components/collections/actions/Action.tsx
Normal file
188
components/collections/actions/Action.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
import { Button } from 'components/Button'
|
||||
import type { DispatchExecuteArgs } from 'components/collections/actions/actions'
|
||||
import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/collections/actions/actions'
|
||||
import { ActionsCombobox } from 'components/collections/actions/Combobox'
|
||||
import { useActionsComboboxState } from 'components/collections/actions/Combobox.hooks'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { TransactionHash } from 'components/TransactionHash'
|
||||
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { MinterInstance } from 'contracts/minter'
|
||||
import type { SG721Instance } from 'contracts/sg721'
|
||||
import type { FormEvent } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
|
||||
import { TextInput } from '../../forms/FormInput'
|
||||
|
||||
interface CollectionActionsProps {
|
||||
minterContractAddress: string
|
||||
sg721ContractAddress: string
|
||||
sg721Messages: SG721Instance | undefined
|
||||
minterMessages: MinterInstance | undefined
|
||||
}
|
||||
|
||||
export const CollectionActions = ({
|
||||
sg721ContractAddress,
|
||||
sg721Messages,
|
||||
minterContractAddress,
|
||||
minterMessages,
|
||||
}: CollectionActionsProps) => {
|
||||
const wallet = useWallet()
|
||||
const [lastTx, setLastTx] = useState('')
|
||||
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||
const [airdropArray, setAirdropArray] = useState<string[]>([])
|
||||
|
||||
const actionComboboxState = useActionsComboboxState()
|
||||
const type = actionComboboxState.value?.id
|
||||
|
||||
const limitState = useNumberInputState({
|
||||
id: 'per-address-limi',
|
||||
name: 'perAddressLimit',
|
||||
title: 'Per Address Limit',
|
||||
subtitle: 'Enter the per address limit',
|
||||
})
|
||||
|
||||
const tokenIdState = useNumberInputState({
|
||||
id: 'token-id',
|
||||
name: 'tokenId',
|
||||
title: 'Token ID',
|
||||
subtitle: 'Enter the token ID',
|
||||
})
|
||||
|
||||
const batchNumberState = useNumberInputState({
|
||||
id: 'batch-number',
|
||||
name: 'batchNumber',
|
||||
title: 'Number of Tokens',
|
||||
subtitle: 'Enter the number of tokens to mint',
|
||||
})
|
||||
|
||||
const tokenIdListState = useInputState({
|
||||
id: 'token-id-list',
|
||||
name: 'tokenIdList',
|
||||
title: 'List of token IDs',
|
||||
subtitle:
|
||||
'Specify individual token IDs separated by commas (e.g., 2, 4, 8) or a range of IDs separated by a colon (e.g., 8:13)',
|
||||
})
|
||||
|
||||
const recipientState = useInputState({
|
||||
id: 'recipient-address',
|
||||
name: 'recipient',
|
||||
title: 'Recipient Address',
|
||||
subtitle: 'Address of the recipient',
|
||||
})
|
||||
|
||||
const whitelistState = useInputState({
|
||||
id: 'whitelist-address',
|
||||
name: 'whitelistAddress',
|
||||
title: 'Whitelist Address',
|
||||
subtitle: 'Address of the whitelist contract',
|
||||
})
|
||||
|
||||
const showWhitelistField = type === 'set_whitelist'
|
||||
const showDateField = type === 'update_start_time'
|
||||
const showLimitField = type === 'update_per_address_limit'
|
||||
const showTokenIdField = isEitherType(type, ['transfer', 'mint_for', 'burn'])
|
||||
const showNumberOfTokensField = type === 'batch_mint'
|
||||
const showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer'])
|
||||
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer'])
|
||||
const showAirdropFileField = type === 'airdrop'
|
||||
|
||||
const payload: DispatchExecuteArgs = {
|
||||
whitelist: whitelistState.value,
|
||||
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||
limit: limitState.value,
|
||||
minterContract: minterContractAddress,
|
||||
sg721Contract: sg721ContractAddress,
|
||||
tokenId: tokenIdState.value,
|
||||
tokenIds: tokenIdListState.value,
|
||||
batchNumber: batchNumberState.value,
|
||||
minterMessages,
|
||||
sg721Messages,
|
||||
recipient: recipientState.value,
|
||||
recipients: airdropArray,
|
||||
txSigner: wallet.address,
|
||||
type,
|
||||
}
|
||||
const { isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!type) {
|
||||
throw new Error('Please select an action!')
|
||||
}
|
||||
if (minterContractAddress === '' && sg721ContractAddress === '') {
|
||||
throw new Error('Please enter minter and sg721 contract addresses!')
|
||||
}
|
||||
const txHash = await toast.promise(dispatchExecute(payload), {
|
||||
error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
|
||||
loading: 'Executing message...',
|
||||
success: (tx) => `Transaction ${tx} success!`,
|
||||
})
|
||||
if (txHash) {
|
||||
setLastTx(txHash)
|
||||
}
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
toast.error(String(error))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const airdropFileOnChange = (data: string[]) => {
|
||||
setAirdropArray(data)
|
||||
}
|
||||
|
||||
return (
|
||||
<form>
|
||||
<div className="grid grid-cols-2 mt-4">
|
||||
<div className="mr-2">
|
||||
<ActionsCombobox {...actionComboboxState} />
|
||||
{showRecipientField && <AddressInput {...recipientState} />}
|
||||
{showWhitelistField && <AddressInput {...whitelistState} />}
|
||||
{showLimitField && <NumberInput {...limitState} />}
|
||||
{showTokenIdField && <NumberInput {...tokenIdState} />}
|
||||
{showTokenIdListField && <TextInput {...tokenIdListState} />}
|
||||
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
|
||||
{showAirdropFileField && (
|
||||
<FormGroup subtitle="TXT file that contains the airdrop addresses" title="Airdrop File">
|
||||
<WhitelistUpload onChange={airdropFileOnChange} />
|
||||
</FormGroup>
|
||||
)}
|
||||
<Conditional test={showDateField}>
|
||||
<FormControl htmlId="start-date" subtitle="Start time for the minting" title="Start Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
</div>
|
||||
<div className="-mt-6">
|
||||
<div className="relative mb-2">
|
||||
<Button
|
||||
className="absolute top-0 right-0"
|
||||
isLoading={isLoading}
|
||||
onClick={mutate}
|
||||
rightIcon={<FaArrowRight />}
|
||||
>
|
||||
Execute
|
||||
</Button>
|
||||
<FormControl subtitle="View execution transaction hash" title="Transaction Hash">
|
||||
<TransactionHash hash={lastTx} />
|
||||
</FormControl>
|
||||
</div>
|
||||
<FormControl subtitle="View current message to be sent" title="Payload Preview">
|
||||
<JsonPreview content={previewExecutePayload(payload)} isCopyable />
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
@ -26,7 +26,7 @@ export const ActionsCombobox = ({ value, onChange }: ActionsComboboxProps) => {
|
||||
labelAs={Combobox.Label}
|
||||
onChange={onChange}
|
||||
subtitle="Collection actions"
|
||||
title="Action"
|
||||
title=""
|
||||
value={value}
|
||||
>
|
||||
<div className="relative">
|
||||
|
@ -52,7 +52,7 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={royaltyState === 'none'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="royaltyRadio1"
|
||||
name="royaltyRadioOptions1"
|
||||
onClick={() => {
|
||||
@ -61,14 +61,17 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
type="radio"
|
||||
value="None"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="royaltyRadio1">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="royaltyRadio1"
|
||||
>
|
||||
No royalty
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={royaltyState === 'new'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="royaltyRadio2"
|
||||
name="royaltyRadioOptions2"
|
||||
onClick={() => {
|
||||
@ -77,7 +80,10 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => {
|
||||
type="radio"
|
||||
value="Existing"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="royaltyRadio2">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="royaltyRadio2"
|
||||
>
|
||||
Configure royalty details
|
||||
</label>
|
||||
</div>
|
||||
|
@ -41,24 +41,23 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
const nftStorageApiKeyState = useInputState({
|
||||
id: 'nft-storage-api-key',
|
||||
name: 'nftStorageApiKey',
|
||||
title: 'NFT Storage API Key',
|
||||
placeholder: '...',
|
||||
defaultValue:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6ZXRocjoweDJBODk5OGI4ZkE2YTM1NzMyYmMxQTRDQzNhOUU2M0Y2NUM3ZjA1RWIiLCJpc3MiOiJuZnQtc3RvcmFnZSIsImlhdCI6MTY1NTE5MTcwNDQ2MiwibmFtZSI6IlRlc3QifQ.IbdV_26bkPHSdd81sxox5AoG-5a4CCEY4aCrdbCXwAE',
|
||||
title: 'NFT.Storage API Key',
|
||||
placeholder: 'Enter NFT.Storage API Key',
|
||||
defaultValue: '',
|
||||
})
|
||||
const pinataApiKeyState = useInputState({
|
||||
id: 'pinata-api-key',
|
||||
name: 'pinataApiKey',
|
||||
title: 'Pinata API Key',
|
||||
placeholder: '...',
|
||||
defaultValue: 'c8c2ea440c09ee8fa639',
|
||||
placeholder: 'Enter Pinata API Key',
|
||||
defaultValue: '',
|
||||
})
|
||||
const pinataSecretKeyState = useInputState({
|
||||
id: 'pinata-secret-key',
|
||||
name: 'pinataSecretKey',
|
||||
title: 'Pinata Secret Key',
|
||||
placeholder: '...',
|
||||
defaultValue: '9d6f42dc01eaab15f52eac8f36cc4f0ee4184944cb3cdbcda229d06ecf877ee7',
|
||||
placeholder: 'Enter Pinata Secret Key',
|
||||
defaultValue: '',
|
||||
})
|
||||
|
||||
const baseTokenUriState = useInputState({
|
||||
@ -86,7 +85,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
//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) {
|
||||
if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) {
|
||||
toast.error('The file names should be in numerical order starting from 1.')
|
||||
//clear the input
|
||||
event.target.value = ''
|
||||
@ -125,7 +124,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
//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) {
|
||||
if (isNaN(Number(sortedFileNames[i])) || parseInt(sortedFileNames[i]) !== i + 1) {
|
||||
toast.error('The file names should be in numerical order starting from 1.')
|
||||
//clear the input
|
||||
event.target.value = ''
|
||||
@ -202,27 +201,10 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
return (
|
||||
<div className="justify-items-start mt-5 mb-3 rounded border border-2 border-white/20 flex-column">
|
||||
<div className="flex justify-center">
|
||||
<div className="mt-3 ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={uploadMethod === 'existing'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
id="inlineRadio1"
|
||||
name="inlineRadioOptions1"
|
||||
onClick={() => {
|
||||
setUploadMethod('existing')
|
||||
}}
|
||||
type="radio"
|
||||
value="Existing"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="inlineRadio1">
|
||||
Use an existing base URI
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={uploadMethod === 'new'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="inlineRadio2"
|
||||
name="inlineRadioOptions2"
|
||||
onClick={() => {
|
||||
@ -231,10 +213,32 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
type="radio"
|
||||
value="New"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="inlineRadio2">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio2"
|
||||
>
|
||||
Upload assets & metadata
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-3 ml-2 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={uploadMethod === 'existing'}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio1"
|
||||
name="inlineRadioOptions1"
|
||||
onClick={() => {
|
||||
setUploadMethod('existing')
|
||||
}}
|
||||
type="radio"
|
||||
value="Existing"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio1"
|
||||
>
|
||||
Use an existing base URI
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 py-5 pb-8">
|
||||
@ -244,7 +248,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
Though Stargaze's sg721 contract allows for off-chain metadata storage, it is recommended to use a
|
||||
decentralized storage solution, such as IPFS. <br /> You may head over to{' '}
|
||||
<Anchor className="font-bold text-plumbus hover:underline" href="https://nft.storage">
|
||||
NFT Storage
|
||||
NFT.Storage
|
||||
</Anchor>{' '}
|
||||
or{' '}
|
||||
<Anchor className="font-bold text-plumbus hover:underline" href="https://www.pinata.cloud/">
|
||||
@ -267,7 +271,7 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={uploadService === 'nft-storage'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="inlineRadio3"
|
||||
name="inlineRadioOptions3"
|
||||
onClick={() => {
|
||||
@ -276,15 +280,18 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
type="radio"
|
||||
value="nft-storage"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="inlineRadio3">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio3"
|
||||
>
|
||||
Upload using NFT.Storage
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="ml-4 form-check form-check-inline">
|
||||
<div className="ml-2 form-check form-check-inline">
|
||||
<input
|
||||
checked={uploadService === 'pinata'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="inlineRadio4"
|
||||
name="inlineRadioOptions4"
|
||||
onClick={() => {
|
||||
@ -293,7 +300,10 @@ export const UploadDetails = ({ onChange }: UploadDetailsProps) => {
|
||||
type="radio"
|
||||
value="pinata"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="inlineRadio4">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio4"
|
||||
>
|
||||
Upload using Pinata
|
||||
</label>
|
||||
</div>
|
||||
|
@ -97,7 +97,7 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistState === 'none'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="whitelistRadio1"
|
||||
name="whitelistRadioOptions1"
|
||||
onClick={() => {
|
||||
@ -106,14 +106,17 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
type="radio"
|
||||
value="None"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="whitelistRadio1">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="whitelistRadio1"
|
||||
>
|
||||
No whitelist
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistState === 'existing'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="whitelistRadio2"
|
||||
name="whitelistRadioOptions2"
|
||||
onClick={() => {
|
||||
@ -122,14 +125,17 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
type="radio"
|
||||
value="Existing"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="whitelistRadio2">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="whitelistRadio2"
|
||||
>
|
||||
Existing whitelist
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-4 font-bold form-check form-check-inline">
|
||||
<input
|
||||
checked={whitelistState === 'new'}
|
||||
className="float-none mr-2 mb-1 w-4 h-4 align-middle bg-white checked:bg-stargaze bg-center bg-no-repeat bg-contain rounded-full border border-gray-300 checked:border-white focus:outline-none transition duration-200 appearance-none cursor-pointer form-check-input"
|
||||
className="peer sr-only"
|
||||
id="whitelistRadio3"
|
||||
name="whitelistRadioOptions3"
|
||||
onClick={() => {
|
||||
@ -138,7 +144,10 @@ export const WhitelistDetails = ({ onChange }: WhitelistDetailsProps) => {
|
||||
type="radio"
|
||||
value="New"
|
||||
/>
|
||||
<label className="inline-block text-white cursor-pointer form-check-label" htmlFor="whitelistRadio3">
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="whitelistRadio3"
|
||||
>
|
||||
New whitelist
|
||||
</label>
|
||||
</div>
|
||||
|
@ -25,7 +25,7 @@ export const QueryCombobox = ({ value, onChange }: QueryComboboxProps) => {
|
||||
labelAs={Combobox.Label}
|
||||
onChange={onChange}
|
||||
subtitle="Collection queries"
|
||||
title="Query"
|
||||
title=""
|
||||
value={value}
|
||||
>
|
||||
<div className="relative">
|
||||
|
85
components/collections/queries/Queries.tsx
Normal file
85
components/collections/queries/Queries.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import { QueryCombobox } from 'components/collections/queries/Combobox'
|
||||
import { useQueryComboboxState } from 'components/collections/queries/Combobox.hooks'
|
||||
import { dispatchQuery } from 'components/collections/queries/query'
|
||||
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 { toast } from 'react-hot-toast'
|
||||
import { useQuery } from 'react-query'
|
||||
|
||||
interface CollectionQueriesProps {
|
||||
minterContractAddress: string
|
||||
sg721ContractAddress: string
|
||||
sg721Messages: SG721Instance | undefined
|
||||
minterMessages: MinterInstance | undefined
|
||||
}
|
||||
export const CollectionQueries = ({
|
||||
sg721ContractAddress,
|
||||
sg721Messages,
|
||||
minterContractAddress,
|
||||
minterMessages,
|
||||
}: CollectionQueriesProps) => {
|
||||
const comboboxState = useQueryComboboxState()
|
||||
const type = comboboxState.value?.id
|
||||
|
||||
const tokenIdState = useInputState({
|
||||
id: 'token-id',
|
||||
name: 'tokenId',
|
||||
title: 'Token ID',
|
||||
subtitle: 'Enter the token ID',
|
||||
placeholder: '1',
|
||||
})
|
||||
const tokenId = tokenIdState.value
|
||||
|
||||
const addressState = useInputState({
|
||||
id: 'address',
|
||||
name: 'address',
|
||||
title: 'User Address',
|
||||
subtitle: 'Address of the user',
|
||||
})
|
||||
const address = addressState.value
|
||||
|
||||
const showTokenIdField = type === 'token_info'
|
||||
const showAddressField = type === 'tokens_minted_to_user'
|
||||
|
||||
const { data: response } = useQuery(
|
||||
[sg721Messages, minterMessages, type, tokenId, address] as const,
|
||||
async ({ queryKey }) => {
|
||||
const [_sg721Messages, _minterMessages, _type, _tokenId, _address] = queryKey
|
||||
const result = await dispatchQuery({
|
||||
tokenId: _tokenId,
|
||||
minterMessages: _minterMessages,
|
||||
sg721Messages: _sg721Messages,
|
||||
address: _address,
|
||||
type: _type,
|
||||
})
|
||||
return result
|
||||
},
|
||||
{
|
||||
placeholderData: null,
|
||||
onError: (error: any) => {
|
||||
toast.error(error.message)
|
||||
},
|
||||
enabled: Boolean(sg721ContractAddress && minterContractAddress && type),
|
||||
retry: false,
|
||||
},
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 mt-4">
|
||||
<div className="mr-2 space-y-8">
|
||||
<QueryCombobox {...comboboxState} />
|
||||
{showAddressField && <AddressInput {...addressState} />}
|
||||
{showTokenIdField && <TextInput {...tokenIdState} />}
|
||||
</div>
|
||||
<div className="space-y-8">
|
||||
<FormControl title="Query Response">
|
||||
<JsonPreview content={response || {}} isCopyable />
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
18
env.
Normal file
18
env.
Normal file
@ -0,0 +1,18 @@
|
||||
APP_VERSION=0.1.0
|
||||
|
||||
NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS
|
||||
NEXT_PUBLIC_WHITELIST_CODE_ID=3
|
||||
NEXT_PUBLIC_MINTER_CODE_ID=2
|
||||
NEXT_PUBLIC_SG721_CODE_ID=1
|
||||
|
||||
NEXT_PUBLIC_API_URL=https://
|
||||
NEXT_PUBLIC_BLOCK_EXPLORER_URL=https://testnet-explorer.publicawesome.dev/stargaze
|
||||
NEXT_PUBLIC_NETWORK=testnet
|
||||
NEXT_PUBLIC_STARGAZE_WEBSITE_URL=https://testnet.publicawesome.dev
|
||||
NEXT_PUBLIC_WEBSITE_URL=https://
|
||||
|
||||
NEXT_PUBLIC_S3_BUCKET= # TODO
|
||||
NEXT_PUBLIC_S3_ENDPOINT= # TODO
|
||||
NEXT_PUBLIC_S3_KEY= # TODO
|
||||
NEXT_PUBLIC_S3_REGION= # TODO
|
||||
NEXT_PUBLIC_S3_SECRET= # TODO
|
1
env.d.ts
vendored
1
env.d.ts
vendored
@ -22,6 +22,7 @@ declare namespace NodeJS {
|
||||
readonly NEXT_PUBLIC_API_URL: string
|
||||
readonly NEXT_PUBLIC_BLOCK_EXPLORER_URL: string
|
||||
readonly NEXT_PUBLIC_NETWORK: string
|
||||
readonly NEXT_PUBLIC_STARGAZE_WEBSITE_URL: string
|
||||
readonly NEXT_PUBLIC_WEBSITE_URL: string
|
||||
}
|
||||
}
|
||||
|
@ -1,42 +1,22 @@
|
||||
import { Button } from 'components/Button'
|
||||
import type { DispatchExecuteArgs } from 'components/collections/actions/actions'
|
||||
import { dispatchExecute, isEitherType, previewExecutePayload } from 'components/collections/actions/actions'
|
||||
import { ActionsCombobox } from 'components/collections/actions/Combobox'
|
||||
import { useActionsComboboxState } from 'components/collections/actions/Combobox.hooks'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { CollectionActions } from 'components/collections/actions/Action'
|
||||
import { CollectionQueries } from 'components/collections/queries/Queries'
|
||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||
import { FormControl } from 'components/FormControl'
|
||||
import { FormGroup } from 'components/FormGroup'
|
||||
import { AddressInput, NumberInput } from 'components/forms/FormInput'
|
||||
import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks'
|
||||
import { InputDateTime } from 'components/InputDateTime'
|
||||
import { JsonPreview } from 'components/JsonPreview'
|
||||
import { TransactionHash } from 'components/TransactionHash'
|
||||
import { WhitelistUpload } from 'components/WhitelistUpload'
|
||||
import { AddressInput } from 'components/forms/FormInput'
|
||||
import { useInputState } from 'components/forms/FormInput.hooks'
|
||||
import { useContracts } from 'contexts/contracts'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { NextPage } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import type { FormEvent } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
import { TextInput } from '../../components/forms/FormInput'
|
||||
|
||||
const CollectionActionsPage: NextPage = () => {
|
||||
const { minter: minterContract, sg721: sg721Contract } = useContracts()
|
||||
const wallet = useWallet()
|
||||
const [lastTx, setLastTx] = useState('')
|
||||
|
||||
const [timestamp, setTimestamp] = useState<Date | undefined>(undefined)
|
||||
const [airdropArray, setAirdropArray] = useState<string[]>([])
|
||||
|
||||
const comboboxState = useActionsComboboxState()
|
||||
const type = comboboxState.value?.id
|
||||
const [action, setAction] = useState<boolean>(false)
|
||||
|
||||
const sg721ContractState = useInputState({
|
||||
id: 'sg721-contract-address',
|
||||
@ -52,58 +32,6 @@ const CollectionActionsPage: NextPage = () => {
|
||||
subtitle: 'Address of the Minter contract',
|
||||
})
|
||||
|
||||
const limitState = useNumberInputState({
|
||||
id: 'per-address-limi',
|
||||
name: 'perAddressLimit',
|
||||
title: 'Per Address Limit',
|
||||
subtitle: 'Enter the per address limit',
|
||||
})
|
||||
|
||||
const tokenIdState = useNumberInputState({
|
||||
id: 'token-id',
|
||||
name: 'tokenId',
|
||||
title: 'Token ID',
|
||||
subtitle: 'Enter the token ID',
|
||||
})
|
||||
|
||||
const batchNumberState = useNumberInputState({
|
||||
id: 'batch-number',
|
||||
name: 'batchNumber',
|
||||
title: 'Number of Tokens',
|
||||
subtitle: 'Enter the number of tokens to mint',
|
||||
})
|
||||
|
||||
const tokenIdListState = useInputState({
|
||||
id: 'token-id-list',
|
||||
name: 'tokenIdList',
|
||||
title: 'List of token IDs',
|
||||
subtitle:
|
||||
'Specify individual token IDs separated by commas (e.g., 2, 4, 8) or a range of IDs separated by a colon (e.g., 8:13)',
|
||||
})
|
||||
|
||||
const recipientState = useInputState({
|
||||
id: 'recipient-address',
|
||||
name: 'recipient',
|
||||
title: 'Recipient Address',
|
||||
subtitle: 'Address of the recipient',
|
||||
})
|
||||
|
||||
const whitelistState = useInputState({
|
||||
id: 'whitelist-address',
|
||||
name: 'whitelistAddress',
|
||||
title: 'Whitelist Address',
|
||||
subtitle: 'Address of the whitelist contract',
|
||||
})
|
||||
|
||||
const showWhitelistField = type === 'set_whitelist'
|
||||
const showDateField = type === 'update_start_time'
|
||||
const showLimitField = type === 'update_per_address_limit'
|
||||
const showTokenIdField = isEitherType(type, ['transfer', 'mint_for', 'burn'])
|
||||
const showNumberOfTokensField = type === 'batch_mint'
|
||||
const showTokenIdListField = isEitherType(type, ['batch_burn', 'batch_transfer'])
|
||||
const showRecipientField = isEitherType(type, ['transfer', 'mint_to', 'mint_for', 'batch_mint', 'batch_transfer'])
|
||||
const showAirdropFileField = type === 'airdrop'
|
||||
|
||||
const minterMessages = useMemo(
|
||||
() => minterContract?.use(minterContractState.value),
|
||||
[minterContract, minterContractState.value],
|
||||
@ -112,50 +40,31 @@ const CollectionActionsPage: NextPage = () => {
|
||||
() => sg721Contract?.use(sg721ContractState.value),
|
||||
[sg721Contract, sg721ContractState.value],
|
||||
)
|
||||
const payload: DispatchExecuteArgs = {
|
||||
whitelist: whitelistState.value,
|
||||
startTime: timestamp ? (timestamp.getTime() * 1_000_000).toString() : '',
|
||||
limit: limitState.value,
|
||||
minterContract: minterContractState.value,
|
||||
sg721Contract: sg721ContractState.value,
|
||||
tokenId: tokenIdState.value,
|
||||
tokenIds: tokenIdListState.value,
|
||||
batchNumber: batchNumberState.value,
|
||||
minterMessages,
|
||||
sg721Messages,
|
||||
recipient: recipientState.value,
|
||||
recipients: airdropArray,
|
||||
txSigner: wallet.address,
|
||||
type,
|
||||
}
|
||||
const { isLoading, mutate } = useMutation(
|
||||
async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!type) {
|
||||
throw new Error('Please select an action!')
|
||||
}
|
||||
if (minterContractState.value === '' && sg721ContractState.value === '') {
|
||||
throw new Error('Please enter minter and sg721 contract addresses!')
|
||||
}
|
||||
const txHash = await toast.promise(dispatchExecute(payload), {
|
||||
error: `${type.charAt(0).toUpperCase() + type.slice(1)} execute failed!`,
|
||||
loading: 'Executing message...',
|
||||
success: (tx) => `Transaction ${tx} success!`,
|
||||
})
|
||||
if (txHash) {
|
||||
setLastTx(txHash)
|
||||
}
|
||||
},
|
||||
{
|
||||
onError: (error) => {
|
||||
toast.error(String(error))
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const airdropFileOnChange = (data: string[]) => {
|
||||
setAirdropArray(data)
|
||||
}
|
||||
const sg721ContractAddress = sg721ContractState.value
|
||||
const minterContractAddress = minterContractState.value
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (minterContractAddress.length > 0 && sg721ContractAddress.length === 0) {
|
||||
void router.replace({ query: { minterContractAddress } })
|
||||
}
|
||||
if (sg721ContractAddress.length > 0 && minterContractAddress.length === 0) {
|
||||
void router.replace({ query: { sg721ContractAddress } })
|
||||
}
|
||||
if (sg721ContractAddress.length > 0 && minterContractAddress.length > 0) {
|
||||
void router.replace({ query: { sg721ContractAddress, minterContractAddress } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [sg721ContractAddress, minterContractAddress])
|
||||
|
||||
useEffect(() => {
|
||||
const initialMinter = new URL(document.URL).searchParams.get('minterContractAddress')
|
||||
const initialSg721 = new URL(document.URL).searchParams.get('sg721ContractAddress')
|
||||
if (initialMinter && initialMinter.length > 0) minterContractState.onChange(initialMinter)
|
||||
if (initialSg721 && initialSg721.length > 0) sg721ContractState.onChange(initialSg721)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
@ -166,40 +75,71 @@ const CollectionActionsPage: NextPage = () => {
|
||||
title="Collection Actions"
|
||||
/>
|
||||
|
||||
<form className="grid grid-cols-2 p-4 space-x-8" onSubmit={mutate}>
|
||||
<div className="space-y-8">
|
||||
<AddressInput {...sg721ContractState} />
|
||||
<form className="p-4">
|
||||
<div className="grid grid-cols-2">
|
||||
<AddressInput {...sg721ContractState} className="mr-2" />
|
||||
<AddressInput {...minterContractState} />
|
||||
<ActionsCombobox {...comboboxState} />
|
||||
{showRecipientField && <AddressInput {...recipientState} />}
|
||||
{showWhitelistField && <AddressInput {...whitelistState} />}
|
||||
{showLimitField && <NumberInput {...limitState} />}
|
||||
{showTokenIdField && <NumberInput {...tokenIdState} />}
|
||||
{showTokenIdListField && <TextInput {...tokenIdListState} />}
|
||||
{showNumberOfTokensField && <NumberInput {...batchNumberState} />}
|
||||
{showAirdropFileField && (
|
||||
<FormGroup subtitle="TXT file that contains the airdrop addresses" title="Airdrop File">
|
||||
<WhitelistUpload onChange={airdropFileOnChange} />
|
||||
</FormGroup>
|
||||
)}
|
||||
<Conditional test={showDateField}>
|
||||
<FormControl htmlId="start-date" subtitle="Start time for the minting" title="Start Time">
|
||||
<InputDateTime minDate={new Date()} onChange={(date) => setTimestamp(date)} value={timestamp} />
|
||||
</FormControl>
|
||||
</Conditional>
|
||||
</div>
|
||||
<div className="space-y-8">
|
||||
<div className="relative">
|
||||
<Button className="absolute top-0 right-0" isLoading={isLoading} rightIcon={<FaArrowRight />} type="submit">
|
||||
Execute
|
||||
</Button>
|
||||
<FormControl subtitle="View execution transaction hash" title="Transaction Hash">
|
||||
<TransactionHash hash={lastTx} />
|
||||
</FormControl>
|
||||
<div className="mt-4">
|
||||
<div className="mr-2">
|
||||
<div className="flex justify-items-start font-bold">
|
||||
<div className="form-check form-check-inline">
|
||||
<input
|
||||
checked={!action}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio4"
|
||||
name="inlineRadioOptions4"
|
||||
onClick={() => {
|
||||
setAction(false)
|
||||
}}
|
||||
type="radio"
|
||||
value="false"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio4"
|
||||
>
|
||||
Queries
|
||||
</label>
|
||||
</div>
|
||||
<div className="ml-2 form-check form-check-inline">
|
||||
<input
|
||||
checked={action}
|
||||
className="peer sr-only"
|
||||
id="inlineRadio3"
|
||||
name="inlineRadioOptions3"
|
||||
onClick={() => {
|
||||
setAction(true)
|
||||
}}
|
||||
type="radio"
|
||||
value="true"
|
||||
/>
|
||||
<label
|
||||
className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black hover:rounded-sm peer-checked:border-b-2 hover:border-b-2 peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
|
||||
htmlFor="inlineRadio3"
|
||||
>
|
||||
Actions
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{(action && (
|
||||
<CollectionActions
|
||||
minterContractAddress={minterContractState.value}
|
||||
minterMessages={minterMessages}
|
||||
sg721ContractAddress={sg721ContractState.value}
|
||||
sg721Messages={sg721Messages}
|
||||
/>
|
||||
)) || (
|
||||
<CollectionQueries
|
||||
minterContractAddress={minterContractState.value}
|
||||
minterMessages={minterMessages}
|
||||
sg721ContractAddress={sg721ContractState.value}
|
||||
sg721Messages={sg721Messages}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<FormControl subtitle="View current message to be sent" title="Payload Preview">
|
||||
<JsonPreview content={previewExecutePayload(payload)} isCopyable />
|
||||
</FormControl>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
@ -29,7 +29,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { upload } from 'services/upload'
|
||||
import { compareFileArrays } from 'utils/compareFileArrays'
|
||||
import { MINTER_CODE_ID, SG721_CODE_ID, WHITELIST_CODE_ID } from 'utils/constants'
|
||||
import { MINTER_CODE_ID, SG721_CODE_ID, STARGAZE_URL, WHITELIST_CODE_ID } from 'utils/constants'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
@ -37,6 +37,14 @@ import type { UploadMethod } from '../../components/collections/creation/UploadD
|
||||
import { ConfirmationModal } from '../../components/ConfirmationModal'
|
||||
import { getAssetType } from '../../utils/getAssetType'
|
||||
|
||||
export interface CollectionData {
|
||||
name: string
|
||||
address: string
|
||||
minter: string
|
||||
imageURL: string
|
||||
time: number
|
||||
}
|
||||
|
||||
const CollectionCreationPage: NextPage = () => {
|
||||
const wallet = useWallet()
|
||||
const { minter: minterContract, whitelist: whitelistContract } = useContracts()
|
||||
@ -49,21 +57,23 @@ const CollectionCreationPage: NextPage = () => {
|
||||
const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
|
||||
|
||||
const [uploading, setUploading] = useState(false)
|
||||
const [creatingCollection, setCreatingCollection] = useState(false)
|
||||
const [readyToCreate, setReadyToCreate] = useState(false)
|
||||
const [minterContractAddress, setMinterContractAddress] = useState<string | null>(null)
|
||||
const [sg721ContractAddress, setSg721ContractAddress] = useState<string | null>(null)
|
||||
const [whitelistContractAddress, setWhitelistContractAddress] = useState<string | null | undefined>(null)
|
||||
const [baseTokenUri, setBaseTokenUri] = useState<string | null>(null)
|
||||
const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
|
||||
const [transactionHash, setTransactionHash] = useState<string | null>(null)
|
||||
|
||||
const performChecks = () => {
|
||||
try {
|
||||
// setReadyToCreate(false)
|
||||
// checkUploadDetails()
|
||||
// checkCollectionDetails()
|
||||
// checkMintingDetails()
|
||||
// checkWhitelistDetails()
|
||||
// checkRoyaltyDetails()
|
||||
setReadyToCreate(false)
|
||||
checkUploadDetails()
|
||||
checkCollectionDetails()
|
||||
checkMintingDetails()
|
||||
checkWhitelistDetails()
|
||||
checkRoyaltyDetails()
|
||||
setReadyToCreate(true)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
@ -74,10 +84,12 @@ const CollectionCreationPage: NextPage = () => {
|
||||
|
||||
const createCollection = async () => {
|
||||
try {
|
||||
setCreatingCollection(true)
|
||||
setBaseTokenUri(null)
|
||||
setCoverImageUrl(null)
|
||||
setMinterContractAddress(null)
|
||||
setSg721ContractAddress(null)
|
||||
setWhitelistContractAddress(null)
|
||||
setTransactionHash(null)
|
||||
if (uploadDetails?.uploadMethod === 'new') {
|
||||
setUploading(true)
|
||||
@ -101,6 +113,7 @@ const CollectionCreationPage: NextPage = () => {
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiate(baseUri, coverImageUri, whitelist)
|
||||
} else {
|
||||
@ -110,12 +123,15 @@ const CollectionCreationPage: NextPage = () => {
|
||||
let whitelist: string | undefined
|
||||
if (whitelistDetails?.whitelistType === 'existing') whitelist = whitelistDetails.contractAddress
|
||||
else if (whitelistDetails?.whitelistType === 'new') whitelist = await instantiateWhitelist()
|
||||
setWhitelistContractAddress(whitelist as string)
|
||||
|
||||
await instantiate(baseTokenUri as string, coverImageUrl as string, whitelist)
|
||||
}
|
||||
setCreatingCollection(false)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
toast.error(error.message)
|
||||
setCreatingCollection(false)
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
@ -185,6 +201,41 @@ const CollectionCreationPage: NextPage = () => {
|
||||
setTransactionHash(data.transactionHash)
|
||||
setMinterContractAddress(data.contractAddress)
|
||||
setSg721ContractAddress(data.logs[0].events[3].attributes[2].value)
|
||||
console.log(`ipfs://${coverImageUri}/${collectionDetails?.imageFile[0].name as string}`)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const allCollections: Record<string, CollectionData[]>[] = localStorage['collections']
|
||||
? JSON.parse(localStorage['collections'])
|
||||
: []
|
||||
console.log('allCollections', allCollections)
|
||||
|
||||
const newCollectionData: CollectionData = {
|
||||
name: collectionDetails?.name as string,
|
||||
address: data.logs[0].events[3].attributes[2].value,
|
||||
minter: data.contractAddress,
|
||||
imageURL: `https://ipfs.stargaze.zone/ipfs/${coverImageUri}/${collectionDetails?.imageFile[0].name as string}`,
|
||||
time: new Date().getTime(),
|
||||
}
|
||||
|
||||
//Get the CollectionData array for the current wallet address
|
||||
const myCollections = allCollections.find((c) => Object.keys(c)[0] === wallet.address)
|
||||
console.log('myCollections', myCollections)
|
||||
if (myCollections === undefined) {
|
||||
//If there is no CollectionData array for the current wallet address, create one in allCollections
|
||||
allCollections.push({ [wallet.address]: [newCollectionData] })
|
||||
//allCollections[allCollections.indexOf(myCollections)] = {[wallet.address]: myCollectionList}
|
||||
} else {
|
||||
//If there is a CollectionData array for the current wallet address, push the new collection data to it in allCollections
|
||||
allCollections[allCollections.indexOf(myCollections)][wallet.address].push(newCollectionData)
|
||||
}
|
||||
|
||||
//List of all collections for the current wallet address
|
||||
const myCollectionList = myCollections ? Object.values(myCollections)[0] : []
|
||||
console.log('myCollectionList', myCollectionList)
|
||||
|
||||
//Update the localStorage
|
||||
localStorage['collections'] = JSON.stringify(allCollections)
|
||||
|
||||
console.log(localStorage['collections'])
|
||||
}
|
||||
|
||||
const uploadFiles = async (): Promise<string> => {
|
||||
@ -265,7 +316,7 @@ const CollectionCreationPage: NextPage = () => {
|
||||
if (uploadDetails.uploadMethod === 'new') {
|
||||
if (uploadDetails.uploadService === 'nft-storage') {
|
||||
if (uploadDetails.nftStorageApiKey === '') {
|
||||
throw new Error('Please enter a valid NFT Storage API key')
|
||||
throw new Error('Please enter a valid NFT.Storage API key')
|
||||
}
|
||||
} else if (uploadDetails.pinataApiKey === '' || uploadDetails.pinataSecretKey === '') {
|
||||
throw new Error('Please enter Pinata API and secret keys')
|
||||
@ -409,6 +460,17 @@ const CollectionCreationPage: NextPage = () => {
|
||||
{sg721ContractAddress}
|
||||
</Anchor>
|
||||
<br />
|
||||
<Conditional test={whitelistContractAddress !== null && whitelistContractAddress !== undefined}>
|
||||
Whitelist Contract Address:{' '}
|
||||
<Anchor
|
||||
className="text-stargaze hover:underline"
|
||||
external
|
||||
href={`/contracts/whitelist/query/?contractAddress=${whitelistContractAddress as string}`}
|
||||
>
|
||||
{whitelistContractAddress}
|
||||
</Anchor>
|
||||
<br />
|
||||
</Conditional>
|
||||
Transaction Hash: {' '}
|
||||
<Anchor
|
||||
className="text-stargaze hover:underline"
|
||||
@ -417,6 +479,15 @@ const CollectionCreationPage: NextPage = () => {
|
||||
>
|
||||
{transactionHash}
|
||||
</Anchor>
|
||||
<Button className="mt-2">
|
||||
<Anchor
|
||||
className="text-white"
|
||||
external
|
||||
href={`${STARGAZE_URL}/launchpad/${minterContractAddress as string}`}
|
||||
>
|
||||
View on Launchpad
|
||||
</Anchor>
|
||||
</Button>
|
||||
</div>
|
||||
</Alert>
|
||||
</Conditional>
|
||||
@ -443,9 +514,9 @@ const CollectionCreationPage: NextPage = () => {
|
||||
</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">
|
||||
<Button className="px-0 mb-6 max-h-12" isLoading={creatingCollection} 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"
|
||||
className="relative justify-end w-full h-full text-white bg-plumbus hover:bg-plumbus-light border-0 btn modal-button"
|
||||
htmlFor="my-modal-2"
|
||||
>
|
||||
Create Collection
|
||||
|
@ -27,6 +27,13 @@ const HomePage: NextPage = () => {
|
||||
>
|
||||
Upload your assets, enter collection metadata and deploy your collection.
|
||||
</HomeCard>
|
||||
<HomeCard
|
||||
className="p-4 -m-4 hover:bg-gray-500/10 rounded"
|
||||
link="/collections/myCollections"
|
||||
title="My Collections"
|
||||
>
|
||||
View a list of your collections.
|
||||
</HomeCard>
|
||||
<HomeCard
|
||||
className="p-4 -m-4 hover:bg-gray-500/10 rounded"
|
||||
link="/collections/actions"
|
||||
@ -34,13 +41,6 @@ const HomePage: NextPage = () => {
|
||||
>
|
||||
Execute messages on a collection.
|
||||
</HomeCard>
|
||||
<HomeCard
|
||||
className="p-4 -m-4 hover:bg-gray-500/10 rounded"
|
||||
link="/collections/queries"
|
||||
title="Collection Queries"
|
||||
>
|
||||
Query data from a collection.
|
||||
</HomeCard>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
118
pages/collections/myCollections.tsx
Normal file
118
pages/collections/myCollections.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import { Alert } from 'components/Alert'
|
||||
import { Anchor } from 'components/Anchor'
|
||||
import { Button } from 'components/Button'
|
||||
import { Conditional } from 'components/Conditional'
|
||||
import { ContractPageHeader } from 'components/ContractPageHeader'
|
||||
import { useWallet } from 'contexts/wallet'
|
||||
import type { NextPage } from 'next'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { FaRocket, FaSlidersH } from 'react-icons/fa'
|
||||
import { STARGAZE_URL } from 'utils/constants'
|
||||
import { withMetadata } from 'utils/layout'
|
||||
import { links } from 'utils/links'
|
||||
|
||||
import type { CollectionData } from './create'
|
||||
|
||||
const CollectionList: NextPage = () => {
|
||||
const wallet = useWallet()
|
||||
const [clearFlag, setClearFlag] = useState(false)
|
||||
let allCollections: Record<string, CollectionData[]>[] = []
|
||||
let myCollections: Record<string, CollectionData[]> | undefined
|
||||
let myCollectionList: CollectionData[] = []
|
||||
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
allCollections = localStorage['collections'] ? JSON.parse(localStorage['collections']) : []
|
||||
myCollections = allCollections.find((c) => Object.keys(c)[0] === wallet.address)
|
||||
myCollectionList = myCollections ? Object.values(myCollections)[0] : []
|
||||
console.log(localStorage['collections'])
|
||||
}
|
||||
|
||||
const renderTable = useCallback(() => {
|
||||
return (
|
||||
<div className="overflow-x-auto w-full">
|
||||
{myCollectionList.length > 0 && (
|
||||
<table className="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="pl-20 text-lg font-bold text-left bg-black">Collection Name</th>
|
||||
<th className="text-lg font-bold bg-black">Contract Address</th>
|
||||
<th className="text-lg font-bold bg-black">Creation Time</th>
|
||||
<th className="bg-black" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{myCollectionList.map((collection, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="bg-black">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="avatar">
|
||||
<div className="w-12 h-12 mask mask-squircle">
|
||||
<img alt="Cover" src={collection.imageURL} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="ml-2 font-bold">{collection.name}</div>
|
||||
<div className="text-sm opacity-50" />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="bg-black">
|
||||
{collection.address}
|
||||
{/* <br /> */}
|
||||
{/* <span className="badge badge-ghost badge-sm"></span> */}
|
||||
</td>
|
||||
<td className="bg-black">{new Date(collection.time).toDateString()}</td>
|
||||
<th className="bg-black">
|
||||
<div className="flex items-center space-x-8">
|
||||
<Anchor
|
||||
className="text-xl text-plumbus"
|
||||
href={`/collections/actions?sg721ContractAddress=${collection.address}&minterContractAddress=${collection.minter}`}
|
||||
>
|
||||
<FaSlidersH />
|
||||
</Anchor>
|
||||
|
||||
<Anchor
|
||||
className="text-xl text-plumbus"
|
||||
external
|
||||
href={`${STARGAZE_URL}/launchpad/${collection.minter}`}
|
||||
>
|
||||
<FaRocket />
|
||||
</Anchor>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}, [clearFlag, wallet.address])
|
||||
|
||||
const clearMyCollections = () => {
|
||||
console.log('Cleared!')
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage['collections'] = JSON.stringify(allCollections.filter((c) => Object.keys(c)[0] !== wallet.address))
|
||||
myCollectionList = []
|
||||
setClearFlag(!clearFlag)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="My Collections" />
|
||||
<ContractPageHeader description="A list of your collections." link={links.Documentation} title="My Collections" />
|
||||
<hr />
|
||||
<div>{renderTable()}</div>
|
||||
<br />
|
||||
{myCollectionList.length > 0 && <Button onClick={clearMyCollections}>Clear Collection List</Button>}
|
||||
<Conditional test={myCollectionList.length === 0}>
|
||||
<Alert type="info">You haven't created any collections so far.</Alert>
|
||||
</Conditional>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
export default withMetadata(CollectionList, { center: false })
|
@ -16,9 +16,10 @@ import { useWallet } from 'contexts/wallet'
|
||||
import type { DispatchExecuteArgs } from 'contracts/minter/messages/execute'
|
||||
import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/minter/messages/execute'
|
||||
import type { NextPage } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import type { FormEvent } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
@ -62,6 +63,7 @@ const MinterExecutePage: NextPage = () => {
|
||||
title: 'Minter Address',
|
||||
subtitle: 'Address of the Minter contract',
|
||||
})
|
||||
const contractAddress = contractState.value
|
||||
|
||||
const recipientState = useInputState({
|
||||
id: 'recipient-address',
|
||||
@ -122,6 +124,19 @@ const MinterExecutePage: NextPage = () => {
|
||||
},
|
||||
)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (contractAddress.length > 0) {
|
||||
void router.replace({ query: { contractAddress } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [contractAddress])
|
||||
useEffect(() => {
|
||||
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="Execute Minter Contract" />
|
||||
|
@ -15,9 +15,10 @@ import { useWallet } from 'contexts/wallet'
|
||||
import type { DispatchExecuteArgs } from 'contracts/sg721/messages/execute'
|
||||
import { dispatchExecute, isEitherType, previewExecutePayload } from 'contracts/sg721/messages/execute'
|
||||
import type { NextPage } from 'next'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NextSeo } from 'next-seo'
|
||||
import type { FormEvent } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { toast } from 'react-hot-toast'
|
||||
import { FaArrowRight } from 'react-icons/fa'
|
||||
import { useMutation } from 'react-query'
|
||||
@ -49,6 +50,8 @@ const Sg721ExecutePage: NextPage = () => {
|
||||
subtitle: 'Address of the Sg721 contract',
|
||||
})
|
||||
|
||||
const contractAddress = contractState.value
|
||||
|
||||
const messageState = useInputState({
|
||||
id: 'message',
|
||||
name: 'message',
|
||||
@ -121,6 +124,19 @@ const Sg721ExecutePage: NextPage = () => {
|
||||
},
|
||||
)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (contractAddress.length > 0) {
|
||||
void router.replace({ query: { contractAddress } })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [contractAddress])
|
||||
useEffect(() => {
|
||||
const initial = new URL(document.URL).searchParams.get('contractAddress')
|
||||
if (initial && initial.length > 0) contractState.onChange(initial)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<section className="py-6 px-12 space-y-4">
|
||||
<NextSeo title="Execute Sg721 Contract" />
|
||||
|
@ -40,7 +40,7 @@ const WhitelistQueryPage: NextPage = () => {
|
||||
})
|
||||
const address = addressState.value
|
||||
|
||||
const [type, setType] = useState<QueryType>('has_started')
|
||||
const [type, setType] = useState<QueryType>('config')
|
||||
|
||||
const addressVisible = type === 'has_member'
|
||||
|
||||
@ -98,6 +98,7 @@ const WhitelistQueryPage: NextPage = () => {
|
||||
'placeholder:text-white/50',
|
||||
'focus:ring focus:ring-plumbus-20',
|
||||
)}
|
||||
defaultValue="config"
|
||||
id="contract-query-type"
|
||||
name="query-type"
|
||||
onChange={(e) => setType(e.target.value as QueryType)}
|
||||
|
@ -6,7 +6,7 @@ const HomePage: NextPage = () => {
|
||||
return (
|
||||
<section className="px-8 pt-4 pb-16 mx-auto space-y-8 max-w-4xl">
|
||||
<div className="flex justify-center items-center py-8 max-w-xl">
|
||||
<img alt="Brand Text" className="ml-[50%] w-full" src="/stargaze-text.png" />
|
||||
<img alt="Brand Text" className="ml-[50%] w-full" src="/stargaze_logo_800.svg" />
|
||||
</div>
|
||||
<h1 className="font-heading text-4xl font-bold">Welcome!</h1>
|
||||
<p className="text-xl">
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 991 KiB |
16
public/stargaze_logo_800.svg
Normal file
16
public/stargaze_logo_800.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="801px" height="161px" viewBox="0 0 801 161" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>stargaze_logo</title>
|
||||
<defs>
|
||||
<linearGradient x1="0.0995425432%" y1="47.2856461%" x2="104.967972%" y2="50.3175741%" id="linearGradient-1">
|
||||
<stop stop-color="#FFF59E" offset="0%"></stop>
|
||||
<stop stop-color="#81EFD9" offset="63.5417%"></stop>
|
||||
<stop stop-color="#6B9BE3" offset="100%"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="stargaze_logo" transform="translate(0.357912, -0.000003)" fill="url(#linearGradient-1)">
|
||||
<path d="M41.3060853,35.0506617 L77.4908947,35.0506617 L77.4908947,0.0952535567 L41.9975438,0.0952535567 C6.73463446,-0.86903264 0.972725983,4.91668454 0.0178962024,43.9703426 L0.0178962024,53.8539743 C-0.410124003,88.809852 6.73463446,98.6934837 32.7784769,97.9705038 L39.2317768,97.9705038 L39.2317768,125.934669 L0.742217452,125.934669 L0.742217452,160.889876 L39.0013353,160.889876 C66.658268,160.889876 68.5026045,160.408337 75.4165191,151.970665 C80.0260191,144.255973 80.9481873,139.193772 81.6396458,115.568827 L81.6396458,105.684525 C81.6396458,71.6937384 74.7250606,62.5330866 48.6813523,63.0152968 L41.3060853,63.0152968 L41.3060853,35.0506617 Z M146.173316,35.0501252 L164.842025,35.0501252 L164.842025,0.0947170224 L86.2498165,0.0947170224 L86.2498165,35.0501252 L104.918526,35.0501252 L104.918526,160.889876 L146.173316,160.889876 L146.173316,35.0501252 Z M215.051572,160.889876 L173.796112,160.889876 L173.796112,32.8804477 C174.25686,4.43400491 179.557819,0.0947170224 215.314474,0.0947170224 L232.139518,0.0947170224 C267.86331,0.0947170224 273.164268,4.19289983 273.625017,32.8804477 L273.625017,160.889876 L232.370227,160.889876 L232.370227,126.898419 L215.084435,126.898419 L215.084435,160.889876 L215.051572,160.889876 Z M232.337365,91.2195615 L232.337365,48.7911029 C232.337365,38.4251269 229.57153,33.8447339 223.579113,33.8447339 C217.586696,33.8447339 215.051572,38.6661649 215.051572,48.7911029 L215.051572,91.2195615 L232.337365,91.2195615 Z M388.369575,31.1927121 C387.217368,3.46945045 382.147119,-0.628732359 351.493575,0.0944487553 L288.573532,0.0944487553 L288.573532,160.889206 L329.828993,160.889206 L329.828993,97.9698331 L335.129951,97.9698331 C343.887532,98.2106029 347.344824,102.309054 347.114785,112.434126 L347.114785,133.88946 C347.114785,145.219723 348.036283,153.898164 350.341368,160.889206 L391.596158,160.889206 C389.752492,154.139605 388.369575,144.496743 388.369575,133.88946 L388.369575,109.058655 C388.830995,90.9787917 383.068617,83.2640997 365.782824,78.4426687 C383.068617,76.9960382 389.061034,66.8716368 388.369575,41.5588223 L388.369575,31.1927121 Z M347.344824,49.2733131 C347.344824,60.6035753 345.270449,63.0139555 335.5907,63.0139555 L329.828993,63.0139555 L329.828993,35.0498569 L335.5907,35.0498569 C344.348951,35.0498569 347.344824,38.6659637 347.344824,49.2733131 Z M501.568244,125.211689 C502.028993,157.273635 497.189454,161.613527 460.774203,160.889876 L443.718449,160.889876 C404.537364,161.372086 401.772201,158.479496 402.23295,117.496997 L402.00224,104.961545 L402.00224,56.7472353 C402.430126,4.43440731 405.887419,-0.14598566 443.685587,0.0951194231 L501.535381,0.0951194231 L501.535381,39.6308535 C496.695171,37.220138 485.171757,35.5326707 471.573296,35.0505276 L457.053338,35.0505276 C444.377045,35.0505276 443.224838,36.2558518 443.224838,49.7561939 L443.224838,106.889715 C442.994128,123.765059 444.146335,125.693229 453.596045,125.934669 L460.27992,125.934669 L460.27992,87.1217811 C460.27992,80.6129498 459.127713,75.0685389 457.053338,69.0419179 L501.535381,69.0419179 L501.535381,125.211689 L501.568244,125.211689 Z M515.59392,160.889876 L556.84871,160.889876 L556.84871,126.898419 L574.134502,126.898419 L574.134502,160.889876 L615.389963,160.889876 L615.389963,32.8804477 C614.929214,4.19289983 609.628256,0.0947170224 573.904463,0.0947170224 L557.07942,0.0947170224 C521.355627,0.0947170224 516.054669,4.43400491 515.59392,32.8804477 L515.59392,160.889876 Z M574.134502,48.7911029 L574.134502,91.2195615 L556.84871,91.2195615 L556.84871,48.7911029 C556.84871,38.6661649 559.384505,33.8447339 565.376922,33.8447339 C571.369339,33.8447339 574.134502,38.4251269 574.134502,48.7911029 Z M709.42631,0.0947170224 L675.081411,125.933999 L711.961435,125.933999 L707.34724,160.889876 L624.839002,160.889876 L659.642638,35.0501252 L625.300421,35.0501252 L630.13996,0.0947170224 L709.42631,0.0947170224 Z M761.973134,35.0497899 L800,35.0497899 L800,0.0943146217 L761.510374,0.0943146217 C725.555872,-0.387828477 721.639171,4.43366958 720.720357,51.6836932 L720.720357,103.032034 C720.485623,126.656979 722.329959,144.014533 725.555872,149.558943 C731.779669,159.924785 734.086766,160.648436 761.510374,160.889206 L800,160.889206 L800,125.933999 L761.973134,125.933999 L761.973134,97.9691624 L800,97.9691624 L800,63.0139555 L761.973134,63.0139555 L761.973134,35.0497899 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
@ -2,7 +2,7 @@ import type { CIDString } from 'nft.storage'
|
||||
import { NFTStorage } from 'nft.storage'
|
||||
|
||||
export const uploadToNftStorage = async (fileArray: File[], nftStorageApiKey: string): Promise<CIDString> => {
|
||||
console.log('Uploading to NFT Storage...')
|
||||
console.log('Uploading to NFT.Storage...')
|
||||
const client = new NFTStorage({ token: nftStorageApiKey })
|
||||
return client.storeDirectory(fileArray)
|
||||
}
|
||||
|
@ -12,24 +12,24 @@ module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
stargaze: { DEFAULT: '#FFA900' },
|
||||
stargaze: { DEFAULT: '#DB2676' },
|
||||
dark: { DEFAULT: '#06090B' },
|
||||
gray: { DEFAULT: '#F3F6F8' },
|
||||
gray: { DEFAULT: '#A9A9A9' },
|
||||
'dark-gray': { DEFAULT: '#191D20' },
|
||||
purple: { DEFAULT: '#7E5DFF' },
|
||||
|
||||
neutral: colors.neutral,
|
||||
plumbus: {
|
||||
DEFAULT: '#FFA900',
|
||||
light: '#FFC922',
|
||||
DEFAULT: '#DB2676',
|
||||
light: '#AF1F5F',
|
||||
matte: '#5D89E9',
|
||||
dark: '#FFC900',
|
||||
10: '#FFF0ED',
|
||||
20: '#5D89E9',
|
||||
30: '#F5A7A2',
|
||||
40: '#FFA900',
|
||||
50: '#FFA900',
|
||||
60: '#FFA900',
|
||||
40: '#DB2676',
|
||||
50: '#DB2676',
|
||||
60: '#DB2676',
|
||||
70: '#AB5152',
|
||||
80: '#944144',
|
||||
90: '#7D3136',
|
||||
|
@ -4,6 +4,7 @@ export const WHITELIST_CODE_ID = parseInt(process.env.NEXT_PUBLIC_WHITELIST_CODE
|
||||
|
||||
export const PINATA_ENDPOINT_URL = process.env.NEXT_PUBLIC_PINATA_ENDPOINT_URL
|
||||
export const NETWORK = process.env.NEXT_PUBLIC_NETWORK
|
||||
export const STARGAZE_URL = process.env.NEXT_PUBLIC_STARGAZE_WEBSITE_URL
|
||||
|
||||
export const BLOCK_EXPLORER_URL = process.env.NEXT_PUBLIC_BLOCK_EXPLORER_URL
|
||||
|
||||
|
@ -8,7 +8,7 @@ export const links = {
|
||||
deuslabs: `https://deuslabs.fi`,
|
||||
Discord: `https://discord.gg/stargaze`,
|
||||
Docs: `https://docs.stargaze.zone/`,
|
||||
GitHub: `https://github.com/public-awesome`,
|
||||
GitHub: `https://github.com/public-awesome/stargaze-studio`,
|
||||
Stargaze: `https://stargaze.zone/`,
|
||||
Telegram: `https://t.me/joinchat/ZQ95YmIn3AI0ODFh`,
|
||||
Twitter: `https://twitter.com/stargazezone`,
|
||||
|
Loading…
Reference in New Issue
Block a user