diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ArtworkUpload.css.ts b/apps/web/src/modules/create-proposal/components/ArtworkUpload/ArtworkUpload.css.ts similarity index 100% rename from apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ArtworkUpload.css.ts rename to apps/web/src/modules/create-proposal/components/ArtworkUpload/ArtworkUpload.css.ts diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ArtworkUpload.tsx b/apps/web/src/modules/create-proposal/components/ArtworkUpload/ArtworkUpload.tsx similarity index 100% rename from apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ArtworkUpload.tsx rename to apps/web/src/modules/create-proposal/components/ArtworkUpload/ArtworkUpload.tsx diff --git a/apps/web/src/modules/create-proposal/components/ArtworkUpload/index.ts b/apps/web/src/modules/create-proposal/components/ArtworkUpload/index.ts new file mode 100644 index 00000000..1cb754f3 --- /dev/null +++ b/apps/web/src/modules/create-proposal/components/ArtworkUpload/index.ts @@ -0,0 +1 @@ +export * from './ArtworkUpload' diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtwork.tsx b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtwork.tsx new file mode 100644 index 00000000..e7598212 --- /dev/null +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtwork.tsx @@ -0,0 +1,103 @@ +import { Stack } from '@zoralabs/zord' +import { ethers } from 'ethers' +import React, { useEffect, useMemo } from 'react' +import useSWR from 'swr' + +import SWR_KEYS from 'src/constants/swrKeys' +import { metadataAbi } from 'src/data/contract/abis' +import { getPropertyItemsCount } from 'src/data/contract/requests/getPropertyItemsCount' +import { transformFileProperties } from 'src/modules/create-dao' +import { TransactionType } from 'src/modules/create-proposal/constants' +import { useProposalStore } from 'src/modules/create-proposal/stores' +import { useArtworkStore } from 'src/modules/create-proposal/stores/useArtworkStore' +import { useDaoStore } from 'src/modules/dao' +import { AddressType } from 'src/typings' + +import { AddArtworkForm } from './AddArtworkForm' + +export const AddArtwork = () => { + const { orderedLayers, ipfsUpload, isUploadingToIPFS, resetForm } = useArtworkStore() + const addresses = useDaoStore((x) => x.addresses) + const addTransaction = useProposalStore((state) => state.addTransaction) + + const contractOrderedLayers = [...orderedLayers].reverse() // traits in the contract are reversed + + const { data } = useSWR( + addresses.metadata ? SWR_KEYS.ARTWORK_PROPERTY_ITEMS_COUNT : undefined, + () => { + if (!addresses.metadata) return + return getPropertyItemsCount(addresses?.metadata) + } + ) + + useEffect(() => { + resetForm() + }, []) + + const { propertiesCount, propertyItemsCount } = data || {} + + const isPropertyCountValid = useMemo(() => { + if (!propertiesCount) return false + return orderedLayers.length >= propertiesCount + }, [propertiesCount, orderedLayers]) + + const invalidPropertyIndex = useMemo(() => { + if (!propertyItemsCount || propertyItemsCount.length < 1) return -1 + return contractOrderedLayers.findIndex((x, i) => { + console.log(x.properties.length, propertyItemsCount[i]) + return x.properties.length < propertyItemsCount[i] + }) + }, [orderedLayers, propertyItemsCount]) + + const isValid = + isPropertyCountValid && + invalidPropertyIndex < 0 && + !isUploadingToIPFS && + ipfsUpload.length !== 0 + + const transactions = React.useMemo(() => { + if (!orderedLayers || !ipfsUpload) return + + return transformFileProperties(orderedLayers, ipfsUpload, 500) + }, [orderedLayers, ipfsUpload]) + + const handleReplaceArtworkTransaction = () => { + if (!transactions || !isValid) return + const metadataInterface = new ethers.utils.Interface(metadataAbi) + + const formattedTransactions = transactions.map((transaction) => { + const functionSignature = + 'addProperties(string[], (uint256,string,bool)[], (string,string))' + + return { + functionSignature, + target: addresses?.metadata as AddressType, + value: '', + calldata: metadataInterface.encodeFunctionData(functionSignature, [ + transaction.names, + transaction.items, + transaction.data, + ]), + } + }) + + addTransaction({ + type: TransactionType.ADD_ARTWORK, + summary: 'Add artwork', + transactions: formattedTransactions, + }) + + resetForm() + } + + return ( + + + + ) +} diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.css.ts b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.css.ts new file mode 100644 index 00000000..6e9ad18d --- /dev/null +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.css.ts @@ -0,0 +1,35 @@ +import { style, styleVariants } from '@vanilla-extract/css' +import { atoms } from '@zoralabs/zord' + +export const checkboxWrapperStyle = style({ + borderRadius: '16px', + border: `1px solid #F2F2F2`, +}) + +export const checkboxStyle = style({ + minHeight: 26, + height: 26, + width: 26, + minWidth: 26, + border: `1px solid #000`, + borderRadius: '5px', + selectors: { + '&:hover': { cursor: 'pointer', background: '#000' }, + }, +}) + +export const checkboxStyleVariants = styleVariants({ + default: [checkboxStyle], + confirmed: [checkboxStyle, { background: '#000' }], +}) + +export const checkboxHelperText = style([ + atoms({ + display: 'inline', + }), + { + lineHeight: '24px', + color: '#808080', + fontSize: '14px', + }, +]) diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.schema.ts b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.schema.ts new file mode 100644 index 00000000..1371b32e --- /dev/null +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.schema.ts @@ -0,0 +1,22 @@ +import * as Yup from 'yup' + +export interface ArtworkType { + trait: string + properties: string[] +} + +export interface ArtworkFormValues { + artwork: Array + filesLength: number | string +} + +export const validationSchemaArtwork = Yup.object().shape({ + artwork: Yup.array() + .of( + Yup.object().shape({ + trait: Yup.string(), + properties: Yup.array().of(Yup.string()), + }) + ) + .min(1, 'Artwork required'), +}) diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.tsx b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.tsx new file mode 100644 index 00000000..2b5594cc --- /dev/null +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/AddArtworkForm.tsx @@ -0,0 +1,146 @@ +import { Box, Button, Flex, Text, atoms } from '@zoralabs/zord' +import { Form, Formik } from 'formik' +import isEmpty from 'lodash/isEmpty' +import React, { useState } from 'react' + +import { Icon } from 'src/components/Icon' +import { NetworkController } from 'src/components/NetworkController' +import { Uploading } from 'src/components/Uploading' +import { useArtworkStore } from 'src/modules/create-proposal/stores/useArtworkStore' + +import { ArtworkUpload } from '../../ArtworkUpload' +import { checkboxHelperText, checkboxStyleVariants } from './AddArtworkForm.css' +import { ArtworkFormValues, validationSchemaArtwork } from './AddArtworkForm.schema' + +export interface InvalidProperty { + currentVariantCount: number + currentLayerName: string + nextName: string +} +export interface AddArtworkFormProps { + disabled: boolean + isPropertyCountValid: boolean + propertiesCount: number + invalidProperty?: InvalidProperty + handleSubmit: (values: ArtworkFormValues) => void +} + +export const AddArtworkForm: React.FC = ({ + disabled, + isPropertyCountValid, + propertiesCount, + handleSubmit, +}) => { + const { isUploadingToIPFS, ipfsUpload, setUpArtwork } = useArtworkStore() + const [hasConfirmed, setHasConfirmed] = useState( + process.env.NEXT_PUBLIC_CHAIN_ID === '5' ? true : false + ) + + const initialValues = { + artwork: setUpArtwork?.artwork || [], + filesLength: setUpArtwork?.filesLength || '', + } + + const showPropertyErrors = ipfsUpload.length > 0 + + return ( + + Requirements for Add Artwork proposal: + + + The total number of new traits must be equal to or greater than the number of + old traits. + + + All trait folders should be included in the same order as the original upload + even if they contain no new artwork. + + + Previously uploaded variants should be removed to avoid dulicates. + + New traits must be added to the end of the folder hierarchy. + + + + initialValues={initialValues} + enableReinitialize + validateOnBlur={false} + validateOnMount={true} + validateOnChange={true} + validationSchema={validationSchemaArtwork} + onSubmit={handleSubmit} + > + {(formik) => ( + + + + {showPropertyErrors && !isPropertyCountValid && ( + {`Current total number of traits is ${propertiesCount}. The new folder of traits must have a minimum total of ${propertiesCount}`} + )} + + + + setHasConfirmed((bool) => !bool)} + > + {hasConfirmed && } + + + + I confirm I have tested an artwork replacement proposal on{' '} + + testnet + + + + + + + + )} + + + ) +} diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/index.ts b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/index.ts new file mode 100644 index 00000000..2a039235 --- /dev/null +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/AddArtwork/index.ts @@ -0,0 +1,2 @@ +export * from './AddArtworkForm' +export * from './AddArtwork' diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ReplaceArtworkForm.tsx b/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ReplaceArtworkForm.tsx index 6be2d5c8..72299ac9 100644 --- a/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ReplaceArtworkForm.tsx +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/ReplaceArtworkForm.tsx @@ -8,7 +8,7 @@ import { NetworkController } from 'src/components/NetworkController' import { Uploading } from 'src/components/Uploading' import { useArtworkStore } from 'src/modules/create-proposal/stores/useArtworkStore' -import { ArtworkUpload } from './ArtworkUpload' +import { ArtworkUpload } from '../../ArtworkUpload' import { checkboxHelperText, checkboxStyleVariants } from './ReplaceArtworkForm.css' import { ArtworkFormValues, validationSchemaArtwork } from './ReplaceArtworkForm.schema' diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/index.ts b/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/index.ts index 4af520bb..63ca63bf 100644 --- a/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/index.ts +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/ReplaceArtwork/index.ts @@ -1,3 +1,2 @@ -export * from './ArtworkUpload' export * from './ReplaceArtworkForm' export * from './ReplaceArtwork' diff --git a/apps/web/src/modules/create-proposal/components/TransactionForm/TransactionForm.tsx b/apps/web/src/modules/create-proposal/components/TransactionForm/TransactionForm.tsx index 6dcdb2cf..77075606 100644 --- a/apps/web/src/modules/create-proposal/components/TransactionForm/TransactionForm.tsx +++ b/apps/web/src/modules/create-proposal/components/TransactionForm/TransactionForm.tsx @@ -2,6 +2,7 @@ import React, { ReactNode } from 'react' import { TransactionType } from 'src/modules/create-proposal/constants' +import { AddArtwork } from './AddArtwork/AddArtwork' import { Airdrop } from './Airdrop' import { CustomTransaction } from './CustomTransaction' import { Droposal } from './Droposal' @@ -19,6 +20,7 @@ export const TRANSACTION_FORM_OPTIONS = [ TransactionType.SEND_ETH, TransactionType.AIRDROP, TransactionType.PAUSE_AUCTIONS, + TransactionType.ADD_ARTWORK, TransactionType.REPLACE_ARTWORK, TransactionType.DROPOSAL, TransactionType.CUSTOM, @@ -31,6 +33,7 @@ export const TransactionForm = ({ type }: TransactionFormProps) => { [TransactionType.DROPOSAL]: , [TransactionType.SEND_ETH]: , [TransactionType.PAUSE_AUCTIONS]: , + [TransactionType.ADD_ARTWORK]: , [TransactionType.REPLACE_ARTWORK]: , } diff --git a/apps/web/src/modules/create-proposal/constants/transactionType.tsx b/apps/web/src/modules/create-proposal/constants/transactionType.tsx index 72ba231a..5e2116f6 100644 --- a/apps/web/src/modules/create-proposal/constants/transactionType.tsx +++ b/apps/web/src/modules/create-proposal/constants/transactionType.tsx @@ -11,6 +11,7 @@ export enum TransactionType { PAUSE_AUCTIONS = 'pause-auctions', UPDATE_MINTER = 'update-minter', REPLACE_ARTWORK = 'replace-artwork', + ADD_ARTWORK = 'add-artwork', } export interface TransactionTypeProps { @@ -62,6 +63,12 @@ export const TRANSACTION_TYPES = { icon: 'brush', iconBackdrop: 'rgba(236, 113, 75, 0.1)', }, + [TransactionType.ADD_ARTWORK]: { + title: 'Add Artwork', + subTitle: 'Create a proposal to add new artwork', + icon: 'brush', + iconBackdrop: 'rgba(236, 113, 75, 0.1)', + }, [TransactionType.CUSTOM]: { title: 'Custom Transaction', subTitle: 'Create any other kind of transaction',