Skip to content

Commit

Permalink
Merge pull request #34 from neuefische/33-disable-add-button-until-in…
Browse files Browse the repository at this point in the history
…put-is-validated-in-frontend

33 disable add button until input is validated in frontend
  • Loading branch information
josch87 authored Jun 10, 2024
2 parents fdc64f0 + c08be2c commit cef636a
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 28 deletions.
11 changes: 9 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"@logtail/browser": "^0.4.21",
"axios": "^1.7.2",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^5.2.1",
Expand All @@ -29,6 +30,7 @@
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.5",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/styled-components": "^5.1.34",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import RestaurantForm from "./RestaurantForm.tsx";
import { MemoryRouter } from "react-router-dom";
import { NewRestaurantDTOType } from "../../model/Restaurant.ts";

const initialFormData: NewRestaurantDTOType = { title: "", city: "" };

test('RestaurantForm component displays label "title"', () => {
render(
<MemoryRouter>
<RestaurantForm onSubmit={jest.fn()} restaurantData={null} />
<RestaurantForm
onSubmit={jest.fn()}
initialFormData={initialFormData}
buttonText="Create"
/>
</MemoryRouter>
);
const titleInput = screen.getByLabelText("Title");
Expand All @@ -16,7 +23,11 @@ test('RestaurantForm component displays label "title"', () => {
test('RestaurantForm component displays input field "title"', () => {
render(
<MemoryRouter>
<RestaurantForm onSubmit={jest.fn()} restaurantData={null} />
<RestaurantForm
onSubmit={jest.fn()}
initialFormData={initialFormData}
buttonText="Create"
/>
</MemoryRouter>
);
const titleInput = screen.getByRole("textbox", {
Expand All @@ -28,7 +39,11 @@ test('RestaurantForm component displays input field "title"', () => {
test('RestaurantForm component displays label "city"', () => {
render(
<MemoryRouter>
<RestaurantForm onSubmit={jest.fn()} restaurantData={null} />
<RestaurantForm
onSubmit={jest.fn()}
initialFormData={initialFormData}
buttonText="Create"
/>
</MemoryRouter>
);
const titleLabel = screen.getByLabelText("City");
Expand All @@ -38,7 +53,11 @@ test('RestaurantForm component displays label "city"', () => {
test('RestaurantForm component displays input field "city"', () => {
render(
<MemoryRouter>
<RestaurantForm onSubmit={jest.fn()} restaurantData={null} />
<RestaurantForm
onSubmit={jest.fn()}
initialFormData={initialFormData}
buttonText="Create"
/>
</MemoryRouter>
);
const cityInput = screen.getByRole("textbox", {
Expand All @@ -47,14 +66,34 @@ test('RestaurantForm component displays input field "city"', () => {
expect(cityInput).toBeInTheDocument();
});

test('RestaurantForm component displays "save" button', () => {
test('RestaurantForm component displays "create" button', () => {
render(
<MemoryRouter>
<RestaurantForm
onSubmit={jest.fn()}
initialFormData={initialFormData}
buttonText="Create"
/>
</MemoryRouter>
);
const saveButton = screen.getByRole("button", {
name: /create/i,
});
expect(saveButton).toBeInTheDocument();
});

test('RestaurantForm component displays "create" button', () => {
render(
<MemoryRouter>
<RestaurantForm onSubmit={jest.fn()} restaurantData={null} />
<RestaurantForm
onSubmit={jest.fn()}
initialFormData={initialFormData}
buttonText="Update"
/>
</MemoryRouter>
);
const saveButton = screen.getByRole("button", {
name: /save/i,
name: /update/i,
});
expect(saveButton).toBeInTheDocument();
});
27 changes: 17 additions & 10 deletions frontend/src/components/RestaurantForm/RestaurantForm.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
import { ChangeEvent, useState } from "react";
import {
NewRestaurantDTOType,
RestaurantType,
} from "../../model/Restaurant.ts";
import { NewRestaurantDTOType } from "../../model/Restaurant.ts";
import {
StyledFieldError,
StyledForm,
StyledFormBody,
StyledFormRow,
StyledInputField,
} from "./RestaurantForm.styled.ts";
import _ from "lodash";

type RestaurantFormProps = {
restaurantData: RestaurantType | null;
onSubmit: (rg0: NewRestaurantDTOType) => void;
initialFormData: NewRestaurantDTOType;
buttonText: "Create" | "Update";
};

export default function RestaurantForm({
restaurantData,
onSubmit,
initialFormData,
buttonText,
}: Readonly<RestaurantFormProps>) {
const initialFieldValidation = {
title: "",
city: "",
};

const [formData, setFormData] = useState<NewRestaurantDTOType>(
restaurantData || { title: "", city: "" }
);
const [formData, setFormData] =
useState<NewRestaurantDTOType>(initialFormData);
const [fieldValidation, setFieldValidation] = useState<NewRestaurantDTOType>(
initialFieldValidation
);
Expand All @@ -45,6 +44,12 @@ export default function RestaurantForm({
}
}

const hasInputChanges = _.isEqual(formData, initialFormData);
const hasValidationErrors = _.isEqual(
fieldValidation,
initialFieldValidation
);

return (
<StyledForm
onSubmit={(event) => {
Expand Down Expand Up @@ -79,7 +84,9 @@ export default function RestaurantForm({
<StyledFieldError>{fieldValidation.city}</StyledFieldError>
</StyledFormRow>
</StyledFormBody>
<button type="submit">Save</button>
<button type="submit" disabled={hasInputChanges || !hasValidationErrors}>
{buttonText}
</button>
</StyledForm>
);
}
6 changes: 5 additions & 1 deletion frontend/src/pages/CreateRestaurantPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ export default function CreateRestaurantPage() {

return (
<DefaultPageTemplate pageTitle="New restaurant">
<RestaurantForm onSubmit={handleAddRestaurant} restaurantData={null} />
<RestaurantForm
onSubmit={handleAddRestaurant}
initialFormData={{ title: "", city: "" }}
buttonText="Create"
/>
</DefaultPageTemplate>
);
}
5 changes: 3 additions & 2 deletions frontend/src/pages/UpdateRestaurantPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default function UpdateRestaurantPage() {
}

function handleEditRestaurant(formData: NewRestaurantDTOType) {
logtail.info("Trying to update data for restaurant with ID " + id);
logtail.info(`Trying to update data for restaurant with ID ${id}`);

axios
.put(`/api/restaurants/${id}`, formData)
Expand All @@ -62,7 +62,8 @@ export default function UpdateRestaurantPage() {
<DefaultPageTemplate pageTitle="Edit restaurant">
<RestaurantForm
onSubmit={handleEditRestaurant}
restaurantData={restaurant}
initialFormData={restaurant}
buttonText="Update"
/>
</DefaultPageTemplate>
);
Expand Down
35 changes: 29 additions & 6 deletions frontend/src/pages/ViewRestaurantPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useRestaurant } from "../data/restaurantData.ts";
import { logtail } from "../logger.ts";
import AlertBox from "../components/AlertBox/AlertBox.tsx";
import { mutate } from "swr";
import { RestaurantType } from "../model/Restaurant.ts";

export default function ViewRestaurantPage() {
const navigate = useNavigate();
Expand Down Expand Up @@ -40,19 +41,41 @@ export default function ViewRestaurantPage() {
return <DefaultPageTemplate>Error</DefaultPageTemplate>;
}

function deleteRestaurantById() {
axios.delete(`/api/restaurants/${id}`).then(() => {
mutate("/api/restaurants");
navigate("/");
});
function deleteRestaurantById(id: string, restaurant: RestaurantType) {
const isConfirmed = confirm(
`Do you really want to delete "${restaurant.title}"?`
);

if (!isConfirmed) {
return;
}

logtail.info(`Trying to delete data for restaurant with ID ${id}`);

axios
.delete(`/api/restaurants/${id}`)
.then(() => {
logtail.info(`Deleted data of restaurant with ID ${id}`);
mutate("/api/restaurants");
navigate("/");
})
.catch((error) => {
logtail.error(error.message, {
error: error,
});
window.console.error(error.message);
});
}

return (
<DefaultPageTemplate pageTitle={restaurant.title}>
<p>{restaurant.city}</p>
<ButtonLink href={`/restaurants/edit/${id}`}>Edit</ButtonLink>
<ButtonLink href="/">Back</ButtonLink>
<Button buttonType="delete" handleOnClick={deleteRestaurantById}>
<Button
buttonType="delete"
handleOnClick={() => deleteRestaurantById(id, restaurant)}
>
Delete
</Button>
</DefaultPageTemplate>
Expand Down

0 comments on commit cef636a

Please sign in to comment.