From dc8565b5f55a7384bc908d2414819cb90863864c Mon Sep 17 00:00:00 2001 From: Andy Espagnolo Date: Wed, 28 Jun 2023 14:27:04 -0300 Subject: [PATCH 1/7] refactor: remove unnecessary onClicks from link buttons (#1055) --- .../Layout/BurgerMenu/MobileNavigation.tsx | 15 +-------------- src/pages/proposals.tsx | 10 +--------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/src/components/Layout/BurgerMenu/MobileNavigation.tsx b/src/components/Layout/BurgerMenu/MobileNavigation.tsx index ee193bf15..7657b1544 100644 --- a/src/components/Layout/BurgerMenu/MobileNavigation.tsx +++ b/src/components/Layout/BurgerMenu/MobileNavigation.tsx @@ -1,11 +1,10 @@ import React from 'react' -import prevent from 'decentraland-gatsby/dist/utils/react/prevent' import { Button } from 'decentraland-ui/dist/components/Button/Button' import { Header } from 'decentraland-ui/dist/components/Header/Header' import useFormatMessage from '../../../hooks/useFormatMessage' -import locations, { navigate } from '../../../utils/locations' +import locations from '../../../utils/locations' import Link from '../../Common/Link' import { NavigationProps, NavigationTab } from '../Navigation' @@ -34,9 +33,6 @@ function MobileNavigation({ activeTab }: NavigationProps) { {...getButtonProps(NavigationTab.Home, activeTab)} as={Link} href={locations.home()} - onClick={prevent(() => { - navigate(locations.home()) - })} > {t('navigation.home')} @@ -46,9 +42,6 @@ function MobileNavigation({ activeTab }: NavigationProps) { {...getButtonProps(NavigationTab.Proposals, activeTab)} as={Link} href={locations.proposals()} - onClick={prevent(() => { - navigate(locations.proposals()) - })} > {t('navigation.proposals')} @@ -58,9 +51,6 @@ function MobileNavigation({ activeTab }: NavigationProps) { {...getButtonProps(NavigationTab.Grants, activeTab)} as={Link} href={locations.grants()} - onClick={prevent(() => { - navigate(locations.grants()) - })} > {t('navigation.grants')} @@ -70,9 +60,6 @@ function MobileNavigation({ activeTab }: NavigationProps) { {...getButtonProps(NavigationTab.Transparency, activeTab)} as={Link} href={locations.transparency()} - onClick={prevent(() => { - navigate(locations.transparency()) - })} > {t('navigation.transparency')} diff --git a/src/pages/proposals.tsx b/src/pages/proposals.tsx index a90a0739b..dca4404b7 100644 --- a/src/pages/proposals.tsx +++ b/src/pages/proposals.tsx @@ -4,7 +4,6 @@ import { useLocation } from '@reach/router' import { useQuery } from '@tanstack/react-query' import Head from 'decentraland-gatsby/dist/components/Head/Head' import useAuthContext from 'decentraland-gatsby/dist/context/Auth/useAuthContext' -import prevent from 'decentraland-gatsby/dist/utils/react/prevent' import { Button } from 'decentraland-ui/dist/components/Button/Button' import { Container } from 'decentraland-ui/dist/components/Container/Container' import { Header } from 'decentraland-ui/dist/components/Header/Header' @@ -175,14 +174,7 @@ export default function ProposalsPage() { !searching && ( <> {proposals && } - From 932ef3581940c81de8bc645fc61ef1d27f50bbc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Comerci?= <45410089+ncomerci@users.noreply.github.com> Date: Wed, 28 Jun 2023 16:39:26 -0300 Subject: [PATCH 2/7] refactor: Replace useClipboardCopy from dcl-gatsby (#1051) * refactor: clipboard copy replaced * copy renamed to handleCopy --- package-lock.json | 1 + package.json | 1 + src/components/Error/ErrorMessage.tsx | 19 ++++++++------- src/components/Modal/SuccessModal.tsx | 15 ++++++------ src/hooks/useClipboardCopy.ts | 34 +++++++++++++++++++++++++++ src/hooks/useForumConnect.ts | 8 +++---- 6 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 src/hooks/useClipboardCopy.ts diff --git a/package-lock.json b/package-lock.json index 9d883a303..964c55366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "autoprefixer": "^10.4.4", "chart.js": "^3.8.2", "classnames": "^2.3.2", + "clipboard-copy": "^4.0.1", "core-js": "^3.21.1", "decentraland-gatsby": "^5.67.2", "decentraland-ui": "^3.102.0", diff --git a/package.json b/package.json index 698b1ff6a..8f2be9615 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "autoprefixer": "^10.4.4", "chart.js": "^3.8.2", "classnames": "^2.3.2", + "clipboard-copy": "^4.0.1", "core-js": "^3.21.1", "decentraland-gatsby": "^5.67.2", "decentraland-ui": "^3.102.0", diff --git a/src/components/Error/ErrorMessage.tsx b/src/components/Error/ErrorMessage.tsx index 8524a0b76..4c6825b38 100644 --- a/src/components/Error/ErrorMessage.tsx +++ b/src/components/Error/ErrorMessage.tsx @@ -1,10 +1,10 @@ -import React, { useCallback, useState } from 'react' +import React, { useState } from 'react' import classNames from 'classnames' import Markdown from 'decentraland-gatsby/dist/components/Text/Markdown' -import useClipboardCopy from 'decentraland-gatsby/dist/hooks/useClipboardCopy' import { Button } from 'decentraland-ui/dist/components/Button/Button' +import useClipboardCopy from '../../hooks/useClipboardCopy' import useFormatMessage from '../../hooks/useFormatMessage' import Time from '../../utils/date/Time' import Link from '../Common/Link' @@ -19,17 +19,13 @@ interface Props { export default function ErrorMessage({ label, errorMessage }: Props) { const t = useFormatMessage() - const [copied, state] = useClipboardCopy(Time.Second) + const { copiedValue, handleCopy } = useClipboardCopy(Time.Second) const [open, setOpen] = useState(false) const toggleHandler = () => { setOpen(!open) } - const handleCopy = useCallback(() => { - state.copy(errorMessage) - }, [errorMessage, state]) - return (
@@ -42,8 +38,13 @@ export default function ErrorMessage({ label, errorMessage }: Props) {
{errorMessage}
-
{t('error.message.call_for_action')} diff --git a/src/components/Modal/SuccessModal.tsx b/src/components/Modal/SuccessModal.tsx index a1835849d..cd9ce1068 100644 --- a/src/components/Modal/SuccessModal.tsx +++ b/src/components/Modal/SuccessModal.tsx @@ -1,19 +1,18 @@ import React, { useCallback } from 'react' import classNames from 'classnames' -import useClipboardCopy from 'decentraland-gatsby/dist/hooks/useClipboardCopy' import { Button } from 'decentraland-ui/dist/components/Button/Button' import { Close } from 'decentraland-ui/dist/components/Close/Close' import { Header } from 'decentraland-ui/dist/components/Header/Header' import { Modal, ModalProps } from 'decentraland-ui/dist/components/Modal/Modal' import { JOIN_DISCORD_URL } from '../../entities/Proposal/utils' +import useClipboardCopy from '../../hooks/useClipboardCopy' import useFormatMessage from '../../hooks/useFormatMessage' import Time from '../../utils/date/Time' import Text from '../Common/Text/Text' import './ProposalModal.css' -import './SuccessModal.css' export type SuccessModalProps = Omit & { onDismiss: (e: React.MouseEvent) => void @@ -33,12 +32,12 @@ export function SuccessModal({ ...props }: SuccessModalProps) { const t = useFormatMessage() - const [copied, state] = useClipboardCopy(Time.Second) - const handleCopy = useCallback(() => { + const { copiedValue, handleCopy } = useClipboardCopy(Time.Second) + const handleCopyClick = useCallback(() => { if (linkToCopy) { - state.copy(linkToCopy) + handleCopy(linkToCopy) } - }, [linkToCopy, state]) + }, [linkToCopy, handleCopy]) return (
)} diff --git a/src/hooks/useClipboardCopy.ts b/src/hooks/useClipboardCopy.ts new file mode 100644 index 000000000..a4db0b881 --- /dev/null +++ b/src/hooks/useClipboardCopy.ts @@ -0,0 +1,34 @@ +import { useCallback, useEffect, useState } from 'react' + +import clipboardCopy from 'clipboard-copy' + +export default function useClipboardCopy(timeout?: number) { + const [copiedValue, setCopiedValue] = useState(null) + + const handleCopy = useCallback((value: unknown) => { + const copiedValue = String(value ?? '') + clipboardCopy(copiedValue) + setCopiedValue(copiedValue) + }, []) + + const clear = useCallback(() => setCopiedValue(null), []) + + useEffect(() => { + let copyTimeout: null | ReturnType = null + if (copiedValue && timeout && timeout > 0) { + copyTimeout = setTimeout(() => clear(), timeout) + } + + return () => { + if (copyTimeout) { + clearTimeout(copyTimeout) + } + } + }, [clear, copiedValue, timeout]) + + return { + copiedValue, + handleCopy, + clear, + } +} diff --git a/src/hooks/useForumConnect.ts b/src/hooks/useForumConnect.ts index 44fa040aa..1acc68fc6 100644 --- a/src/hooks/useForumConnect.ts +++ b/src/hooks/useForumConnect.ts @@ -2,7 +2,6 @@ import { useCallback, useEffect, useState } from 'react' import useAuthContext from 'decentraland-gatsby/dist/context/Auth/useAuthContext' import useTrackContext from 'decentraland-gatsby/dist/context/Track/useTrackContext' -import useClipboardCopy from 'decentraland-gatsby/dist/hooks/useClipboardCopy' import useSign from 'decentraland-gatsby/dist/hooks/useSign' import { Governance } from '../clients/Governance' @@ -13,6 +12,7 @@ import { DISCOURSE_API } from '../entities/User/utils' import { openUrl } from '../helpers' import Time from '../utils/date/Time' +import useClipboardCopy from './useClipboardCopy' import useTimer from './useTimer' export const THREAD_URL = `${DISCOURSE_API}${ @@ -25,7 +25,7 @@ export default function useForumConnect() { const [user, userState] = useAuthContext() const track = useTrackContext() const [sign, signState] = useSign(user, userState.provider) - const [copied, clipboardState] = useClipboardCopy(Time.Second) + const { handleCopy } = useClipboardCopy(Time.Second) const [signatureResolution, setSignatureResolution] = useState<{ resolve: (value: unknown) => void reject: (reason?: unknown) => void @@ -80,9 +80,9 @@ export default function useForumConnect() { const copyMessageToClipboard = useCallback(() => { const { message, signature } = sign if (message && signature) { - clipboardState.copy(`${message}\nSignature: ${signature}`) + handleCopy(`${message}\nSignature: ${signature}`) } - }, [clipboardState, sign]) + }, [handleCopy, sign]) const openThread = () => { openUrl(THREAD_URL) From 861f8686cbbdad0d378035c7a8228dcdaaa8679e Mon Sep 17 00:00:00 2001 From: Andy Espagnolo Date: Thu, 29 Jun 2023 13:02:53 -0300 Subject: [PATCH 3/7] refactor: use react-hook-form in all forms (#1049) * refactor: use react-hook-form in update proposal status modal * refactor: use react-hook-form in grant category section * refactor: use react-hook-form in http status test form * refactor: use react-hook-form in team and budget modals * refactor: use react-hook-form in grant general info submit * refactor: remove assert function from linked-wearables * fix: mobile styles * remove console logs * add function to get all field props in core unit * add function to get all field props in other categories --- src/components/Common/Form/TextArea.tsx | 33 ++ src/components/Common/Text/Text.css | 3 + src/components/Common/Text/Text.tsx | 2 +- src/components/Debug/HttpStatus.tsx | 111 +++---- .../GrantRequest/AddBudgetBreakdownModal.tsx | 305 +++++++++--------- src/components/GrantRequest/AddModal.tsx | 36 ++- .../GrantRequest/AddTeamMemberModal.tsx | 176 +++++----- .../CategorySection/AcceleratorSection.tsx | 165 ++++------ .../CategorySection/CoreUnitSection.tsx | 126 +++----- .../CategorySection/DocumentationSection.tsx | 100 +++--- .../CategorySection/InWorldContentSection.tsx | 121 ++++--- .../CategorySection/PlatformSection.tsx | 78 ++--- .../SocialMediaContentSection.tsx | 195 +++++------ .../CategorySection/SponsorshipSection.tsx | 277 ++++++++-------- .../GrantRequestCategorySection.tsx | 42 ++- .../GrantRequestGeneralInfoSection.tsx | 252 +++++++-------- .../GrantRequest/GrantRequestSection.css | 23 -- .../GrantRequest/GrantRequestSectionCard.css | 1 + .../GrantRequest/MultipleChoiceField.css | 3 + .../GrantRequest/MultipleChoiceField.tsx | 20 +- src/components/Home/OpenProposal.css | 4 + src/components/Home/OpenProposal.tsx | 2 +- .../UpdateProposalStatusModal.tsx | 207 ++++++------ .../Profile/ProposalCreatedItem.css | 20 +- .../Profile/ProposalCreatedItem.tsx | 18 +- src/hooks/useGrantCategoryEditor.ts | 22 -- src/pages/proposal.tsx | 2 +- src/pages/submit/grant.tsx | 14 +- src/pages/submit/linked-wearables.tsx | 20 +- src/ui-overrides.css | 26 ++ 30 files changed, 1160 insertions(+), 1244 deletions(-) create mode 100644 src/components/Common/Form/TextArea.tsx create mode 100644 src/components/GrantRequest/MultipleChoiceField.css delete mode 100644 src/hooks/useGrantCategoryEditor.ts diff --git a/src/components/Common/Form/TextArea.tsx b/src/components/Common/Form/TextArea.tsx new file mode 100644 index 000000000..1251d5f0b --- /dev/null +++ b/src/components/Common/Form/TextArea.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { Control, Controller, FieldValues, Path, PathValue } from 'react-hook-form' + +import { + TextAreaField as DCLTextArea, + TextAreaFieldProps, +} from 'decentraland-ui/dist/components/TextAreaField/TextAreaField' + +interface Props extends TextAreaFieldProps { + control: Control + name: Path + defaultValue?: PathValue> | undefined + rules?: any +} + +export default function TextArea({ + control, + name, + defaultValue, + rules, + ...fieldProps +}: Props) { + return ( + } + /> + ) +} diff --git a/src/components/Common/Text/Text.css b/src/components/Common/Text/Text.css index 57d0dd402..787556f16 100644 --- a/src/components/Common/Text/Text.css +++ b/src/components/Common/Text/Text.css @@ -43,6 +43,9 @@ .Text.Text--color-secondary { color: var(--secondary-text); } +.Text.Text--color-error { + color: var(--red-800); +} .Text.Text--style-normal { font-style: normal; diff --git a/src/components/Common/Text/Text.tsx b/src/components/Common/Text/Text.tsx index 400af369c..002904c10 100644 --- a/src/components/Common/Text/Text.tsx +++ b/src/components/Common/Text/Text.tsx @@ -10,7 +10,7 @@ const DEFAULT_FONT_SIZE: FontSize = 'md' const DEFAULT_FONT_STYLE: FontStyle = 'normal' type FontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' type FontWeight = 'bold' | 'semi-bold' | 'normal' -type TextColor = 'default' | 'primary' | 'secondary' +type TextColor = 'default' | 'primary' | 'secondary' | 'error' type FontStyle = 'normal' | 'italic' interface Props { diff --git a/src/components/Debug/HttpStatus.tsx b/src/components/Debug/HttpStatus.tsx index ea59d14c6..63c9fdb40 100644 --- a/src/components/Debug/HttpStatus.tsx +++ b/src/components/Debug/HttpStatus.tsx @@ -1,11 +1,11 @@ -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' +import { SubmitHandler, useForm } from 'react-hook-form' -import useEditor, { assert, createValidator } from 'decentraland-gatsby/dist/hooks/useEditor' import { Button } from 'decentraland-ui/dist/components/Button/Button' -import { Field } from 'decentraland-ui/dist/components/Field/Field' import { HttpStat } from '../../clients/HttpStat' import useFormatMessage from '../../hooks/useFormatMessage' +import Field from '../Common/Form/Field' import Label from '../Common/Label' import ErrorMessage from '../Error/ErrorMessage' import { ContentSection } from '../Layout/ContentLayout' @@ -20,96 +20,83 @@ const initialState: TestState = { sleepTime: 0, } -const edit = (state: TestState, props: Partial) => { - return { - ...state, - ...props, - } -} - const MAX_SLEEP_TIME = 300000 // 5 minutes -const validate = createValidator({ - httpStatus: (state) => ({ - httpStatus: assert(state.httpStatus.length === 3, 'error.debug.invalid_http_status'), - }), - sleepTime: (state) => ({ - sleepTime: assert(state.sleepTime >= 0 && state.sleepTime <= MAX_SLEEP_TIME, 'error.debug.invalid_sleep_time'), - }), - '*': (state) => ({ - httpStatus: assert(state.httpStatus.length === 3, 'error.debug.invalid_http_status'), - sleepTime: assert(state.sleepTime >= 0 && state.sleepTime <= MAX_SLEEP_TIME, 'error.debug.invalid_sleep_time'), - }), -}) - interface Props { className?: string } export default function HttpStatus({ className }: Props) { const t = useFormatMessage() - const [state, editor] = useEditor(edit, validate, initialState) const [formDisabled, setFormDisabled] = useState(false) + const { + handleSubmit, + formState: { isSubmitting, errors }, + control, + } = useForm({ defaultValues: initialState, mode: 'onTouched' }) + const [error, setError] = useState('') - useEffect(() => { - if (state.validated) { - setFormDisabled(true) - Promise.resolve() - .then(async () => { - return HttpStat.get().fetchResponse(state.value.httpStatus, state.value.sleepTime) - }) - .then((result) => { - console.log('result', result) - editor.error({ '*': '' }) - setFormDisabled(false) - }) - .catch((err) => { - console.error(err, { ...err }) - editor.error({ '*': err.body?.error || err.message }) - setFormDisabled(false) - }) + const onSubmit: SubmitHandler = async (data) => { + try { + const result = await HttpStat.get().fetchResponse(data.httpStatus, data.sleepTime) + console.log('result', result) + setFormDisabled(false) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (err: any) { + console.error(err, { ...err }) + setError(err.body?.error || err.message) + setFormDisabled(false) } - }, [editor, state.validated, state.value]) + } return ( -
+
editor.set({ httpStatus: value })} - onBlur={() => editor.set({ httpStatus: state.value.httpStatus.trim() })} - error={!!state.error.httpStatus} + name="httpStatus" + control={control} + error={!!errors.httpStatus} + message={errors.httpStatus?.message} disabled={formDisabled} - message={t(state.error.httpStatus)} + rules={{ + required: { value: true, message: t('error.draft.title_empty') }, + validate: (value: string) => { + if (value.length !== 3) { + return t('error.debug.invalid_http_status') + } + }, + }} /> editor.set({ sleepTime: value ? Number(value) : undefined })} - onBlur={() => editor.set({ sleepTime: state.value.sleepTime })} - error={!!state.error.sleepTime} - message={t(state.error.sleepTime)} + name="sleepTime" + control={control} + error={!!errors.sleepTime} + message={errors.sleepTime?.message} disabled={formDisabled} + rules={{ + required: { value: true, message: t('error.draft.title_empty') }, + validate: (value: string) => { + if (Number(value) >= 0 && Number(value) <= MAX_SLEEP_TIME) { + return t('error.debug.invalid_sleep_time') + } + }, + }} /> - - {state.error['*'] && ( + {error && ( - + )} -
+ ) } diff --git a/src/components/GrantRequest/AddBudgetBreakdownModal.tsx b/src/components/GrantRequest/AddBudgetBreakdownModal.tsx index 8fbc6dfe2..a145d0396 100644 --- a/src/components/GrantRequest/AddBudgetBreakdownModal.tsx +++ b/src/components/GrantRequest/AddBudgetBreakdownModal.tsx @@ -1,13 +1,12 @@ -import React, { useEffect, useMemo } from 'react' - -import Textarea from 'decentraland-gatsby/dist/components/Form/Textarea' -import useEditor, { assert, createValidator } from 'decentraland-gatsby/dist/hooks/useEditor' -import { Field } from 'decentraland-ui/dist/components/Field/Field' +import React, { useEffect } from 'react' +import { SubmitHandler, useForm, useWatch } from 'react-hook-form' import { BudgetBreakdownConcept, BudgetBreakdownConceptSchema } from '../../entities/Grant/types' import { asNumber } from '../../entities/Proposal/utils' import { isHttpsURL } from '../../helpers' import useFormatMessage from '../../hooks/useFormatMessage' +import Field from '../Common/Form/Field' +import TextArea from '../Common/Form/TextArea' import Label from '../Common/Label' import { ContentSection } from '../Layout/ContentLayout' @@ -16,7 +15,7 @@ import './AddModal.css' import BudgetInput from './BudgetInput' import NumberSelector from './NumberSelector' -export const INITIAL_BUDGET_BREAKDOWN_CONCEPT: BudgetBreakdownConcept = { +const INITIAL_BUDGET_BREAKDOWN_CONCEPT: BudgetBreakdownConcept = { concept: '', duration: 1, estimatedBudget: '', @@ -25,52 +24,6 @@ export const INITIAL_BUDGET_BREAKDOWN_CONCEPT: BudgetBreakdownConcept = { } const schema = BudgetBreakdownConceptSchema -const validate = (fundingLeftToDisclose: number) => - createValidator({ - concept: (state) => ({ - concept: - assert(state.concept.length <= schema.concept.maxLength, 'error.grant.due_diligence.concept_too_large') || - assert(state.concept.length > 0, 'error.grant.due_diligence.concept_empty') || - assert(state.concept.length >= schema.concept.minLength, 'error.grant.due_diligence.concept_too_short') || - undefined, - }), - estimatedBudget: (state) => ({ - estimatedBudget: - assert( - Number.isInteger(asNumber(state.estimatedBudget)), - 'error.grant.due_diligence.estimated_budget_invalid' - ) || - assert( - !state.estimatedBudget || asNumber(state.estimatedBudget) >= schema.estimatedBudget.minimum, - 'error.grant.due_diligence.estimated_budget_too_low' - ) || - assert( - !state.estimatedBudget || asNumber(state.estimatedBudget) <= fundingLeftToDisclose, - 'error.grant.due_diligence.estimated_budget_too_big' - ) || - undefined, - }), - aboutThis: (state) => ({ - aboutThis: - assert( - state.aboutThis.length <= schema.aboutThis.maxLength, - 'error.grant.due_diligence.about_this_too_large' - ) || - assert(state.aboutThis.length > 0, 'error.grant.due_diligence.about_this_empty') || - assert( - state.aboutThis.length >= schema.aboutThis.minLength, - 'error.grant.due_diligence.about_this_too_short' - ) || - undefined, - }), - }) - -const edit = (state: BudgetBreakdownConcept, props: Partial) => { - return { - ...state, - ...props, - } -} interface Props { isOpen: boolean @@ -82,7 +35,7 @@ interface Props { selectedConcept: BudgetBreakdownConcept | null } -const AddBudgetBreakdownModal = ({ +export default function AddBudgetBreakdownModal({ isOpen, onClose, onSubmit, @@ -90,130 +43,170 @@ const AddBudgetBreakdownModal = ({ fundingLeftToDisclose, selectedConcept, projectDuration, -}: Props) => { +}: Props) { const t = useFormatMessage() const leftToDisclose = selectedConcept ? fundingLeftToDisclose + Number(selectedConcept.estimatedBudget) : fundingLeftToDisclose - const validator = useMemo(() => validate(leftToDisclose), [leftToDisclose]) - const [state, editor] = useEditor(edit, validator, INITIAL_BUDGET_BREAKDOWN_CONCEPT) + + const { + formState: { errors }, + control, + reset, + watch, + setValue, + handleSubmit, + register, + } = useForm({ + defaultValues: INITIAL_BUDGET_BREAKDOWN_CONCEPT, + mode: 'onTouched', + }) + + const values = useWatch({ control }) const hasInvalidUrl = - state.value.relevantLink !== '' && - !!state.value.relevantLink && - (!isHttpsURL(state.value.relevantLink) || state.value.relevantLink?.length >= schema.relevantLink.maxLength) + values.relevantLink !== '' && + !!values.relevantLink && + (!isHttpsURL(values.relevantLink) || values.relevantLink?.length >= schema.relevantLink.maxLength) - useEffect(() => { - if (state.validated) { - onSubmit(state.value) - onClose() - editor.set(INITIAL_BUDGET_BREAKDOWN_CONCEPT) + const onSubmitForm: SubmitHandler = (data) => { + if (hasInvalidUrl) { + return } - }, [editor, onClose, onSubmit, state.validated, state.value]) + + onSubmit(data) + onClose() + reset() + } useEffect(() => { if (selectedConcept) { - editor.set({ ...selectedConcept }) + const { concept, aboutThis, duration, relevantLink } = selectedConcept + setValue('concept', concept) + setValue('aboutThis', aboutThis) + setValue('duration', duration) + setValue('relevantLink', relevantLink) } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedConcept]) + }, [selectedConcept, setValue]) return ( !hasInvalidUrl && editor.validate()} + onPrimaryClick={handleSubmit(onSubmitForm)} onSecondaryClick={selectedConcept ? onDelete : undefined} > -
- - - editor.set({ concept: value })} - error={!!state.error.concept} - message={ - t(state.error.concept) + - ' ' + - t('page.submit.character_counter', { - current: state.value.concept.length, - limit: schema.concept.maxLength, - }) - } - /> - - - - editor.set({ - estimatedBudget: currentTarget.value !== '' ? Number(currentTarget.value) : currentTarget.value, - }) - } - subtitle={t('page.submit_grant.due_diligence.budget_breakdown_modal.estimated_budget_left_to_disclose', { + + + + + + - editor.set({ duration: Number(value) })} - label={t('page.submit_grant.due_diligence.budget_breakdown_modal.duration_label')} - unitLabel={t('page.submit_grant.due_diligence.budget_breakdown_modal.duration_unit_label')} - subtitle={t('page.submit_grant.due_diligence.budget_breakdown_modal.duration_subtitle')} - /> - - - -