diff --git a/src/devhub/components/molecule/Input.jsx b/src/devhub/components/molecule/Input.jsx index 79c9d34c0..7440fcac6 100644 --- a/src/devhub/components/molecule/Input.jsx +++ b/src/devhub/components/molecule/Input.jsx @@ -21,6 +21,29 @@ const TextInput = ({ ? type : "text"; + const isValid = () => { + if (!value || value.length === 0) { + return !inputProps.required; + } else if (inputProps.min && inputProps.min > value?.length) { + return false; + } else if (inputProps.max && inputProps.max < value?.length) { + return false; + } else if ( + inputProps.allowCommaAndSpace === false && + /^[^,\s]*$/.test(value) === false + ) { + return false; + } else if ( + inputProps.validUrl === true && + /^(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/.test( + value + ) === false + ) { + return false; + } + return true; + }; + const renderedLabels = [ (label?.length ?? 0) > 0 ? ( @@ -36,7 +59,9 @@ const TextInput = ({ format === "comma-separated" ? ( {format} @@ -44,9 +69,10 @@ const TextInput = ({ ) : null, (inputProps.max ?? null) !== null ? ( - {`${ - value?.length ?? 0 - } / ${inputProps.max}`} + {`${value?.length ?? 0} / ${inputProps.max}`} ) : null, ].filter((label) => label !== null); @@ -81,6 +107,7 @@ const TextInput = ({ " " )} type={typeAttribute} + maxLength={inputProps.max} {...{ onChange, placeholder, value, ...inputProps }} /> @@ -94,6 +121,7 @@ const TextInput = ({ } style={{ resize: inputProps.resize ?? "vertical" }} type={typeAttribute} + maxLength={inputProps.max} {...{ onChange, placeholder, value, ...inputProps }} /> )} diff --git a/src/devhub/components/organism/Configurator.jsx b/src/devhub/components/organism/Configurator.jsx index 682ce9985..5a0f4855d 100644 --- a/src/devhub/components/organism/Configurator.jsx +++ b/src/devhub/components/organism/Configurator.jsx @@ -258,7 +258,41 @@ const Configurator = ({ ? toFormatted(form.values) : form.values; - const isFormValid = isValid ? isValid(formFormattedValues) : true; + const internalValidation = () => + Object.keys(schema).every((key) => { + const fieldDefinition = schema[key]; + const value = form.values[key]; + if (!value || value.length === 0) { + return !fieldDefinition.inputProps.required; + } else if ( + fieldDefinition.inputProps.min && + fieldDefinition.inputProps.min > value?.length + ) { + return false; + } else if ( + fieldDefinition.inputProps.max && + fieldDefinition.inputProps.max < value?.length + ) { + return false; + } else if ( + fieldDefinition.inputProps.allowCommaAndSpace === false && + /^[^,\s]*$/.test(value) === false + ) { + return false; + } else if ( + fieldDefinition.inputProps.validUrl === true && + /^(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/.test( + value + ) === false + ) { + return false; + } + return true; + }); + + const isFormValid = () => { + return internalValidation() && (!isValid || isValid(formFormattedValues)); + }; const onCancelClick = () => { form.reset(); @@ -266,7 +300,7 @@ const Configurator = ({ }; const onSubmitClick = () => { - if (onSubmit && isFormValid) { + if (onSubmit && isFormValid()) { onSubmit(formFormattedValues); } }; @@ -298,7 +332,7 @@ const Configurator = ({ src={"${REPL_DEVHUB}/widget/devhub.components.molecule.Button"} props={{ classNames: { root: classNames.submit || "btn-success" }, - disabled: !form.hasUnsubmittedChanges || !isFormValid, + disabled: !form.hasUnsubmittedChanges || !isFormValid(), icon: submitIcon || { type: "bootstrap_icon", variant: "bi-check-circle-fill", diff --git a/src/devhub/entity/community/Spawner.jsx b/src/devhub/entity/community/Spawner.jsx index b73783694..84c1a94c1 100644 --- a/src/devhub/entity/community/Spawner.jsx +++ b/src/devhub/entity/community/Spawner.jsx @@ -11,10 +11,9 @@ const CommunityInputsPartialSchema = { inputProps: { min: 2, max: 40, - + allowCommaAndSpace: false, placeholder: "Choose unique URL handle for your community. Example: zero-knowledge.", - required: true, }, @@ -38,7 +37,7 @@ const CommunityInputsPartialSchema = { inputProps: { min: 2, max: 30, - + allowCommaAndSpace: false, placeholder: "Any posts with this tag will show up in your community feed.", diff --git a/src/devhub/entity/community/configuration/AboutConfigurator.jsx b/src/devhub/entity/community/configuration/AboutConfigurator.jsx index 8f3afbaa3..e0c60eafd 100644 --- a/src/devhub/entity/community/configuration/AboutConfigurator.jsx +++ b/src/devhub/entity/community/configuration/AboutConfigurator.jsx @@ -8,7 +8,7 @@ const CommunityAboutSchema = { placeholder: "Tell people about your community. This will appear on your community’s homepage.", - + required: true, resize: "none", }, @@ -37,7 +37,7 @@ const CommunityAboutSchema = { }, website_url: { - inputProps: { prefix: "https://", min: 2, max: 60 }, + inputProps: { prefix: "https://", min: 2, max: 60, validUrl: true }, label: "Website", order: 5, }, diff --git a/src/devhub/entity/community/configuration/InformationConfigurator.jsx b/src/devhub/entity/community/configuration/InformationConfigurator.jsx index 1f470868f..44a7e707c 100644 --- a/src/devhub/entity/community/configuration/InformationConfigurator.jsx +++ b/src/devhub/entity/community/configuration/InformationConfigurator.jsx @@ -30,7 +30,7 @@ const CommunityInformationSchema = { inputProps: { min: 2, max: 40, - + allowCommaAndSpace: false, placeholder: "Choose unique URL handle for your community. Example: zero-knowledge.", @@ -45,7 +45,7 @@ const CommunityInformationSchema = { inputProps: { min: 2, max: 30, - + allowCommaAndSpace: false, placeholder: "Any posts with this tag will show up in your community feed.", diff --git a/src/devhub/entity/post/Post.jsx b/src/devhub/entity/post/Post.jsx index b845b7682..068e43906 100644 --- a/src/devhub/entity/post/Post.jsx +++ b/src/devhub/entity/post/Post.jsx @@ -561,6 +561,25 @@ const toggleEditor = () => { State.update({ showEditor: !state.showEditor }); }; +let amount = null; +let token = null; +let supervisor = null; + +if (state.postType === "Solution") { + const amountMatch = post.snapshot.description.match( + /Requested amount: (\d+(\.\d+)?) (\w+)/ + ); + amount = amountMatch ? parseFloat(amountMatch[1]) : null; + token = amountMatch ? amountMatch[3] : null; + + const sponsorMatch = post.snapshot.description.match( + /Requested sponsor: @([^\s]+)/ + ); + supervisor = sponsorMatch ? sponsorMatch[1] : null; +} + +const seekingFunding = amount !== null || token !== null || supervisor !== null; + function Editor() { return (
@@ -595,9 +614,11 @@ function Editor() { labels: post.snapshot.labels, name: post.snapshot.name, description: post.snapshot.description, - amount: post.snapshot.amount, - token: tokenResolver(post.snapshot.sponsorship_token), - supervisor: post.snapshot.supervisor, + amount: post.snapshot.amount || amount, + token: tokenResolver(post.snapshot.sponsorship_token || token), + supervisor: + post.snapshot.post.snapshot.supervisor || supervisor, + seekingFunding: seekingFunding, githubLink: post.snapshot.github_link, onDraftStateChange, draftState: diff --git a/src/devhub/entity/post/PostEditor.jsx b/src/devhub/entity/post/PostEditor.jsx index 7481cf6a8..a95772b4f 100644 --- a/src/devhub/entity/post/PostEditor.jsx +++ b/src/devhub/entity/post/PostEditor.jsx @@ -42,8 +42,15 @@ const labels = labelStrings.map((s) => { return { name: s }; }); +const cleanDescription = (description) => { + return description.replace( + /###### Requested amount: .+?\n###### Requested sponsor: @[^\s]+\n/g, + "" + ); +}; + initState({ - seekingFunding: false, + seekingFunding: props.seekingFunding ?? false, author_id: context.accountId, // Should be a list of objects with field "name". labels, @@ -52,7 +59,10 @@ initState({ labelStrings, postType, name: props.name ?? "", - description: props.description ?? "", + description: + (props.postType === "Solution" + ? cleanDescription(props.description) + : props.description) ?? "", amount: props.amount ?? "0", token: props.token ?? "USDT", supervisor: props.supervisor ?? "neardevdao.near", diff --git a/src/devhub/page/post.jsx b/src/devhub/page/post.jsx index e11ff268b..daf7cdb03 100644 --- a/src/devhub/page/post.jsx +++ b/src/devhub/page/post.jsx @@ -3,6 +3,7 @@ const { id } = props; const Container = styled.div` padding: 0 3rem 3rem 3rem; width: 100%; + max-width: 100%; @media screen and (max-width: 768px) { padding: 0 1rem 1rem 1rem;