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',