Skip to content

Commit af151ad

Browse files
committed
feat: use formik smart input for proposal metadata
1 parent 4db323b commit af151ad

4 files changed

Lines changed: 240 additions & 143 deletions

File tree

apps/web/src/pages/dao/[network]/[token]/proposal/create.tsx

Lines changed: 114 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,19 @@ import { DropdownSelect } from '@buildeross/ui/DropdownSelect'
3434
import { isChainIdSupportedByCoining } from '@buildeross/utils/coining'
3535
import { isChainIdSupportedByDroposal } from '@buildeross/utils/droposal'
3636
import { isChainIdSupportedByEAS } from '@buildeross/utils/eas'
37+
import { getEnsAddress } from '@buildeross/utils/ens'
38+
import { getProvider } from '@buildeross/utils/provider'
3739
import { isChainIdSupportedBySablier } from '@buildeross/utils/sablier/constants'
3840
import { Box, Button, Flex, Icon, Stack, Text } from '@buildeross/zord'
41+
import { Formik, FormikProps } from 'formik'
3942
import { GetServerSideProps } from 'next'
4043
import { useRouter } from 'next/router'
4144
import React, { useCallback, useEffect, useMemo } from 'react'
4245
import { getDaoLayout } from 'src/layouts/DaoLayout'
4346
import { NextPageWithLayout } from 'src/pages/_app'
4447
import { notFoundWrap } from 'src/styles/404.css'
4548
import * as styles from 'src/styles/create.css'
46-
import { isAddress, isAddressEqual } from 'viem'
49+
import { getAddress, isAddress, isAddressEqual } from 'viem'
4750
import { useAccount, useReadContract } from 'wagmi'
4851

