Skip to content

Commit

Permalink
feat: project forms validation (#1794)
Browse files Browse the repository at this point in the history
* chore: wip updating BasicInfo and Description form validation

* chore: use yup for validation wip

* chore: update DescriptionForm validation

* chore: update remaining project forms validation

* fix: FieldFormControl causing validation with previous value

* chore: rm unused

* chore: clean up rdf validate types

* fix: revert LocationField handleChange

* chore: update type error for schema location

* chore: rm validate comments
  • Loading branch information
blushi authored Feb 27, 2023
1 parent 39df91f commit 7335597
Show file tree
Hide file tree
Showing 34 changed files with 421 additions and 513 deletions.
4 changes: 3 additions & 1 deletion web-components/src/components/inputs/FieldFormControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const useStyles = makeStyles<StyleProps>()((theme, { error }) => ({
fontFamily: '"Lato",-apple-system,sans-serif',
fontWeight: 'bold',
visibility: error ? 'visible' : 'hidden',
whiteSpace: 'pre-wrap',
[theme.breakpoints.up('sm')]: {
fontSize: theme.spacing(3.5),
},
Expand Down Expand Up @@ -93,7 +94,8 @@ export default function FieldFormControl({

async function handleChange(value: any): Promise<void> {
form.setFieldValue(field.name, value);
form.setFieldTouched(field.name, true);
// see https://github.com/jaredpalmer/formik/issues/2083:
setTimeout(() => form.setFieldTouched(field.name, true));
}

function handleBlur(value: string): void {
Expand Down
2 changes: 1 addition & 1 deletion web-components/src/components/inputs/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ export const invalidVCSRetirement: string =
'Please enter a valid VCS retirement serial number';
export const invalidVCSID: string = 'Please enter a valid VCS Project ID';
export const invalidJSON: string = 'Please enter valid JSON-LD';
export const invalidAddress: string = 'Invalid address';
export const invalidRegenAddress: string = 'Invalid regen address';
export const invalidPolygonAddress: string = 'Invalid Polygon address';
export const requiredDenom: string = 'Please choose a denom';
export const invalidDecimalCount: string = `More than ${MAX_FRACTION_DIGITS} decimal places not allowed`;
export const invalidMemoLength: string = `Must be ${MEMO_MAX_LENGTH} characters or fewer`;
export const positiveNumber = 'Must be positive';

/* Validation Functions */

Expand Down
12 changes: 5 additions & 7 deletions web-components/src/components/modal/ProfileModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { Box, useTheme } from '@mui/material';
import { DefaultTheme as Theme } from '@mui/styles';
import { Field, Form, Formik, FormikErrors, useFormikContext } from 'formik';
import { Field, Form, Formik, useFormikContext } from 'formik';
import { makeStyles } from 'tss-react/mui';

import { Button } from '../buttons/Button';
Expand All @@ -19,9 +19,7 @@ interface ProfileModalProps {
profile: ProfileFormValues;
onClose: () => void;
onSubmit: (profile: ProfileFormValues) => void;
validate: (
values: ProfileFormValues,
) => Promise<FormikErrors<ProfileFormValues>>;
validationSchema: any;
apiServerUrl: string;
projectId: string;
}
Expand All @@ -41,7 +39,7 @@ function ProfileModal({
profile,
onClose,
onSubmit,
validate,
validationSchema,
apiServerUrl,
projectId,
}: ProfileModalProps): JSX.Element {
Expand Down Expand Up @@ -71,13 +69,13 @@ function ProfileModal({
onSubmit={async (values, { setSubmitting }) => {
setSubmitting(true);
try {
await onSubmit(values);
onSubmit(values);
setSubmitting(false);
} catch (e) {
setSubmitting(false);
}
}}
validate={validate}
validationSchema={validationSchema}
>
{({ submitForm }) => {
return (
Expand Down
5 changes: 3 additions & 2 deletions web-registry/src/components/molecules/RoleField/RoleField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ interface Props extends FieldProps {
mapboxToken: string;
apiServerUrl: string;
projectId: string;
profileValidationSchema: any;
}

interface RoleOptionType {
Expand Down Expand Up @@ -143,7 +144,7 @@ const RoleField: React.FC<React.PropsWithChildren<Props>> = ({
placeholder,
mapboxToken,
onSaveProfile,
validateEntity,
profileValidationSchema,
apiServerUrl,
projectId,
...fieldProps
Expand Down Expand Up @@ -287,7 +288,7 @@ const RoleField: React.FC<React.PropsWithChildren<Props>> = ({
profile={profileEdit}
onClose={closeProfileModal}
onSubmit={saveProfile}
validate={validateEntity}
validationSchema={profileValidationSchema}
apiServerUrl={apiServerUrl}
projectId={projectId}
/>
Expand Down
61 changes: 18 additions & 43 deletions web-registry/src/components/organisms/BasicInfoForm.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import React from 'react';
import { Box } from '@mui/material';
import { Field, Form, Formik, FormikErrors } from 'formik';
import { Field, Form, Formik } from 'formik';
import { makeStyles } from 'tss-react/mui';
import * as Yup from 'yup';

import OnBoardingCard from 'web-components/lib/components/cards/OnBoardingCard';
import ControlledTextField from 'web-components/lib/components/inputs/ControlledTextField';
import InputLabel from 'web-components/lib/components/inputs/InputLabel';
import SelectTextField from 'web-components/lib/components/inputs/SelectTextField';
import TextField from 'web-components/lib/components/inputs/TextField';
import {
positiveNumber,
requiredMessage,
} from 'web-components/lib/components/inputs/validation';
import { Theme } from 'web-components/lib/theme/muiTheme';

import { useProjectEditContext } from 'pages';

// import { requiredMessage } from 'web-components/lib/components/inputs/validation'; TODO: regen-registry#1048
import { useShaclGraphByUriQuery } from '../../generated/graphql';
// import {
// validate,
// getProjectBaseData,
// getCompactedPath,
// } from '../../lib/rdf'; TODO: regen-registry#1048
import { ProjectPageFooter } from '../molecules';

export interface BasicInfoFormValues {
Expand Down Expand Up @@ -53,6 +51,16 @@ const useStyles = makeStyles()((theme: Theme) => ({
},
}));

const BasicInfoFormSchema = Yup.object().shape({
'schema:name': Yup.string().required(requiredMessage),
'regen:projectSize': Yup.object().shape({
'qudt:numericValue': Yup.number()
.positive(positiveNumber)
.required(requiredMessage),
'qudt:unit': Yup.string().required(requiredMessage),
}),
});

const BasicInfoForm: React.FC<
React.PropsWithChildren<{
submit: ({ values }: { values: BasicInfoFormValues }) => Promise<void>;
Expand All @@ -62,11 +70,6 @@ const BasicInfoForm: React.FC<
> = ({ submit, initialValues, onNext }) => {
const { classes, cx } = useStyles();
const { confirmSave, isEdit } = useProjectEditContext();
const { data: graphData } = useShaclGraphByUriQuery({
variables: {
uri: 'http://regen.network/ProjectPageShape',
},
});

return (
<Formik
Expand All @@ -79,40 +82,12 @@ const BasicInfoForm: React.FC<
'qudt:unit': 'unit:HA',
},
}}
validate={async (values: BasicInfoFormValues) => {
const errors: FormikErrors<BasicInfoFormValues> = {};
if (graphData?.shaclGraphByUri?.graph) {
// TODO: Fix Validation. regen-registry#1048 .
// Temporarily commented out to enable testing.
// const projectPageData = { ...getProjectBaseData(), ...values };
// const report = await validate(
// graphData.shaclGraphByUri.graph,
// projectPageData,
// 'http://regen.network/ProjectPageBasicInfoGroup',
// );
// for (const result of report.results) {
// const path: string = result.path.value;
// const compactedPath = getCompactedPath(path) as
// | keyof BasicInfoFormValues
// | undefined;
// if (compactedPath === 'regen:size') {
// errors[compactedPath] = {
// 'qudt:numericValue': {
// '@value': requiredMessage,
// },
// };
// } else if (compactedPath) {
// errors[compactedPath] = requiredMessage;
// }
// }
}
return errors;
}}
onSubmit={async (values, { setTouched }) => {
await submit({ values });
setTouched({}); // reset to untouched
if (isEdit && confirmSave) confirmSave();
}}
validationSchema={BasicInfoFormSchema}
>
{({ submitForm, isValid, isSubmitting, dirty }) => {
return (
Expand All @@ -133,7 +108,7 @@ const BasicInfoForm: React.FC<
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-end',
alignItems: 'baseline',
}}
>
<Field
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const DESCRIPTION_MAX_LENGTH = 1400;
Original file line number Diff line number Diff line change
@@ -1,60 +1,36 @@
import React from 'react';
import { Field, Form, Formik, FormikErrors, FormikHelpers } from 'formik';
import { Field, Form, Formik, FormikHelpers } from 'formik';
import * as Yup from 'yup';

import OnBoardingCard from 'web-components/lib/components/cards/OnBoardingCard';
import ControlledTextField from 'web-components/lib/components/inputs/ControlledTextField';
import { requiredMessage } from 'web-components/lib/components/inputs/validation';

import { getCompactedPath, getProjectBaseData, validate } from 'lib/rdf';

import { ShaclGraphByUriQuery } from '../../generated/graphql';
import { useProjectEditContext } from '../../pages/ProjectEdit';
import { ProjectPageFooter } from '../molecules';
import { useProjectEditContext } from '../../../pages/ProjectEdit';
import { ProjectPageFooter } from '../../molecules';
import { DESCRIPTION_MAX_LENGTH } from './DescriptionForm.constants';

interface DescriptionFormProps {
submit: ({ values }: { values: DescriptionValues }) => Promise<void>;
onNext?: () => void;
onPrev?: () => void;
initialValues?: DescriptionValues;
graphData?: ShaclGraphByUriQuery;
}

export interface DescriptionValues {
'schema:description'?: string;
}

const DescriptionFormSchema = Yup.object().shape({
'schema:description': Yup.string().max(DESCRIPTION_MAX_LENGTH),
});

const DescriptionForm = ({
submit,
initialValues,
graphData,
...props
}: DescriptionFormProps): JSX.Element => {
const { confirmSave, isEdit } = useProjectEditContext();

const validateForm = async (
values: DescriptionValues,
): Promise<FormikErrors<DescriptionValues>> => {
const errors: FormikErrors<DescriptionValues> = {};
if (graphData?.shaclGraphByUri?.graph) {
const projectPageData = { ...getProjectBaseData(), ...values };
const report = await validate(
graphData.shaclGraphByUri.graph,
projectPageData,
'http://regen.network/ProjectPageDescriptionGroup',
);
for (const result of report.results) {
const path: string = result.path.value;
const compactedPath = getCompactedPath(path) as
| keyof DescriptionValues
| undefined;
if (compactedPath) {
errors[compactedPath] = requiredMessage;
}
}
}
return errors;
};

const onSubmit = async (
values: DescriptionValues,
{ setSubmitting, setTouched }: FormikHelpers<DescriptionValues>,
Expand All @@ -78,15 +54,15 @@ const DescriptionForm = ({
initialValues?.['schema:description'] || undefined,
}
}
validate={validateForm}
validationSchema={DescriptionFormSchema}
onSubmit={onSubmit}
>
{({ submitForm, isValid, isSubmitting, dirty }) => {
return (
<Form translate="yes">
<OnBoardingCard>
<Field
charLimit={600}
charLimit={DESCRIPTION_MAX_LENGTH}
component={ControlledTextField}
label="Project description"
description="Describe the story of this property."
Expand Down
5 changes: 4 additions & 1 deletion web-registry/src/components/organisms/EntityDisplayForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ function getInitialValues(values?: DisplayValues): DisplayValues | undefined {
}
}

/**
* @deprecated part of legacy project forms
*/
const EntityDisplayForm: React.FC<
React.PropsWithChildren<EntityDisplayFormProps>
> = ({ submit, initialValues, ...props }) => {
Expand Down Expand Up @@ -420,7 +423,7 @@ const EntityDisplayForm: React.FC<
}
if (validateProject) {
const projectPageData = {
...getProjectBaseData(),
...getProjectBaseData(''),
...values,
};
const report = await validate(
Expand Down
Loading

0 comments on commit 7335597

Please sign in to comment.