Parcourir la source

Add validations and Toast notifications when savind recipes

Tatiana Inama il y a 6 ans
Parent
commit
3bdd6b74d4

+ 17 - 6
recipes/src/components/Input/index.tsx

@@ -18,11 +18,12 @@ type InputProps = {
 	label?: string,
 	type?: 'text' | 'number' ,
 	onChange?: (e: React.FormEvent<HTMLInputElement>) => void,
+	error?: string,
 }
 
-export const Input = ({label, type = 'text', ...props }: InputProps) => {
+export const Input = ({label, type = 'text', error, children, ...props }: InputProps) => {
 	return (
-		<div className="cbk-light-input">
+		<div className={`cbk-light-input${error ? ' cbk-light-input--invalid' : ''}`}>
 			{ label && (<label htmlFor={props.name}>{label}</label>)}
 			<input
 				className={props.value ? 'has-value': ''}
@@ -30,13 +31,16 @@ export const Input = ({label, type = 'text', ...props }: InputProps) => {
 				{...props}
 			/>
 			<span></span>
+			{ error && (
+				<div className="cbk-light-input--invalid__error"> { error } </div>
+			)}
 		</div>
 	)
 }
 
-export const ControlledInput = forwardRef<HTMLInputElement, InputProps>(({label, type = 'text', ...props }, ref) => {
+export const ControlledInput = forwardRef<HTMLInputElement, InputProps>(({label, type = 'text', error, children, ...props }, ref) => {
 	return (
-		<div className="cbk-light-input">
+		<div className={`cbk-light-input${error ? ' cbk-light-input--invalid' : ''}`}>
 			{ label && (<label htmlFor={props.name}>{label}</label>)}
 			<input
 				ref={ref}
@@ -45,6 +49,9 @@ export const ControlledInput = forwardRef<HTMLInputElement, InputProps>(({label,
 				{...props}
 			/>
 			<span></span>
+			{ error && (
+				<div className="cbk-light-input--invalid__error"> { error } </div>
+			)}
 		</div>
 	)
 })
@@ -54,11 +61,12 @@ type TextareaProps = {
 	label?: string,
 	rows?: number
 	onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void,
+	error?: string,
 }
 
-export const Textarea = ({label, type = 'text', rows = 3, ...props }: TextareaProps) => {
+export const Textarea = ({label, type = 'text', rows = 3, error, children, ...props }: TextareaProps) => {
 	return (
-		<div className="cbk-light-input">
+		<div className={`cbk-light-input${error ? ' cbk-light-input--invalid' : ''}`}>
 			{ label && (<label htmlFor={props.name}>{label}</label>)}
 			<textarea
 				rows={rows}
@@ -66,6 +74,9 @@ export const Textarea = ({label, type = 'text', rows = 3, ...props }: TextareaPr
 				{...props}
 			/>
 			<span></span>
+			{ error && (
+				<div className="cbk-light-input--invalid__error"> { error } </div>
+			)}
 		</div>
 	)
 }

+ 14 - 0
recipes/src/components/Input/styles.scss

@@ -72,6 +72,20 @@
     border-color: $grey-400;
     font-style: italic;
   }
+  &--invalid {
+    display: block;
+    label {
+      color: $red;
+    }
+    span::before {
+      bottom: 1.25rem;
+      width: 100%;
+      box-shadow: 2px 2px 0 1px $red;
+    }
+    &__error {
+      @include error();
+    }
+  }
 }
 
 .cbk-input-2 {

+ 32 - 20
recipes/src/components/RecipeForm/index.tsx

@@ -1,11 +1,11 @@
 import React, { useState, forwardRef } from 'react';
 
-import Recipe, { Ingredient, Suggestion, _ingredient, _subRecipe, DBRecipe } from 'types/recipes';
+import Recipe, { Ingredient, Suggestion, _ingredient, _subRecipe, DBRecipe, _recipe } from 'types/recipes';
 import { Grid, Row, Cell } from '@material/react-layout-grid';
-import { Field, FieldArray, Formik, useField, Form, FieldArrayRenderProps } from 'formik';
+import { Field, FieldArray, Formik, useField, Form, FieldArrayRenderProps, FormikValues } from 'formik';
 import { MeasuresTypes } from 'services/measurements';
 import { Input, Textarea, ControlledInput } from 'components/Input';
-
+import { values, isEmpty } from 'ramda';
 import ImageUploader from 'components/ImageUploader';
 import Button from 'components/Button';
 import DialogConverter from 'components/DialogConverter';
@@ -19,6 +19,7 @@ type RecipeFormProps<T> = {
   initialValues: T,
   onSubmit: (data: T) => void,
   onCancel: () => void,
+  onErrors?: () => void,
 }
 
 const convertIngredient = (ingredient: Ingredient, suggestion: Suggestion): Ingredient => {
@@ -48,13 +49,10 @@ const FormikInput = ({ label, ...props }:FormikInputProps)  => {
     <>
       <Input
         label={label}
+        error={ meta.touched && meta.error ? meta.error : undefined }
         { ...props }
         { ...field }
       />
-      {/* TODO: Add validation errors when onSubmit */}
-      {meta.touched && meta.error ? (
-        <div className="error">{meta.error}</div>
-      ) : null}
     </>
   );
 };
@@ -66,13 +64,10 @@ const FormikFocusInput = forwardRef<HTMLInputElement, FormikInputProps>(({ label
       <ControlledInput
         ref={ref}
         label={label}
+        error={ meta.touched && meta.error ? meta.error : undefined }
         { ...props }
         { ...field }
       />
-      {/* TODO: Add validation errors when onSubmit */}
-      {meta.touched && meta.error ? (
-        <div className="error">{meta.error}</div>
-      ) : null}
     </>
   );
 });
@@ -83,17 +78,28 @@ const FormikTextarea = ({ label, ...props }: FormikInputProps)  => {
     <>
       <Textarea
         label={label}
+        error={ meta.touched && meta.error ? meta.error : undefined }
         { ...props }
         { ...field }
-      />
-      {/* TODO: Add validation errors when onSubmit */}
-      {meta.touched && meta.error ? (
-        <div className="error">{meta.error}</div>
-      ) : null}
+      /> 
     </>
   );
 };
 
+const validateRecipe = (recipe: Recipe | DBRecipe): Recipe | DBRecipe => {
+  let errors: any = {}
+
+  if (!recipe.name) {
+    errors.name = 'recipe must have a name'
+  }
+
+  if (!recipe.ingredients[0].ingredients[0].name) {
+    errors.ingredients = 'the recipe must have at least one ingredient'
+  }
+
+  return errors;
+}
+
 const RecipeForm = <T extends Recipe|DBRecipe>({ initialValues, onSubmit, onCancel}: RecipeFormProps<T>) => {
   const [selectedTab, setSelectedTab] = useState(0);
   const [focusLast, setFocusLast] = useState(false);
@@ -126,9 +132,10 @@ const RecipeForm = <T extends Recipe|DBRecipe>({ initialValues, onSubmit, onCanc
       enableReinitialize
       initialValues={initialValues}
       onSubmit={onSubmit}
+      validate={validateRecipe}
     >
       {
-        ({setFieldValue, submitForm, values}) => {
+        ({setFieldValue, submitForm, values, errors}) => {
           return (
           <Grid>
             <Form className='cbk-recipe-form' encType="multipart/form-data">
@@ -151,7 +158,7 @@ const RecipeForm = <T extends Recipe|DBRecipe>({ initialValues, onSubmit, onCanc
             <section>
               <Row>
                 <Cell columns={3}>
-                  <FormikInput name='author.name' label='name' />
+                  <FormikInput name='author.name' label='name' required/>
                 </Cell>
                 <Cell columns={3}>
                   <FormikInput name='author.website' label='website' />
@@ -211,7 +218,8 @@ const RecipeForm = <T extends Recipe|DBRecipe>({ initialValues, onSubmit, onCanc
               <FieldArray name='ingredients'>
                 {({remove, push}) => {
                   return (
-                    <div className='subrecipe-tabs'>
+                    <>
+                    <div className={`subrecipe-tabs${errors.ingredients ? ' subrecipe-tabs--invalid' : ''}`}>
                       <ul className='tab-header'>
                         {
                           values.ingredients.map((subrecipe, subrecipeIdx) => (
@@ -254,7 +262,7 @@ const RecipeForm = <T extends Recipe|DBRecipe>({ initialValues, onSubmit, onCanc
                                   {
                                     values.ingredients[selectedTab] && values.ingredients[selectedTab].ingredients.map((ingredient, index, array) => (
                                       <div className='ingredients-form__content__item' key={index}>
-                                        <FormikFocusInput name={`ingredients[${selectedTab}].ingredients[${index}].name`} 
+                                        <FormikFocusInput name={`ingredients[${selectedTab}].ingredients[${index}].name` } 
                                           ref={(ref: any) => {
                                             if(index === array.length-1 && ref && focusLast) {
                                               ref.focus();
@@ -329,6 +337,10 @@ const RecipeForm = <T extends Recipe|DBRecipe>({ initialValues, onSubmit, onCanc
                         </div>
                       </div>
                     </div>
+                    { errors.ingredients && (
+                      <div className="subrecipe-tabs__error"> {errors.ingredients} </div>
+                    )}
+                    </>
                   );
                 }}
               </FieldArray>

+ 8 - 2
recipes/src/components/RecipeForm/styles.scss

@@ -53,9 +53,15 @@
           padding: 0px;
         }
       }
-
     }
-
+    
+    &--invalid {
+      @include solidBoxShadow($red)
+    }
+    &__error {
+      @include error();
+    }
+    
     .ingredients-form {
       padding: $spacing-xs;
 

+ 4 - 2
recipes/src/containers/Recipes/Create/index.tsx

@@ -4,6 +4,7 @@ import Input from 'components/Input';
 import Button from 'components/Button';
 import RecipeForm from 'components/RecipeForm';
 import Spinner from 'components/Spinner';
+import { toast } from "react-toastify";
 
 import './styles.scss';
 
@@ -57,9 +58,10 @@ class CreateRecipe extends React.Component<any, CreateRecipeState> {
     saveRecipe(recipe)
       .then(response => {
         if (response.status === 200) {
-          this.goToViewList()
+          toast.success("Recipe created correctly!");
+          this.goToViewList();
         } else {
-          alert(response.statusText)
+          toast.error("There was an error saving the recipe: " + response.statusText);
         }
       })
   }

+ 7 - 0
recipes/src/styles/_fonts.scss

@@ -34,4 +34,11 @@ $letter-spacing-enhanced: 0.5px;
   font-size: $font-size-regular;
   font-weight: $font-weight-bold;
   letter-spacing: $letter-spacing-enhanced;
+}
+
+@mixin error() {
+  font-family: $font-family-text;
+  color: $red;
+  line-height: $font-size-x-large;
+  font-style: italic;
 }