4952
const createSelectOption = (type: TransactionType) => ({
@@ -130,6 +133,14 @@ const validateDiscussionUrl = (value?: string | null): string | undefined => {
130133
return undefined
131134
}
132135

136+
type DraftFormValues = {
137+
title: string
138+
summary: string
139+
representedAddress: string
140+
discussionUrl: string
141+
representedAddressEnabled: boolean
142+
}
143+
133144
const CreateProposalPage: NextPageWithLayout = () => {
134145
const { query, push } = useRouter()
135146
const addresses = useDaoStore((x) => x.addresses)
@@ -164,10 +175,6 @@ const CreateProposalPage: NextPageWithLayout = () => {
164175
? 'transactions'
165176
: 'draft'
166177
)
167-
const [titleTouched, setTitleTouched] = React.useState(false)
168-
const [summaryTouched, setSummaryTouched] = React.useState(false)
169-
const [representedAddressTouched, setRepresentedAddressTouched] = React.useState(false)
170-
const [discussionUrlTouched, setDiscussionUrlTouched] = React.useState(false)
171178

172179
const { data: paused } = useReadContract({
173180
abi: auctionAbi,
@@ -314,26 +321,6 @@ const CreateProposalPage: NextPageWithLayout = () => {
314321
return requirements
315322
}, [title, summary, representedAddress, representedAddressEnabled, discussionUrl])
316323

317-
const titleError = useMemo(() => {
318-
if (!titleTouched) return undefined
319-
return validateTitle(title)
320-
}, [title, titleTouched])
321-
322-
const summaryError = useMemo(() => {
323-
if (!summaryTouched) return undefined
324-
return validateSummary(summary)
325-
}, [summary, summaryTouched])
326-
327-
const representedAddressError = useMemo(() => {
328-
if (!representedAddressTouched) return undefined
329-
return validateRepresentedAddress(representedAddress, representedAddressEnabled)
330-
}, [representedAddress, representedAddressEnabled, representedAddressTouched])
331-
332-
const discussionUrlError = useMemo(() => {
333-
if (!discussionUrlTouched) return undefined
334-
return validateDiscussionUrl(discussionUrl)
335-
}, [discussionUrl, discussionUrlTouched])
336-
337324
const missingReviewRequirements = useMemo(() => {
338325
const requirements = [...missingDraftRequirements]
339326

@@ -458,14 +445,51 @@ const CreateProposalPage: NextPageWithLayout = () => {
458445

459446
const onResetProposal = useCallback(() => {
460447
clearProposal()
461-
setTitleTouched(false)
462-
setSummaryTouched(false)
463-
setRepresentedAddressTouched(false)
464-
setDiscussionUrlTouched(false)
465448
setCreateStage('draft')
466449
setFurthestStage('draft')
467450
}, [clearProposal])
468451

452+
const resolveAndStoreRepresentedAddress = useCallback(
453+
async (formik: FormikProps<DraftFormValues>) => {
454+
if (!formik.values.representedAddressEnabled) {
455+
setRepresentedAddress(undefined)
456+
return true
457+
}
458+
459+
const rawValue = (formik.values.representedAddress || '').trim()
460+
461+
if (!rawValue) {
462+
setRepresentedAddress(undefined)
463+
return true
464+
}
465+
466+
try {
467+
const resolved = await getEnsAddress(rawValue, getProvider(chain.id))
468+
if (!resolved || !isAddress(resolved, { strict: false })) {
469+
void formik.setFieldError(
470+
'representedAddress',
471+
PROPOSAL_REPRESENTED_ADDRESS_FORMAT_ERROR
472+
)
473+
return false
474+
}
475+
476+
const normalizedAddress = getAddress(resolved)
477+
if (normalizedAddress !== formik.values.representedAddress) {
478+
void formik.setFieldValue('representedAddress', normalizedAddress)
479+
}
480+
setRepresentedAddress(normalizedAddress)
481+
return true
482+
} catch {
483+
void formik.setFieldError(
484+
'representedAddress',
485+
PROPOSAL_REPRESENTED_ADDRESS_FORMAT_ERROR
486+
)
487+
return false
488+
}
489+
},
490+
[chain.id, setRepresentedAddress]
491+
)
492+
469493
const onStageSelect = useCallback(
470494
(stage: 'draft' | 'transactions' | 'review') => {
471495
if (stage === 'draft') {
@@ -585,32 +609,69 @@ const CreateProposalPage: NextPageWithLayout = () => {
585609
borderRadius={'curved'}
586610
gap={'x2'}
587611
>
588-
<ProposalDraftForm
589-
title={title || ''}
590-
summary={summary || ''}
591-
representedAddress={representedAddress || ''}
592-
discussionUrl={discussionUrl || ''}
593-
representedAddressEnabled={representedAddressEnabled}
594-
onTitleChange={setTitle}
595-
onSummaryChange={setSummary}
596-
onRepresentedAddressChange={setRepresentedAddress}
597-
onDiscussionUrlChange={setDiscussionUrl}
598-
onRepresentedAddressEnabledChange={(value) => {
599-
setRepresentedAddressEnabled(value)
600-
setRepresentedAddressTouched(true)
601-
if (!value) {
602-
setRepresentedAddress(undefined)
612+
<Formik<DraftFormValues>
613+
initialValues={{
614+
title: title || '',
615+
summary: summary || '',
616+
representedAddress: representedAddress || '',
617+
discussionUrl: discussionUrl || '',
618+
representedAddressEnabled,
619+
}}
620+
enableReinitialize
621+
validateOnBlur
622+
validateOnChange
623+
validate={(values) => {
624+
const errors: Partial<Record<keyof DraftFormValues, string>> = {}
625+
const titleValidationError = validateTitle(values.title)
626+
if (titleValidationError) errors.title = titleValidationError
627+
628+
const summaryValidationError = validateSummary(values.summary)
629+
if (summaryValidationError) errors.summary = summaryValidationError
630+
631+
const representedAddressValidationError = validateRepresentedAddress(
632+
values.representedAddress,
633+
values.representedAddressEnabled
634+
)
635+
if (representedAddressValidationError) {
636+
errors.representedAddress = representedAddressValidationError
637+
}
638+
639+
const discussionUrlValidationError = validateDiscussionUrl(
640+
values.discussionUrl
641+
)
642+
if (discussionUrlValidationError) {
643+
errors.discussionUrl = discussionUrlValidationError
603644
}
645+
646+
return errors
604647
}}
605-
onTitleBlur={() => setTitleTouched(true)}
606-
onSummaryBlur={() => setSummaryTouched(true)}
607-
onRepresentedAddressBlur={() => setRepresentedAddressTouched(true)}
608-
onDiscussionUrlBlur={() => setDiscussionUrlTouched(true)}
609-
titleError={titleError}
610-
summaryError={summaryError}
611-
representedAddressError={representedAddressError}
612-
discussionUrlError={discussionUrlError}
613-
/>
648+
onSubmit={() => undefined}
649+
>
650+
{(formik) => (
651+
<ProposalDraftForm
652+
formik={formik}
653+
onTitleChange={(value) => {
654+
setTitle(value)
655+
}}
656+
onSummaryChange={(value) => {
657+
setSummary(value)
658+
}}
659+
onDiscussionUrlChange={(value) => {
660+
setDiscussionUrl(value)
661+
}}
662+
onRepresentedAddressEnabledChange={(value) => {
663+
setRepresentedAddressEnabled(value)
664+
if (!value) {
665+
setRepresentedAddress(undefined)
666+
void formik.setFieldValue('representedAddress', '')
667+
}
668+
}}
669+
onRepresentedAddressBlur={async () => {
670+
await resolveAndStoreRepresentedAddress(formik)
671+
}}
672+
/>
673+
)}
674+
</Formik>
614675
</Stack>
615676
) : (
616677
<TwoColumnLayout

0 commit comments

Comments
 (0)