Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proposal template add artwork #252

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ArtworkUpload'
Original file line number Diff line number Diff line change
@@ -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 (
<Stack>
<AddArtworkForm
disabled={!isValid}
isPropertyCountValid={isPropertyCountValid}
propertiesCount={propertiesCount || 0}
handleSubmit={handleReplaceArtworkTransaction}
/>
</Stack>
)
}
Original file line number Diff line number Diff line change
@@ -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',
},
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as Yup from 'yup'

export interface ArtworkType {
trait: string
properties: string[]
}

export interface ArtworkFormValues {
artwork: Array<ArtworkType>
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'),
})
Original file line number Diff line number Diff line change
@@ -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<AddArtworkFormProps> = ({
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 (
<Box w={'100%'}>
<Text fontWeight={'display'}>Requirements for Add Artwork proposal:</Text>
<Box as="ul" color="text3" mt="x6">
<Box as="li" mb="x3">
The total number of new traits must be equal to or greater than the number of
old traits.
</Box>
<Box as="li" mb="x3">
All trait folders should be included in the same order as the original upload
even if they contain no new artwork.
</Box>
<Box as="li" mb="x3">
Previously uploaded variants should be removed to avoid dulicates.
</Box>
<Box as="li">New traits must be added to the end of the folder hierarchy.</Box>
</Box>
<Uploading isUploadingToIPFS={isUploadingToIPFS} />
<Formik<ArtworkFormValues>
initialValues={initialValues}
enableReinitialize
validateOnBlur={false}
validateOnMount={true}
validateOnChange={true}
validationSchema={validationSchemaArtwork}
onSubmit={handleSubmit}
>
{(formik) => (
<Flex as={Form} direction={'column'} mt="x8">
<ArtworkUpload
{...formik.getFieldProps('artwork')}
inputLabel={'Artwork'}
formik={formik}
id={'artwork'}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
helperText={
'Builder uses folder hierarchy to organize your assets. Upload a single folder containing a subfolder for each trait. Each subfolder should contain every variant for that trait.'
}
errorMessage={
formik.touched.artwork && formik.errors?.artwork
? formik.errors?.artwork
: undefined
}
/>

{showPropertyErrors && !isPropertyCountValid && (
<Text
w="100%"
textAlign={'center'}
color={'negative'}
>{`Current total number of traits is ${propertiesCount}. The new folder of traits must have a minimum total of ${propertiesCount}`}</Text>
)}

<NetworkController.Mainnet>
<Flex align={'center'} justify={'center'} gap={'x4'} mt="x4">
<Flex
align={'center'}
justify={'center'}
className={
checkboxStyleVariants[hasConfirmed ? 'confirmed' : 'default']
}
onClick={() => setHasConfirmed((bool) => !bool)}
>
{hasConfirmed && <Icon fill="background1" id="check" />}
</Flex>

<Flex className={checkboxHelperText}>
I confirm I have tested an artwork replacement proposal on{' '}
<a
href={'https://testnet.nouns.build'}
target="_blank"
className={atoms({ color: 'accent' })}
rel="noreferrer"
>
testnet
</a>
</Flex>
</Flex>
</NetworkController.Mainnet>

<Button
mt={'x9'}
variant={'outline'}
borderRadius={'curved'}
type="submit"
disabled={
disabled ||
!hasConfirmed ||
!isEmpty(formik.errors) ||
formik.isSubmitting
}
>
Add Transaction to Queue
</Button>
</Flex>
)}
</Formik>
</Box>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './AddArtworkForm'
export * from './AddArtwork'
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from './ArtworkUpload'
export * from './ReplaceArtworkForm'
export * from './ReplaceArtwork'
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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,
Expand All @@ -31,6 +33,7 @@ export const TransactionForm = ({ type }: TransactionFormProps) => {
[TransactionType.DROPOSAL]: <Droposal />,
[TransactionType.SEND_ETH]: <SendEth />,
[TransactionType.PAUSE_AUCTIONS]: <PauseAuctions />,
[TransactionType.ADD_ARTWORK]: <AddArtwork />,
[TransactionType.REPLACE_ARTWORK]: <ReplaceArtwork />,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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',
Expand Down