diff --git a/frontend/src/common.ts b/frontend/src/common.ts index f05485f4..a1d826d0 100644 --- a/frontend/src/common.ts +++ b/frontend/src/common.ts @@ -1,4 +1,4 @@ -import { AxiosInstance } from "axios"; +import axios, { AxiosError, AxiosInstance } from "axios"; export const isBigScreen = (): boolean => { return window.screen.width > 768; @@ -13,7 +13,8 @@ const readImage = (requestor: AxiosInstance, imageUrl: string): Promise requestor.get(`image/content${imageUrl}`) .then((res) => { resolve(`data:application/octet-stream;base64,${res.data}`); - }); + }) + .catch((err) => reject(err)); }); }; @@ -38,3 +39,14 @@ export const isBackendReachable = (requestor: AxiosInstance): Promise = .catch((_err) => resolve(false)); }); }; + +export const getErrMessage = (err: any): string => { + console.error(err); + if (err.response != undefined) { + return err.response.data.message; + } + if (axios.isAxiosError(err)) { + return (err as AxiosError).message; + } + return "Some error occurred"; +}; \ No newline at end of file diff --git a/frontend/src/components/AddPlant.tsx b/frontend/src/components/AddPlant.tsx index 5433fed4..e8d9b94e 100644 --- a/frontend/src/components/AddPlant.tsx +++ b/frontend/src/components/AddPlant.tsx @@ -1,12 +1,11 @@ import { Box, Button, Divider, Drawer, Switch, TextField } from "@mui/material"; -import { AxiosError, AxiosInstance, AxiosResponse } from "axios"; +import { AxiosInstance } from "axios"; import { botanicalInfo, plant } from "../interfaces"; import { useEffect, useState } from "react"; import dayjs, { Dayjs } from "dayjs"; import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import AddPhotoAlternateOutlinedIcon from '@mui/icons-material/AddPhotoAlternateOutlined'; -import ErrorDialog from "./ErrorDialog"; export default function AddPlant(props: { requestor: AxiosInstance, @@ -14,7 +13,8 @@ export default function AddPlant(props: { setOpen: (arg: boolean) => void, entity?: botanicalInfo, plants: plant[], - name?: string; + name?: string, + printError: (err: any) => void; }) { const [plantName, setPlantName] = useState(); const [plantNameError, setPlantNameError] = useState(); @@ -26,8 +26,6 @@ export default function AddPlant(props: { const [selectedImage, setSelectedImage] = useState(); const [downloadedImg, setDownloadedImg] = useState(); const [imageDownloaded, setImageDownloaded] = useState(false); - const [errorDialogShown, setErrorDialogShown] = useState(false); - const [errorDialogText, setErrorDialogText] = useState(); const readImage = (imageUrl: string): void => { props.requestor.get(`image/content${imageUrl}`) @@ -35,7 +33,7 @@ export default function AddPlant(props: { setDownloadedImg(res.data); setImageDownloaded(true); }) - .catch((err) => showErrorDialog(err)); + .catch((err) => props.printError(err)); }; const setAbsoluteImageUrl = (imageUrl: string | undefined, publicUrl: string): string => { @@ -120,7 +118,7 @@ export default function AddPlant(props: { } }) .catch((err) => { - showErrorDialog(err); + props.printError(err); }); }; @@ -141,7 +139,7 @@ export default function AddPlant(props: { cleanup(); }) .catch((err) => { - showErrorDialog(err); + props.printError(err); }); }; @@ -170,20 +168,21 @@ export default function AddPlant(props: { props.setOpen(false); res.botanicalInfo.imageUrl = "/" + imgRes.data.id; props.plants.push(res); - //props.entity!.id = res.botanicalInfo.id; setName(); cleanup(); + }) + .catch((err) => { + props.printError(err); }); } else { props.setOpen(false); props.plants.push(res); - //props.entity!.id = res.botanicalInfo.id; setName(); cleanup(); } }) .catch((err) => { - showErrorDialog(err); + props.printError(err); }); }; @@ -217,11 +216,6 @@ export default function AddPlant(props: { setPlantName(name); }; - const showErrorDialog = (res: AxiosError) => { - setErrorDialogText((res.response?.data as any).message); - setErrorDialogShown(true); - }; - useEffect(() => { setImageDownloaded(false); setImageSrc(); @@ -240,13 +234,6 @@ export default function AddPlant(props: { style: { borderRadius: "15px 15px 0 0" } }} > - - setErrorDialogShown(false)} - /> - void, + printError: (err: any) => void; }) { const pageSize = 5; const [entities, setEntities] = useState([]); @@ -230,6 +231,9 @@ export default function AllLogs(props: { } else { setCircularProgressVisible(false); } + }) + .catch((err) => { + props.printError(err); }); }; diff --git a/frontend/src/components/Auth.tsx b/frontend/src/components/Auth.tsx index ee27e052..c113f0ed 100644 --- a/frontend/src/components/Auth.tsx +++ b/frontend/src/components/Auth.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import axios, { AxiosError, AxiosInstance } from 'axios'; +import { AxiosError, AxiosInstance } from 'axios'; import { NavigateFunction, useNavigate } from "react-router"; import secureLocalStorage from "react-secure-storage"; import Avatar from '@mui/material/Avatar'; @@ -20,7 +20,7 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff'; import FormControl from '@mui/material/FormControl'; import { FormHelperText } from "@mui/material"; import ErrorDialog from "./ErrorDialog"; -import { isBackendReachable } from "../common"; +import { getErrMessage, isBackendReachable } from "../common"; export default function (props: { requestor: AxiosInstance; }) { const navigate: NavigateFunction = useNavigate(); @@ -45,8 +45,8 @@ export default function (props: { requestor: AxiosInstance; }) { getOrCreateApiKey(jwt); secureLocalStorage.setItem("plant-it-username", username); }) - .catch((err: AxiosError) => { - setErrorDialogText((err.response?.data as any).message); + .catch((err) => { + setErrorDialogText(getErrMessage(err)); setErrorDialogShown(true); }); }; @@ -73,7 +73,7 @@ export default function (props: { requestor: AxiosInstance; }) { navigate('/'); }) .catch((err) => { - setErrorDialogText((err.response?.data as any).message); + setErrorDialogText(getErrMessage(err)); setErrorDialogShown(true); }); }); @@ -85,11 +85,11 @@ export default function (props: { requestor: AxiosInstance; }) { username: username, password: password }) - .then((_response) => { + .then((_res) => { doLogin(event); }) - .catch((err: AxiosError) => { - setErrorDialogText((err.response?.data as any).message); + .catch((err) => { + setErrorDialogText(getErrMessage(err)); setErrorDialogShown(true); }); }; diff --git a/frontend/src/components/EditEvent.tsx b/frontend/src/components/EditEvent.tsx index 7856f333..f85f9339 100644 --- a/frontend/src/components/EditEvent.tsx +++ b/frontend/src/components/EditEvent.tsx @@ -70,7 +70,8 @@ export default function EditEvent(props: { toEdit?: diaryEntry, open: boolean, setOpen: (arg: boolean) => void, - removeFromLog: (arg: number) => void; + removeFromLog: (arg: number) => void, + printError: (err: any) => void; }) { const [date, setDate] = useState(dayjs()); const [selectedPlantName, setSelectedPlantName] = useState(); @@ -82,8 +83,6 @@ export default function EditEvent(props: { const [confirmDialogTitle, setConfirmDialogTitle] = useState(""); const [confirmDialogText, setConfirmDialogText] = useState(""); const [confirmDialogCallback, setConfirmDialogCallback] = useState<() => void>(() => () => { }); - const [errorDialogShown, setErrorDialogShown] = useState(false); - const [errorDialogText, setErrorDialogText] = useState(); const updateEvent = (): void => { let newTarget: plant = props.plants.filter((en) => en.personalName === selectedPlantName)[0]; @@ -100,7 +99,7 @@ export default function EditEvent(props: { setEdit(false); }) .catch((err) => { - showErrorDialog(err); + props.printError(err); }); }; @@ -113,16 +112,11 @@ export default function EditEvent(props: { setConfirmDialogOpen(false); }) .catch((err) => { - showErrorDialog(err); + props.printError(err); }); }; - const showErrorDialog = (res: AxiosError) => { - setErrorDialogText((res.response?.data as any).message); - setErrorDialogShown(true); - }; - useEffect(() => { setSelectedPlantName(props.toEdit?.diaryTargetPersonalName); setSelectedEventType(props.toEdit?.type); @@ -134,12 +128,6 @@ export default function EditEvent(props: { return ( <> - setErrorDialogShown(false)} - /> - setConfirmDialogOpen(false)} diff --git a/frontend/src/components/Home.tsx b/frontend/src/components/Home.tsx index c2b5fcb7..c4983136 100644 --- a/frontend/src/components/Home.tsx +++ b/frontend/src/components/Home.tsx @@ -1,4 +1,4 @@ -import { AxiosInstance } from "axios"; +import { AxiosError, AxiosInstance } from "axios"; import { useEffect, useState } from "react"; import { NavigateFunction, useNavigate } from "react-router-dom"; import { Avatar, Box, InputAdornment, Link, OutlinedInput, Typography } from "@mui/material"; @@ -21,7 +21,7 @@ import Settings from "./Settings"; import secureLocalStorage from "react-secure-storage"; import EditEvent from "./EditEvent"; import PlantDetails from "./PlantDetails"; -import { isBackendReachable } from "../common"; +import { getErrMessage, isBackendReachable } from "../common"; import ErrorDialog from "./ErrorDialog"; @@ -53,7 +53,8 @@ function UserTopBar(props: {}) { function UserPlantsList(props: { requestor: AxiosInstance, plants: plant[], - gotoDetails: (arg: plant) => void; + gotoDetails: (arg: plant) => void, + printError: (err: AxiosError) => void; }) { const [plantName, setPlantName] = useState(""); @@ -116,6 +117,7 @@ function UserPlantsList(props: { key={entity.id} requestor={props.requestor} onClick={() => props.gotoDetails(entity)} + printError={props.printError} /> ; }) @@ -181,6 +183,9 @@ export default function Home(props: { isLoggedIn: () => boolean, requestor: Axio .then((res) => { let entitiesSize = res.data > 0 ? res.data : 1; getEntities(entitiesSize); + }) + .catch((err) => { + printError(err); }); }; @@ -192,6 +197,9 @@ export default function Home(props: { isLoggedIn: () => boolean, requestor: Axio newEntities.push(en); }); setplants(newEntities); + }) + .catch((err) => { + printError(err); }); }; @@ -214,6 +222,9 @@ export default function Home(props: { isLoggedIn: () => boolean, requestor: Axio diaryTargetPersonalName: "Foo", } as diaryEntry); } + }) + .catch((err) => { + printError(err); }); }; @@ -221,9 +232,18 @@ export default function Home(props: { isLoggedIn: () => boolean, requestor: Axio props.requestor.get("diary/entry/type") .then((res) => { setAllEventTypes(res.data); + }) + .catch((err) => { + printError(err); }); }; + + const printError = (err: any) => { + setErrorDialogText(getErrMessage(err)); + setErrorDialogShown(true); + }; + useEffect(() => { if (!props.isLoggedIn()) { navigate("/auth"); @@ -247,7 +267,6 @@ export default function Home(props: { isLoggedIn: () => boolean, requestor: Axio return ( <> - boolean, requestor: Axio setLogEntries(newLogEntries); }} toEdit={eventToEdit} + printError={printError} /> boolean, requestor: Axio close={() => setPlantDetailsOpen(false)} entity={plantDetails} requestor={props.requestor} + printError={printError} /> @@ -289,6 +310,7 @@ export default function Home(props: { isLoggedIn: () => boolean, requestor: Axio setPlantDetails(arg); setPlantDetailsOpen(true); }} + printError={printError} /> boolean, requestor: Axio setEventToEdit(arg); setEditEventVisible(true); }} + printError={printError} /> @@ -316,11 +339,16 @@ export default function Home(props: { isLoggedIn: () => boolean, requestor: Axio - + diff --git a/frontend/src/components/PlantDetails.tsx b/frontend/src/components/PlantDetails.tsx index 85dbc3f3..b373607c 100644 --- a/frontend/src/components/PlantDetails.tsx +++ b/frontend/src/components/PlantDetails.tsx @@ -32,7 +32,7 @@ function PlantImage(props: { key: string, active: boolean, requestor: AxiosInstance, - showErrorDialog: (text: AxiosError) => void; + printError: (err: any) => void; }) { const [loaded, setLoaded] = useState(false); const [imgBase64, setImgBase64] = useState(); @@ -43,7 +43,7 @@ function PlantImage(props: { setImgBase64(res.data); }) .catch((err) => { - props.showErrorDialog(err); + props.printError(err); }); }, [props.imgId]); @@ -54,7 +54,11 @@ function PlantImage(props: { } { @@ -79,7 +83,7 @@ function Bottombar(props: { visible: boolean, entity?: plant, addImageBase64: (arg: string) => void, - showErrorDialog: (text: AxiosError) => void; + printError: (err: any) => void; }) { const addEntityImage = (toUpload: File): void => { let formData = new FormData(); @@ -91,7 +95,7 @@ function Bottombar(props: { }); }) .catch((err) => { - props.showErrorDialog(err); + props.printError(err); }); }; @@ -184,7 +188,7 @@ function ReadMoreReadLess(props: { function PlantHeader(props: { requestor: AxiosInstance, entity?: plant, - showErrorDialog: (text: AxiosError) => void; + printError: (err: any) => void; }) { const [imageLoaded, setImageLoaded] = useState(false); const [checkedImages, setCheckedImages] = useState(false); @@ -202,12 +206,15 @@ function PlantHeader(props: { setCheckedImages(true); }) .catch((err) => { - props.showErrorDialog(err); + props.printError(err); }); return; } setImageIds(res.data.reverse()); setCheckedImages(true); + }) + .catch((err) => { + props.printError(err); }); }, [props.entity]); @@ -254,7 +261,7 @@ function PlantHeader(props: { key={index.toString()} active={index === activeIndex} requestor={props.requestor} - showErrorDialog={props.showErrorDialog} + printError={props.printError} /> ; }) @@ -330,16 +337,10 @@ export default function PlantDetails(props: { open: boolean, close: () => void; entity?: plant, - requestor: AxiosInstance; + requestor: AxiosInstance, + printError: (err: any) => void; }) { const [selectedTab, setSelectedTab] = useState(0); - const [errorDialogShown, setErrorDialogShown] = useState(false); - const [errorDialogText, setErrorDialogText] = useState(); - - const showErrorDialog = (res: AxiosError) => { - setErrorDialogText((res.response?.data as any).message); - setErrorDialogShown(true); - }; return ( - setErrorDialogShown(false)} - /> - { }} - showErrorDialog={showErrorDialog} + printError={props.printError} /> diff --git a/frontend/src/components/SearchPage.tsx b/frontend/src/components/SearchPage.tsx index 7d948d10..b9505b92 100644 --- a/frontend/src/components/SearchPage.tsx +++ b/frontend/src/components/SearchPage.tsx @@ -14,7 +14,8 @@ function BotanicalEntity(props: { entity: botanicalInfo, requestor: AxiosInstance, addClick: (arg: botanicalInfo) => void, - addEntity: (arg: plant) => void; + addEntity: (arg: plant) => void, + printError: (err: any) => void; }) { const [imageLoaded, setImageLoaded] = useState(false); const [imgSrc, setImgSrc] = useState(); @@ -24,6 +25,9 @@ function BotanicalEntity(props: { .then((res) => { setImageLoaded(true); setImgSrc(res); + }) + .catch((err) => { + props.printError(err); }); }; @@ -47,7 +51,11 @@ function BotanicalEntity(props: { > { !imageLoaded && - + } void; }) { const [scientificName, setScientificName] = useState(""); const [botanicalInfos, setBotanicalInfos] = useState([]); @@ -175,6 +184,9 @@ export default function SearchPage(props: { }); setBotanicalInfos(newBotanicalInfos); })) + .catch((err) => { + props.printError(err); + }) .finally(() => setLoading(false)); }; @@ -191,6 +203,7 @@ export default function SearchPage(props: { entity={selectedBotanicalInfo} plants={props.plants} name={scientificName} + printError={props.printError} /> - - + + } {botanicalInfos.map(botanicalInfo => { @@ -238,9 +259,11 @@ export default function SearchPage(props: { setAddPlantOpen(true); }} addEntity={(en: plant) => props.plants.push(en)} + key={botanicalInfo.id} + printError={props.printError} />; })} - < AddNewBotanicalInfo addClick={() => { + { setSelectedBotanicalInfo(undefined); setAddPlantOpen(true); }} diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index 506ee1f7..56af3523 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -32,7 +32,8 @@ function StatsSection(props: { function Stats(props: { requestor: AxiosInstance, - visibility: boolean; + visibility: boolean, + printError: (err: any) => void; }) { const [expanded, setExpanded] = useState(true); const [totalEvents, setTotalEvents] = useState(0); @@ -43,13 +44,17 @@ function Stats(props: { useEffect(() => { if (props.visibility) { props.requestor.get("diary/entry/_count") - .then((res) => setTotalEvents(res.data)); + .then((res) => setTotalEvents(res.data)) + .catch((err) => props.printError(err)); props.requestor.get("plant/_count") - .then((res) => setTotalPlants(res.data)); + .then((res) => setTotalPlants(res.data)) + .catch((err) => props.printError(err)); props.requestor.get("plant/_countBotanicalInfo") - .then((res) => setTotalBotanicalInfo(res.data)); + .then((res) => setTotalBotanicalInfo(res.data)) + .catch((err) => props.printError(err)); props.requestor.get("image/entity/_count") - .then((res) => setTotalPhotos(res.data)); + .then((res) => setTotalPhotos(res.data)) + .catch((err) => props.printError(err)); } }); @@ -126,10 +131,11 @@ function Stats(props: { ); } -// TODO make stats responsive on changes + export default function Settings(props: { requestor: AxiosInstance, - visibility: boolean; + visibility: boolean, + printError: (err: any) => void; }) { let navigate: NavigateFunction = useNavigate(); const [version, setVersion] = useState(); @@ -141,7 +147,8 @@ export default function Settings(props: { const getVersion = (): void => { props.requestor.get("/info/version") - .then((res) => setVersion(res.data)); + .then((res) => setVersion(res.data)) + .catch((err) => props.printError(err)); }; useEffect(() => { @@ -154,7 +161,11 @@ export default function Settings(props: { flexDirection: "column", }}> - + void; + onClick: () => void, + printError: (err: any) => void; }) { const [imageLoaded, setImageLoaded] = useState(false); const [imgSrc, setImgSrc] = useState(); @@ -18,12 +19,22 @@ export default function UserPlant(props: { .then((res) => { setImageLoaded(true); setImgSrc(res); + }) + .catch((err) => { + props.printError(err); + getBotanicalInfoImg(props.requestor, undefined) + .then((res) => { + setImageLoaded(true); + setImgSrc(res); + }) + .catch((err) => { + console.log(err); + props.printError("Cannot load image for plant " + props.entity.personalName); + }); }); }; useEffect(() => { - setImageSrc(); - }, [props.entity]); return (