(FIX): Validation and dirty checks have been implemented.
Review this, but it looks right.
This commit is contained in:
parent
c729e883e0
commit
ce6d748731
@ -2,7 +2,6 @@ import { Reorder, useDragControls } from "motion/react";
|
||||
import { INGREDIENT_UNITS, type RecipeIngredient } from "../../types/recipe";
|
||||
import DeleteIconSmall from "../icons/DeleteIconSmall";
|
||||
import DragIconSmall from "../icons/DragIconSmall";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface IngredientItemProps {
|
||||
classes: string;
|
||||
@ -10,35 +9,19 @@ interface IngredientItemProps {
|
||||
onChange: (id: string, name: "Amount" | "Unit" | "Name", value: string) => void;
|
||||
removeIngredientHandler: (id: string) => void;
|
||||
allowDelete: boolean;
|
||||
valid: boolean;
|
||||
dirty: boolean;
|
||||
markDirty: (id: string) => void;
|
||||
}
|
||||
|
||||
export default function IngredientItem({ classes, ingredient, onChange, removeIngredientHandler, allowDelete }: IngredientItemProps) {
|
||||
const [dirty, setDirty] = useState<boolean>(false);
|
||||
const [valid, setValid] = useState<boolean>(false);
|
||||
|
||||
export default function IngredientItem({ classes, ingredient, onChange, removeIngredientHandler, allowDelete, valid, dirty, markDirty }: IngredientItemProps) {
|
||||
const controls = useDragControls();
|
||||
|
||||
|
||||
// HANDLERS
|
||||
const changeHandler = (name: "Amount" | "Unit" | "Name", value: string) => {
|
||||
if (!dirty) setDirty(true);
|
||||
if (!dirty) markDirty(ingredient.Id);
|
||||
onChange(ingredient.Id, name, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let _valid = true;
|
||||
if (ingredient.Name.trim() === "") _valid = false;
|
||||
if (ingredient.Unit === "") _valid = false;
|
||||
if (ingredient.Amount <= 0) _valid = false;
|
||||
setValid(_valid);
|
||||
}, [ingredient]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("@dirty", dirty);
|
||||
}, [dirty]);
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
key={ingredient.Id}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Reorder } from "motion/react";
|
||||
import type { RecipeIngredient, RecipeIngredientSection } from "../../types/recipe";
|
||||
import IngredientItem from "./IngredientItem";
|
||||
import type { RecipeValidationEntry } from "../../pages/Create";
|
||||
|
||||
interface IngredientListProps {
|
||||
classes: string;
|
||||
@ -9,9 +10,12 @@ interface IngredientListProps {
|
||||
setSectionIngredients: (sectionId: string, ingredients: RecipeIngredient[]) => void;
|
||||
ingredientChangeHandler: (id: string, name: "Amount" | "Unit" | "Name", value: string) => void;
|
||||
removeIngredientHandler: (id: string) => void;
|
||||
validList: RecipeValidationEntry[];
|
||||
dirtyList: Record<string, boolean>;
|
||||
markDirty: (id: string) => void;
|
||||
}
|
||||
|
||||
export default function IngredientList({ classes, section, ingredients, setSectionIngredients, ingredientChangeHandler, removeIngredientHandler }: IngredientListProps) {
|
||||
export default function IngredientList({ classes, section, ingredients, setSectionIngredients, ingredientChangeHandler, removeIngredientHandler, validList, dirtyList, markDirty }: IngredientListProps) {
|
||||
const sectionIngredients = ingredients.filter(x => x.SectionId === section.Id);
|
||||
|
||||
const reorderHandler = (ingredients: RecipeIngredient[]) => {
|
||||
@ -25,14 +29,17 @@ export default function IngredientList({ classes, section, ingredients, setSecti
|
||||
onReorder={reorderHandler}
|
||||
className="flex flex-col"
|
||||
>
|
||||
{sectionIngredients.map(ing =>
|
||||
{sectionIngredients.map(ingredient =>
|
||||
<IngredientItem
|
||||
key={ing.Id}
|
||||
key={ingredient.Id}
|
||||
classes={classes}
|
||||
ingredient={ing}
|
||||
ingredient={ingredient}
|
||||
onChange={ingredientChangeHandler}
|
||||
removeIngredientHandler={removeIngredientHandler}
|
||||
allowDelete={sectionIngredients.length > 1}
|
||||
valid={validList.find(x => x.id === ingredient.Id)?.valid ?? true}
|
||||
dirty={dirtyList[ingredient.Id] ?? false}
|
||||
markDirty={markDirty}
|
||||
/>
|
||||
)}
|
||||
</Reorder.Group>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, type ChangeEvent } from "react";
|
||||
import { type ChangeEvent } from "react";
|
||||
import type { RecipeInstruction } from "../../types/recipe";
|
||||
import { Reorder, useDragControls } from "motion/react";
|
||||
import DragIconSmall from "../icons/DragIconSmall";
|
||||
@ -10,28 +10,19 @@ interface InstructionElementProps {
|
||||
allowDelete: boolean;
|
||||
onChange: (id: string, value: string) => void;
|
||||
onDelete: (id: string) => void;
|
||||
valid: boolean;
|
||||
dirty: boolean;
|
||||
markDirty: (id: string) => void;
|
||||
}
|
||||
|
||||
export default function InstructionElement({ instruction, index, allowDelete, onChange, onDelete }: InstructionElementProps) {
|
||||
export default function InstructionElement({ instruction, index, allowDelete, onChange, onDelete, valid, dirty, markDirty }: InstructionElementProps) {
|
||||
const controls = useDragControls();
|
||||
|
||||
const [valid, setValid] = useState<boolean>(true);
|
||||
const [dirty, setDirty] = useState<boolean>(false);
|
||||
|
||||
// HANDLERS
|
||||
const changeHandler = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
// No need to set many times
|
||||
if (!dirty) setDirty(true);
|
||||
|
||||
if (!dirty) markDirty(instruction.Id)
|
||||
onChange(instruction.Id, e.target.value);
|
||||
}
|
||||
|
||||
// EFFECTS
|
||||
useEffect(() => {
|
||||
if (dirty)
|
||||
setValid(instruction.Content !== "");
|
||||
}, [dirty, instruction]);
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
value={instruction}
|
||||
@ -43,7 +34,7 @@ export default function InstructionElement({ instruction, index, allowDelete, on
|
||||
<h2 className="text-lg md:text-xl mr-4 text-gray-500">{index + 1}.</h2>
|
||||
<div className="flex flex-col flex-grow">
|
||||
<textarea
|
||||
className="flex-grow border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all min-h-40 md:min-h-26 shadow-sm"
|
||||
className={`flex-grow border border-gray-300 px-4 py-2 rounded-lg focus:outline-none focus:ring-blue-500 focus:ring-2 duration-200 ease-in-out transition-all min-h-40 md:min-h-26 shadow-sm ${!valid && dirty ? "border-red-500" : ""}`}
|
||||
name="instructions"
|
||||
value={instruction.Content}
|
||||
onChange={changeHandler}
|
||||
@ -52,7 +43,7 @@ export default function InstructionElement({ instruction, index, allowDelete, on
|
||||
minLength={1}
|
||||
placeholder="Describe this step..."
|
||||
/>
|
||||
{!valid && (
|
||||
{(!valid && dirty) && (
|
||||
<p className="text-xs text-red-500 my-1">
|
||||
Please enter an instruction (blank entries are not allowed).
|
||||
</p>
|
||||
|
||||
@ -2,14 +2,18 @@ import { Reorder } from "motion/react";
|
||||
import InstructionElement from "./InstructionElement";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import type { RecipeInstruction } from "../../types/recipe";
|
||||
import type { RecipeValidationEntry } from "../../pages/Create";
|
||||
|
||||
|
||||
interface InstructionListProps {
|
||||
instructions: RecipeInstruction[];
|
||||
setInstructions: Dispatch<SetStateAction<RecipeInstruction[]>>;
|
||||
validList: RecipeValidationEntry[];
|
||||
dirtyList: Record<string, boolean>;
|
||||
markDirty: (id: string) => void;
|
||||
}
|
||||
|
||||
export default function InstructionList({ instructions, setInstructions }: InstructionListProps) {
|
||||
export default function InstructionList({ instructions, setInstructions, validList, dirtyList, markDirty }: InstructionListProps) {
|
||||
const handleChange = (id: string, value: string) => {
|
||||
setInstructions(prev =>
|
||||
prev.map(instr =>
|
||||
@ -39,6 +43,9 @@ export default function InstructionList({ instructions, setInstructions }: Instr
|
||||
allowDelete={instructions.length > 1}
|
||||
onChange={handleChange}
|
||||
onDelete={handleDelete}
|
||||
valid={validList.find(x => x.id === instruction.Id)?.valid ?? true}
|
||||
dirty={dirtyList[instruction.Id] ?? false}
|
||||
markDirty={markDirty}
|
||||
/>
|
||||
))}
|
||||
</Reorder.Group>
|
||||
|
||||
@ -29,6 +29,16 @@ export default function ValidationErrorList({ validation }: ValidationErrorListP
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
{validation.ingredients.filter(x => !x.valid).length > 0 && (
|
||||
<p className="text-sm text-red-500">
|
||||
{MESSAGES.ingredients}
|
||||
</p>
|
||||
)}
|
||||
{validation.instructions.filter(x => !x.valid).length > 0 && (
|
||||
<p className="text-sm text-red-500">
|
||||
{MESSAGES.instructions}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, type ChangeEvent, type Dispatch, type SetStateAction } from "react";
|
||||
import type { CreateRecipeFormEntries } from "../../pages/Create";
|
||||
import { type ChangeEvent, type Dispatch, type SetStateAction } from "react";
|
||||
import type { CreateRecipeFormDirtyEntries } from "../../pages/Create";
|
||||
|
||||
export interface RecipeCreateDropdownOption {
|
||||
value: string;
|
||||
@ -14,7 +14,7 @@ interface RecipeCreateFormDropdownProps {
|
||||
valid: boolean;
|
||||
value: string;
|
||||
setValue: Dispatch<SetStateAction<string>>;
|
||||
setDirty: Dispatch<SetStateAction<CreateRecipeFormEntries>>;
|
||||
setDirty: Dispatch<SetStateAction<CreateRecipeFormDirtyEntries>>;
|
||||
options: RecipeCreateDropdownOption[];
|
||||
error: string;
|
||||
parentClasses?: string;
|
||||
@ -27,10 +27,6 @@ export default function RecipeCreateDropdownInput({ label, name, desc, required
|
||||
setValue(e.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.debug(`@${name}`, value);
|
||||
}, [name, value]);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col ${parentClasses}`}>
|
||||
<label htmlFor={name} className="text-sm">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, type ChangeEvent, type Dispatch, type InputHTMLAttributes, type SetStateAction } from "react";
|
||||
import type { CreateRecipeFormEntries } from "../../pages/Create";
|
||||
import type { CreateRecipeFormDirtyEntries } from "../../pages/Create";
|
||||
|
||||
interface RecipeCreateFormInputProps
|
||||
extends Omit<
|
||||
@ -15,23 +15,18 @@ interface RecipeCreateFormInputProps
|
||||
valid: boolean;
|
||||
value: string;
|
||||
setValue: Dispatch<SetStateAction<string>>;
|
||||
setDirty: Dispatch<SetStateAction<CreateRecipeFormEntries>>;
|
||||
setDirty: Dispatch<SetStateAction<CreateRecipeFormDirtyEntries>>;
|
||||
error: string;
|
||||
parentClasses?: string;
|
||||
classes: string;
|
||||
};
|
||||
|
||||
export default function RecipeCreateFormInput({ label, name, desc, placeholder, type = "text", required = false, valid, value, setDirty, setValue, error, parentClasses = "", classes, ...inputProps }: RecipeCreateFormInputProps) {
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setDirty(prev => ({...prev, [name]: true}));
|
||||
setDirty(prev => ({ ...prev, [name]: true }));
|
||||
setValue(e.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.debug(`@${name}`, value);
|
||||
}, [name, value]);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col ${parentClasses}`}>
|
||||
<label htmlFor={name} className="text-sm">
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect, type ChangeEvent, type Dispatch, type SetStateAction, type TextareaHTMLAttributes } from "react";
|
||||
import type { CreateRecipeFormEntries } from "../../pages/Create";
|
||||
import { type ChangeEvent, type Dispatch, type SetStateAction, type TextareaHTMLAttributes } from "react";
|
||||
import type { CreateRecipeFormDirtyEntries } from "../../pages/Create";
|
||||
|
||||
interface RecipeCreateFormInputProps
|
||||
extends Omit<
|
||||
@ -14,7 +14,7 @@ interface RecipeCreateFormInputProps
|
||||
valid: boolean;
|
||||
value: string;
|
||||
setValue: Dispatch<SetStateAction<string>>;
|
||||
setDirty: Dispatch<SetStateAction<CreateRecipeFormEntries>>;
|
||||
setDirty: Dispatch<SetStateAction<CreateRecipeFormDirtyEntries>>;
|
||||
error: string;
|
||||
parentClasses?: string;
|
||||
classes: string;
|
||||
@ -26,10 +26,6 @@ export default function RecipeCreateFormTextArea({ label, name, desc, placeholde
|
||||
setValue(e.target.value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.debug(`@${name}`, value);
|
||||
}, [name, value]);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col ${parentClasses}`}>
|
||||
<label htmlFor={name} className="text-sm">
|
||||
|
||||
@ -46,7 +46,7 @@ export function useIngredients() {
|
||||
const ingredientChange = (id: string, name: "Amount" | "Unit" | "Name", value: string) => {
|
||||
setIngredients(prev =>
|
||||
prev.map(ing =>
|
||||
ing.Id === id
|
||||
ing.Id === id
|
||||
? { ...ing, [name]: name === "Amount" ? Number(value) : value }
|
||||
: ing
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { CreateRecipeFormEntries } from "../pages/Create";
|
||||
import { isRecipeMeal } from "../types/recipe";
|
||||
import type { CreateRecipeFormDirtyEntries, CreateRecipeFormEntries } from "../pages/Create";
|
||||
import { isRecipeMeal, type RecipeIngredient, type RecipeInstruction } from "../types/recipe";
|
||||
|
||||
export interface CreateRecipeFormValues {
|
||||
title: string;
|
||||
@ -9,11 +9,13 @@ export interface CreateRecipeFormValues {
|
||||
servingSize: string;
|
||||
category: string;
|
||||
difficulty: string;
|
||||
ingredients: RecipeIngredient[];
|
||||
instructions: RecipeInstruction[];
|
||||
}
|
||||
|
||||
export function validateCreateRecipeForm(values: CreateRecipeFormValues, dirty: CreateRecipeFormEntries): CreateRecipeFormEntries {
|
||||
return {
|
||||
title: dirty.title
|
||||
export function validateCreateRecipeForm(values: CreateRecipeFormValues, dirty: CreateRecipeFormDirtyEntries): CreateRecipeFormEntries {
|
||||
return {
|
||||
title: dirty.title
|
||||
? values.title.length >= 1 && values.title.length <= 128
|
||||
: true,
|
||||
description: dirty.description
|
||||
@ -21,28 +23,45 @@ export function validateCreateRecipeForm(values: CreateRecipeFormValues, dirty:
|
||||
: true,
|
||||
prepTime: dirty.prepTime
|
||||
? values.prepTime !== "" &&
|
||||
Number(values.prepTime) >= 0 &&
|
||||
Number(values.prepTime) <= 300
|
||||
Number(values.prepTime) >= 0 &&
|
||||
Number(values.prepTime) <= 300
|
||||
: true,
|
||||
cookTime: dirty.cookTime
|
||||
? values.cookTime !== "" &&
|
||||
Number(values.cookTime) >= 0 &&
|
||||
Number(values.cookTime) <= 300
|
||||
Number(values.cookTime) >= 0 &&
|
||||
Number(values.cookTime) <= 300
|
||||
: true,
|
||||
servingSize: dirty.servingSize
|
||||
? values.servingSize !== "" &&
|
||||
Number(values.servingSize) >= 1 &&
|
||||
Number(values.servingSize) <= 16
|
||||
Number(values.servingSize) >= 1 &&
|
||||
Number(values.servingSize) <= 16
|
||||
: true,
|
||||
category: dirty.category
|
||||
? values.category !== "" && isRecipeMeal(values.category)
|
||||
: true,
|
||||
difficulty: dirty.difficulty
|
||||
? values.difficulty !== "" &&
|
||||
Number(values.difficulty) >= 1 &&
|
||||
Number(values.difficulty) <= 5
|
||||
Number(values.difficulty) >= 1 &&
|
||||
Number(values.difficulty) <= 5
|
||||
: true,
|
||||
ingredients: true,
|
||||
instructions: true,
|
||||
ingredients: values.ingredients.map(ingredient => {
|
||||
if (!dirty.ingredients[ingredient.Id]) {
|
||||
return { id: ingredient.Id, valid: true };
|
||||
}
|
||||
let valid = true;
|
||||
if (ingredient.Name.trim() === "") valid = false;
|
||||
if (ingredient.Unit === "") valid = false;
|
||||
if (ingredient.Amount <= 0) valid = false;
|
||||
return { id: ingredient.Id, valid };
|
||||
}),
|
||||
instructions: values.instructions.map(instruction => {
|
||||
if (!dirty.instructions[instruction.Id]) {
|
||||
return { id: instruction.Id, valid: true }
|
||||
}
|
||||
return {
|
||||
id: instruction.Id,
|
||||
valid: instruction.Content.trim() !== ""
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,7 +14,12 @@ import RecipeCreateFormTagsInputs from "../components/inputs/RecipeCreateFormTag
|
||||
import { useIngredients } from "../hooks/useIngredients";
|
||||
import { validateCreateRecipeForm } from "../hooks/validation";
|
||||
|
||||
// TODO: Move this
|
||||
// TODO: Move these
|
||||
export interface RecipeValidationEntry {
|
||||
id: string;
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
export interface CreateRecipeFormEntries {
|
||||
title: boolean;
|
||||
description: boolean;
|
||||
@ -23,8 +28,21 @@ export interface CreateRecipeFormEntries {
|
||||
servingSize: boolean;
|
||||
category: boolean;
|
||||
difficulty: boolean;
|
||||
ingredients: boolean;
|
||||
instructions: boolean;
|
||||
ingredients: RecipeValidationEntry[];
|
||||
instructions: RecipeValidationEntry[];
|
||||
// TODO: Image
|
||||
}
|
||||
|
||||
export interface CreateRecipeFormDirtyEntries {
|
||||
title: boolean;
|
||||
description: boolean;
|
||||
prepTime: boolean;
|
||||
cookTime: boolean;
|
||||
servingSize: boolean;
|
||||
category: boolean;
|
||||
difficulty: boolean;
|
||||
ingredients: Record<string, boolean>;
|
||||
instructions: Record<string, boolean>;
|
||||
// TODO: Image
|
||||
}
|
||||
|
||||
@ -82,13 +100,13 @@ export default function Create() {
|
||||
servingSize: true,
|
||||
category: true,
|
||||
difficulty: true,
|
||||
ingredients: true,
|
||||
instructions: true,
|
||||
ingredients: [],
|
||||
instructions: [],
|
||||
});
|
||||
const [isFormValid, setIsFormValid] = useState<boolean>(false);
|
||||
|
||||
// Dirty State
|
||||
const [dirty, setDirty] = useState<CreateRecipeFormEntries>({
|
||||
const [dirty, setDirty] = useState<CreateRecipeFormDirtyEntries>({
|
||||
title: false,
|
||||
description: false,
|
||||
prepTime: false,
|
||||
@ -96,8 +114,8 @@ export default function Create() {
|
||||
servingSize: false,
|
||||
category: false,
|
||||
difficulty: false,
|
||||
ingredients: true, // Can this be ignored since they're self contained?
|
||||
instructions: true, // Can this be ignored since they're self contained?
|
||||
ingredients: {},
|
||||
instructions: {},
|
||||
});
|
||||
|
||||
// Import ingredients
|
||||
@ -119,22 +137,90 @@ export default function Create() {
|
||||
setInstructions([...instructions, { Id: crypto.randomUUID(), Content: "" }]);
|
||||
}
|
||||
|
||||
// Dirty handlers
|
||||
const markInstructionDirty = (id: string) => {
|
||||
setDirty(prev => ({
|
||||
...prev,
|
||||
instructions: {
|
||||
...prev.instructions,
|
||||
[id]: true,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const markIngredientDirty = (id: string) => {
|
||||
setDirty(prev => ({
|
||||
...prev,
|
||||
ingredients: {
|
||||
...prev.ingredients,
|
||||
[id]: true,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const markAllIngredientsDirty = (): Record<string, boolean> => {
|
||||
const all: Record<string, boolean> = {};
|
||||
for (const ing of ingredients) all[ing.Id] = true;
|
||||
return all;
|
||||
};
|
||||
|
||||
const markAllInstructionsDirty = (): Record<string, boolean> => {
|
||||
const all: Record<string, boolean> = {};
|
||||
for (const instr of instructions) all[instr.Id] = true;
|
||||
return all;
|
||||
};
|
||||
|
||||
// HANDLERS
|
||||
const submitHandler = () => {
|
||||
// If any inputs are not dirty, simply dirty them all and return
|
||||
const scalar_dirty = [
|
||||
dirty.title,
|
||||
dirty.description,
|
||||
dirty.prepTime,
|
||||
dirty.cookTime,
|
||||
dirty.servingSize,
|
||||
dirty.category,
|
||||
dirty.difficulty,
|
||||
];
|
||||
const ingredients_dirty = Object.values(dirty.ingredients).every(Boolean);
|
||||
const instructions_dirty = Object.values(dirty.instructions).every(Boolean);
|
||||
|
||||
const all_dirty = scalar_dirty.every(Boolean) && ingredients_dirty && instructions_dirty;
|
||||
|
||||
if (!all_dirty) {
|
||||
setDirty({
|
||||
title: true,
|
||||
description: true,
|
||||
prepTime: true,
|
||||
cookTime: true,
|
||||
servingSize: true,
|
||||
category: true,
|
||||
difficulty: true,
|
||||
ingredients: markAllIngredientsDirty(),
|
||||
instructions: markAllInstructionsDirty(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// EFFECTS
|
||||
useEffect(() => {
|
||||
// Execute validation every time inputs change
|
||||
setValidation(
|
||||
validateCreateRecipeForm(
|
||||
{ title, description, prepTime, cookTime, servingSize, category, difficulty },
|
||||
{ title, description, prepTime, cookTime, servingSize, category, difficulty, ingredients, instructions },
|
||||
dirty
|
||||
)
|
||||
);
|
||||
}, [title, description, prepTime, cookTime, servingSize, category, difficulty, instructions, ingredients, dirty]);
|
||||
|
||||
useEffect(() => {
|
||||
// The form is only valid when every item has been touched, and every item is valid!
|
||||
const allValid = Object.values(validation).every(x => x === true);
|
||||
const allDirty = Object.values(dirty).every(x => x === true);
|
||||
setIsFormValid(allValid && allDirty);
|
||||
// The form is only valid when every item is valid!
|
||||
const bools_valid = Object.values(validation).filter(x => typeof x === "boolean").every(x => x);
|
||||
const ingredients_valid = validation.ingredients.filter(x => !x.valid).length === 0;
|
||||
const instructions_valid = validation.instructions.filter(x => !x.valid).length === 0;
|
||||
setIsFormValid(bools_valid && ingredients_valid && instructions_valid);
|
||||
}, [validation, dirty]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -291,7 +377,7 @@ export default function Create() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* TODO: Ingredient Inputs */}
|
||||
{/* Ingredient Inputs */}
|
||||
<RecipeCreateFormWrapper
|
||||
label="Ingredients"
|
||||
name="ingredients"
|
||||
@ -320,6 +406,9 @@ export default function Create() {
|
||||
setSectionIngredients={setSectionIngredients}
|
||||
ingredientChangeHandler={ingredientChange}
|
||||
removeIngredientHandler={removeIngredient}
|
||||
validList={validation.ingredients}
|
||||
dirtyList={dirty.ingredients}
|
||||
markDirty={markIngredientDirty}
|
||||
/>
|
||||
<button
|
||||
onClick={() => addIngredient(section.Id)}
|
||||
@ -350,6 +439,9 @@ export default function Create() {
|
||||
<InstructionList
|
||||
instructions={instructions}
|
||||
setInstructions={setInstructions}
|
||||
validList={validation.instructions}
|
||||
dirtyList={dirty.instructions}
|
||||
markDirty={markInstructionDirty}
|
||||
/>
|
||||
<button
|
||||
onClick={addInstructionHandler}
|
||||
@ -380,7 +472,11 @@ export default function Create() {
|
||||
{/* Display the reason for the invalidation */}
|
||||
<ValidationErrorList validation={validation} />
|
||||
|
||||
<button disabled={!isFormValid} className={`${isFormValid ? "bg-gradient-to-r from-blue-200 to-purple-200 cursor-pointer" : "bg-gray-200 text-gray-500 cursor-not-allowed"} w-full py-2 rounded-lg text-lg shadow-md`}>
|
||||
<button
|
||||
onClick={submitHandler}
|
||||
disabled={!isFormValid}
|
||||
className={`${isFormValid ? "bg-gradient-to-r from-blue-200 to-purple-200 cursor-pointer" : "bg-gray-200 text-gray-500 cursor-not-allowed"} w-full py-2 rounded-lg text-lg shadow-md`}
|
||||
>
|
||||
Create Recipe
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user