From 2fdb0fc5234591ab67a653750dcc179fdc1d662f Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 8 Apr 2024 14:29:33 +0200 Subject: [PATCH 01/14] Refactor CreateProposalTemplateForm into ProposalBuilder and make it more generic to be compatible with creating regular proposal --- .../ProposalDetails.tsx} | 10 +- .../ProposalMetadata.tsx} | 30 ++- .../ProposalTransaction.tsx} | 14 +- .../ProposalTransactions.tsx} | 19 +- .../ProposalTransactionsForm.tsx} | 24 +-- .../constants.ts | 8 +- src/components/ProposalBuilder/index.tsx | 192 ++++++++++++++++++ .../ProposalTemplateCard.tsx | 2 +- .../ui/modals/ForkProposalTemplateModal.tsx | 2 +- .../ui/modals/ProposalTemplateModal.tsx | 2 +- .../DAO/proposal/useCreateProposalTemplate.ts | 8 +- .../proposal-templates/new/index.tsx | 180 +--------------- src/providers/App/governance/action.ts | 2 +- src/types/createProposalTemplate.ts | 32 --- src/types/fractal.ts | 2 +- src/types/proposalBuilder.ts | 27 +++ 16 files changed, 287 insertions(+), 267 deletions(-) rename src/components/{CreateProposalTemplate/ProposalTemplateDetails.tsx => ProposalBuilder/ProposalDetails.tsx} (93%) rename src/components/{CreateProposalTemplate/ProposalTemplateMetadata.tsx => ProposalBuilder/ProposalMetadata.tsx} (54%) rename src/components/{CreateProposalTemplate/ProposalTemplateTransaction.tsx => ProposalBuilder/ProposalTransaction.tsx} (96%) rename src/components/{CreateProposalTemplate/ProposalTemplateTransactions.tsx => ProposalBuilder/ProposalTransactions.tsx} (86%) rename src/components/{CreateProposalTemplate/ProposalTemplateTransactionsForm.tsx => ProposalBuilder/ProposalTransactionsForm.tsx} (78%) rename src/components/{CreateProposalTemplate => ProposalBuilder}/constants.ts (52%) create mode 100644 src/components/ProposalBuilder/index.tsx delete mode 100644 src/types/createProposalTemplate.ts create mode 100644 src/types/proposalBuilder.ts diff --git a/src/components/CreateProposalTemplate/ProposalTemplateDetails.tsx b/src/components/ProposalBuilder/ProposalDetails.tsx similarity index 93% rename from src/components/CreateProposalTemplate/ProposalTemplateDetails.tsx rename to src/components/ProposalBuilder/ProposalDetails.tsx index a470459623..4bb24d63d0 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateDetails.tsx +++ b/src/components/ProposalBuilder/ProposalDetails.tsx @@ -3,7 +3,7 @@ import { FormikProps } from 'formik'; import { Fragment, PropsWithChildren } from 'react'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; -import { CreateProposalTemplateForm } from '../../types/createProposalTemplate'; +import { CreateProposalForm } from '../../types/proposalBuilder'; import Markdown from '../ui/proposal/Markdown'; import '../../assets/css/Markdown.css'; @@ -29,10 +29,10 @@ export function TransactionValueContainer({ } export default function ProposalTemplateDetails({ - values: { proposalTemplateMetadata, transactions }, -}: FormikProps) { + values: { proposalMetadata, transactions }, +}: FormikProps) { const { t } = useTranslation(['proposalTemplate', 'proposal']); - const trimmedTitle = proposalTemplateMetadata.title?.trim(); + const trimmedTitle = proposalMetadata.title?.trim(); return ( {t('proposalTemplateDescription')} diff --git a/src/components/CreateProposalTemplate/ProposalTemplateMetadata.tsx b/src/components/ProposalBuilder/ProposalMetadata.tsx similarity index 54% rename from src/components/CreateProposalTemplate/ProposalTemplateMetadata.tsx rename to src/components/ProposalBuilder/ProposalMetadata.tsx index 3d58b366e3..29573b0a88 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateMetadata.tsx +++ b/src/components/ProposalBuilder/ProposalMetadata.tsx @@ -1,22 +1,20 @@ import { Button, Divider, VStack } from '@chakra-ui/react'; import { FormikProps } from 'formik'; import { useTranslation } from 'react-i18next'; -import { - CreateProposalTemplateForm, - CreateProposalTemplateFormState, -} from '../../types/createProposalTemplate'; +import { CreateProposalState } from '../../types'; +import { CreateProposalForm } from '../../types/proposalBuilder'; import { InputComponent, TextareaComponent } from '../ui/forms/InputComponent'; -export interface ProposalTemplateMetadataProps extends FormikProps { - setFormState: (state: CreateProposalTemplateFormState) => void; +export interface ProposalMetadataProps extends FormikProps { + setFormState: (state: CreateProposalState) => void; } -export default function ProposalTemplateMetadata({ - values: { proposalTemplateMetadata }, +export default function ProposalMetadata({ + values: { proposalMetadata }, setFieldValue, - errors: { proposalTemplateMetadata: proposalTemplateMetadataError }, + errors: { proposalMetadata: proposalMetadataError }, setFormState, -}: ProposalTemplateMetadataProps) { +}: ProposalMetadataProps) { const { t } = useTranslation(['proposalTemplate', 'common']); return ( @@ -30,8 +28,8 @@ export default function ProposalTemplateMetadata({ label={t('proposalTemplateTitle')} helper={t('proposalTemplateTitleHelperText')} isRequired - value={proposalTemplateMetadata.title} - onChange={e => setFieldValue('proposalTemplateMetadata.title', e.target.value)} + value={proposalMetadata.title} + onChange={e => setFieldValue('proposalMetadata.title', e.target.value)} disabled={false} testId="metadata.title" maxLength={50} @@ -41,8 +39,8 @@ export default function ProposalTemplateMetadata({ subLabel={t('')} helper={t('proposalTemplateDescriptionHelperText')} isRequired={false} - value={proposalTemplateMetadata.description} - onChange={e => setFieldValue('proposalTemplateMetadata.description', e.target.value)} + value={proposalMetadata.description} + onChange={e => setFieldValue('proposalMetadata.description', e.target.value)} disabled={false} rows={12} /> @@ -54,8 +52,8 @@ export default function ProposalTemplateMetadata({ /> diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx b/src/components/ProposalBuilder/ProposalTransaction.tsx similarity index 96% rename from src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx rename to src/components/ProposalBuilder/ProposalTransaction.tsx index 33e5c5bdd5..b9f83bede1 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransaction.tsx +++ b/src/components/ProposalBuilder/ProposalTransaction.tsx @@ -2,14 +2,14 @@ import { VStack, HStack, Text, Box, Flex, IconButton } from '@chakra-ui/react'; import { AddPlus, Minus } from '@decent-org/fractal-ui'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { CreateProposalTemplateTransaction } from '../../types/createProposalTemplate'; +import { CreateProposalTransaction } from '../../types/proposalBuilder'; import ABISelector, { ABIElement } from '../ui/forms/ABISelector'; import ExampleLabel from '../ui/forms/ExampleLabel'; import { BigNumberComponent, InputComponent } from '../ui/forms/InputComponent'; -import { DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION } from './constants'; +import { DEFAULT_PROPOSAL_TRANSACTION } from './constants'; -interface ProposalTemplateTransactionProps { - transaction: CreateProposalTemplateTransaction; +interface ProposalTransactionProps { + transaction: CreateProposalTransaction; transactionIndex: number; transactionPending: boolean; txAddressError?: string; @@ -17,14 +17,14 @@ interface ProposalTemplateTransactionProps { setFieldValue: (field: string, value: any, shouldValidate?: boolean | undefined) => void; } -export default function ProposalTemplateTransaction({ +export default function ProposalTransaction({ transaction, transactionIndex, transactionPending, txAddressError, txFunctionError, setFieldValue, -}: ProposalTemplateTransactionProps) { +}: ProposalTransactionProps) { const { t } = useTranslation(['proposal', 'proposalTemplate', 'common']); const handleABISelectorChange = useCallback( (value: ABIElement) => { @@ -123,7 +123,7 @@ export default function ProposalTemplateTransaction({ onClick={() => setFieldValue(`transactions.${transactionIndex}.parameters`, [ ...transaction.parameters, - DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION, + DEFAULT_PROPOSAL_TRANSACTION, ]) } > diff --git a/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx b/src/components/ProposalBuilder/ProposalTransactions.tsx similarity index 86% rename from src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx rename to src/components/ProposalBuilder/ProposalTransactions.tsx index 1b4c5b4849..1f114ec7b6 100644 --- a/src/components/CreateProposalTemplate/ProposalTemplateTransactions.tsx +++ b/src/components/ProposalBuilder/ProposalTransactions.tsx @@ -12,25 +12,22 @@ import { FormikErrors, FormikProps } from 'formik'; import { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { BigNumberValuePair } from '../../types'; -import { - CreateProposalTemplateForm, - CreateProposalTemplateTransaction, -} from '../../types/createProposalTemplate'; -import ProposalTemplateTransaction from './ProposalTemplateTransaction'; +import { CreateProposalForm, CreateProposalTransaction } from '../../types/proposalBuilder'; +import ProposalTransaction from './ProposalTransaction'; -interface ProposalTemplateTransactionsProps extends FormikProps { +interface ProposalTransactionsProps extends FormikProps { pendingTransaction: boolean; expandedIndecies: number[]; setExpandedIndecies: Dispatch>; } -export default function ProposalTemplateTransactions({ +export default function ProposalTransactions({ values: { transactions }, errors, setFieldValue, pendingTransaction, expandedIndecies, setExpandedIndecies, -}: ProposalTemplateTransactionsProps) { +}: ProposalTransactionsProps) { const { t } = useTranslation(['proposal', 'proposalTemplate', 'common']); const removeTransaction = (transactionIndex: number) => { @@ -46,7 +43,7 @@ export default function ProposalTemplateTransactions({ > {transactions.map((_, index) => { const txErrors = errors?.transactions?.[index] as - | FormikErrors> + | FormikErrors> | undefined; const txAddressError = txErrors?.targetAddress; const txFunctionError = txErrors?.functionName; @@ -101,8 +98,8 @@ export default function ProposalTemplateTransactions({ )} - { +interface ProposalTransactionsFormProps extends FormikProps { pendingTransaction: boolean; - setFormState: (state: CreateProposalTemplateFormState) => void; + setFormState: (state: CreateProposalState) => void; canUserCreateProposal?: boolean; safeNonce?: number; } -export default function ProposalTemplateTransactionsForm( - props: ProposalTemplateTransactionsFormProps, -) { +export default function ProposalTransactionsForm(props: ProposalTransactionsFormProps) { const { pendingTransaction, setFormState, @@ -41,7 +37,7 @@ export default function ProposalTemplateTransactionsForm( return ( - { - setFieldValue('transactions', [...transactions, DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION]); + setFieldValue('transactions', [...transactions, DEFAULT_PROPOSAL_TRANSACTION]); setExpandedIndecies([transactions.length]); scrollToBottom(); }} @@ -85,7 +81,7 @@ export default function ProposalTemplateTransactionsForm( textStyle="text-md-mono-regular" color="gold.500" cursor="pointer" - onClick={() => setFormState(CreateProposalTemplateFormState.METADATA_FORM)} + onClick={() => setFormState(CreateProposalState.METADATA_FORM)} mb={4} > {t('back', { ns: 'common' })} diff --git a/src/components/CreateProposalTemplate/constants.ts b/src/components/ProposalBuilder/constants.ts similarity index 52% rename from src/components/CreateProposalTemplate/constants.ts rename to src/components/ProposalBuilder/constants.ts index 4c7930e184..bd76faf013 100644 --- a/src/components/CreateProposalTemplate/constants.ts +++ b/src/components/ProposalBuilder/constants.ts @@ -1,6 +1,6 @@ -import { CreateProposalTemplateTransaction } from '../../types/createProposalTemplate'; +import { CreateProposalTransaction } from '../../types/proposalBuilder'; -export const DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION: CreateProposalTemplateTransaction = { +export const DEFAULT_PROPOSAL_TRANSACTION: CreateProposalTransaction = { targetAddress: '', ethValue: { value: '', bigNumberValue: undefined }, functionName: '', @@ -15,9 +15,9 @@ export const DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION: CreateProposalTemplateTransa export const DEFAULT_PROPOSAL_TEMPLATE = { nonce: undefined, - proposalTemplateMetadata: { + proposalMetadata: { title: '', description: '', }, - transactions: [DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION], + transactions: [DEFAULT_PROPOSAL_TRANSACTION], }; diff --git a/src/components/ProposalBuilder/index.tsx b/src/components/ProposalBuilder/index.tsx new file mode 100644 index 0000000000..590f84f6aa --- /dev/null +++ b/src/components/ProposalBuilder/index.tsx @@ -0,0 +1,192 @@ +import { Box, Flex, Grid, GridItem, Text } from '@chakra-ui/react'; +import { Trash } from '@decent-org/fractal-ui'; +import { Formik, FormikProps } from 'formik'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; +import { DAO_ROUTES, BASE_ROUTES } from '../../constants/routes'; +import useSubmitProposal from '../../hooks/DAO/proposal/useSubmitProposal'; +import useCreateProposalTemplateSchema from '../../hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema'; +import { useCanUserCreateProposal } from '../../hooks/utils/useCanUserSubmitProposal'; +import { useFractal } from '../../providers/App/AppProvider'; +import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; +import { CreateProposalState, ProposalExecuteData } from '../../types'; +import { CreateProposalForm } from '../../types/proposalBuilder'; +import { CustomNonceInput } from '../ui/forms/CustomNonceInput'; +import PageHeader from '../ui/page/Header/PageHeader'; +import ProposalDetails from './ProposalDetails'; +import ProposalMetadata from './ProposalMetadata'; +import ProposalTransactionsForm from './ProposalTransactionsForm'; +import { DEFAULT_PROPOSAL_TEMPLATE } from './constants'; + +interface IProposalBuilder { + mode: 'proposal' | 'template'; + prepareProposalData: (values: CreateProposalForm) => Promise; + initialValues: typeof DEFAULT_PROPOSAL_TEMPLATE; +} + +const templateAreaTwoCol = '"content details"'; +const templateAreaSingleCol = `"content" + "details"`; + +export default function ProposalBuilder({ mode, initialValues, prepareProposalData }: IProposalBuilder) { + const [formState, setFormState] = useState(CreateProposalState.METADATA_FORM); + const { t } = useTranslation(['proposalTemplate', 'proposal']); + + const isProposalMode = mode === 'proposal'; + + const navigate = useNavigate(); + const { + node: { daoAddress, safe }, + } = useFractal(); + const { addressPrefix } = useNetworkConfig(); + const { submitProposal, pendingCreateTx } = useSubmitProposal(); + const { canUserCreateProposal } = useCanUserCreateProposal(); + const { createProposalTemplateValidation } = useCreateProposalTemplateSchema(); + + const successCallback = () => { + if (daoAddress) { + // Redirecting to proposals page so that user will see Proposal for Proposal Template creation + navigate(DAO_ROUTES.proposals.relative(addressPrefix, daoAddress)); + } + }; + + return ( + + validationSchema={createProposalTemplateValidation} + initialValues={initialValues} + enableReinitialize + onSubmit={async values => { + if (canUserCreateProposal) { + const proposalData = await prepareProposalData(values); + if (proposalData) { + submitProposal({ + proposalData, + nonce: values?.nonce, + pendingToastMessage: t('proposalCreatePendingToastMessage', { ns: 'proposal' }), + successToastMessage: t('proposalCreateSuccessToastMessage', { ns: 'proposal' }), + failedToastMessage: t('proposalCreateFailureToastMessage', { ns: 'proposal' }), + successCallback, + }); + } + } + }} + > + {(formikProps: FormikProps) => { + const { handleSubmit } = formikProps; + + if (!daoAddress) { + return; + } + + return ( +
+ + + navigate( + daoAddress + ? isProposalMode + ? DAO_ROUTES.proposals.relative(addressPrefix, daoAddress) + : DAO_ROUTES.proposalTemplates.relative(addressPrefix, daoAddress) + : BASE_ROUTES.landing, + ) + } + isButtonDisabled={pendingCreateTx} + /> + + + + + {formState === CreateProposalState.METADATA_FORM ? ( + + ) : ( + <> + + + {formikProps.values.proposalMetadata.title} + + formikProps.setFieldValue('nonce', newNonce)} + align="end" + /> + + + + )} + + + + + + + + +
+ ); + }} + + ); +} diff --git a/src/components/ProposalTemplates/ProposalTemplateCard.tsx b/src/components/ProposalTemplates/ProposalTemplateCard.tsx index cc8336146a..fcecd49fdf 100644 --- a/src/components/ProposalTemplates/ProposalTemplateCard.tsx +++ b/src/components/ProposalTemplates/ProposalTemplateCard.tsx @@ -9,7 +9,7 @@ import useSubmitProposal from '../../hooks/DAO/proposal/useSubmitProposal'; import { useCanUserCreateProposal } from '../../hooks/utils/useCanUserSubmitProposal'; import { useFractal } from '../../providers/App/AppProvider'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; -import { ProposalTemplate } from '../../types/createProposalTemplate'; +import { ProposalTemplate } from '../../types/proposalBuilder'; import ContentBox from '../ui/containers/ContentBox'; import { OptionMenu } from '../ui/menus/OptionMenu'; import { ModalType } from '../ui/modals/ModalProvider'; diff --git a/src/components/ui/modals/ForkProposalTemplateModal.tsx b/src/components/ui/modals/ForkProposalTemplateModal.tsx index f63cf23d7d..ccfe6c5536 100644 --- a/src/components/ui/modals/ForkProposalTemplateModal.tsx +++ b/src/components/ui/modals/ForkProposalTemplateModal.tsx @@ -9,7 +9,7 @@ import { useCanUserCreateProposal } from '../../../hooks/utils/useCanUserSubmitP import useSignerOrProvider from '../../../hooks/utils/useSignerOrProvider'; import { useFractal } from '../../../providers/App/AppProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; -import { ProposalTemplate } from '../../../types/createProposalTemplate'; +import { ProposalTemplate } from '../../../types/proposalBuilder'; import { InputComponent } from '../forms/InputComponent'; interface IForkProposalTemplateModalProps { diff --git a/src/components/ui/modals/ProposalTemplateModal.tsx b/src/components/ui/modals/ProposalTemplateModal.tsx index 210849e214..d9250b6941 100644 --- a/src/components/ui/modals/ProposalTemplateModal.tsx +++ b/src/components/ui/modals/ProposalTemplateModal.tsx @@ -20,7 +20,7 @@ import { useCanUserCreateProposal } from '../../../hooks/utils/useCanUserSubmitP import { useFractal } from '../../../providers/App/AppProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { BigNumberValuePair } from '../../../types'; -import { ProposalTemplate } from '../../../types/createProposalTemplate'; +import { ProposalTemplate } from '../../../types/proposalBuilder'; import { isValidUrl } from '../../../utils/url'; import { CustomNonceInput } from '../forms/CustomNonceInput'; import { BigNumberComponent, InputComponent } from '../forms/InputComponent'; diff --git a/src/hooks/DAO/proposal/useCreateProposalTemplate.ts b/src/hooks/DAO/proposal/useCreateProposalTemplate.ts index b804e740f2..625005cf84 100644 --- a/src/hooks/DAO/proposal/useCreateProposalTemplate.ts +++ b/src/hooks/DAO/proposal/useCreateProposalTemplate.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { useFractal } from '../../../providers/App/AppProvider'; import useIPFSClient from '../../../providers/App/hooks/useIPFSClient'; import { ProposalExecuteData } from '../../../types'; -import { CreateProposalTemplateForm } from '../../../types/createProposalTemplate'; +import { CreateProposalForm } from '../../../types/proposalBuilder'; import { couldBeENS } from '../../../utils/url'; import useSafeContracts from '../../safe/useSafeContracts'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; @@ -18,7 +18,7 @@ export default function useCreateProposalTemplate() { } = useFractal(); const prepareProposalTemplateProposal = useCallback( - async (values: CreateProposalTemplateForm) => { + async (values: CreateProposalForm) => { if (proposalTemplates && signerOrProvider && keyValuePairsContract) { const proposalMetadata = { title: 'Create Proposal Template', @@ -28,8 +28,8 @@ export default function useCreateProposalTemplate() { }; const proposalTemplateData = { - title: values.proposalTemplateMetadata.title.trim(), - description: values.proposalTemplateMetadata.description.trim(), + title: values.proposalMetadata.title.trim(), + description: values.proposalMetadata.description.trim(), transactions: await Promise.all( values.transactions.map(async tx => ({ ...tx, diff --git a/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx b/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx index d557d064f7..a5366c743b 100644 --- a/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx +++ b/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx @@ -1,42 +1,17 @@ -import { Box, Flex, Grid, GridItem, Text } from '@chakra-ui/react'; -import { Trash } from '@decent-org/fractal-ui'; import { BigNumber } from 'ethers'; -import { Formik, FormikProps } from 'formik'; import { useEffect, useState, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate, useSearchParams } from 'react-router-dom'; -import ProposalTemplateDetails from '../../../../../components/CreateProposalTemplate/ProposalTemplateDetails'; -import ProposalTemplateMetadata from '../../../../../components/CreateProposalTemplate/ProposalTemplateMetadata'; -import ProposalTemplateTransactionsForm from '../../../../../components/CreateProposalTemplate/ProposalTemplateTransactionsForm'; -import { DEFAULT_PROPOSAL_TEMPLATE } from '../../../../../components/CreateProposalTemplate/constants'; -import { CustomNonceInput } from '../../../../../components/ui/forms/CustomNonceInput'; -import PageHeader from '../../../../../components/ui/page/Header/PageHeader'; -import { BACKGROUND_SEMI_TRANSPARENT } from '../../../../../constants/common'; -import { BASE_ROUTES, DAO_ROUTES } from '../../../../../constants/routes'; +import { useSearchParams } from 'react-router-dom'; +import ProposalBuilder from '../../../../../components/ProposalBuilder'; +import { DEFAULT_PROPOSAL_TEMPLATE } from '../../../../../components/ProposalBuilder/constants'; import { logError } from '../../../../../helpers/errorLogging'; import useCreateProposalTemplate from '../../../../../hooks/DAO/proposal/useCreateProposalTemplate'; -import useSubmitProposal from '../../../../../hooks/DAO/proposal/useSubmitProposal'; -import useCreateProposalTemplateSchema from '../../../../../hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema'; -import { useCanUserCreateProposal } from '../../../../../hooks/utils/useCanUserSubmitProposal'; -import { useFractal } from '../../../../../providers/App/AppProvider'; import useIPFSClient from '../../../../../providers/App/hooks/useIPFSClient'; -import { useNetworkConfig } from '../../../../../providers/NetworkConfig/NetworkConfigProvider'; -import { - CreateProposalTemplateForm, - CreateProposalTemplateFormState, - ProposalTemplate, -} from '../../../../../types/createProposalTemplate'; - -const templateAreaTwoCol = '"content details"'; -const templateAreaSingleCol = `"content" - "details"`; +import { ProposalTemplate } from '../../../../../types/proposalBuilder'; export default function CreateProposalTemplatePage() { - const [formState, setFormState] = useState(CreateProposalTemplateFormState.METADATA_FORM); + const ipfsClient = useIPFSClient(); const [initialProposalTemplate, setInitialProposalTemplate] = useState(DEFAULT_PROPOSAL_TEMPLATE); - const { t } = useTranslation(['proposalTemplate', 'proposal']); - - const navigate = useNavigate(); + const { prepareProposalTemplateProposal } = useCreateProposalTemplate(); const [searchParams] = useSearchParams(); const defaultProposalTemplatesHash = useMemo( () => searchParams?.get('templatesHash'), @@ -47,24 +22,6 @@ export default function CreateProposalTemplatePage() { [searchParams], ); - const { - node: { daoAddress, safe }, - } = useFractal(); - const { addressPrefix } = useNetworkConfig(); - - const { prepareProposalTemplateProposal } = useCreateProposalTemplate(); - const { submitProposal, pendingCreateTx } = useSubmitProposal(); - const { canUserCreateProposal } = useCanUserCreateProposal(); - const { createProposalTemplateValidation } = useCreateProposalTemplateSchema(); - const ipfsClient = useIPFSClient(); - - const successCallback = () => { - if (daoAddress) { - // Redirecting to proposals page so that user will see Proposal for Proposal Template creation - navigate(DAO_ROUTES.proposals.relative(addressPrefix, daoAddress)); - } - }; - useEffect(() => { const loadInitialTemplate = async () => { if (defaultProposalTemplatesHash && defaultProposalTemplateIndex) { @@ -74,7 +31,7 @@ export default function CreateProposalTemplatePage() { if (initialTemplate) { const newInitialValue = { nonce: undefined, - proposalTemplateMetadata: { + proposalMetadata: { title: initialTemplate.title, description: initialTemplate.description || '', }, @@ -97,125 +54,10 @@ export default function CreateProposalTemplatePage() { }, [defaultProposalTemplatesHash, defaultProposalTemplateIndex, ipfsClient]); return ( - - validationSchema={createProposalTemplateValidation} + { - if (canUserCreateProposal) { - const proposalData = await prepareProposalTemplateProposal(values); - if (proposalData) { - submitProposal({ - proposalData, - nonce: values?.nonce, - pendingToastMessage: t('proposalCreatePendingToastMessage', { ns: 'proposal' }), - successToastMessage: t('proposalCreateSuccessToastMessage', { ns: 'proposal' }), - failedToastMessage: t('proposalCreateFailureToastMessage', { ns: 'proposal' }), - successCallback, - }); - } - } - }} - > - {(formikProps: FormikProps) => { - const { handleSubmit } = formikProps; - - if (!daoAddress) { - return; - } - - return ( -
- - - navigate( - daoAddress - ? DAO_ROUTES.proposalTemplates.relative(addressPrefix, daoAddress) - : BASE_ROUTES.landing, - ) - } - isButtonDisabled={pendingCreateTx} - /> - - - - - {formState === CreateProposalTemplateFormState.METADATA_FORM ? ( - - ) : ( - <> - - - {formikProps.values.proposalTemplateMetadata.title} - - formikProps.setFieldValue('nonce', newNonce)} - align="end" - /> - - - - )} - - - - - - - - -
- ); - }} - + prepareProposalData={prepareProposalTemplateProposal} + /> ); } diff --git a/src/providers/App/governance/action.ts b/src/providers/App/governance/action.ts index 0293593ea8..70b14153b6 100644 --- a/src/providers/App/governance/action.ts +++ b/src/providers/App/governance/action.ts @@ -11,7 +11,7 @@ import { GovernanceType, ERC721TokenData, } from '../../../types'; -import { ProposalTemplate } from '../../../types/createProposalTemplate'; +import { ProposalTemplate } from '../../../types/proposalBuilder'; export enum FractalGovernanceAction { SET_GOVERNANCE_TYPE = 'SET_GOVERNANCE_TYPE', diff --git a/src/types/createProposalTemplate.ts b/src/types/createProposalTemplate.ts deleted file mode 100644 index bab428f939..0000000000 --- a/src/types/createProposalTemplate.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { BigNumberValuePair } from './common'; - -export enum CreateProposalTemplateFormState { - METADATA_FORM, - TRANSACTIONS_FORM, -} - -export interface CreateProposalTemplateTransaction { - targetAddress: string; - ethValue: T; - functionName: string; - parameters: { - signature: string; - label?: string; - value?: string; - }[]; -} - -export type CreateProposalTemplateMetadata = { - title: string; - description: string; -}; - -export type CreateProposalTemplateForm = { - transactions: CreateProposalTemplateTransaction[]; - proposalTemplateMetadata: CreateProposalTemplateMetadata; - nonce?: number; -}; - -export type ProposalTemplate = { - transactions: CreateProposalTemplateTransaction[]; -} & CreateProposalTemplateMetadata; diff --git a/src/types/fractal.ts b/src/types/fractal.ts index 94223dd398..24b48dc666 100644 --- a/src/types/fractal.ts +++ b/src/types/fractal.ts @@ -35,10 +35,10 @@ import { TreasuryActions } from '../providers/App/treasury/action'; import { NodeActions } from './../providers/App/node/action'; import { ERC721TokenData, VotesTokenData } from './account'; import { ContractConnection } from './contract'; -import { ProposalTemplate } from './createProposalTemplate'; import { FreezeGuardType, FreezeVotingType } from './daoGovernance'; import { ProposalData, MultisigProposal, AzoriusProposal, SnapshotProposal } from './daoProposal'; import { TreasuryActivity } from './daoTreasury'; +import { ProposalTemplate } from './proposalBuilder'; import { AllTransfersListResponse, SafeInfoResponseWithGuard } from './safeGlobal'; import { BNFormattedPair } from './votingFungibleToken'; /** diff --git a/src/types/proposalBuilder.ts b/src/types/proposalBuilder.ts new file mode 100644 index 0000000000..8b957137e9 --- /dev/null +++ b/src/types/proposalBuilder.ts @@ -0,0 +1,27 @@ +import { BigNumberValuePair } from './common'; + +export interface CreateProposalTransaction { + targetAddress: string; + ethValue: T; + functionName: string; + parameters: { + signature: string; + label?: string; + value?: string; + }[]; +} + +export type CreateProposalMetadata = { + title: string; + description: string; +}; + +export type CreateProposalForm = { + transactions: CreateProposalTransaction[]; + proposalMetadata: CreateProposalMetadata; + nonce?: number; +}; + +export type ProposalTemplate = { + transactions: CreateProposalTransaction[]; +} & CreateProposalMetadata; From 9fd2565fb3f5eca888b80d91784980824c5a1d1c Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 8 Apr 2024 14:37:17 +0200 Subject: [PATCH 02/14] Prettier --- src/components/ProposalBuilder/index.tsx | 28 ++++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/components/ProposalBuilder/index.tsx b/src/components/ProposalBuilder/index.tsx index 590f84f6aa..354dd8fd91 100644 --- a/src/components/ProposalBuilder/index.tsx +++ b/src/components/ProposalBuilder/index.tsx @@ -30,7 +30,11 @@ const templateAreaTwoCol = '"content details"'; const templateAreaSingleCol = `"content" "details"`; -export default function ProposalBuilder({ mode, initialValues, prepareProposalData }: IProposalBuilder) { +export default function ProposalBuilder({ + mode, + initialValues, + prepareProposalData, +}: IProposalBuilder) { const [formState, setFormState] = useState(CreateProposalState.METADATA_FORM); const { t } = useTranslation(['proposalTemplate', 'proposal']); @@ -59,17 +63,17 @@ export default function ProposalBuilder({ mode, initialValues, prepareProposalDa enableReinitialize onSubmit={async values => { if (canUserCreateProposal) { - const proposalData = await prepareProposalData(values); - if (proposalData) { - submitProposal({ - proposalData, - nonce: values?.nonce, - pendingToastMessage: t('proposalCreatePendingToastMessage', { ns: 'proposal' }), - successToastMessage: t('proposalCreateSuccessToastMessage', { ns: 'proposal' }), - failedToastMessage: t('proposalCreateFailureToastMessage', { ns: 'proposal' }), - successCallback, - }); - } + const proposalData = await prepareProposalData(values); + if (proposalData) { + submitProposal({ + proposalData, + nonce: values?.nonce, + pendingToastMessage: t('proposalCreatePendingToastMessage', { ns: 'proposal' }), + successToastMessage: t('proposalCreateSuccessToastMessage', { ns: 'proposal' }), + failedToastMessage: t('proposalCreateFailureToastMessage', { ns: 'proposal' }), + successCallback, + }); + } } }} > From 392eeea211f0576c50675d887ed716db6c72ab3d Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 8 Apr 2024 15:22:39 +0200 Subject: [PATCH 03/14] Eliminate CreateProposal and use ProposalBuilder instead --- .../ProposalBuilder/ProposalDetails.tsx | 33 ++-- .../ProposalBuilder/ProposalMetadata.tsx | 29 +++- .../ProposalBuilder/ProposalTransaction.tsx | 77 +++++---- .../ProposalBuilder/ProposalTransactions.tsx | 9 +- .../ProposalTransactionsForm.tsx | 3 +- src/components/ProposalBuilder/constants.ts | 2 +- src/components/ProposalBuilder/index.tsx | 26 +-- .../ProposalCreate/ProposalDetails.tsx | 70 -------- .../ProposalCreate/ProposalHeader.tsx | 47 ------ .../ProposalCreate/ProposalMetadata.tsx | 81 ---------- src/components/ProposalCreate/Transaction.tsx | 131 --------------- .../ProposalCreate/Transactions.tsx | 118 -------------- .../ProposalCreate/TransactionsForm.tsx | 97 ----------- src/components/ProposalCreate/constants.ts | 19 --- .../ui/modals/ProposalTemplateModal.tsx | 22 +-- .../loaders/governance/useAzoriusListeners.ts | 4 +- .../loaders/governance/useAzoriusProposals.ts | 6 +- src/hooks/DAO/proposal/useGetMetadata.ts | 20 ++- src/hooks/DAO/proposal/usePrepareProposal.ts | 16 +- src/hooks/DAO/proposal/useSubmitProposal.ts | 4 +- .../useCreateProposalSchema.ts} | 15 +- .../proposalCreate/useCreateProposalSchema.ts | 75 --------- src/i18n/locales/en/proposal.json | 2 +- .../proposal-templates/new/index.tsx | 4 +- .../daos/[daoAddress]/proposals/new/index.tsx | 152 +----------------- src/types/createProposal.ts | 27 ---- src/types/daoProposal.ts | 6 +- src/types/index.ts | 2 +- src/types/proposalBuilder.ts | 6 + 29 files changed, 178 insertions(+), 925 deletions(-) delete mode 100644 src/components/ProposalCreate/ProposalDetails.tsx delete mode 100644 src/components/ProposalCreate/ProposalHeader.tsx delete mode 100644 src/components/ProposalCreate/ProposalMetadata.tsx delete mode 100644 src/components/ProposalCreate/Transaction.tsx delete mode 100644 src/components/ProposalCreate/Transactions.tsx delete mode 100644 src/components/ProposalCreate/TransactionsForm.tsx delete mode 100644 src/components/ProposalCreate/constants.ts rename src/hooks/schemas/{createProposalTemplate/useCreateProposalTemplateSchema.ts => proposalBuilder/useCreateProposalSchema.ts} (83%) delete mode 100644 src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts delete mode 100644 src/types/createProposal.ts diff --git a/src/components/ProposalBuilder/ProposalDetails.tsx b/src/components/ProposalBuilder/ProposalDetails.tsx index 4bb24d63d0..e7b917e2bd 100644 --- a/src/components/ProposalBuilder/ProposalDetails.tsx +++ b/src/components/ProposalBuilder/ProposalDetails.tsx @@ -3,7 +3,7 @@ import { FormikProps } from 'formik'; import { Fragment, PropsWithChildren } from 'react'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; -import { CreateProposalForm } from '../../types/proposalBuilder'; +import { CreateProposalForm, ProposalBuilderMode } from '../../types/proposalBuilder'; import Markdown from '../ui/proposal/Markdown'; import '../../assets/css/Markdown.css'; @@ -30,7 +30,8 @@ export function TransactionValueContainer({ export default function ProposalTemplateDetails({ values: { proposalMetadata, transactions }, -}: FormikProps) { + mode, +}: FormikProps & { mode: ProposalBuilderMode }) { const { t } = useTranslation(['proposalTemplate', 'proposal']); const trimmedTitle = proposalMetadata.title?.trim(); @@ -51,19 +52,21 @@ export default function ProposalTemplateDetails({ {t('previewTitle')} {trimmedTitle} - - {t('previewThumnbail')} - {trimmedTitle && ( - title.slice(0, 2)} - /> - )} - + {mode === 'template' && ( + + {t('previewThumnbail')} + {trimmedTitle && ( + title.slice(0, 2)} + /> + )} + + )} {t('proposalTemplateDescription')} { setFormState: (state: CreateProposalState) => void; + mode: ProposalBuilderMode; } export default function ProposalMetadata({ @@ -14,8 +15,10 @@ export default function ProposalMetadata({ setFieldValue, errors: { proposalMetadata: proposalMetadataError }, setFormState, + mode, }: ProposalMetadataProps) { - const { t } = useTranslation(['proposalTemplate', 'common']); + const { t } = useTranslation(['proposalTemplate', 'proposal', 'common']); + const isProposalMode = mode === 'proposal'; return ( <> @@ -25,8 +28,14 @@ export default function ProposalMetadata({ mt={4} > setFieldValue('proposalMetadata.title', e.target.value)} @@ -35,9 +44,17 @@ export default function ProposalMetadata({ maxLength={50} /> setFieldValue('proposalMetadata.description', e.target.value)} diff --git a/src/components/ProposalBuilder/ProposalTransaction.tsx b/src/components/ProposalBuilder/ProposalTransaction.tsx index b9f83bede1..ad256b811b 100644 --- a/src/components/ProposalBuilder/ProposalTransaction.tsx +++ b/src/components/ProposalBuilder/ProposalTransaction.tsx @@ -2,7 +2,7 @@ import { VStack, HStack, Text, Box, Flex, IconButton } from '@chakra-ui/react'; import { AddPlus, Minus } from '@decent-org/fractal-ui'; import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { CreateProposalTransaction } from '../../types/proposalBuilder'; +import { CreateProposalTransaction, ProposalBuilderMode } from '../../types/proposalBuilder'; import ABISelector, { ABIElement } from '../ui/forms/ABISelector'; import ExampleLabel from '../ui/forms/ExampleLabel'; import { BigNumberComponent, InputComponent } from '../ui/forms/InputComponent'; @@ -15,6 +15,7 @@ interface ProposalTransactionProps { txAddressError?: string; txFunctionError?: string; setFieldValue: (field: string, value: any, shouldValidate?: boolean | undefined) => void; + mode: ProposalBuilderMode; } export default function ProposalTransaction({ @@ -24,7 +25,9 @@ export default function ProposalTransaction({ txAddressError, txFunctionError, setFieldValue, + mode, }: ProposalTransactionProps) { + const isProposalMode = mode === 'proposal'; const { t } = useTranslation(['proposal', 'proposalTemplate', 'common']); const handleABISelectorChange = useCallback( (value: ABIElement) => { @@ -170,34 +173,38 @@ export default function ProposalTransaction({ alignItems="center" mt={4} > - - setFieldValue( - `transactions.${transactionIndex}.parameters.${i}.label`, - e.target.value, - ) - } - disabled={transactionPending || !!parameter.value} - testId={`transactions.${transactionIndex}.parameters.${i}.label`} - subLabel={ - - {t('helperParameterLabel', { ns: 'proposalTemplate' })} - - } - gridContainerProps={{ - display: 'inline-flex', - flexWrap: 'wrap', - width: '30%', - }} - inputContainerProps={{ - width: '100%', - }} - /> - {t('or', { ns: 'common' })} + {!isProposalMode && ( + <> + + setFieldValue( + `transactions.${transactionIndex}.parameters.${i}.label`, + e.target.value, + ) + } + disabled={transactionPending || !!parameter.value} + testId={`transactions.${transactionIndex}.parameters.${i}.label`} + subLabel={ + + {t('helperParameterLabel', { ns: 'proposalTemplate' })} + + } + gridContainerProps={{ + display: 'inline-flex', + flexWrap: 'wrap', + width: '30%', + }} + inputContainerProps={{ + width: '100%', + }} + /> + {t('or', { ns: 'common' })} + + )} {t('example', { ns: 'common' })}: value - - {t('proposalTemplateLeaveBlank', { ns: 'proposalTemplate' })} - + {!isProposalMode && ( + + {t('proposalTemplateLeaveBlank', { ns: 'proposalTemplate' })} + + )} } @@ -282,7 +291,9 @@ export default function ProposalTransaction({ {`${t('example', { ns: 'common' })}:`} {'1.2'} - {t('ethParemeterHelper', { ns: 'proposalTemplate' })} + {!isProposalMode && ( + {t('ethParemeterHelper', { ns: 'proposalTemplate' })} + )} } errorMessage={undefined} diff --git a/src/components/ProposalBuilder/ProposalTransactions.tsx b/src/components/ProposalBuilder/ProposalTransactions.tsx index 1f114ec7b6..6191e73acd 100644 --- a/src/components/ProposalBuilder/ProposalTransactions.tsx +++ b/src/components/ProposalBuilder/ProposalTransactions.tsx @@ -12,13 +12,18 @@ import { FormikErrors, FormikProps } from 'formik'; import { Dispatch, SetStateAction } from 'react'; import { useTranslation } from 'react-i18next'; import { BigNumberValuePair } from '../../types'; -import { CreateProposalForm, CreateProposalTransaction } from '../../types/proposalBuilder'; +import { + CreateProposalForm, + CreateProposalTransaction, + ProposalBuilderMode, +} from '../../types/proposalBuilder'; import ProposalTransaction from './ProposalTransaction'; interface ProposalTransactionsProps extends FormikProps { pendingTransaction: boolean; expandedIndecies: number[]; setExpandedIndecies: Dispatch>; + mode: ProposalBuilderMode; } export default function ProposalTransactions({ values: { transactions }, @@ -27,6 +32,7 @@ export default function ProposalTransactions({ pendingTransaction, expandedIndecies, setExpandedIndecies, + mode, }: ProposalTransactionsProps) { const { t } = useTranslation(['proposal', 'proposalTemplate', 'common']); @@ -105,6 +111,7 @@ export default function ProposalTransactions({ transactionIndex={index} setFieldValue={setFieldValue} transactionPending={pendingTransaction} + mode={mode} />
diff --git a/src/components/ProposalBuilder/ProposalTransactionsForm.tsx b/src/components/ProposalBuilder/ProposalTransactionsForm.tsx index a79037f0b6..25e267eb00 100644 --- a/src/components/ProposalBuilder/ProposalTransactionsForm.tsx +++ b/src/components/ProposalBuilder/ProposalTransactionsForm.tsx @@ -4,7 +4,7 @@ import { FormikProps } from 'formik'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CreateProposalState } from '../../types'; -import { CreateProposalForm } from '../../types/proposalBuilder'; +import { CreateProposalForm, ProposalBuilderMode } from '../../types/proposalBuilder'; import { scrollToBottom } from '../../utils/ui'; import ProposalTransactions from './ProposalTransactions'; import { DEFAULT_PROPOSAL_TRANSACTION } from './constants'; @@ -14,6 +14,7 @@ interface ProposalTransactionsFormProps extends FormikProps setFormState: (state: CreateProposalState) => void; canUserCreateProposal?: boolean; safeNonce?: number; + mode: ProposalBuilderMode; } export default function ProposalTransactionsForm(props: ProposalTransactionsFormProps) { diff --git a/src/components/ProposalBuilder/constants.ts b/src/components/ProposalBuilder/constants.ts index bd76faf013..8eb29adad4 100644 --- a/src/components/ProposalBuilder/constants.ts +++ b/src/components/ProposalBuilder/constants.ts @@ -13,7 +13,7 @@ export const DEFAULT_PROPOSAL_TRANSACTION: CreateProposalTransaction = { ], }; -export const DEFAULT_PROPOSAL_TEMPLATE = { +export const DEFAULT_PROPOSAL = { nonce: undefined, proposalMetadata: { title: '', diff --git a/src/components/ProposalBuilder/index.tsx b/src/components/ProposalBuilder/index.tsx index 354dd8fd91..93d3cbfe19 100644 --- a/src/components/ProposalBuilder/index.tsx +++ b/src/components/ProposalBuilder/index.tsx @@ -7,23 +7,22 @@ import { useNavigate } from 'react-router-dom'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; import { DAO_ROUTES, BASE_ROUTES } from '../../constants/routes'; import useSubmitProposal from '../../hooks/DAO/proposal/useSubmitProposal'; -import useCreateProposalTemplateSchema from '../../hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema'; +import useCreateProposalSchema from '../../hooks/schemas/proposalBuilder/useCreateProposalSchema'; import { useCanUserCreateProposal } from '../../hooks/utils/useCanUserSubmitProposal'; import { useFractal } from '../../providers/App/AppProvider'; import { useNetworkConfig } from '../../providers/NetworkConfig/NetworkConfigProvider'; import { CreateProposalState, ProposalExecuteData } from '../../types'; -import { CreateProposalForm } from '../../types/proposalBuilder'; +import { CreateProposalForm, ProposalBuilderMode } from '../../types/proposalBuilder'; import { CustomNonceInput } from '../ui/forms/CustomNonceInput'; import PageHeader from '../ui/page/Header/PageHeader'; import ProposalDetails from './ProposalDetails'; import ProposalMetadata from './ProposalMetadata'; import ProposalTransactionsForm from './ProposalTransactionsForm'; -import { DEFAULT_PROPOSAL_TEMPLATE } from './constants'; interface IProposalBuilder { - mode: 'proposal' | 'template'; + mode: ProposalBuilderMode; prepareProposalData: (values: CreateProposalForm) => Promise; - initialValues: typeof DEFAULT_PROPOSAL_TEMPLATE; + initialValues: CreateProposalForm; } const templateAreaTwoCol = '"content details"'; @@ -47,7 +46,7 @@ export default function ProposalBuilder({ const { addressPrefix } = useNetworkConfig(); const { submitProposal, pendingCreateTx } = useSubmitProposal(); const { canUserCreateProposal } = useCanUserCreateProposal(); - const { createProposalTemplateValidation } = useCreateProposalTemplateSchema(); + const { createProposalValidation } = useCreateProposalSchema(); const successCallback = () => { if (daoAddress) { @@ -58,7 +57,7 @@ export default function ProposalBuilder({ return ( - validationSchema={createProposalTemplateValidation} + validationSchema={createProposalValidation} initialValues={initialValues} enableReinitialize onSubmit={async values => { @@ -88,7 +87,11 @@ export default function ProposalBuilder({
) : ( @@ -173,6 +177,7 @@ export default function ProposalBuilder({ canUserCreateProposal={canUserCreateProposal} pendingTransaction={pendingCreateTx} safeNonce={safe?.nonce} + mode={mode} {...formikProps} /> @@ -184,7 +189,10 @@ export default function ProposalBuilder({ area="details" w="100%" > - + diff --git a/src/components/ProposalCreate/ProposalDetails.tsx b/src/components/ProposalCreate/ProposalDetails.tsx deleted file mode 100644 index 2592d2f3de..0000000000 --- a/src/components/ProposalCreate/ProposalDetails.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Box, Divider, Flex, HStack, Text } from '@chakra-ui/react'; -import { useTranslation } from 'react-i18next'; -import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; -import { useFractal } from '../../providers/App/AppProvider'; -import { AzoriusGovernance, GovernanceType } from '../../types'; -import ContentBoxTitle from '../ui/containers/ContentBox/ContentBoxTitle'; -import { BarLoader } from '../ui/loaders/BarLoader'; - -export function ProposalDetails() { - const { - node: { daoAddress, safe }, - governance, - } = useFractal(); - const { type } = governance; - const azoriusGovernance = governance as AzoriusGovernance; - const { t } = useTranslation(['proposal']); - return ( - - {!type || !daoAddress || !safe ? ( - - - - ) : ( - - {t('proposalSummaryTitle')} - - {type === GovernanceType.MULTISIG ? ( - - {t('labelProposalSigners')} - - {safe.threshold}/{safe.owners?.length} - - - ) : ( - <> - - {t('labelProposalVotingPeriod')} - {azoriusGovernance.votingStrategy?.votingPeriod?.formatted} - - - {t('labelProposalQuorum')} - - {azoriusGovernance.votingStrategy?.quorumPercentage?.formatted || - azoriusGovernance.votingStrategy?.quorumThreshold?.formatted} - - - - {t('labelProposalTimelock')} - {azoriusGovernance.votingStrategy?.timeLockPeriod?.formatted} - - - )} - - )} - - ); -} diff --git a/src/components/ProposalCreate/ProposalHeader.tsx b/src/components/ProposalCreate/ProposalHeader.tsx deleted file mode 100644 index 38983b7773..0000000000 --- a/src/components/ProposalCreate/ProposalHeader.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { HStack, Spacer, Text, Tooltip } from '@chakra-ui/react'; -import { Alert } from '@decent-org/fractal-ui'; -import { useTranslation } from 'react-i18next'; -import { TOOLTIP_MAXW } from '../../constants/common'; -import { CustomNonceInput } from '../ui/forms/CustomNonceInput'; - -export function ProposalHeader({ - isAzorius, - metadataTitle, - nonce, - setNonce, -}: { - isAzorius?: boolean; - metadataTitle?: string; - nonce?: number; - setNonce: (nonce?: number) => void; -}) { - const { t } = useTranslation('proposal'); - - return ( - - - {metadataTitle ? metadataTitle : t('proposal', { ns: 'proposal' })} - - {!isAzorius && ( - - - - )} - - {!isAzorius && ( - - )} - - ); -} diff --git a/src/components/ProposalCreate/ProposalMetadata.tsx b/src/components/ProposalCreate/ProposalMetadata.tsx deleted file mode 100644 index a25642fc5c..0000000000 --- a/src/components/ProposalCreate/ProposalMetadata.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Button, Divider, VStack } from '@chakra-ui/react'; -import { FormikProps } from 'formik'; -import { useTranslation } from 'react-i18next'; -import { CreateProposalForm, CreateProposalState } from '../../types'; -import { InputComponent, TextareaComponent } from '../ui/forms/InputComponent'; - -export interface AzoriusMetadataProps extends FormikProps { - isVisible: boolean; - setFormState: (state: CreateProposalState) => void; -} - -function ProposalMetadata(props: AzoriusMetadataProps) { - const { - values: { proposalMetadata }, - setFieldValue, - errors: { proposalMetadata: proposalMetadataError }, - isVisible, - setFormState, - } = props; - const { t } = useTranslation(['proposal', 'common']); - - if (!isVisible) return null; - - return ( - <> - - setFieldValue('proposalMetadata.title', e.target.value)} - disabled={false} - placeholder={t('proposalTitlePlaceholder')} - testId="metadata.title" - /> - setFieldValue('proposalMetadata.description', e.target.value)} - disabled={false} - placeholder={t('proposalDescriptionPlaceholder')} - rows={9} - /> - setFieldValue('proposalMetadata.documentationUrl', e.target.value)} - value={proposalMetadata.documentationUrl} - disabled={false} - placeholder={t('proposalAdditionalResourcesPlaceholder')} - errorMessage={ - proposalMetadata.documentationUrl && proposalMetadataError?.documentationUrl - } - testId="metadata.documentationUrl" - /> - - - - - ); -} - -export default ProposalMetadata; diff --git a/src/components/ProposalCreate/Transaction.tsx b/src/components/ProposalCreate/Transaction.tsx deleted file mode 100644 index 481328f087..0000000000 --- a/src/components/ProposalCreate/Transaction.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { VStack, HStack, Text } from '@chakra-ui/react'; -import { useTranslation } from 'react-i18next'; -import { CreateProposalTransaction } from '../../types/createProposal'; -import ExampleLabel from '../ui/forms/ExampleLabel'; -import { BigNumberComponent, InputComponent } from '../ui/forms/InputComponent'; - -interface TransactionProps { - transaction: CreateProposalTransaction; - transactionIndex: number; - transactionPending: boolean; - txAddressError?: string; - txFunctionError?: string; - setFieldValue: (field: string, value: any, shouldValidate?: boolean | undefined) => void; -} - -function Transaction({ - transaction, - transactionIndex, - transactionPending, - txAddressError, - txFunctionError, - setFieldValue, -}: TransactionProps) { - const { t } = useTranslation(['proposal', 'common']); - - return ( - - - {`${t('example', { ns: 'common' })}:`} - 0x4168592... - - } - errorMessage={transaction.targetAddress && txAddressError ? txAddressError : undefined} - value={transaction.targetAddress} - testId="transaction.targetAddress" - onChange={e => - setFieldValue(`transactions.${transactionIndex}.targetAddress`, e.target.value) - } - /> - - - setFieldValue(`transactions.${transactionIndex}.functionName`, e.target.value) - } - disabled={transactionPending} - subLabel={ - - {`${t('example', { ns: 'common' })}:`} - transfer - - } - // @todo update withn new error messages - errorMessage={undefined} - testId="transaction.functionName" - /> - - setFieldValue(`transactions.${transactionIndex}.functionSignature`, e.target.value) - } - disabled={transactionPending} - subLabel={ - - {`${t('example', { ns: 'common' })}:`} - address, uint256 - - } - testId="transaction.functionSignature" - errorMessage={ - transaction.functionSignature && txFunctionError ? txFunctionError : undefined - } - /> - setFieldValue(`transactions.${transactionIndex}.parameters`, e.target.value)} - disabled={transactionPending} - subLabel={ - - {`${t('example', { ns: 'common' })}:`} - - {'0xADC74eE329a23060d3CB431Be0AB313740c191E7, 1000000000'} - - - } - testId="transaction.parameters" - errorMessage={transaction.parameters && txFunctionError ? txFunctionError : undefined} - /> - - - {`${t('example', { ns: 'common' })}:`} - {'1.2'} - - } - errorMessage={undefined} - value={transaction.ethValue.bigNumberValue} - onChange={e => { - setFieldValue(`transactions.${transactionIndex}.ethValue`, e); - }} - decimalPlaces={18} - /> - - ); -} - -export default Transaction; diff --git a/src/components/ProposalCreate/Transactions.tsx b/src/components/ProposalCreate/Transactions.tsx deleted file mode 100644 index 29207f54e5..0000000000 --- a/src/components/ProposalCreate/Transactions.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { - Box, - Accordion, - AccordionButton, - AccordionItem, - AccordionPanel, - HStack, - IconButton, -} from '@chakra-ui/react'; -import { ArrowDown, ArrowRight, Minus } from '@decent-org/fractal-ui'; -import { FormikErrors, FormikProps } from 'formik'; -import { Dispatch, SetStateAction } from 'react'; -import { useTranslation } from 'react-i18next'; -import { BigNumberValuePair, CreateProposalForm, CreateProposalTransaction } from '../../types'; -import Transaction from './Transaction'; - -interface TransactionsProps extends FormikProps { - isVisible: boolean; - pendingTransaction: boolean; - expandedIndecies: number[]; - setExpandedIndecies: Dispatch>; -} -function Transactions({ - values: { transactions }, - errors, - setFieldValue, - pendingTransaction, - expandedIndecies, - setExpandedIndecies, -}: TransactionsProps) { - const { t } = useTranslation(['proposal', 'common']); - - const removeTransaction = (transactionIndex: number) => { - const transactionsArr = [...transactions]; - transactionsArr.splice(transactionIndex, 1); - setFieldValue('transactions', transactionsArr); - }; - return ( - - {transactions.map((_, index) => { - const txErrors = errors?.transactions?.[index] as - | FormikErrors> - | undefined; - const txAddressError = txErrors?.targetAddress; - const txFunctionError = txErrors?.encodedFunctionData; - - return ( - - {({ isExpanded }) => ( - - - { - setExpandedIndecies(indexArray => { - if (indexArray.includes(index)) { - const newTxArr = [...indexArray]; - newTxArr.splice(newTxArr.indexOf(index), 1); - return newTxArr; - } else { - return [...indexArray, index]; - } - }); - }} - p={0} - textStyle="text-button-md-semibold" - color="grayscale.100" - > - {isExpanded ? : } - {t('transaction')} {index + 1} - - {index !== 0 || transactions.length !== 1 ? ( - } - aria-label={t('removetransactionlabel')} - variant="unstyled" - onClick={() => removeTransaction(index)} - minWidth="auto" - _hover={{ color: 'gold.500' }} - _disabled={{ opacity: 0.4, cursor: 'default' }} - sx={{ '&:disabled:hover': { color: 'inherit', opacity: 0.4 } }} - isDisabled={pendingTransaction} - /> - ) : ( - - )} - - - - - - )} - - ); - })} - - ); -} - -export default Transactions; diff --git a/src/components/ProposalCreate/TransactionsForm.tsx b/src/components/ProposalCreate/TransactionsForm.tsx deleted file mode 100644 index 43d83bd404..0000000000 --- a/src/components/ProposalCreate/TransactionsForm.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { Button, Box, Flex, Text, VStack, Divider, Alert, AlertTitle } from '@chakra-ui/react'; -import { Info } from '@decent-org/fractal-ui'; -import { FormikProps } from 'formik'; -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { CreateProposalForm, CreateProposalState } from '../../types'; -import { scrollToBottom } from '../../utils/ui'; -import Transactions from './Transactions'; -import { DEFAULT_TRANSACTION } from './constants'; - -interface TransactionsFormProps extends FormikProps { - isVisible: boolean; - pendingTransaction: boolean; - setFormState: (state: CreateProposalState) => void; - canUserCreateProposal?: boolean; -} - -function TransactionsForm(props: TransactionsFormProps) { - const { - isVisible, - pendingTransaction, - setFormState, - setFieldValue, - values: { transactions }, - errors: { transactions: transactionsError }, - canUserCreateProposal, - } = props; - const { t } = useTranslation(['proposal', 'common']); - const [expandedIndecies, setExpandedIndecies] = useState([0]); - - if (!isVisible) return null; - - return ( - - - - - - - - - {t('transactionExecutionAlertMessage')} - - - - - - - - - - - ); -} - -export default TransactionsForm; diff --git a/src/components/ProposalCreate/constants.ts b/src/components/ProposalCreate/constants.ts deleted file mode 100644 index 6c8c3a7486..0000000000 --- a/src/components/ProposalCreate/constants.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BigNumber } from 'ethers'; -export const DEFAULT_TRANSACTION = { - targetAddress: '', - ethValue: { value: '0', bigNumberValue: BigNumber.from('0') }, - functionName: '', - functionSignature: '', - parameters: '', - encodedFunctionData: undefined, -}; - -export const DEFAULT_PROPOSAL = { - proposalMetadata: { - title: '', - description: '', - documentationUrl: '', - }, - transactions: [DEFAULT_TRANSACTION], - nonce: 0, -}; diff --git a/src/components/ui/modals/ProposalTemplateModal.tsx b/src/components/ui/modals/ProposalTemplateModal.tsx index d9250b6941..a96b227aa5 100644 --- a/src/components/ui/modals/ProposalTemplateModal.tsx +++ b/src/components/ui/modals/ProposalTemplateModal.tsx @@ -8,7 +8,7 @@ import { Switch, VStack, } from '@chakra-ui/react'; -import { utils, BigNumber } from 'ethers'; +import { BigNumber } from 'ethers'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; @@ -21,7 +21,6 @@ import { useFractal } from '../../../providers/App/AppProvider'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; import { BigNumberValuePair } from '../../../types'; import { ProposalTemplate } from '../../../types/proposalBuilder'; -import { isValidUrl } from '../../../utils/url'; import { CustomNonceInput } from '../forms/CustomNonceInput'; import { BigNumberComponent, InputComponent } from '../forms/InputComponent'; import Markdown from '../proposal/Markdown'; @@ -115,27 +114,10 @@ export default function ProposalTemplateModal({ documentationUrl: '', }; - const proposalTransactions = filledProposalTransactions.map( - ({ targetAddress, ethValue, functionName, parameters }) => { - return { - targetAddress: utils.getAddress(targetAddress), // Safe proposal creation/execution might fail if targetAddress is not checksummed - ethValue, - functionName, - functionSignature: parameters.map(parameter => parameter.signature.trim()).join(', '), - parameters: parameters - .map(parameter => - isValidUrl(parameter.value!.trim()) - ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL - : parameter.value!.trim(), - ) - .join(', '), - }; - }, - ); try { const proposalData = await prepareProposal({ proposalMetadata, - transactions: proposalTransactions, + transactions: filledProposalTransactions, }); submitProposal({ diff --git a/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts b/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts index 291a938e46..8784feeeda 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusListeners.ts @@ -13,7 +13,7 @@ import { useFractal } from '../../../../providers/App/AppProvider'; import { FractalGovernanceAction } from '../../../../providers/App/governance/action'; import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; import { - ProposalMetadata, + CreateProposalMetadata, VotingStrategyType, DecodedTransaction, FractalActions, @@ -49,7 +49,7 @@ const proposalCreatedEventListener = ( return; } - const metaDataEvent: ProposalMetadata = JSON.parse(metadata); + const metaDataEvent: CreateProposalMetadata = JSON.parse(metadata); const proposalData = { metaData: { title: metaDataEvent.title, diff --git a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts index 1e8b61d0f5..3988776699 100644 --- a/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts +++ b/src/hooks/DAO/loaders/governance/useAzoriusProposals.ts @@ -8,7 +8,7 @@ import { VotedEvent as ERC721VotedEvent } from '@fractal-framework/fractal-contr import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useFractal } from '../../../../providers/App/AppProvider'; import { useEthersProvider } from '../../../../providers/Ethers/hooks/useEthersProvider'; -import { ProposalMetadata, VotingStrategyType, DecodedTransaction } from '../../../../types'; +import { CreateProposalMetadata, VotingStrategyType, DecodedTransaction } from '../../../../types'; import { AzoriusProposal } from '../../../../types/daoProposal'; import { Providers } from '../../../../types/network'; import { mapProposalCreatedEventToProposal, decodeTransactions } from '../../../../utils'; @@ -140,7 +140,9 @@ export const useAzoriusProposals = () => { for (const proposalCreatedEvent of proposalCreatedEvents) { let proposalData; if (proposalCreatedEvent.args.metadata) { - const metadataEvent: ProposalMetadata = JSON.parse(proposalCreatedEvent.args.metadata); + const metadataEvent: CreateProposalMetadata = JSON.parse( + proposalCreatedEvent.args.metadata, + ); const decodedTransactions = await decodeTransactions( _decode, proposalCreatedEvent.args.transactions, diff --git a/src/hooks/DAO/proposal/useGetMetadata.ts b/src/hooks/DAO/proposal/useGetMetadata.ts index edcc6975c9..6a80aeee06 100644 --- a/src/hooks/DAO/proposal/useGetMetadata.ts +++ b/src/hooks/DAO/proposal/useGetMetadata.ts @@ -2,7 +2,11 @@ import { utils } from 'ethers'; import { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import useIPFSClient from '../../../providers/App/hooks/useIPFSClient'; -import { FractalProposal, ProposalMetadata, SafeMultisigTransactionResponse } from '../../../types'; +import { + FractalProposal, + CreateProposalMetadata, + SafeMultisigTransactionResponse, +} from '../../../types'; import { CacheKeys } from '../../utils/cache/cacheDefaults'; import { DBObjectKeys, useIndexedDB } from '../../utils/cache/useLocalDB'; @@ -20,15 +24,15 @@ interface Transaction { const useGetMultisigMetadata = (proposal: FractalProposal | null | undefined) => { const ipfsClient = useIPFSClient(); - const [multisigMetadata, setMultisigMetadata] = useState( - undefined, - ); + const [multisigMetadata, setMultisigMetadata] = useState< + undefined | CreateProposalMetadata | null + >(undefined); const [setValue, getValue] = useIndexedDB(DBObjectKeys.DECODED_TRANSACTIONS); const fetchMultisigMetadata = useCallback(async () => { if (!proposal) return; - const cached: ProposalMetadata = await getValue( + const cached: CreateProposalMetadata = await getValue( CacheKeys.MULTISIG_METADATA_PREFIX + proposal.proposalId, ); if (cached) { @@ -59,7 +63,7 @@ const useGetMultisigMetadata = (proposal: FractalProposal | null | undefined) => try { const decoded = new utils.AbiCoder().decode(['string'], encodedMetadata); const ipfsHash = (decoded as string[])[0]; - const meta: ProposalMetadata = await ipfsClient.cat(ipfsHash); + const meta: CreateProposalMetadata = await ipfsClient.cat(ipfsHash); // cache the metadata JSON await setValue(CacheKeys.MULTISIG_METADATA_PREFIX + proposal.proposalId, meta); @@ -78,7 +82,9 @@ const useGetMultisigMetadata = (proposal: FractalProposal | null | undefined) => return { multisigMetadata }; }; -export const useGetMetadata = (proposal: FractalProposal | null | undefined): ProposalMetadata => { +export const useGetMetadata = ( + proposal: FractalProposal | null | undefined, +): CreateProposalMetadata => { const { multisigMetadata } = useGetMultisigMetadata(proposal); const { t } = useTranslation('dashboard'); diff --git a/src/hooks/DAO/proposal/usePrepareProposal.ts b/src/hooks/DAO/proposal/usePrepareProposal.ts index c9914cd0b3..e81a71463c 100644 --- a/src/hooks/DAO/proposal/usePrepareProposal.ts +++ b/src/hooks/DAO/proposal/usePrepareProposal.ts @@ -1,8 +1,8 @@ import { useCallback } from 'react'; import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; -import { CreateProposalForm } from '../../../types'; +import { CreateProposalForm } from '../../../types/proposalBuilder'; import { encodeFunction } from '../../../utils/crypto'; -import { couldBeENS } from '../../../utils/url'; +import { couldBeENS, isValidUrl } from '../../../utils/url'; export function usePrepareProposal() { const signer = useEthersSigner(); @@ -12,7 +12,17 @@ export function usePrepareProposal() { const transactionsWithEncoding = transactions.map(tx => { return { ...tx, - encodedFunctionData: encodeFunction(tx.functionName, tx.functionSignature, tx.parameters), + encodedFunctionData: encodeFunction( + tx.functionName, + tx.parameters.map(parameter => parameter.signature.trim()).join(', '), + tx.parameters + .map(parameter => + isValidUrl(parameter.value!.trim()) + ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL + : parameter.value!.trim(), + ) + .join(', '), + ), }; }); const targets = await Promise.all( diff --git a/src/hooks/DAO/proposal/useSubmitProposal.ts b/src/hooks/DAO/proposal/useSubmitProposal.ts index e4fa5a562e..fe24c2151c 100644 --- a/src/hooks/DAO/proposal/useSubmitProposal.ts +++ b/src/hooks/DAO/proposal/useSubmitProposal.ts @@ -15,7 +15,7 @@ import { useSafeAPI } from '../../../providers/App/hooks/useSafeAPI'; import { useEthersProvider } from '../../../providers/Ethers/hooks/useEthersProvider'; import { useEthersSigner } from '../../../providers/Ethers/hooks/useEthersSigner'; import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; -import { MetaTransaction, ProposalExecuteData, ProposalMetadata } from '../../../types'; +import { MetaTransaction, ProposalExecuteData, CreateProposalMetadata } from '../../../types'; import { buildSafeApiUrl, getAzoriusModuleFromModules } from '../../../utils'; import useSafeContracts from '../../safe/useSafeContracts'; import useSignerOrProvider from '../../utils/useSignerOrProvider'; @@ -116,7 +116,7 @@ export default function useSubmitProposal() { proposalData.metaData.description || proposalData.metaData.documentationUrl ) { - const metaData: ProposalMetadata = { + const metaData: CreateProposalMetadata = { title: proposalData.metaData.title || '', description: proposalData.metaData.description || '', documentationUrl: proposalData.metaData.documentationUrl || '', diff --git a/src/hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema.ts b/src/hooks/schemas/proposalBuilder/useCreateProposalSchema.ts similarity index 83% rename from src/hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema.ts rename to src/hooks/schemas/proposalBuilder/useCreateProposalSchema.ts index 9da83d62af..cb7b9a92ae 100644 --- a/src/hooks/schemas/createProposalTemplate/useCreateProposalTemplateSchema.ts +++ b/src/hooks/schemas/proposalBuilder/useCreateProposalSchema.ts @@ -5,10 +5,10 @@ import { useFractal } from '../../../providers/App/AppProvider'; import { useValidationAddress } from '../common/useValidationAddress'; /** - * validation schema for Create Proposal Template workflow + * validation schema for Create Proposal workflow * @dev https://www.npmjs.com/package/yup */ -const useCreateProposalTemplateSchema = () => { +const useCreateProposalSchema = () => { const { t } = useTranslation('proposal'); const { addressValidationTest } = useValidationAddress(); const { @@ -30,7 +30,7 @@ const useCreateProposalTemplateSchema = () => { return false; }; - const createProposalTemplateValidation = useMemo( + const createProposalValidation = useMemo( () => Yup.object().shape({ transactions: Yup.array() @@ -59,9 +59,10 @@ const useCreateProposalTemplateSchema = () => { ), }), ), - proposalTemplateMetadata: Yup.object().shape({ + proposalMetadata: Yup.object().shape({ title: Yup.string().trim().required().max(50), - description: Yup.string().trim().notRequired().max(300), + description: Yup.string().trim().notRequired(), + documentationUrl: Yup.string().trim().notRequired(), }), nonce: Yup.number() .required() @@ -69,7 +70,7 @@ const useCreateProposalTemplateSchema = () => { }), [addressValidationTest, t, safe], ); - return { createProposalTemplateValidation }; + return { createProposalValidation }; }; -export default useCreateProposalTemplateSchema; +export default useCreateProposalSchema; diff --git a/src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts b/src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts deleted file mode 100644 index 7389278cd8..0000000000 --- a/src/hooks/schemas/proposalCreate/useCreateProposalSchema.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import * as Yup from 'yup'; -import { encodeFunction } from '../../../utils/crypto'; -import { useValidationAddress } from '../common/useValidationAddress'; - -/** - * validation schema for Create Proposal workflow - * @dev https://www.npmjs.com/package/yup - */ -export const useCreateProposalSchema = () => { - const { addressValidationTest } = useValidationAddress(); - - const { t } = useTranslation('proposal'); - - const createProposalValidation = useMemo( - () => - Yup.object().shape({ - transactions: Yup.array() - .min(1) - .of( - Yup.object().shape({ - targetAddress: Yup.string().required().test(addressValidationTest), - ethValue: Yup.object().shape({ - value: Yup.string(), - }), - functionName: Yup.string().matches(/^[a-z0-9]+$/i, { - message: t('functionNameError'), - }), - functionSignature: Yup.string(), - parameters: Yup.string(), - encodedFunctionData: Yup.string().test({ - message: t('errorInvalidFragments'), - test: (_, context) => { - const functionName = context.parent.functionName; - const functionSignature = context.parent.functionSignature; - const parameters = context.parent.parameters; - if (!functionName) return false; - const encodedFunction = encodeFunction( - functionName, - functionSignature, - parameters, - ); - - return !!encodedFunction; - }, - }), - }), - ), - proposalMetadata: Yup.object().shape({ - title: Yup.string().notRequired(), - description: Yup.string().notRequired(), - documentationUrl: Yup.string() - .notRequired() - .when({ - is: (value?: string) => !!value, - then: schema => - schema.test({ - message: t('Invalid URL'), - test: value => { - try { - return Boolean(new URL(value || '')); - } catch (e) { - return false; - } - }, - }), - }), - }), - nonce: Yup.number(), - }), - [addressValidationTest, t], - ); - return { createProposalValidation }; -}; diff --git a/src/i18n/locales/en/proposal.json b/src/i18n/locales/en/proposal.json index 2f8da0fd37..c01bebca56 100644 --- a/src/i18n/locales/en/proposal.json +++ b/src/i18n/locales/en/proposal.json @@ -2,7 +2,7 @@ "proposalOverview": "Proposal Overview", "breakdownTitle": "Breakdown", "labelTargetAddress": "Target Address", - "helperTargetAddress": "The smart contract address this proposal will modify", + "helperTargetAddress": "The smart contract address this proposal will modify. Paste address and we'll try to fetch ABI for this contract from Etherscan", "labelFunctionName": "Function Name", "helperFunctionName": "The name of the function to be called if this proposal passes", "labelFunctionSignature": "Function Signature", diff --git a/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx b/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx index a5366c743b..4ccb364cc3 100644 --- a/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx +++ b/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers'; import { useEffect, useState, useMemo } from 'react'; import { useSearchParams } from 'react-router-dom'; import ProposalBuilder from '../../../../../components/ProposalBuilder'; -import { DEFAULT_PROPOSAL_TEMPLATE } from '../../../../../components/ProposalBuilder/constants'; +import { DEFAULT_PROPOSAL } from '../../../../../components/ProposalBuilder/constants'; import { logError } from '../../../../../helpers/errorLogging'; import useCreateProposalTemplate from '../../../../../hooks/DAO/proposal/useCreateProposalTemplate'; import useIPFSClient from '../../../../../providers/App/hooks/useIPFSClient'; @@ -10,7 +10,7 @@ import { ProposalTemplate } from '../../../../../types/proposalBuilder'; export default function CreateProposalTemplatePage() { const ipfsClient = useIPFSClient(); - const [initialProposalTemplate, setInitialProposalTemplate] = useState(DEFAULT_PROPOSAL_TEMPLATE); + const [initialProposalTemplate, setInitialProposalTemplate] = useState(DEFAULT_PROPOSAL); const { prepareProposalTemplateProposal } = useCreateProposalTemplate(); const [searchParams] = useSearchParams(); const defaultProposalTemplatesHash = useMemo( diff --git a/src/pages/daos/[daoAddress]/proposals/new/index.tsx b/src/pages/daos/[daoAddress]/proposals/new/index.tsx index bd0954259a..7a33cb0a6d 100644 --- a/src/pages/daos/[daoAddress]/proposals/new/index.tsx +++ b/src/pages/daos/[daoAddress]/proposals/new/index.tsx @@ -1,55 +1,17 @@ -import { Grid, GridItem, Box, Flex, Center } from '@chakra-ui/react'; -import { Trash } from '@decent-org/fractal-ui'; -import { Formik, FormikProps } from 'formik'; -import { useState, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { ProposalDetails } from '../../../../../components/ProposalCreate/ProposalDetails'; -import { ProposalHeader } from '../../../../../components/ProposalCreate/ProposalHeader'; -import ProposalMetadata from '../../../../../components/ProposalCreate/ProposalMetadata'; -import TransactionsForm from '../../../../../components/ProposalCreate/TransactionsForm'; -import { DEFAULT_PROPOSAL } from '../../../../../components/ProposalCreate/constants'; +import { Center } from '@chakra-ui/react'; +import ProposalBuilder from '../../../../../components/ProposalBuilder'; +import { DEFAULT_PROPOSAL } from '../../../../../components/ProposalBuilder/constants'; import { BarLoader } from '../../../../../components/ui/loaders/BarLoader'; -import PageHeader from '../../../../../components/ui/page/Header/PageHeader'; -import { BACKGROUND_SEMI_TRANSPARENT, HEADER_HEIGHT } from '../../../../../constants/common'; -import { DAO_ROUTES } from '../../../../../constants/routes'; +import { HEADER_HEIGHT } from '../../../../../constants/common'; import { usePrepareProposal } from '../../../../../hooks/DAO/proposal/usePrepareProposal'; -import useSubmitProposal from '../../../../../hooks/DAO/proposal/useSubmitProposal'; -import { useCreateProposalSchema } from '../../../../../hooks/schemas/proposalCreate/useCreateProposalSchema'; -import { useCanUserCreateProposal } from '../../../../../hooks/utils/useCanUserSubmitProposal'; import { useFractal } from '../../../../../providers/App/AppProvider'; -import { useNetworkConfig } from '../../../../../providers/NetworkConfig/NetworkConfigProvider'; -import { CreateProposalForm, CreateProposalState, GovernanceType } from '../../../../../types'; - -const templateAreaTwoCol = '"content details"'; -const templateAreaSingleCol = `"content" - "details"`; export default function ProposalCreatePage() { const { node: { daoAddress, safe }, governance: { type }, } = useFractal(); - const { addressPrefix } = useNetworkConfig(); - const { createProposalValidation } = useCreateProposalSchema(); const { prepareProposal } = usePrepareProposal(); - const { submitProposal, pendingCreateTx } = useSubmitProposal(); - const { canUserCreateProposal } = useCanUserCreateProposal(); - - const navigate = useNavigate(); - const { t } = useTranslation(['proposal', 'common', 'breadcrumbs']); - - const [formState, setFormState] = useState(CreateProposalState.METADATA_FORM); - const isAzorius = useMemo( - () => type === GovernanceType.AZORIUS_ERC20 || type === GovernanceType.AZORIUS_ERC721, - [type], - ); - - const successCallback = () => { - if (daoAddress) { - navigate(DAO_ROUTES.proposals.relative(addressPrefix, daoAddress)); - } - }; if (!type || !daoAddress || !safe) { return ( @@ -60,108 +22,10 @@ export default function ProposalCreatePage() { } return ( - - validationSchema={createProposalValidation} + { - const { nonce } = values; - const proposalData = await prepareProposal(values); - submitProposal({ - proposalData, - nonce, - pendingToastMessage: t('proposalCreatePendingToastMessage'), - successToastMessage: t('proposalCreateSuccessToastMessage'), - failedToastMessage: t('proposalCreateFailureToastMessage'), - successCallback, - }); - }} - validateOnMount - isInitialValid={false} - > - {(formikProps: FormikProps) => { - const { handleSubmit, setFieldValue, values } = formikProps; - return ( - - - - navigate(DAO_ROUTES.proposals.relative(addressPrefix, daoAddress)) - } - isButtonDisabled={pendingCreateTx} - /> - - - - - { - setFieldValue('nonce', nonce); - }} - /> - - - - - - - - - - - - - ); - }} - + mode="proposal" + prepareProposalData={prepareProposal} + /> ); } diff --git a/src/types/createProposal.ts b/src/types/createProposal.ts deleted file mode 100644 index dbb8522d44..0000000000 --- a/src/types/createProposal.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { BigNumberValuePair } from './common'; - -export enum CreateProposalState { - METADATA_FORM, - TRANSACTIONS_FORM, -} - -export interface CreateProposalTransaction { - targetAddress: string; - ethValue: T; - functionName: string; - functionSignature: string; - parameters: string; - encodedFunctionData?: string; -} - -export type ProposalMetadata = { - title: string; - description: string; - documentationUrl: string; -}; - -export type CreateProposalForm = { - transactions: CreateProposalTransaction[]; - proposalMetadata: ProposalMetadata; - nonce?: number; -}; diff --git a/src/types/daoProposal.ts b/src/types/daoProposal.ts index 3783e86e4f..9e4f57de49 100644 --- a/src/types/daoProposal.ts +++ b/src/types/daoProposal.ts @@ -1,11 +1,11 @@ import { BigNumber, BigNumberish } from 'ethers'; -import { ProposalMetadata } from './createProposal'; import { GovernanceActivity } from './fractal'; +import { CreateProposalMetadata } from './proposalBuilder'; import { SafeMultisigConfirmationResponse } from './safeGlobal'; import { MetaTransaction, DecodedTransaction } from './transaction'; export interface ProposalExecuteData extends ExecuteData { - metaData: ProposalMetadata; + metaData: CreateProposalMetadata; } export interface ExecuteData { @@ -20,7 +20,7 @@ export type CreateProposalFunc = (proposal: { }) => void; export type ProposalData = { - metaData?: ProposalMetadata; + metaData?: CreateProposalMetadata; transactions?: MetaTransaction[]; decodedTransactions: DecodedTransaction[]; }; diff --git a/src/types/index.ts b/src/types/index.ts index e4968af8de..39819b0953 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,7 +2,7 @@ export * from './account'; export * from './common'; export * from './contract'; export * from './createDAO'; -export * from './createProposal'; +export * from './proposalBuilder'; export * from './daoGeneral'; export * from './daoGovernance'; export * from './daoGuard'; diff --git a/src/types/proposalBuilder.ts b/src/types/proposalBuilder.ts index 8b957137e9..28dbf9859b 100644 --- a/src/types/proposalBuilder.ts +++ b/src/types/proposalBuilder.ts @@ -1,5 +1,9 @@ import { BigNumberValuePair } from './common'; +export enum CreateProposalState { + METADATA_FORM, + TRANSACTIONS_FORM, +} export interface CreateProposalTransaction { targetAddress: string; ethValue: T; @@ -14,8 +18,10 @@ export interface CreateProposalTransaction { export type CreateProposalMetadata = { title: string; description: string; + documentationUrl?: string; }; +export type ProposalBuilderMode = 'proposal' | 'template'; export type CreateProposalForm = { transactions: CreateProposalTransaction[]; proposalMetadata: CreateProposalMetadata; From 6b0aade9272e369fd44c407ec8366b005c7b5b74 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 8 Apr 2024 20:31:41 +0200 Subject: [PATCH 04/14] Fix problem with missing bigNumberValue during prepareProposal --- src/hooks/DAO/proposal/usePrepareProposal.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/DAO/proposal/usePrepareProposal.ts b/src/hooks/DAO/proposal/usePrepareProposal.ts index c9914cd0b3..54928a6360 100644 --- a/src/hooks/DAO/proposal/usePrepareProposal.ts +++ b/src/hooks/DAO/proposal/usePrepareProposal.ts @@ -25,7 +25,9 @@ export function usePrepareProposal() { ); return { targets, - values: transactionsWithEncoding.map(transaction => transaction.ethValue.bigNumberValue!), + values: transactionsWithEncoding.map( + transaction => transaction.ethValue.bigNumberValue || 0, + ), calldatas: transactionsWithEncoding.map( transaction => transaction.encodedFunctionData || '', ), From ac192ff959efc52820a75c0c22d8a4ed25bd5cfb Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 8 Apr 2024 20:56:38 +0200 Subject: [PATCH 05/14] Explicitly set bigNumberValue to null while creating proposal template and within BigNumberInput --- src/components/CreateProposalTemplate/constants.ts | 2 +- .../DaoCreator/formComponents/AzoriusTokenAllocation.tsx | 2 +- src/components/ui/forms/BigNumberInput.tsx | 6 +++--- src/hooks/utils/useApproval.tsx | 2 +- src/types/common.ts | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/CreateProposalTemplate/constants.ts b/src/components/CreateProposalTemplate/constants.ts index 4c7930e184..d7673dd6fd 100644 --- a/src/components/CreateProposalTemplate/constants.ts +++ b/src/components/CreateProposalTemplate/constants.ts @@ -2,7 +2,7 @@ import { CreateProposalTemplateTransaction } from '../../types/createProposalTem export const DEFAULT_PROPOSAL_TEMPLATE_TRANSACTION: CreateProposalTemplateTransaction = { targetAddress: '', - ethValue: { value: '', bigNumberValue: undefined }, + ethValue: { value: '', bigNumberValue: null }, functionName: '', parameters: [ { diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx index c066bdda74..4c85f26ce0 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx @@ -11,7 +11,7 @@ interface ITokenAllocations { setFieldValue: (field: string, value: any) => void; addressErrorMessage: string | null; amountErrorMessage: string | null; - amountInputValue: BigNumber | undefined; + amountInputValue: BigNumber | undefined | null; allocationLength: number; } diff --git a/src/components/ui/forms/BigNumberInput.tsx b/src/components/ui/forms/BigNumberInput.tsx index 02214580b5..51cf769f89 100644 --- a/src/components/ui/forms/BigNumberInput.tsx +++ b/src/components/ui/forms/BigNumberInput.tsx @@ -13,12 +13,12 @@ import { BigNumberValuePair } from '../../../types'; export interface BigNumberInputProps extends Omit, FormControlOptions { - value: BigNumber | undefined; + value: BigNumber | undefined | null; onChange: (value: BigNumberValuePair) => void; decimalPlaces?: number; min?: string; max?: string; - maxValue?: BigNumber; + maxValue?: BigNumber | null; } /** * This component will add a chakra Input component that accepts and sets a BigNumber @@ -82,7 +82,7 @@ export function BigNumberInput({ if (stringValue === '') { onChange({ value: stringValue, - bigNumberValue: undefined, + bigNumberValue: null, }); setInputValue(''); return; diff --git a/src/hooks/utils/useApproval.tsx b/src/hooks/utils/useApproval.tsx index 681a41a0c6..8e8881198b 100644 --- a/src/hooks/utils/useApproval.tsx +++ b/src/hooks/utils/useApproval.tsx @@ -8,7 +8,7 @@ import { useTransaction } from './useTransaction'; const useApproval = ( tokenContract?: VotesERC20 | VotesERC20Wrapper, spenderAddress?: string, - userBalance?: BigNumber, + userBalance?: BigNumber | null, ) => { const { address: account } = useAccount(); const [allowance, setAllowance] = useState(BigNumber.from(0)); diff --git a/src/types/common.ts b/src/types/common.ts index de450d9095..e611af1d7b 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -2,7 +2,7 @@ import { BigNumber } from 'ethers'; export interface BigNumberValuePair { value: string; - bigNumberValue?: BigNumber; + bigNumberValue?: BigNumber | null; } export type WithError = { error?: string }; From e3ff66bbc0d7130ab71271eb67257371a21a03be Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Tue, 9 Apr 2024 12:44:34 +0200 Subject: [PATCH 06/14] Do not type amountInputValue and value on BigNumberInput with explicit undefined --- .../DaoCreator/formComponents/AzoriusTokenAllocation.tsx | 2 +- src/components/ui/forms/BigNumberInput.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx index 4c85f26ce0..4f6f574d50 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx @@ -11,7 +11,7 @@ interface ITokenAllocations { setFieldValue: (field: string, value: any) => void; addressErrorMessage: string | null; amountErrorMessage: string | null; - amountInputValue: BigNumber | undefined | null; + amountInputValue?: BigNumber | null; allocationLength: number; } diff --git a/src/components/ui/forms/BigNumberInput.tsx b/src/components/ui/forms/BigNumberInput.tsx index 51cf769f89..c7a8229ed2 100644 --- a/src/components/ui/forms/BigNumberInput.tsx +++ b/src/components/ui/forms/BigNumberInput.tsx @@ -13,7 +13,7 @@ import { BigNumberValuePair } from '../../../types'; export interface BigNumberInputProps extends Omit, FormControlOptions { - value: BigNumber | undefined | null; + value?: BigNumber | null; onChange: (value: BigNumberValuePair) => void; decimalPlaces?: number; min?: string; From 520fe1dbe3b9ff1f9b5a42731d66defa168fe9c2 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Tue, 9 Apr 2024 12:52:15 +0200 Subject: [PATCH 07/14] Transform ProposalBuilderMode into enum --- src/components/ProposalBuilder/ProposalDetails.tsx | 2 +- src/components/ProposalBuilder/ProposalMetadata.tsx | 2 +- src/components/ProposalBuilder/ProposalTransaction.tsx | 2 +- src/components/ProposalBuilder/index.tsx | 2 +- src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx | 4 ++-- src/pages/daos/[daoAddress]/proposals/new/index.tsx | 5 +++-- src/types/proposalBuilder.ts | 5 ++++- 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/ProposalBuilder/ProposalDetails.tsx b/src/components/ProposalBuilder/ProposalDetails.tsx index e7b917e2bd..1417d4f830 100644 --- a/src/components/ProposalBuilder/ProposalDetails.tsx +++ b/src/components/ProposalBuilder/ProposalDetails.tsx @@ -52,7 +52,7 @@ export default function ProposalTemplateDetails({ {t('previewTitle')} {trimmedTitle} - {mode === 'template' && ( + {mode === ProposalBuilderMode.TEMPLATE && ( {t('previewThumnbail')} {trimmedTitle && ( diff --git a/src/components/ProposalBuilder/ProposalMetadata.tsx b/src/components/ProposalBuilder/ProposalMetadata.tsx index 556c923027..65bc1b51ff 100644 --- a/src/components/ProposalBuilder/ProposalMetadata.tsx +++ b/src/components/ProposalBuilder/ProposalMetadata.tsx @@ -18,7 +18,7 @@ export default function ProposalMetadata({ mode, }: ProposalMetadataProps) { const { t } = useTranslation(['proposalTemplate', 'proposal', 'common']); - const isProposalMode = mode === 'proposal'; + const isProposalMode = mode === ProposalBuilderMode.PROPOSAL; return ( <> diff --git a/src/components/ProposalBuilder/ProposalTransaction.tsx b/src/components/ProposalBuilder/ProposalTransaction.tsx index ad256b811b..a4876fa195 100644 --- a/src/components/ProposalBuilder/ProposalTransaction.tsx +++ b/src/components/ProposalBuilder/ProposalTransaction.tsx @@ -27,7 +27,7 @@ export default function ProposalTransaction({ setFieldValue, mode, }: ProposalTransactionProps) { - const isProposalMode = mode === 'proposal'; + const isProposalMode = mode === ProposalBuilderMode.PROPOSAL; const { t } = useTranslation(['proposal', 'proposalTemplate', 'common']); const handleABISelectorChange = useCallback( (value: ABIElement) => { diff --git a/src/components/ProposalBuilder/index.tsx b/src/components/ProposalBuilder/index.tsx index 93d3cbfe19..57e2fc3038 100644 --- a/src/components/ProposalBuilder/index.tsx +++ b/src/components/ProposalBuilder/index.tsx @@ -37,7 +37,7 @@ export default function ProposalBuilder({ const [formState, setFormState] = useState(CreateProposalState.METADATA_FORM); const { t } = useTranslation(['proposalTemplate', 'proposal']); - const isProposalMode = mode === 'proposal'; + const isProposalMode = mode === ProposalBuilderMode.PROPOSAL; const navigate = useNavigate(); const { diff --git a/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx b/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx index 4ccb364cc3..92a8c53e8e 100644 --- a/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx +++ b/src/pages/daos/[daoAddress]/proposal-templates/new/index.tsx @@ -6,7 +6,7 @@ import { DEFAULT_PROPOSAL } from '../../../../../components/ProposalBuilder/cons import { logError } from '../../../../../helpers/errorLogging'; import useCreateProposalTemplate from '../../../../../hooks/DAO/proposal/useCreateProposalTemplate'; import useIPFSClient from '../../../../../providers/App/hooks/useIPFSClient'; -import { ProposalTemplate } from '../../../../../types/proposalBuilder'; +import { ProposalBuilderMode, ProposalTemplate } from '../../../../../types/proposalBuilder'; export default function CreateProposalTemplatePage() { const ipfsClient = useIPFSClient(); @@ -55,7 +55,7 @@ export default function CreateProposalTemplatePage() { return ( diff --git a/src/pages/daos/[daoAddress]/proposals/new/index.tsx b/src/pages/daos/[daoAddress]/proposals/new/index.tsx index 7a33cb0a6d..fada2ade2c 100644 --- a/src/pages/daos/[daoAddress]/proposals/new/index.tsx +++ b/src/pages/daos/[daoAddress]/proposals/new/index.tsx @@ -5,8 +5,9 @@ import { BarLoader } from '../../../../../components/ui/loaders/BarLoader'; import { HEADER_HEIGHT } from '../../../../../constants/common'; import { usePrepareProposal } from '../../../../../hooks/DAO/proposal/usePrepareProposal'; import { useFractal } from '../../../../../providers/App/AppProvider'; +import { ProposalBuilderMode } from '../../../../../types'; -export default function ProposalCreatePage() { +export default function CreateProposalPage() { const { node: { daoAddress, safe }, governance: { type }, @@ -24,7 +25,7 @@ export default function ProposalCreatePage() { return ( ); diff --git a/src/types/proposalBuilder.ts b/src/types/proposalBuilder.ts index 28dbf9859b..e3e59d207e 100644 --- a/src/types/proposalBuilder.ts +++ b/src/types/proposalBuilder.ts @@ -21,7 +21,10 @@ export type CreateProposalMetadata = { documentationUrl?: string; }; -export type ProposalBuilderMode = 'proposal' | 'template'; +export enum ProposalBuilderMode { + PROPOSAL = 'PROPOSAL', + TEMPLATE = 'TEMPLATE' +}; export type CreateProposalForm = { transactions: CreateProposalTransaction[]; proposalMetadata: CreateProposalMetadata; From ee0d4fd8f3c096e8cc6f3e868f3e6b1547849dcc Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Tue, 9 Apr 2024 12:53:41 +0200 Subject: [PATCH 08/14] Prettier --- src/types/proposalBuilder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/proposalBuilder.ts b/src/types/proposalBuilder.ts index e3e59d207e..79a3db8345 100644 --- a/src/types/proposalBuilder.ts +++ b/src/types/proposalBuilder.ts @@ -23,8 +23,8 @@ export type CreateProposalMetadata = { export enum ProposalBuilderMode { PROPOSAL = 'PROPOSAL', - TEMPLATE = 'TEMPLATE' -}; + TEMPLATE = 'TEMPLATE', +} export type CreateProposalForm = { transactions: CreateProposalTransaction[]; proposalMetadata: CreateProposalMetadata; From 4fa08c6a7f5c0a701d291254a08d6c4179fc9b99 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Tue, 9 Apr 2024 13:21:19 +0200 Subject: [PATCH 09/14] If signature is empty - do not call encodeFunction but simply return 0x as calldata --- src/hooks/DAO/proposal/usePrepareProposal.ts | 26 +++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/hooks/DAO/proposal/usePrepareProposal.ts b/src/hooks/DAO/proposal/usePrepareProposal.ts index e81a71463c..945150104b 100644 --- a/src/hooks/DAO/proposal/usePrepareProposal.ts +++ b/src/hooks/DAO/proposal/usePrepareProposal.ts @@ -10,19 +10,22 @@ export function usePrepareProposal() { async (values: CreateProposalForm) => { const { transactions, proposalMetadata } = values; const transactionsWithEncoding = transactions.map(tx => { + const signature = tx.parameters.map(parameter => parameter.signature.trim()).join(', '); + const parameters = tx.parameters + .map(parameter => + isValidUrl(parameter.value!.trim()) + ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL + : parameter.value!.trim(), + ) + .join(', ') + return { ...tx, - encodedFunctionData: encodeFunction( + calldata: signature ? encodeFunction( tx.functionName, - tx.parameters.map(parameter => parameter.signature.trim()).join(', '), - tx.parameters - .map(parameter => - isValidUrl(parameter.value!.trim()) - ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL - : parameter.value!.trim(), - ) - .join(', '), - ), + signature, + parameters, + ) : '0x', }; }); const targets = await Promise.all( @@ -33,11 +36,12 @@ export function usePrepareProposal() { return tx.targetAddress; }), ); + return { targets, values: transactionsWithEncoding.map(transaction => transaction.ethValue.bigNumberValue!), calldatas: transactionsWithEncoding.map( - transaction => transaction.encodedFunctionData || '', + transaction => transaction.calldata || '', ), metaData: { title: proposalMetadata.title, From 6886d707be026761079803ba3e705700ab4092f5 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Tue, 9 Apr 2024 13:26:18 +0200 Subject: [PATCH 10/14] Validate based on function name and not signature. If function name not specified - immediately return transaction with calldata set to 0x --- src/hooks/DAO/proposal/usePrepareProposal.ts | 39 ++++++++++---------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/hooks/DAO/proposal/usePrepareProposal.ts b/src/hooks/DAO/proposal/usePrepareProposal.ts index 945150104b..d8ca234c08 100644 --- a/src/hooks/DAO/proposal/usePrepareProposal.ts +++ b/src/hooks/DAO/proposal/usePrepareProposal.ts @@ -10,23 +10,26 @@ export function usePrepareProposal() { async (values: CreateProposalForm) => { const { transactions, proposalMetadata } = values; const transactionsWithEncoding = transactions.map(tx => { - const signature = tx.parameters.map(parameter => parameter.signature.trim()).join(', '); - const parameters = tx.parameters - .map(parameter => - isValidUrl(parameter.value!.trim()) - ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL - : parameter.value!.trim(), - ) - .join(', ') + if (!tx.functionName) { + return { + ...tx, + calldata: '0x', + }; + } else { + const signature = tx.parameters.map(parameter => parameter.signature.trim()).join(', '); + const parameters = tx.parameters + .map(parameter => + isValidUrl(parameter.value!.trim()) + ? encodeURIComponent(parameter.value!.trim()) // If parameter.value is valid URL with special symbols like ":" or "?" - decoding might fail, thus we need to encode URL + : parameter.value!.trim(), + ) + .join(', '); - return { - ...tx, - calldata: signature ? encodeFunction( - tx.functionName, - signature, - parameters, - ) : '0x', - }; + return { + ...tx, + calldata: encodeFunction(tx.functionName, signature, parameters), + }; + } }); const targets = await Promise.all( transactionsWithEncoding.map(tx => { @@ -40,9 +43,7 @@ export function usePrepareProposal() { return { targets, values: transactionsWithEncoding.map(transaction => transaction.ethValue.bigNumberValue!), - calldatas: transactionsWithEncoding.map( - transaction => transaction.calldata || '', - ), + calldatas: transactionsWithEncoding.map(transaction => transaction.calldata || ''), metaData: { title: proposalMetadata.title, description: proposalMetadata.description, From b26a222d0eea43035f9b34dd6837a442f882a2f1 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Tue, 9 Apr 2024 17:44:04 +0200 Subject: [PATCH 11/14] Rearrange order of types in useGetMultisigMetadata --- src/hooks/DAO/proposal/useGetMetadata.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/DAO/proposal/useGetMetadata.ts b/src/hooks/DAO/proposal/useGetMetadata.ts index 6a80aeee06..cf7bf93d7a 100644 --- a/src/hooks/DAO/proposal/useGetMetadata.ts +++ b/src/hooks/DAO/proposal/useGetMetadata.ts @@ -25,7 +25,7 @@ interface Transaction { const useGetMultisigMetadata = (proposal: FractalProposal | null | undefined) => { const ipfsClient = useIPFSClient(); const [multisigMetadata, setMultisigMetadata] = useState< - undefined | CreateProposalMetadata | null + CreateProposalMetadata | null | undefined >(undefined); const [setValue, getValue] = useIndexedDB(DBObjectKeys.DECODED_TRANSACTIONS); From 278dcf90c60f1462e64706562bcfd6e5a92dd1f4 Mon Sep 17 00:00:00 2001 From: Kyrylo Klymenko Date: Sun, 14 Apr 2024 17:01:07 +0300 Subject: [PATCH 12/14] Update src/i18n/locales/en/proposal.json Update helperTargetAddress copy Co-authored-by: Adam Gall --- src/i18n/locales/en/proposal.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/locales/en/proposal.json b/src/i18n/locales/en/proposal.json index c01bebca56..fe1656b144 100644 --- a/src/i18n/locales/en/proposal.json +++ b/src/i18n/locales/en/proposal.json @@ -2,7 +2,7 @@ "proposalOverview": "Proposal Overview", "breakdownTitle": "Breakdown", "labelTargetAddress": "Target Address", - "helperTargetAddress": "The smart contract address this proposal will modify. Paste address and we'll try to fetch ABI for this contract from Etherscan", + "helperTargetAddress": "The smart contract address this proposal will modify. Paste an address and we'll try to fetch the ABI for this contract.", "labelFunctionName": "Function Name", "helperFunctionName": "The name of the function to be called if this proposal passes", "labelFunctionSignature": "Function Signature", From db9e40942bb7f52c54b515a31351e2ed22033e26 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Sun, 14 Apr 2024 16:18:09 +0200 Subject: [PATCH 13/14] Fix import --- .../DaoCreator/formComponents/AzoriusTokenAllocation.tsx | 2 +- src/components/ui/forms/BigIntInput.tsx | 2 +- src/types/daoProposal.ts | 1 - src/types/fractal.ts | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx index 67d484c7d3..bd73002b2a 100644 --- a/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx +++ b/src/components/DaoCreator/formComponents/AzoriusTokenAllocation.tsx @@ -10,7 +10,7 @@ interface ITokenAllocations { setFieldValue: (field: string, value: any) => void; addressErrorMessage: string | null; amountErrorMessage: string | null; - amountInputValue: bigint | undefined; + amountInputValue?: bigint; allocationLength: number; } diff --git a/src/components/ui/forms/BigIntInput.tsx b/src/components/ui/forms/BigIntInput.tsx index e1a43bf0e5..58ef224efd 100644 --- a/src/components/ui/forms/BigIntInput.tsx +++ b/src/components/ui/forms/BigIntInput.tsx @@ -13,7 +13,7 @@ import { BigIntValuePair } from '../../../types'; export interface BigIntInputProps extends Omit, FormControlOptions { - value: bigint | undefined; + value?: bigint; onChange: (value: BigIntValuePair) => void; decimalPlaces?: number; min?: string; diff --git a/src/types/daoProposal.ts b/src/types/daoProposal.ts index 9cbd02d171..cc6bf6712b 100644 --- a/src/types/daoProposal.ts +++ b/src/types/daoProposal.ts @@ -1,4 +1,3 @@ -import { ProposalMetadata } from './createProposal'; import { GovernanceActivity } from './fractal'; import { CreateProposalMetadata } from './proposalBuilder'; import { SafeMultisigConfirmationResponse } from './safeGlobal'; diff --git a/src/types/fractal.ts b/src/types/fractal.ts index 92cc8eef73..8c0bc7b3b6 100644 --- a/src/types/fractal.ts +++ b/src/types/fractal.ts @@ -177,10 +177,10 @@ export enum SafeTransferType { } export interface ITokenAccount { - userBalance: bigint | undefined; + userBalance?: bigint; userBalanceString: string | undefined; delegatee: string | undefined; - votingWeight: bigint | undefined; + votingWeight?: bigint; votingWeightString: string | undefined; isDelegatesSet: boolean | undefined; } From b9514d94d433828a5bb35feb21580f3d307d8f61 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Sun, 14 Apr 2024 16:28:53 +0200 Subject: [PATCH 14/14] Prettier --- src/hooks/DAO/proposal/usePrepareProposal.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hooks/DAO/proposal/usePrepareProposal.ts b/src/hooks/DAO/proposal/usePrepareProposal.ts index 2e145de92f..4e60043391 100644 --- a/src/hooks/DAO/proposal/usePrepareProposal.ts +++ b/src/hooks/DAO/proposal/usePrepareProposal.ts @@ -42,9 +42,7 @@ export function usePrepareProposal() { return { targets, - values: transactionsWithEncoding.map( - transaction => transaction.ethValue.bigintValue || 0n, - ), + values: transactionsWithEncoding.map(transaction => transaction.ethValue.bigintValue || 0n), calldatas: transactionsWithEncoding.map(transaction => transaction.calldata || ''), metaData: { title: proposalMetadata.title,