diff --git a/.env.example b/.env.example index e98f503..238f510 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,9 @@ -APP_VERSION=0.4.7 +APP_VERSION=0.4.8 NEXT_PUBLIC_PINATA_ENDPOINT_URL=https://api.pinata.cloud/pinning/pinFileToIPFS -NEXT_PUBLIC_SG721_CODE_ID=793 -NEXT_PUBLIC_VENDING_MINTER_CODE_ID=275 -NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1s48apjumprma0d64ge88dmu7lr8exu02tlw90xxupgds0s25gfasx447dn" +NEXT_PUBLIC_SG721_CODE_ID=1702 +NEXT_PUBLIC_VENDING_MINTER_CODE_ID=1701 +NEXT_PUBLIC_VENDING_FACTORY_ADDRESS="stars1xz4d6wzxqn3udgsm5qnr78y032xng4r2ycv7aw6mjtsuw59s2n9s93ec0v" NEXT_PUBLIC_BASE_FACTORY_ADDRESS="stars1c6juqgd7cm80afpmuszun66rl9zdc4kgfht8fk34tfq3zk87l78sdxngzv" NEXT_PUBLIC_SG721_NAME_ADDRESS="stars1fx74nkqkw2748av8j7ew7r3xt9cgjqduwn8m0ur5lhe49uhlsasszc5fhr" NEXT_PUBLIC_BASE_MINTER_CODE_ID=613 diff --git a/components/Tooltip.tsx b/components/Tooltip.tsx index fa43855..b243e18 100644 --- a/components/Tooltip.tsx +++ b/components/Tooltip.tsx @@ -6,6 +6,8 @@ import { usePopper } from 'react-popper' export interface TooltipProps extends ComponentProps<'div'> { label: ReactNode children: ReactElement + placement?: 'top' | 'bottom' | 'left' | 'right' + backgroundColor?: string } export const Tooltip = ({ label, children, ...props }: TooltipProps) => { @@ -14,7 +16,7 @@ export const Tooltip = ({ label, children, ...props }: TooltipProps) => { const [show, setShow] = useState(false) const { styles, attributes } = usePopper(referenceElement, popperElement, { - placement: 'top', + placement: props.placement ? props.placement : 'top', }) return ( @@ -32,7 +34,11 @@ export const Tooltip = ({ label, children, ...props }: TooltipProps) => {
diff --git a/components/badges/creation/BadgeDetails.tsx b/components/badges/creation/BadgeDetails.tsx index 42ff004..652143b 100644 --- a/components/badges/creation/BadgeDetails.tsx +++ b/components/badges/creation/BadgeDetails.tsx @@ -3,24 +3,30 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { toUtf8 } from '@cosmjs/encoding' import clsx from 'clsx' +import { Conditional } from 'components/Conditional' import { FormControl } from 'components/FormControl' import { useInputState, useNumberInputState } from 'components/forms/FormInput.hooks' import { useMetadataAttributesState } from 'components/forms/MetadataAttributes.hooks' import { InputDateTime } from 'components/InputDateTime' import { useWallet } from 'contexts/wallet' import type { Trait } from 'contracts/badgeHub' -import { useEffect, useState } from 'react' +import type { ChangeEvent } from 'react' +import { useEffect, useRef, useState } from 'react' import { toast } from 'react-hot-toast' +import { BADGE_HUB_ADDRESS } from 'utils/constants' import { AddressInput, NumberInput, TextInput } from '../../forms/FormInput' import { MetadataAttributes } from '../../forms/MetadataAttributes' +import { Tooltip } from '../../Tooltip' import type { MintRule, UploadMethod } from './ImageUploadDetails' interface BadgeDetailsProps { onChange: (data: BadgeDetailsDataProps) => void uploadMethod: UploadMethod | undefined mintRule: MintRule + metadataSize: number } export interface BadgeDetailsDataProps { @@ -38,10 +44,14 @@ export interface BadgeDetailsDataProps { youtube_url?: string } -export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { +export const BadgeDetails = ({ metadataSize, onChange }: BadgeDetailsProps) => { const wallet = useWallet() const [timestamp, setTimestamp] = useState(undefined) const [transferrable, setTransferrable] = useState(false) + const [metadataFile, setMetadataFile] = useState() + const [metadataFeeRate, setMetadataFeeRate] = useState(0) + + const metadataFileRef = useRef(null) const managerState = useInputState({ id: 'manager-address', @@ -109,6 +119,79 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { subtitle: 'YouTube URL for the badge', }) + const parseMetadata = async () => { + try { + let parsedMetadata: any + if (metadataFile) { + attributesState.reset() + parsedMetadata = JSON.parse(await metadataFile.text()) + + if (!parsedMetadata.attributes || parsedMetadata.attributes.length === 0) { + attributesState.add({ + trait_type: '', + value: '', + }) + } else { + for (let i = 0; i < parsedMetadata.attributes.length; i++) { + attributesState.add({ + trait_type: parsedMetadata.attributes[i].trait_type, + value: parsedMetadata.attributes[i].value, + }) + } + } + nameState.onChange(parsedMetadata.name ? parsedMetadata.name : '') + descriptionState.onChange(parsedMetadata.description ? parsedMetadata.description : '') + externalUrlState.onChange(parsedMetadata.external_url ? parsedMetadata.external_url : '') + youtubeUrlState.onChange(parsedMetadata.youtube_url ? parsedMetadata.youtube_url : '') + animationUrlState.onChange(parsedMetadata.animation_url ? parsedMetadata.animation_url : '') + backgroundColorState.onChange(parsedMetadata.background_color ? parsedMetadata.background_color : '') + imageDataState.onChange(parsedMetadata.image_data ? parsedMetadata.image_data : '') + } else { + attributesState.reset() + nameState.onChange('') + descriptionState.onChange('') + externalUrlState.onChange('') + youtubeUrlState.onChange('') + animationUrlState.onChange('') + backgroundColorState.onChange('') + imageDataState.onChange('') + } + } catch (error) { + toast.error('Error parsing metadata file: Invalid JSON format.') + if (metadataFileRef.current) metadataFileRef.current.value = '' + setMetadataFile(undefined) + } + } + + const selectMetadata = (event: ChangeEvent) => { + setMetadataFile(undefined) + if (event.target.files === null) return + + let selectedFile: File + const reader = new FileReader() + reader.onload = (e) => { + if (!event.target.files) return toast.error('No file selected.') + if (!e.target?.result) return toast.error('Error parsing file.') + selectedFile = new File([e.target.result], event.target.files[0].name, { type: 'application/json' }) + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (event.target.files[0]) reader.readAsArrayBuffer(event.target.files[0]) + else return toast.error('No file selected.') + reader.onloadend = () => { + if (!event.target.files) return toast.error('No file selected.') + setMetadataFile(selectedFile) + } + } + + useEffect(() => { + void parseMetadata() + if (!metadataFile) + attributesState.add({ + trait_type: '', + value: '', + }) + }, [metadataFile]) + useEffect(() => { try { const data: BadgeDetailsDataProps = { @@ -155,13 +238,25 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { ]) useEffect(() => { - if (attributesState.values.length === 0) - attributesState.add({ - trait_type: '', - value: '', - }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + const retrieveFeeRate = async () => { + try { + if (wallet.client) { + const feeRateRaw = await wallet.client.queryContractRaw( + BADGE_HUB_ADDRESS, + toUtf8(Buffer.from(Buffer.from('fee_rate').toString('hex'), 'hex').toString()), + ) + console.log('Fee Rate Raw: ', feeRateRaw) + const feeRate = JSON.parse(new TextDecoder().decode(feeRateRaw as Uint8Array)) + setMetadataFeeRate(Number(feeRate.metadata)) + } + } catch (error) { + toast.error('Error retrieving metadata fee rate.') + setMetadataFeeRate(0) + console.log('Error retrieving fee rate: ', error) + } + } + void retrieveFeeRate() + }, [wallet.client]) return (
@@ -172,19 +267,35 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { + setTimestamp(date)} value={timestamp} /> -
- +
+
+ +
+ + +
+
Fee Estimate:
+ {(metadataSize * Number(metadataFeeRate)) / 1000000} stars +
+
+
@@ -197,6 +308,40 @@ export const BadgeDetails = ({ onChange }: BadgeDetailsProps) => { title="Traits" />
+
+ +
+ +
+ +
+
+
+
diff --git a/components/badges/creation/ImageUploadDetails.tsx b/components/badges/creation/ImageUploadDetails.tsx index fb8d725..397f541 100644 --- a/components/badges/creation/ImageUploadDetails.tsx +++ b/components/badges/creation/ImageUploadDetails.tsx @@ -172,7 +172,7 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro -
+

@@ -187,8 +187,17 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro {' '} and upload your image manually to get an image URL for your badge.

-
- +
+ + +
+ badge-preview +
+
@@ -252,31 +261,33 @@ export const ImageUploadDetails = ({ onChange, mintRule }: ImageUploadDetailsPro
-
-
- -
- +
+
+ +
+ > + +
diff --git a/components/collections/actions/Action.tsx b/components/collections/actions/Action.tsx index 98cb7c3..305e907 100644 --- a/components/collections/actions/Action.tsx +++ b/components/collections/actions/Action.tsx @@ -151,7 +151,7 @@ export const CollectionActions = ({ name: 'royaltyShare', title: 'Share Percentage', subtitle: 'Percentage of royalties to be paid', - placeholder: '8%', + placeholder: '5%', }) const showTokenUriField = type === 'mint_token_uri' diff --git a/components/collections/creation/RoyaltyDetails.tsx b/components/collections/creation/RoyaltyDetails.tsx index 5ed298e..0d873e2 100644 --- a/components/collections/creation/RoyaltyDetails.tsx +++ b/components/collections/creation/RoyaltyDetails.tsx @@ -36,7 +36,7 @@ export const RoyaltyDetails = ({ onChange }: RoyaltyDetailsProps) => { name: 'royaltyShare', title: 'Share Percentage', subtitle: 'Percentage of royalties to be paid', - placeholder: '8%', + placeholder: '5%', }) useEffect(() => { diff --git a/components/forms/MetadataAttributes.tsx b/components/forms/MetadataAttributes.tsx index acfac6e..d911d23 100644 --- a/components/forms/MetadataAttributes.tsx +++ b/components/forms/MetadataAttributes.tsx @@ -73,9 +73,10 @@ export function MetadataAttribute({ id, isLast, onAdd, onChange, onRemove, defau }, [traitTypeState.value, traitValueState.value, id]) return ( -
+
+
+ +
{ mintRule !== 'by_keys' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5', )} > - + +
{ mintRule !== 'by_minter' ? 'bg-stargaze/5 hover:bg-stargaze/80' : 'hover:bg-white/5', )} > - + +
@@ -671,6 +728,11 @@ const BadgeCreationPage: NextPage = () => {
{ {myBadges.map((badge: any, index: any) => { return ( - +
Cover {
-

{badge.name}

+

+ {badge.name ? badge.name : 'No name provided.'} +

Badge ID: {badge.tokenId}

- - {badge.description} + + {badge.description ? badge.description : 'No description provided.'} {/*
*/} {/* */} diff --git a/pages/contracts/baseMinter/instantiate.tsx b/pages/contracts/baseMinter/instantiate.tsx index 3e79f54..4bcaaa5 100644 --- a/pages/contracts/baseMinter/instantiate.tsx +++ b/pages/contracts/baseMinter/instantiate.tsx @@ -105,7 +105,7 @@ const BaseMinterInstantiatePage: NextPage = () => { name: 'royaltyShare', title: 'Share Percentage', subtitle: 'Percentage of royalties to be paid', - placeholder: '8%', + placeholder: '5%', }) const { data, isLoading, mutate } = useMutation( diff --git a/pages/contracts/vendingMinter/instantiate.tsx b/pages/contracts/vendingMinter/instantiate.tsx index 2695f2f..7c48f12 100644 --- a/pages/contracts/vendingMinter/instantiate.tsx +++ b/pages/contracts/vendingMinter/instantiate.tsx @@ -103,7 +103,7 @@ const VendingMinterInstantiatePage: NextPage = () => { name: 'royaltyShare', title: 'Share Percentage', subtitle: 'Percentage of royalties to be paid', - placeholder: '8%', + placeholder: '5%', }) const unitPriceState = useNumberInputState({