From e07edbbe63261afdff7c7ac6750a4009bb94a0fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 18:14:23 +0200 Subject: [PATCH 01/11] Add SWR for data fetching and optimize RestaurantsPage SWR, a library for remote data fetching, has been added to the project dependencies. This has been specifically used to optimize data fetching in the RestaurantsPage, shifting the data fetch logic from a React useEffect hook to a custom hook `useRestaurants`, created using SWR. This change has led to cleaner, more efficient code in the RestaurantsPage component. --- frontend/package-lock.json | 28 ++++++++- frontend/package.json | 3 +- frontend/src/data/restaurantData.ts | 34 +++++++++++ .../pages/RestaurantsPage/RestaurantsPage.tsx | 57 ++++++++----------- 4 files changed, 86 insertions(+), 36 deletions(-) create mode 100644 frontend/src/data/restaurantData.ts diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 303beca..851346d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,8 @@ "react-dom": "^18.2.0", "react-icons": "^5.2.1", "react-router-dom": "^6.23.1", - "styled-components": "^6.1.11" + "styled-components": "^6.1.11", + "swr": "^2.2.5" }, "devDependencies": { "@babel/core": "^7.24.6", @@ -4851,6 +4852,11 @@ "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", "dev": true }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -9415,6 +9421,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -9683,6 +9701,14 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7064f6a..c614b20 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,8 @@ "react-dom": "^18.2.0", "react-icons": "^5.2.1", "react-router-dom": "^6.23.1", - "styled-components": "^6.1.11" + "styled-components": "^6.1.11", + "swr": "^2.2.5" }, "devDependencies": { "@babel/core": "^7.24.6", diff --git a/frontend/src/data/restaurantData.ts b/frontend/src/data/restaurantData.ts new file mode 100644 index 0000000..f4f1c45 --- /dev/null +++ b/frontend/src/data/restaurantData.ts @@ -0,0 +1,34 @@ +import useSWR from "swr"; +import { RestaurantType } from "../model/Restaurant.ts"; +import axios from "axios"; +import { logtail } from "../logger.ts"; + +const fetcher = (url: string) => { + logtail.info(`Trying to receive all restaurants from ${url}`); + + return axios + .get(url) + .then((response) => { + logtail.info("Received " + response.data.length + " restaurants"); + return response.data; + }) + .catch((error) => { + logtail.error(error.message, { + error: error, + }); + throw error; + }); +}; + +export function useRestaurants() { + const { data, error, isLoading } = useSWR( + "/api/restaurants", + fetcher + ); + + return { + restaurants: data, + isLoading, + isError: error, + }; +} diff --git a/frontend/src/pages/RestaurantsPage/RestaurantsPage.tsx b/frontend/src/pages/RestaurantsPage/RestaurantsPage.tsx index acca783..b4c5d4f 100644 --- a/frontend/src/pages/RestaurantsPage/RestaurantsPage.tsx +++ b/frontend/src/pages/RestaurantsPage/RestaurantsPage.tsx @@ -1,53 +1,42 @@ import RestaurantCardList from "../../components/RestaurantCardList/RestaurantCardList.tsx"; import DefaultPageTemplate from "../templates/DefaultPageTemplate/DefaultPageTemplate.tsx"; -import axios, { AxiosError } from "axios"; -import { useEffect, useState } from "react"; -import { RestaurantType } from "../../model/Restaurant.ts"; + import CreateDataInvitation from "../../components/CreateDataInvitation/CreateDataInvitation.tsx"; import { StyledErrorParagraph } from "./RestaurantsPage.styled.ts"; -import { logtail } from "../../logger.ts"; +import { useRestaurants } from "../../data/restaurantData.ts"; export default function RestaurantsPage() { - const [restaurants, setRestaurants] = useState([]); - const [error, setError] = useState(); - - useEffect(() => { - logtail.info("Trying to receive all restaurants from /api/restaurants"); + const { restaurants, isLoading, isError } = useRestaurants(); - axios - .get("/api/restaurants") - .then((response) => { - logtail.info("Received " + response.data.length + " restaurants"); - setRestaurants(response.data); - }) - .catch((error) => { - logtail.error(error.message, { - error: error, - }); - setError(error); - console.error(error.message); - }); - }, []); + if (isLoading) { + return ( + +

Restaurants are currently loading. Please wait.

+
+ ); + } - if (error) { + if (isError) { return (

Sorry, we encountered an error loading the restaurants. Please try again later.

- {error.message} + {isError.message}
); } - return ( - - {restaurants.length === 0 ? ( - - ) : ( - - )} - - ); + if (restaurants) { + return ( + + {restaurants.length === 0 ? ( + + ) : ( + + )} + + ); + } } From 8722ea48ca6ea5c44d6d4c71a0e45d2e4660c17b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 18:47:47 +0200 Subject: [PATCH 02/11] Refactor restaurant data fetch and error handling This commit refactors the way data is fetched for displaying and editing restaurants. Rather than fetching data directly from the RestaurantDetailsPage and RestaurantEditPage, a custom React hook useRestaurant from restaurantData.ts is now used. This improves code reusability and separation of concerns. Additionally, error messages have been improved to improve user experience in case of issues. --- frontend/src/data/restaurantData.ts | 66 ++++++++++++++------ frontend/src/pages/RestaurantDetailsPage.tsx | 51 ++++++++------- frontend/src/pages/RestaurantEditPage.tsx | 50 +++++++-------- 3 files changed, 98 insertions(+), 69 deletions(-) diff --git a/frontend/src/data/restaurantData.ts b/frontend/src/data/restaurantData.ts index f4f1c45..796b956 100644 --- a/frontend/src/data/restaurantData.ts +++ b/frontend/src/data/restaurantData.ts @@ -3,27 +3,25 @@ import { RestaurantType } from "../model/Restaurant.ts"; import axios from "axios"; import { logtail } from "../logger.ts"; -const fetcher = (url: string) => { - logtail.info(`Trying to receive all restaurants from ${url}`); - - return axios - .get(url) - .then((response) => { - logtail.info("Received " + response.data.length + " restaurants"); - return response.data; - }) - .catch((error) => { - logtail.error(error.message, { - error: error, - }); - throw error; - }); -}; - export function useRestaurants() { const { data, error, isLoading } = useSWR( "/api/restaurants", - fetcher + (url: string) => { + logtail.info(`Trying to receive all restaurants from ${url}`); + + return axios + .get(url) + .then((response) => { + logtail.info("Received " + response.data.length + " restaurants"); + return response.data; + }) + .catch((error) => { + logtail.error(error.message, { + error: error, + }); + throw error; + }); + } ); return { @@ -32,3 +30,35 @@ export function useRestaurants() { isError: error, }; } + +export function useRestaurant(id: string) { + if (!id) { + throw new Error("Restaurant ID is required!"); + } + + const { data, error, isLoading } = useSWR( + `/api/restaurants/${id}`, + (url: string) => { + logtail.info(`Trying to receive restaurant from ${url}`); + + return axios + .get(url) + .then((response) => { + logtail.info(`Received restaurant with ID ${response.data.id}`); + return response.data; + }) + .catch((error) => { + logtail.error(error.message, { + error: error, + }); + throw error; + }); + } + ); + + return { + restaurant: data, + isLoading, + isError: error, + }; +} diff --git a/frontend/src/pages/RestaurantDetailsPage.tsx b/frontend/src/pages/RestaurantDetailsPage.tsx index 45eb672..388748d 100644 --- a/frontend/src/pages/RestaurantDetailsPage.tsx +++ b/frontend/src/pages/RestaurantDetailsPage.tsx @@ -1,43 +1,42 @@ -import { useEffect, useState } from "react"; import { useNavigate, useParams } from "react-router-dom"; import DefaultPageTemplate from "./templates/DefaultPageTemplate/DefaultPageTemplate.tsx"; import axios from "axios"; -import { RestaurantType } from "../model/Restaurant.ts"; import Button from "../components/Button/Button.tsx"; import ButtonLink from "../components/ButtonLink/ButtonLink.tsx"; +import { useRestaurant } from "../data/restaurantData.ts"; +import { StyledErrorParagraph } from "./RestaurantsPage/RestaurantsPage.styled.ts"; import { logtail } from "../logger.ts"; export default function RestaurantDetailsPage() { const navigate = useNavigate(); - const { id } = useParams<{ id: string }>(); - const [restaurant, setRestaurant] = useState(); - const [error, setError] = useState(null); - - useEffect(() => { - logtail.info("Trying to receive data for restaurant with ID " + id); - - axios - .get(`/api/restaurants/${id}`) - .then((response) => { - logtail.info("Received data of restaurant with ID " + id); - setRestaurant(response.data); - }) - .catch((error) => { - logtail.error(error.message, { - error: error, - }); - setError("There was an error fetching the restaurant details!"); - console.error(error); - }); - }, [id]); + const { id: paramId } = useParams<{ id: string }>(); + const id = paramId ?? ""; + const { restaurant, isLoading, isError } = useRestaurant(id); + + if (isLoading) { + return ( + +

Restaurant is currently loading. Please wait.

+
+ ); + } - if (error) { - return {error}; + if (isError) { + return ( + +

+ Sorry, we encountered an error loading the restaurants. Please try + again later. +

+ {isError.message} +
+ ); } if (!restaurant) { - return Loading...; + logtail.error(`There was an error displaying restaurant with ID ${id}`); + return Error; } function deleteRestaurantById() { diff --git a/frontend/src/pages/RestaurantEditPage.tsx b/frontend/src/pages/RestaurantEditPage.tsx index 3eeadb9..90fb90e 100644 --- a/frontend/src/pages/RestaurantEditPage.tsx +++ b/frontend/src/pages/RestaurantEditPage.tsx @@ -1,41 +1,41 @@ import { useNavigate, useParams } from "react-router-dom"; -import { useEffect, useState } from "react"; -import { NewRestaurantDTOType, RestaurantType } from "../model/Restaurant.ts"; +import { NewRestaurantDTOType } from "../model/Restaurant.ts"; import axios from "axios"; import DefaultPageTemplate from "./templates/DefaultPageTemplate/DefaultPageTemplate.tsx"; import RestaurantForm from "../components/RestaurantForm/RestaurantForm.tsx"; import { logtail } from "../logger.ts"; +import { useRestaurant } from "../data/restaurantData.ts"; +import { StyledErrorParagraph } from "./RestaurantsPage/RestaurantsPage.styled.ts"; export default function RestaurantEditPage() { const navigate = useNavigate(); - const { id } = useParams<{ id: string }>(); - const [restaurant, setRestaurant] = useState(); - const [error, setError] = useState(null); + const { id: paramId } = useParams<{ id: string }>(); + const id = paramId ?? ""; + const { restaurant, isLoading, isError } = useRestaurant(id); - useEffect(() => { - logtail.info("Trying to receive data for restaurant with ID " + id); - - axios - .get(`/api/restaurants/${id}`) - .then((response) => { - logtail.info("Received data of restaurant with ID " + id); - setRestaurant(response.data); - }) - .catch((error) => { - logtail.error(error.message, { - error: error, - }); - setError("There was an error fetching the restaurant details!"); - console.error(error); - }); - }, [id]); + if (isLoading) { + return ( + +

Restaurant is currently loading. Please wait.

+
+ ); + } - if (error) { - return {error}; + if (isError) { + return ( + +

+ Sorry, we encountered an error loading the restaurants. Please try + again later. +

+ {isError.message} +
+ ); } if (!restaurant) { - return Loading...; + logtail.error(`There was an error displaying restaurant with ID ${id}`); + return Error; } function handleEditRestaurant(formData: NewRestaurantDTOType) { From 5c15d9ce3f6899059779cdbcfe99c187af4367d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 18:56:51 +0200 Subject: [PATCH 03/11] Replace StyledErrorParagraph with AlertBox component This commit replaces the usage of StyledErrorParagraph with a unified AlertBox component across RestaurantsPage, RestaurantDetailsPage, and RestaurantEditPage. AlertBox is a new styled-components based component for displaying error messages, improving code reusability. Additionally, a typo in an API url has been corrected in restaurantData.ts. --- .../AlertBox/AlertBox.styled.ts} | 2 +- frontend/src/components/AlertBox/AlertBox.tsx | 10 ++++++++++ frontend/src/pages/RestaurantDetailsPage.tsx | 4 ++-- frontend/src/pages/RestaurantEditPage.tsx | 4 ++-- .../pages/{RestaurantsPage => }/RestaurantsPage.tsx | 4 ++-- 5 files changed, 17 insertions(+), 7 deletions(-) rename frontend/src/{pages/RestaurantsPage/RestaurantsPage.styled.ts => components/AlertBox/AlertBox.styled.ts} (70%) create mode 100644 frontend/src/components/AlertBox/AlertBox.tsx rename frontend/src/pages/{RestaurantsPage => }/RestaurantsPage.tsx (89%) diff --git a/frontend/src/pages/RestaurantsPage/RestaurantsPage.styled.ts b/frontend/src/components/AlertBox/AlertBox.styled.ts similarity index 70% rename from frontend/src/pages/RestaurantsPage/RestaurantsPage.styled.ts rename to frontend/src/components/AlertBox/AlertBox.styled.ts index 592946f..355897b 100644 --- a/frontend/src/pages/RestaurantsPage/RestaurantsPage.styled.ts +++ b/frontend/src/components/AlertBox/AlertBox.styled.ts @@ -1,6 +1,6 @@ import styled from "styled-components"; -export const StyledErrorParagraph = styled.p` +export const StyledErrorContainer = styled.div` font-size: 0.8rem; color: var(--error-color); margin-top: 1rem; diff --git a/frontend/src/components/AlertBox/AlertBox.tsx b/frontend/src/components/AlertBox/AlertBox.tsx new file mode 100644 index 0000000..6b97769 --- /dev/null +++ b/frontend/src/components/AlertBox/AlertBox.tsx @@ -0,0 +1,10 @@ +import { StyledErrorContainer } from "./AlertBox.styled.ts"; +import { ReactNode } from "react"; + +type AlertBoxProps = { + children: ReactNode; +}; + +export default function AlertBox({ children }: Readonly) { + return {children}; +} diff --git a/frontend/src/pages/RestaurantDetailsPage.tsx b/frontend/src/pages/RestaurantDetailsPage.tsx index 388748d..c15c54a 100644 --- a/frontend/src/pages/RestaurantDetailsPage.tsx +++ b/frontend/src/pages/RestaurantDetailsPage.tsx @@ -5,8 +5,8 @@ import axios from "axios"; import Button from "../components/Button/Button.tsx"; import ButtonLink from "../components/ButtonLink/ButtonLink.tsx"; import { useRestaurant } from "../data/restaurantData.ts"; -import { StyledErrorParagraph } from "./RestaurantsPage/RestaurantsPage.styled.ts"; import { logtail } from "../logger.ts"; +import AlertBox from "../components/AlertBox/AlertBox.tsx"; export default function RestaurantDetailsPage() { const navigate = useNavigate(); @@ -29,7 +29,7 @@ export default function RestaurantDetailsPage() { Sorry, we encountered an error loading the restaurants. Please try again later.

- {isError.message} + {isError.message} ); } diff --git a/frontend/src/pages/RestaurantEditPage.tsx b/frontend/src/pages/RestaurantEditPage.tsx index 90fb90e..b8427f3 100644 --- a/frontend/src/pages/RestaurantEditPage.tsx +++ b/frontend/src/pages/RestaurantEditPage.tsx @@ -5,7 +5,7 @@ import DefaultPageTemplate from "./templates/DefaultPageTemplate/DefaultPageTemp import RestaurantForm from "../components/RestaurantForm/RestaurantForm.tsx"; import { logtail } from "../logger.ts"; import { useRestaurant } from "../data/restaurantData.ts"; -import { StyledErrorParagraph } from "./RestaurantsPage/RestaurantsPage.styled.ts"; +import AlertBox from "../components/AlertBox/AlertBox.tsx"; export default function RestaurantEditPage() { const navigate = useNavigate(); @@ -28,7 +28,7 @@ export default function RestaurantEditPage() { Sorry, we encountered an error loading the restaurants. Please try again later.

- {isError.message} + {isError.message} ); } diff --git a/frontend/src/pages/RestaurantsPage/RestaurantsPage.tsx b/frontend/src/pages/RestaurantsPage.tsx similarity index 89% rename from frontend/src/pages/RestaurantsPage/RestaurantsPage.tsx rename to frontend/src/pages/RestaurantsPage.tsx index b4c5d4f..5409288 100644 --- a/frontend/src/pages/RestaurantsPage/RestaurantsPage.tsx +++ b/frontend/src/pages/RestaurantsPage.tsx @@ -2,8 +2,8 @@ import RestaurantCardList from "../../components/RestaurantCardList/RestaurantCa import DefaultPageTemplate from "../templates/DefaultPageTemplate/DefaultPageTemplate.tsx"; import CreateDataInvitation from "../../components/CreateDataInvitation/CreateDataInvitation.tsx"; -import { StyledErrorParagraph } from "./RestaurantsPage.styled.ts"; import { useRestaurants } from "../../data/restaurantData.ts"; +import AlertBox from "../../components/AlertBox/AlertBox.tsx"; export default function RestaurantsPage() { const { restaurants, isLoading, isError } = useRestaurants(); @@ -23,7 +23,7 @@ export default function RestaurantsPage() { Sorry, we encountered an error loading the restaurants. Please try again later.

- {isError.message} + {isError.message} ); } From 8d658f0f2a331b636ecb5d79bcf66f55cc496e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 19:09:06 +0200 Subject: [PATCH 04/11] Update data after restaurant deletion The commit modifies the deleteRestaurantById function in RestaurantDetailsPage to trigger a SWR mutate call after a restaurant is deleted. This ensures data gets updated in the RestaurantsPage. Also, the file import paths across various files have been restructured for a cleaner hierarchy. --- frontend/src/App.tsx | 2 +- frontend/src/pages/RestaurantDetailsPage.tsx | 6 +++++- frontend/src/pages/RestaurantsPage.tsx | 11 +++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6ef698a..fdf4639 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,9 @@ -import RestaurantsPage from "./pages/RestaurantsPage/RestaurantsPage.tsx"; import RestaurantDetailsPage from "./pages/RestaurantDetailsPage.tsx"; import { Route, Routes } from "react-router-dom"; import AddRestaurantsPage from "./pages/AddRestaurantsPage.tsx"; import RestaurantEditPage from "./pages/RestaurantEditPage.tsx"; import "./App.css"; +import RestaurantsPage from "./pages/RestaurantsPage.tsx"; function App() { return ( diff --git a/frontend/src/pages/RestaurantDetailsPage.tsx b/frontend/src/pages/RestaurantDetailsPage.tsx index c15c54a..9801103 100644 --- a/frontend/src/pages/RestaurantDetailsPage.tsx +++ b/frontend/src/pages/RestaurantDetailsPage.tsx @@ -7,6 +7,7 @@ import ButtonLink from "../components/ButtonLink/ButtonLink.tsx"; import { useRestaurant } from "../data/restaurantData.ts"; import { logtail } from "../logger.ts"; import AlertBox from "../components/AlertBox/AlertBox.tsx"; +import { mutate } from "swr"; export default function RestaurantDetailsPage() { const navigate = useNavigate(); @@ -40,7 +41,10 @@ export default function RestaurantDetailsPage() { } function deleteRestaurantById() { - axios.delete(`/api/restaurants/${id}`).then(() => navigate("/")); + axios.delete(`/api/restaurants/${id}`).then(() => { + mutate("/api/restaurants"); + navigate("/"); + }); } return ( diff --git a/frontend/src/pages/RestaurantsPage.tsx b/frontend/src/pages/RestaurantsPage.tsx index 5409288..5a34da8 100644 --- a/frontend/src/pages/RestaurantsPage.tsx +++ b/frontend/src/pages/RestaurantsPage.tsx @@ -1,9 +1,8 @@ -import RestaurantCardList from "../../components/RestaurantCardList/RestaurantCardList.tsx"; -import DefaultPageTemplate from "../templates/DefaultPageTemplate/DefaultPageTemplate.tsx"; - -import CreateDataInvitation from "../../components/CreateDataInvitation/CreateDataInvitation.tsx"; -import { useRestaurants } from "../../data/restaurantData.ts"; -import AlertBox from "../../components/AlertBox/AlertBox.tsx"; +import { useRestaurants } from "../data/restaurantData.ts"; +import DefaultPageTemplate from "./templates/DefaultPageTemplate/DefaultPageTemplate.tsx"; +import AlertBox from "../components/AlertBox/AlertBox.tsx"; +import CreateDataInvitation from "../components/CreateDataInvitation/CreateDataInvitation.tsx"; +import RestaurantCardList from "../components/RestaurantCardList/RestaurantCardList.tsx"; export default function RestaurantsPage() { const { restaurants, isLoading, isError } = useRestaurants(); From e0ce77c84acffac56936f45c9ffecd9695b0d777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 19:13:43 +0200 Subject: [PATCH 05/11] Rename restaurant-related pages for improved clarity The file and function names for the restaurant-related pages (RestaurantDetailsPage, RestaurantEditPage, AddRestaurantsPage) have been updated to reflect their specific functionalities. They have been renamed to ViewRestaurantPage, UpdateRestaurantPage, and CreateRestaurantPage respectively. Corresponding changes were also made to correctly reflect these changes in App.tsx. --- frontend/src/App.tsx | 12 ++++++------ ...dRestaurantsPage.tsx => CreateRestaurantPage.tsx} | 2 +- ...staurantEditPage.tsx => UpdateRestaurantPage.tsx} | 2 +- ...taurantDetailsPage.tsx => ViewRestaurantPage.tsx} | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename frontend/src/pages/{AddRestaurantsPage.tsx => CreateRestaurantPage.tsx} (95%) rename frontend/src/pages/{RestaurantEditPage.tsx => UpdateRestaurantPage.tsx} (97%) rename frontend/src/pages/{RestaurantDetailsPage.tsx => ViewRestaurantPage.tsx} (97%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fdf4639..a6d28a2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,7 +1,7 @@ -import RestaurantDetailsPage from "./pages/RestaurantDetailsPage.tsx"; +import ViewRestaurantPage from "./pages/ViewRestaurantPage.tsx"; import { Route, Routes } from "react-router-dom"; -import AddRestaurantsPage from "./pages/AddRestaurantsPage.tsx"; -import RestaurantEditPage from "./pages/RestaurantEditPage.tsx"; +import CreateRestaurantPage from "./pages/CreateRestaurantPage.tsx"; +import UpdateRestaurantPage from "./pages/UpdateRestaurantPage.tsx"; import "./App.css"; import RestaurantsPage from "./pages/RestaurantsPage.tsx"; @@ -9,9 +9,9 @@ function App() { return ( } /> - } /> - } /> - } /> + } /> + } /> + } /> ); } diff --git a/frontend/src/pages/AddRestaurantsPage.tsx b/frontend/src/pages/CreateRestaurantPage.tsx similarity index 95% rename from frontend/src/pages/AddRestaurantsPage.tsx rename to frontend/src/pages/CreateRestaurantPage.tsx index 2bc4bf6..8bb519b 100644 --- a/frontend/src/pages/AddRestaurantsPage.tsx +++ b/frontend/src/pages/CreateRestaurantPage.tsx @@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom"; import { NewRestaurantDTOType, RestaurantType } from "../model/Restaurant.ts"; import { logtail } from "../logger.ts"; -export default function AddRestaurantsPage() { +export default function CreateRestaurantPage() { const navigate = useNavigate(); function handleAddRestaurant(formData: NewRestaurantDTOType) { diff --git a/frontend/src/pages/RestaurantEditPage.tsx b/frontend/src/pages/UpdateRestaurantPage.tsx similarity index 97% rename from frontend/src/pages/RestaurantEditPage.tsx rename to frontend/src/pages/UpdateRestaurantPage.tsx index b8427f3..6135943 100644 --- a/frontend/src/pages/RestaurantEditPage.tsx +++ b/frontend/src/pages/UpdateRestaurantPage.tsx @@ -7,7 +7,7 @@ import { logtail } from "../logger.ts"; import { useRestaurant } from "../data/restaurantData.ts"; import AlertBox from "../components/AlertBox/AlertBox.tsx"; -export default function RestaurantEditPage() { +export default function UpdateRestaurantPage() { const navigate = useNavigate(); const { id: paramId } = useParams<{ id: string }>(); const id = paramId ?? ""; diff --git a/frontend/src/pages/RestaurantDetailsPage.tsx b/frontend/src/pages/ViewRestaurantPage.tsx similarity index 97% rename from frontend/src/pages/RestaurantDetailsPage.tsx rename to frontend/src/pages/ViewRestaurantPage.tsx index 9801103..040fe1a 100644 --- a/frontend/src/pages/RestaurantDetailsPage.tsx +++ b/frontend/src/pages/ViewRestaurantPage.tsx @@ -9,7 +9,7 @@ import { logtail } from "../logger.ts"; import AlertBox from "../components/AlertBox/AlertBox.tsx"; import { mutate } from "swr"; -export default function RestaurantDetailsPage() { +export default function ViewRestaurantPage() { const navigate = useNavigate(); const { id: paramId } = useParams<{ id: string }>(); const id = paramId ?? ""; From 3888039c8920aaf3b91a426f8dcba2a588928d63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 19:18:02 +0200 Subject: [PATCH 06/11] Add autoFocus to RestaurantForm title field The title field in the RestaurantForm component now automatically focuses when loaded. This improves user experience by allowing immediate typing upon form loading. --- frontend/src/components/RestaurantForm/RestaurantForm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/RestaurantForm/RestaurantForm.tsx b/frontend/src/components/RestaurantForm/RestaurantForm.tsx index 5910298..41ccb52 100644 --- a/frontend/src/components/RestaurantForm/RestaurantForm.tsx +++ b/frontend/src/components/RestaurantForm/RestaurantForm.tsx @@ -19,7 +19,7 @@ type RestaurantFormProps = { export default function RestaurantForm({ restaurantData, onSubmit, -}: RestaurantFormProps) { +}: Readonly) { const initialFieldValidation = { title: "", city: "", @@ -62,6 +62,7 @@ export default function RestaurantForm({ onChange={handleUserInput} value={formData.title} required + autoFocus /> {fieldValidation.title} From b6441db4285208a251a93b1c59aaef1512585f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 19:19:51 +0200 Subject: [PATCH 07/11] Make component props readonly Changed the type of props in several components (RestaurantCardList, RestaurantCardDetail, ButtonLink, RestaurantCard, Button, DefaultPageTemplate) from regular to Readonly. This ensures that the properties of these objects will not be able to be modified, adding an extra layer of safety. --- frontend/src/components/Button/Button.tsx | 2 +- frontend/src/components/ButtonLink/ButtonLink.tsx | 5 ++++- frontend/src/components/RestaurantCard/RestaurantCard.tsx | 4 +++- .../components/RestaurantCardDetail/RestaurantCardDetail.tsx | 2 +- .../src/components/RestaurantCardList/RestaurantCardList.tsx | 2 +- .../templates/DefaultPageTemplate/DefaultPageTemplate.tsx | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/Button/Button.tsx b/frontend/src/components/Button/Button.tsx index 095b65e..6073b57 100644 --- a/frontend/src/components/Button/Button.tsx +++ b/frontend/src/components/Button/Button.tsx @@ -13,7 +13,7 @@ export default function Button({ children, handleOnClick, buttonType, -}: ButtonProps) { +}: Readonly) { return ( {children} diff --git a/frontend/src/components/ButtonLink/ButtonLink.tsx b/frontend/src/components/ButtonLink/ButtonLink.tsx index 583a8cf..6950d18 100644 --- a/frontend/src/components/ButtonLink/ButtonLink.tsx +++ b/frontend/src/components/ButtonLink/ButtonLink.tsx @@ -6,6 +6,9 @@ type ButtonLinkProps = { href: string; }; -export default function ButtonLink({ children, href }: ButtonLinkProps) { +export default function ButtonLink({ + children, + href, +}: Readonly) { return {children}; } diff --git a/frontend/src/components/RestaurantCard/RestaurantCard.tsx b/frontend/src/components/RestaurantCard/RestaurantCard.tsx index 2ccbbdf..8980e1c 100644 --- a/frontend/src/components/RestaurantCard/RestaurantCard.tsx +++ b/frontend/src/components/RestaurantCard/RestaurantCard.tsx @@ -14,7 +14,9 @@ type RestaurantCardProps = { restaurant: RestaurantType; }; -export default function RestaurantCard({ restaurant }: RestaurantCardProps) { +export default function RestaurantCard({ + restaurant, +}: Readonly) { return ( diff --git a/frontend/src/components/RestaurantCardDetail/RestaurantCardDetail.tsx b/frontend/src/components/RestaurantCardDetail/RestaurantCardDetail.tsx index ec92a89..9f989a4 100644 --- a/frontend/src/components/RestaurantCardDetail/RestaurantCardDetail.tsx +++ b/frontend/src/components/RestaurantCardDetail/RestaurantCardDetail.tsx @@ -9,7 +9,7 @@ type RestaurantCardDetailProps = { export default function RestaurantCardDetail({ icon, value, -}: RestaurantCardDetailProps) { +}: Readonly) { return (
{icon}
diff --git a/frontend/src/components/RestaurantCardList/RestaurantCardList.tsx b/frontend/src/components/RestaurantCardList/RestaurantCardList.tsx index c91bf44..fcbe723 100644 --- a/frontend/src/components/RestaurantCardList/RestaurantCardList.tsx +++ b/frontend/src/components/RestaurantCardList/RestaurantCardList.tsx @@ -8,7 +8,7 @@ type RestaurantCardListProps = { export default function RestaurantCardList({ restaurants, -}: RestaurantCardListProps) { +}: Readonly) { return ( {restaurants.map((restaurant) => { diff --git a/frontend/src/pages/templates/DefaultPageTemplate/DefaultPageTemplate.tsx b/frontend/src/pages/templates/DefaultPageTemplate/DefaultPageTemplate.tsx index 37954ba..b61e6b0 100644 --- a/frontend/src/pages/templates/DefaultPageTemplate/DefaultPageTemplate.tsx +++ b/frontend/src/pages/templates/DefaultPageTemplate/DefaultPageTemplate.tsx @@ -10,7 +10,7 @@ type DefaultPageTemplateProps = { export default function DefaultPageTemplate({ children, pageTitle, -}: DefaultPageTemplateProps) { +}: Readonly) { return ( <>
From aaa54f3d8ace70b7f941239b8c298b0cb922392e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 19:52:28 +0200 Subject: [PATCH 08/11] Add tests for ButtonLink component This commit introduces tests for the ButtonLink component to validate three main functionalities: display of children as text, the rendering of a link, and ensuring the link points to the correct target. --- .../ButtonLink/ButtonLink.component.test.tsx | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 frontend/src/components/ButtonLink/ButtonLink.component.test.tsx diff --git a/frontend/src/components/ButtonLink/ButtonLink.component.test.tsx b/frontend/src/components/ButtonLink/ButtonLink.component.test.tsx new file mode 100644 index 0000000..e1a8d91 --- /dev/null +++ b/frontend/src/components/ButtonLink/ButtonLink.component.test.tsx @@ -0,0 +1,34 @@ +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import ButtonLink from "./ButtonLink.tsx"; +import { MemoryRouter } from "react-router-dom"; + +test("ButtonLink component displays children as text", () => { + render( + + Click here + + ); + const text = screen.getByText(/click here/i); + expect(text).toBeInTheDocument(); +}); + +test("ButtonLink component displays a link", () => { + render( + + Click here + + ); + const button = screen.getByRole("link"); + expect(button).toBeInTheDocument(); +}); + +test("ButtonLink component links to the correct target", () => { + render( + + Click here + + ); + const button = screen.getByRole("link"); + expect(button).toHaveAttribute("href", "/my-url"); +}); From 911a03d77239d012918af9a698c0f8c39185652c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 20:01:59 +0200 Subject: [PATCH 09/11] Add tests for Button and ButtonLink components A new test file, Button.component.test.tsx, has been added for the Button component to validate if it renders a button and correctly displays the provided children as text. Tests in ButtonLink.component.test.tsx have been reorganized for clarity. The tests now more clearly verify rendering of a link and the display of children as text in the ButtonLink component. --- .../Button/Button.component.test.tsx | 23 +++++++++++++++++++ .../ButtonLink/ButtonLink.component.test.tsx | 16 ++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/Button/Button.component.test.tsx diff --git a/frontend/src/components/Button/Button.component.test.tsx b/frontend/src/components/Button/Button.component.test.tsx new file mode 100644 index 0000000..caab3da --- /dev/null +++ b/frontend/src/components/Button/Button.component.test.tsx @@ -0,0 +1,23 @@ +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import Button from "./Button.tsx"; + +test("Button component renders a button", () => { + render( + + ); + const button = screen.getByRole("button"); + expect(button).toBeInTheDocument(); +}); + +test("Button component renders children as text", () => { + render( + + ); + const text = screen.getByText(/delete/i); + expect(text).toBeInTheDocument(); +}); diff --git a/frontend/src/components/ButtonLink/ButtonLink.component.test.tsx b/frontend/src/components/ButtonLink/ButtonLink.component.test.tsx index e1a8d91..753d209 100644 --- a/frontend/src/components/ButtonLink/ButtonLink.component.test.tsx +++ b/frontend/src/components/ButtonLink/ButtonLink.component.test.tsx @@ -3,32 +3,32 @@ import "@testing-library/jest-dom"; import ButtonLink from "./ButtonLink.tsx"; import { MemoryRouter } from "react-router-dom"; -test("ButtonLink component displays children as text", () => { +test("ButtonLink component renders a link", () => { render( Click here ); - const text = screen.getByText(/click here/i); - expect(text).toBeInTheDocument(); + const button = screen.getByRole("link"); + expect(button).toBeInTheDocument(); }); -test("ButtonLink component displays a link", () => { +test("ButtonLink component links to the correct target", () => { render( Click here ); const button = screen.getByRole("link"); - expect(button).toBeInTheDocument(); + expect(button).toHaveAttribute("href", "/my-url"); }); -test("ButtonLink component links to the correct target", () => { +test("ButtonLink component renders children as text", () => { render( Click here ); - const button = screen.getByRole("link"); - expect(button).toHaveAttribute("href", "/my-url"); + const text = screen.getByText(/click here/i); + expect(text).toBeInTheDocument(); }); From b7a3167b615ac1cb27da0f6a92915f2ea558ee12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 20:14:19 +0200 Subject: [PATCH 10/11] Implement mutation in restaurant pages Added SWR mutate function to the CreateRestaurantPage and UpdateRestaurantPage to update the cache after a restaurant's information has been either updated or created. This ensures that the user will see the most accurate, up-to-date restaurant data immediately, without needing to refresh the page or risk encountering stale data. --- frontend/src/pages/CreateRestaurantPage.tsx | 2 ++ frontend/src/pages/UpdateRestaurantPage.tsx | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/CreateRestaurantPage.tsx b/frontend/src/pages/CreateRestaurantPage.tsx index 8bb519b..4eb5d41 100644 --- a/frontend/src/pages/CreateRestaurantPage.tsx +++ b/frontend/src/pages/CreateRestaurantPage.tsx @@ -4,6 +4,7 @@ import axios from "axios"; import { useNavigate } from "react-router-dom"; import { NewRestaurantDTOType, RestaurantType } from "../model/Restaurant.ts"; import { logtail } from "../logger.ts"; +import { mutate } from "swr"; export default function CreateRestaurantPage() { const navigate = useNavigate(); @@ -16,6 +17,7 @@ export default function CreateRestaurantPage() { .then((response) => { const savedRestaurant: RestaurantType = response.data; logtail.info("Created new restaurant with ID " + savedRestaurant.id); + mutate("/api/restaurants"); navigate("/restaurants/" + savedRestaurant.id); }) .catch((error) => { diff --git a/frontend/src/pages/UpdateRestaurantPage.tsx b/frontend/src/pages/UpdateRestaurantPage.tsx index 6135943..738ffdf 100644 --- a/frontend/src/pages/UpdateRestaurantPage.tsx +++ b/frontend/src/pages/UpdateRestaurantPage.tsx @@ -6,6 +6,7 @@ import RestaurantForm from "../components/RestaurantForm/RestaurantForm.tsx"; import { logtail } from "../logger.ts"; import { useRestaurant } from "../data/restaurantData.ts"; import AlertBox from "../components/AlertBox/AlertBox.tsx"; +import { mutate } from "swr"; export default function UpdateRestaurantPage() { const navigate = useNavigate(); @@ -44,8 +45,10 @@ export default function UpdateRestaurantPage() { axios .put(`/api/restaurants/${id}`, formData) .then(() => { - logtail.info("Updated data of restaurant with ID " + id); - navigate("/"); + logtail.info(`Updated data of restaurant with ID ${id}`); + mutate(`/api/restaurants/${id}`); + mutate("/api/restaurants"); + navigate(`/restaurants/${id}`); }) .catch((error) => { logtail.error(error.message, { From e7c4a9dc29c5dd6e37b549044d43c8012e147402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aljoscha=20Z=C3=B6ller?= Date: Sun, 9 Jun 2024 20:18:02 +0200 Subject: [PATCH 11/11] Add test for Logo component A new test file has been added for the Logo component. The test ensures that the rendered component correctly displays the text "restaurantapp". --- frontend/src/components/Logo/Logo.component.test.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 frontend/src/components/Logo/Logo.component.test.tsx diff --git a/frontend/src/components/Logo/Logo.component.test.tsx b/frontend/src/components/Logo/Logo.component.test.tsx new file mode 100644 index 0000000..12a3d2d --- /dev/null +++ b/frontend/src/components/Logo/Logo.component.test.tsx @@ -0,0 +1,9 @@ +import { render, screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; +import Logo from "./Logo.tsx"; + +test("Button component renders a button", () => { + render(); + const button = screen.getByText(/restaurantapp/i); + expect(button).toBeInTheDocument(); +});