From 6692adec0a158b5afef17c394ced2e05d3be606f Mon Sep 17 00:00:00 2001 From: Maxime Beaulieu Date: Thu, 19 Dec 2024 13:48:07 -0500 Subject: [PATCH 1/2] feat: handle promises and errors --- .../src/components/MainLayout.tsx | 19 ++++++++++- canopeum_frontend/src/components/Navbar.tsx | 13 ++++++-- .../src/components/analytics/BatchActions.tsx | 29 +++++++++-------- .../src/components/analytics/BatchTable.tsx | 12 ++++++- .../analytics/FertilizersSelector.tsx | 11 +++++-- .../analytics/MulchLayersSelector.tsx | 12 +++++-- .../analytics/SiteSummaryActions.tsx | 8 ++++- .../analytics/SupportSpeciesSelector.tsx | 12 +++++-- .../analytics/TreeSpeciesSelector.tsx | 12 +++++-- .../analytics/site-modal/SiteModal.tsx | 29 ++++++++++++----- .../settings/AdminInvitationDialog.tsx | 29 +++++++++++------ .../src/components/settings/ManageAdmins.tsx | 32 ++++++++++++------- .../components/social/PostCommentsDialog.tsx | 18 +++++++++-- .../src/components/social/SharePostDialog.tsx | 13 ++++++-- .../social/site-modal/SiteContactModal.tsx | 11 ++++--- .../src/hooks/PostsInfiniteScrollingHook.tsx | 20 +++++++++--- canopeum_frontend/src/locale/en/auth.ts | 1 + canopeum_frontend/src/locale/en/errors.ts | 18 +++++++++++ canopeum_frontend/src/locale/fr/auth.ts | 1 + canopeum_frontend/src/locale/fr/errors.ts | 19 +++++++++++ canopeum_frontend/src/pages/Analytics.tsx | 24 ++++++++------ canopeum_frontend/src/pages/AnalyticsSite.tsx | 17 ++++++++-- .../src/pages/PostDetailsPage.tsx | 12 +++++-- canopeum_frontend/src/pages/Register.tsx | 11 ++++++- .../src/pages/SiteSocialPage.tsx | 24 ++++++++++++-- 25 files changed, 319 insertions(+), 88 deletions(-) diff --git a/canopeum_frontend/src/components/MainLayout.tsx b/canopeum_frontend/src/components/MainLayout.tsx index 690399657..2aab0dcad 100644 --- a/canopeum_frontend/src/components/MainLayout.tsx +++ b/canopeum_frontend/src/components/MainLayout.tsx @@ -1,10 +1,12 @@ import { useContext, useEffect } from 'react' +import { useTranslation } from 'react-i18next' import { Navigate, Outlet, Route, Routes } from 'react-router-dom' import { AuthenticationContext } from './context/AuthenticationContext' import Navbar from './Navbar' import TermsAndPolicies from '@components/settings/TermsAndPolicies' import { appRoutes } from '@constants/routes.constant' +import useErrorHandling from '@hooks/ErrorHandlingHook' import Analytics from '@pages/Analytics' import AnalyticsSite from '@pages/AnalyticsSite' import Home from '@pages/Home' @@ -53,9 +55,24 @@ const AuthenticatedRoutes = () => { const MainLayout = () => { const { initAuth } = useContext(AuthenticationContext) + const { t: translate } = useTranslation() + const { getErrorMessage } = useErrorHandling() + // Try authenticating user on app start if token was saved in storage - useEffect(() => void initAuth(), [initAuth]) + useEffect(() => { + const runInitAuth = async () => initAuth() + + runInitAuth().catch((error: unknown) => { + const errorMessage = getErrorMessage( + error, + translate('auth.user-token-not-found') + ) + console.error(errorMessage); + }); + }, []); + + return ( diff --git a/canopeum_frontend/src/components/Navbar.tsx b/canopeum_frontend/src/components/Navbar.tsx index f7892c433..200f49761 100644 --- a/canopeum_frontend/src/components/Navbar.tsx +++ b/canopeum_frontend/src/components/Navbar.tsx @@ -3,7 +3,9 @@ import { useTranslation } from 'react-i18next' import { Link, useLocation } from 'react-router-dom' import { AuthenticationContext } from './context/AuthenticationContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import { appRoutes } from '@constants/routes.constant' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { RoleEnum } from '@services/api' type NavbarItem = { @@ -46,7 +48,8 @@ const Navbar = () => { const { i18n: { changeLanguage, language }, t: translate } = useTranslation() const [currentLanguage, setCurrentLanguage] = useState(language) const { currentUser } = useContext(AuthenticationContext) - + const { getErrorMessage } = useErrorHandling() + const { openAlertSnackbar } = useContext(SnackbarContext) const location = useLocation() const handleChangeLanguage = () => { @@ -54,7 +57,13 @@ const Navbar = () => { ? 'fr' : 'en' setCurrentLanguage(newLanguage) - void changeLanguage(newLanguage) + changeLanguage(newLanguage).catch((error: unknown) => { + const errorMessage = getErrorMessage( + error, + translate('errors.change-language-failed') + ) + openAlertSnackbar(errorMessage, { severity: 'error' }) + }) } const { isAuthenticated, showLogoutModal } = useContext(AuthenticationContext) diff --git a/canopeum_frontend/src/components/analytics/BatchActions.tsx b/canopeum_frontend/src/components/analytics/BatchActions.tsx index 4306db095..2994ee4f3 100644 --- a/canopeum_frontend/src/components/analytics/BatchActions.tsx +++ b/canopeum_frontend/src/components/analytics/BatchActions.tsx @@ -7,6 +7,7 @@ import EditBatchModal from '@components/analytics/batch-modal/EditBatchModal' import { SnackbarContext } from '@components/context/SnackbarContext' import ConfirmationDialog from '@components/dialogs/ConfirmationDialog' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { BatchDetail } from '@services/api' type Props = { @@ -19,6 +20,7 @@ const BatchActions = ({ onEdit, onDelete, batchDetail }: Props) => { const { t: translate } = useTranslation() const { openAlertSnackbar } = useContext(SnackbarContext) const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() const whisperRef = useRef(null) @@ -27,23 +29,24 @@ const BatchActions = ({ onEdit, onDelete, batchDetail }: Props) => { const deleteBatch = async () => { whisperRef.current?.close() - try { - await getApiClient().batchClient.delete(batchDetail.id) - openAlertSnackbar( - translate('analyticsSite.delete-batch.success', { batchName: batchDetail.name }), - ) - onDelete() - } catch { - openAlertSnackbar( - translate('analyticsSite.delete-batch.error', { batchName: batchDetail.name }), - { severity: 'error' }, - ) - } + + await getApiClient().batchClient.delete(batchDetail.id) + openAlertSnackbar( + translate('analyticsSite.delete-batch.success', { batchName: batchDetail.name }), + ) + onDelete() + } const handleConfirmDeleteClose = (proceed: boolean) => { setConfirmDeleteOpen(false) - if (proceed) void deleteBatch() + if (proceed) deleteBatch().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('analyticsSite.delete-batch.error', + { batchName: batchDetail.name })), + { severity: 'error' }, + ) + ) } return ( diff --git a/canopeum_frontend/src/components/analytics/BatchTable.tsx b/canopeum_frontend/src/components/analytics/BatchTable.tsx index 81e3af89e..87c3599a8 100644 --- a/canopeum_frontend/src/components/analytics/BatchTable.tsx +++ b/canopeum_frontend/src/components/analytics/BatchTable.tsx @@ -5,7 +5,9 @@ import { useTranslation } from 'react-i18next' import BatchActions from '@components/analytics/BatchActions' import BatchSponsorLogo from '@components/batches/BatchSponsorLogo' import { LanguageContext } from '@components/context/LanguageContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { BatchDetail } from '@services/api' const BATCH_HEADER_CLASS = @@ -22,6 +24,8 @@ const BatchTable = (props: Props) => { const { t } = useTranslation() const { translateValue } = useContext(LanguageContext) const { getApiClient } = useApiClient() + const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const [batches, setBatches] = useState(props.batches) @@ -71,7 +75,13 @@ const BatchTable = (props: Props) => { setBatches(previous => previous.filter(b => b.id !== batch.id))} - onEdit={() => void fetchBatch(props.siteId)} + onEdit={() => fetchBatch(props.siteId).catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, t('errors.fetch-batch-failed', + { batchName: batch.name })), + { severity: 'error' }, + ) + )} /> diff --git a/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx b/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx index 61b427d55..138cda306 100644 --- a/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx +++ b/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx @@ -3,7 +3,9 @@ import { useTranslation } from 'react-i18next' import OptionQuantitySelector, { type SelectorOption, type SelectorOptionQuantity } from '@components/analytics/OptionQuantitySelector' import { LanguageContext } from '@components/context/LanguageContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import { FertilizerType } from '@services/api' import { notEmpty } from '@utils/arrayUtils' @@ -17,6 +19,8 @@ const FertilizersSelector = ({ onChange, fertilizers }: Props) => { const { t: translate } = useTranslation() const { translateValue } = useContext(LanguageContext) const { getApiClient } = useApiClient() + const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const [availableFertilizers, setAvailableFertilizers] = useState>( new Map(), @@ -41,8 +45,11 @@ const FertilizersSelector = ({ onChange, fertilizers }: Props) => { setAvailableFertilizers(fertilizerMap) setOptions(fertilizerOptions) } - void fetchFertilizers() - }, [getApiClient, translateValue]) + fetchFertilizers().catch((error: unknown) => + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-fertilizers-failed')), + { severity: 'error' }) + ) + }, []) useEffect(() => fertilizers diff --git a/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx b/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx index 8d0cdae84..974ee3f61 100644 --- a/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx +++ b/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx @@ -3,7 +3,9 @@ import { useTranslation } from 'react-i18next' import OptionQuantitySelector, { type SelectorOption, type SelectorOptionQuantity } from '@components/analytics/OptionQuantitySelector' import { LanguageContext } from '@components/context/LanguageContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import { MulchLayerType } from '@services/api' import { notEmpty } from '@utils/arrayUtils' @@ -17,6 +19,8 @@ const MulchLayersSelector = ({ onChange, mulchLayers }: Props) => { const { t: translate } = useTranslation() const { translateValue } = useContext(LanguageContext) const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() + const { openAlertSnackbar } = useContext(SnackbarContext) const [availableMulchLayers, setAvailableMulchLayers] = useState>( new Map(), @@ -41,8 +45,12 @@ const MulchLayersSelector = ({ onChange, mulchLayers }: Props) => { setAvailableMulchLayers(mulchLayerMap) setOptions(mulchLayerOptions) } - void fetchMulchLayers() - }, [getApiClient, translateValue]) + fetchMulchLayers().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-mulch-layers-failed')) + ) + ) + }, []) useEffect(() => mulchLayers diff --git a/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx b/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx index 2cd1a4d87..2fafd9e29 100644 --- a/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx +++ b/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx @@ -8,6 +8,7 @@ import { SnackbarContext } from '@components/context/SnackbarContext' import ConfirmationDialog from '@components/dialogs/ConfirmationDialog' import SearchBar from '@components/SearchBar' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { SiteSummary, User } from '@services/api' import { PatchedSiteAdminUpdateRequest } from '@services/api' @@ -22,6 +23,7 @@ const SiteSummaryActions = ({ siteSummary, admins, onSiteChange, onSiteEdit }: P const { t: translate } = useTranslation() const { openAlertSnackbar } = useContext(SnackbarContext) const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() const whisperRef = useRef(null) const [filteredAdmins, setFilteredAdmins] = useState(admins) @@ -111,7 +113,11 @@ const SiteSummaryActions = ({ siteSummary, admins, onSiteChange, onSiteEdit }: P return } - void deleteSite() + deleteSite().catch((error: unknown)=> + openAlertSnackbar( + getErrorMessage(error, translate('errors.delete-site-failed')) + ) + ) } const administratorsSelection = ( diff --git a/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx b/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx index 4faaa709d..6807aa7e7 100644 --- a/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx +++ b/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx @@ -3,7 +3,9 @@ import { useTranslation } from 'react-i18next' import OptionQuantitySelector, { type SelectorOption, type SelectorOptionQuantity } from '@components/analytics/OptionQuantitySelector' import { LanguageContext } from '@components/context/LanguageContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import { TreeType } from '@services/api' import { notEmpty } from '@utils/arrayUtils' @@ -17,6 +19,8 @@ const SupportSpeciesSelector = ({ onChange, species }: Props) => { const { t: translate } = useTranslation() const { translateValue } = useContext(LanguageContext) const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() + const { openAlertSnackbar } = useContext(SnackbarContext) const [availableSpecies, setAvailableSpecies] = useState>(new Map()) const [options, setOptions] = useState[]>([]) @@ -39,8 +43,12 @@ const SupportSpeciesSelector = ({ onChange, species }: Props) => { setAvailableSpecies(speciesMap) setOptions(speciesOptions) } - void fetchTreeSpecies() - }, [getApiClient, translateValue]) + fetchTreeSpecies().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-support-species-failed')) + ) + ) + }, []) useEffect(() => species diff --git a/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx b/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx index 2db53c701..27c2ce3f4 100644 --- a/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx +++ b/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx @@ -3,7 +3,9 @@ import { useTranslation } from 'react-i18next' import OptionQuantitySelector, { type SelectorOption, type SelectorOptionQuantity } from '@components/analytics/OptionQuantitySelector' import { LanguageContext } from '@components/context/LanguageContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import { Species, type TreeType } from '@services/api' import { notEmpty } from '@utils/arrayUtils' @@ -20,6 +22,8 @@ const TreeSpeciesSelector = ( const { t: translate } = useTranslation() const { translateValue } = useContext(LanguageContext) const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() + const { openAlertSnackbar } = useContext(SnackbarContext) const [availableSpecies, setAvailableSpecies] = useState>(new Map()) const [options, setOptions] = useState[]>([]) @@ -42,8 +46,12 @@ const TreeSpeciesSelector = ( setAvailableSpecies(speciesMap) setOptions(speciesOptions) } - void fetchTreeSpecies() - }, [getApiClient, translateValue]) + fetchTreeSpecies().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-tree-species-failed')) + ) + ) + }, [setAvailableSpecies, setOptions]) useEffect(() => species diff --git a/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx b/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx index 2946ab9b6..9d64bf649 100644 --- a/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx +++ b/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx @@ -6,7 +6,9 @@ import ImageUpload from '@components/analytics/ImageUpload' import SiteCoordinates from '@components/analytics/site-modal/SiteCoordinates' import TreeSpeciesSelector from '@components/analytics/TreeSpeciesSelector' import { LanguageContext } from '@components/context/LanguageContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import { type DefaultCoordinate, defaultLatitude, defaultLongitude, extractCoordinate } from '@models/Coordinate' import { type SiteType, Species } from '@services/api' import { getApiBaseUrl } from '@services/apiSettings' @@ -46,6 +48,8 @@ const SiteModal = ({ open, handleClose, siteId }: Props) => { const { t } = useTranslation() const { getApiClient } = useApiClient() const { translateValue } = useContext(LanguageContext) + const { getErrorMessage } = useErrorHandling() + const { openAlertSnackbar } = useContext(SnackbarContext) const [site, setSite] = useState(defaultSiteDto) const [availableSiteTypes, setAvailableSiteTypes] = useState([]) @@ -84,23 +88,32 @@ const SiteModal = ({ open, handleClose, siteId }: Props) => { setSiteImageURL(URL.createObjectURL(blob)) }, [siteId, getApiClient]) - const fetchSiteTypes = useCallback( - async () => setAvailableSiteTypes(await getApiClient().siteClient.types()), - [getApiClient], - ) - const onImageUpload = (file: File) => { setSite(value => ({ ...value, siteImage: file })) setSiteImageURL(URL.createObjectURL(file)) } - useEffect(() => void fetchSiteTypes(), [fetchSiteTypes]) + useEffect(() => { + const fetchSiteTypes = async () => + setAvailableSiteTypes(await getApiClient().siteClient.types()) + + + fetchSiteTypes().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, t('errors.fetch-site-types-failed')) + ) + ) + }, []) useEffect(() => { if (!open) return - void fetchSite() - }, [open, fetchSite]) + fetchSite().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, t('errors.fetch-site-failed')) + ) + ) + }, []) useEffect(() => setSite(defaultSiteDto), [siteId]) diff --git a/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx b/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx index 4cf76b6e5..58ab553e3 100644 --- a/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx +++ b/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx @@ -29,14 +29,19 @@ const AdminInvitationDialog = ({ open, handleClose }: Props) => { const [emailError, setEmailError] = useState() const [generateLinkError, setGenerateLinkError] = useState() - const fetchAllSites = useCallback(async () => { - const sites = await getApiClient().siteClient.all() + useEffect(() => { + const fetchAllSites = async () => { + const sites = await getApiClient().siteClient.all() - setSiteOptions(sites.map(site => ({ displayText: site.name, value: site.id }))) - }, [getApiClient]) - - useEffect(() => void fetchAllSites(), [fetchAllSites]) + setSiteOptions(sites.map(site => ({ displayText: site.name, value: site.id }))) + } + fetchAllSites().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-all-sites-failed')) + ) + ) + }, []) const validateEmail = () => { if (!email) { setEmailError('required') @@ -83,9 +88,15 @@ const AdminInvitationDialog = ({ open, handleClose }: Props) => { const handleCopyLinkClick = () => { if (!invitationLink) return - - void navigator.clipboard.writeText(invitationLink) - openAlertSnackbar(`${translate('generic.copied-clipboard')}!`, { severity: 'info' }) + navigator.clipboard.writeText(invitationLink) + .then(() => + openAlertSnackbar(`${translate('generic.copied-clipboard')}!`, { severity: 'info' }) + ) + .catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.copy-to-clipboard-failed')) + ) + ) } const onCloseModal = () => { diff --git a/canopeum_frontend/src/components/settings/ManageAdmins.tsx b/canopeum_frontend/src/components/settings/ManageAdmins.tsx index ddc2ee91e..cdff85ec6 100644 --- a/canopeum_frontend/src/components/settings/ManageAdmins.tsx +++ b/canopeum_frontend/src/components/settings/ManageAdmins.tsx @@ -1,33 +1,41 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' +import { SnackbarContext } from '@components/context/SnackbarContext' import AdminCard from '@components/settings/AdminCard' import AdminInvitationDialog from '@components/settings/AdminInvitationDialog' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import LoadingPage from '@pages/LoadingPage' import type { SiteAdmins } from '@services/api' const ManageAdmins = () => { const { t: translate } = useTranslation() const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() + const { openAlertSnackbar } = useContext(SnackbarContext) const [isLoadingAdmins, setIsLoadingAdmins] = useState(true) const [siteAdminList, setSiteAdminList] = useState([]) const [showAdminInviteDialog, setShowAdminInviteDialog] = useState(false) - const fetchSiteAdmins = useCallback(async () => { - try { - const adminsList = await getApiClient().adminUserSitesClient.all() - setSiteAdminList(adminsList) - setIsLoadingAdmins(false) - } catch { - setIsLoadingAdmins(false) + useEffect(() => { + const fetchSiteAdmins = async () => { + try { + const adminsList = await getApiClient().adminUserSitesClient.all() + setSiteAdminList(adminsList) + setIsLoadingAdmins(false) + } catch { + setIsLoadingAdmins(false) + } } - }, [getApiClient]) - useEffect((): void => { - void fetchSiteAdmins() - }, [fetchSiteAdmins]) + fetchSiteAdmins().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-support-species-failed')) + ) + ) + }, [setSiteAdminList, setIsLoadingAdmins]) if (isLoadingAdmins) { return diff --git a/canopeum_frontend/src/components/social/PostCommentsDialog.tsx b/canopeum_frontend/src/components/social/PostCommentsDialog.tsx index d4ea2a1b3..cb8d71162 100644 --- a/canopeum_frontend/src/components/social/PostCommentsDialog.tsx +++ b/canopeum_frontend/src/components/social/PostCommentsDialog.tsx @@ -8,6 +8,7 @@ import { SnackbarContext } from '@components/context/SnackbarContext' import ConfirmationDialog from '@components/dialogs/ConfirmationDialog' import PostComment from '@components/social/PostComment' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import { type Comment, CreateComment } from '@services/api' import usePostsStore from '@store/postsStore' import { numberOfWordsInText } from '@utils/stringUtils' @@ -28,6 +29,7 @@ const PostCommentsDialog = ({ open, postId, siteId, handleClose }: Props) => { const { currentUser } = useContext(AuthenticationContext) const { commentChange } = usePostsStore() const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() const [comments, setComments] = useState([]) const [commentsLoaded, setCommentsLoaded] = useState(false) @@ -45,8 +47,15 @@ const PostCommentsDialog = ({ open, postId, siteId, handleClose }: Props) => { const fetchComments = async () => setComments(await getApiClient().commentClient.all(postId)) - void fetchComments() - setCommentsLoaded(true) + fetchComments() + .then(() => setCommentsLoaded(true)) + .catch((error: unknown) => { + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-comments-failed')), + { severity: 'error' }) + setCommentsLoaded(false) + }) + + }, [postId, open, commentsLoaded, getApiClient]) useEffect(() => { @@ -130,7 +139,10 @@ const PostCommentsDialog = ({ open, postId, siteId, handleClose }: Props) => { if (!proceedWithDelete || !commentToDelete) return - void deleteComment(commentToDelete) + deleteComment(commentToDelete).catch((error: unknown) => + openAlertSnackbar(getErrorMessage(error, translate('errors.delete-comment-failed')), + { severity: 'error' }) + ) } return ( diff --git a/canopeum_frontend/src/components/social/SharePostDialog.tsx b/canopeum_frontend/src/components/social/SharePostDialog.tsx index a9bc95c3a..47f75f4de 100644 --- a/canopeum_frontend/src/components/social/SharePostDialog.tsx +++ b/canopeum_frontend/src/components/social/SharePostDialog.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next' import { SnackbarContext } from '@components/context/SnackbarContext' import { appRoutes } from '@constants/routes.constant' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { Post } from '@services/api' type Props = { @@ -15,6 +16,7 @@ type Props = { const SharePostDialog = ({ onClose, open, post }: Props) => { const { t: translate } = useTranslation() const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const [shareUrl, setShareUrl] = useState('') @@ -26,8 +28,15 @@ const SharePostDialog = ({ onClose, open, post }: Props) => { const handleCopyLinkClick = () => { if (!shareUrl) return - void navigator.clipboard.writeText(shareUrl) - openAlertSnackbar(`${translate('generic.copied-clipboard')}!`, { severity: 'info' }) + navigator.clipboard.writeText(shareUrl) + .then(() => + openAlertSnackbar(`${translate('generic.copied-clipboard')}!`, { severity: 'info' })) + .catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.copy-to-clibboard-failed')), { severity: 'error' } + ) + ) + } return ( diff --git a/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx b/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx index 6c631964f..24776a3ca 100644 --- a/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx +++ b/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx @@ -11,6 +11,7 @@ import EmailTextField from '@components/inputs/EmailTextField' import PhoneTextField from '@components/inputs/PhoneTextField' import UrlTextField from '@components/inputs/UrlTextField' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { Contact, PatchedContact } from '@services/api' type Props = { @@ -35,8 +36,10 @@ const SiteContactModal = ({ contact, isOpen, handleClose }: Props) => { const [isFormValid, setIsFormValid] = useState(true) const { getApiClient } = useApiClient() const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() - const handleSubmitSiteContact = (): void => { + + const handleSubmitSiteContact = () => getApiClient().contactClient.update(contact.id, editedContact as PatchedContact).then( () => { openAlertSnackbar( @@ -44,13 +47,11 @@ const SiteContactModal = ({ contact, isOpen, handleClose }: Props) => { ) handleClose(editedContact as Contact) }, - ).catch(() => + ).catch((error: unknown) => openAlertSnackbar( - t('social.contact.feedback.edit-error'), - { severity: 'error' }, + getErrorMessage(error, t('social.contact.feedback.edit-error')), { severity: 'error' } ) ) - } return ( handleClose(null)} open={isOpen}> diff --git a/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx b/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx index d01d09b8e..601901c83 100644 --- a/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx +++ b/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx @@ -1,6 +1,7 @@ -import { type RefObject, useCallback, useEffect, useRef, useState } from 'react' +import { type RefObject, useCallback, useContext, useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' import useErrorHandling from '@hooks/ErrorHandlingHook' import usePostsStore from '@store/postsStore' @@ -11,8 +12,9 @@ const PAGE_SIZE = 5 const usePostsInfiniteScrolling = () => { const { setPosts, morePostsLoaded } = usePostsStore() const { t: translate } = useTranslation() - const { getErrorMessage } = useErrorHandling() const { getApiClient } = useApiClient() + const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const [siteIds, setSiteIds] = useState() const [currentPage, setCurrentPage] = useState(0) @@ -74,8 +76,13 @@ const usePostsInfiniteScrolling = () => { return } - void fetchPostsPage() - isMounted.current = true + fetchPostsPage().then(() => + isMounted.current = true + ) + .catch(() => + isMounted.current = false + ) + }, [fetchPostsPage, siteIds]) // The scrollable container should be the parent container with overflow y auto/scroll @@ -93,7 +100,10 @@ const usePostsInfiniteScrolling = () => { if (scrollTop + clientHeight < scrollHeight - INCERTITUDE_MARGIN) return setIsLoadingMore(true) - void fetchPostsPage() + fetchPostsPage().catch((error: unknown) => + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-posts-failed')), + { severity: 'error' }) + ) } return { diff --git a/canopeum_frontend/src/locale/en/auth.ts b/canopeum_frontend/src/locale/en/auth.ts index 69ecd46a6..48d10c3a9 100644 --- a/canopeum_frontend/src/locale/en/auth.ts +++ b/canopeum_frontend/src/locale/en/auth.ts @@ -1,4 +1,5 @@ export default { + 'user-token-not-found': 'User token not found in storage', 'keep-password': 'Keep Same Password', 'change-password': 'Change Password', 'log-in-header-text': 'Log In to Your Account', diff --git a/canopeum_frontend/src/locale/en/errors.ts b/canopeum_frontend/src/locale/en/errors.ts index 4aff41889..9bf5003aa 100644 --- a/canopeum_frontend/src/locale/en/errors.ts +++ b/canopeum_frontend/src/locale/en/errors.ts @@ -5,4 +5,22 @@ export default { 'email-invalid': 'Invalid email format (e.g. john.doe@contoso.com)', 'url-invalid': 'Invalid URL format (e.g. https://www.contoso.com)', 'phone-invalid': 'Invalid phone number format (e.g. +1 123 456 7890)', + 'change-language-failed': 'Error: could not change language', + 'fetch-batch-failed': 'Error: could not fetch batch {{batchName}}', + 'fetch-fertilizers-failed': 'Error: could not fetch fertilizers', + 'fetch-mulch-layers-failed': 'Error: could not fetch mulch layers', + 'fetch-support-species-failed': 'Error: could not fetch support species', + 'fetch-tree-species-failed': 'Error: could not fetch tree species', + 'fetch-site-types-failed': 'Error: could not fetch site types', + 'fetch-site-failed': 'Error: could not fetch site', + 'fetch-site-data-failed': 'Error: could not fetch site data', + 'delete-site-failed': 'Error: could not delete site', + 'fetch-all-sites-failed': 'Error: could not fetch sites', + 'copy-to-clibboard-failed': 'Error: could not copy to clipboard', + 'fetch-comments-failed': 'Error: could not fetch comments', + 'delete-comment-failed': 'Error: could not delete comment', + 'fetch-posts-failed': 'Error: could not fetch posts', + 'fetch-post-failed': 'Error: could not fetch post', + 'fetch-admins-failed': 'Error: could not fetch admins', + 'fetch-user-invitation-failed': 'Error: could not fetch user invitation', } diff --git a/canopeum_frontend/src/locale/fr/auth.ts b/canopeum_frontend/src/locale/fr/auth.ts index 26d87f399..601e43060 100644 --- a/canopeum_frontend/src/locale/fr/auth.ts +++ b/canopeum_frontend/src/locale/fr/auth.ts @@ -1,6 +1,7 @@ import type Shape from '../en/auth' export default { + 'user-token-not-found': 'Token de l\'usager absent du stockage', 'keep-password': 'Garder le même mot de passe', 'change-password': 'Change de mot de passe', 'log-in-header-text': 'Connectez-vous à votre compte', diff --git a/canopeum_frontend/src/locale/fr/errors.ts b/canopeum_frontend/src/locale/fr/errors.ts index cbdd96975..2f445e332 100644 --- a/canopeum_frontend/src/locale/fr/errors.ts +++ b/canopeum_frontend/src/locale/fr/errors.ts @@ -7,4 +7,23 @@ export default { 'email-invalid': "Format d'adresse courriel invalide (exemple: john.doe@contoso.com)", 'url-invalid': "Format d'URL invalide (exemple: https://www.contoso.com)", 'phone-invalid': 'Format de numéro de téléphone invalide (exemple: +1 123 456 7890)', + 'change-language-failed': 'Erreur: changement de langue impossible', + 'fetch-batch-failed': 'Erreur: le chargement du lot {{batchName}} a échoué', + 'fetch-fertilizers-failed': 'Erreur: le chargement des engrais a échoué', + 'fetch-mulch-layers-failed': 'Erreur: le chargement des couches de paillis a échoué', + 'fetch-support-species-failed': 'Erreur: le chargement des espèces de support a échoué', + 'fetch-tree-species-failed': 'Erreur: le chargement des espèces d\'arbres a échoué', + 'fetch-site-types-failed': 'Erreur: le chargement des types de sites a échoué', + 'fetch-site-failed': 'Erreur: le chargement du site a échoué', + 'fetch-site-data-failed': 'Erreur: le chargement des données du site a échoué', + 'delete-site-failed': 'Erreur: la suppression du site a échouée', + 'fetch-all-sites-failed': 'Erreur: le chargement des sites a échoué', + 'copy-to-clibboard-failed': 'Erreur: la copie vers le presse-papiers a échouée', + 'fetch-comments-failed': 'Erreur: le chargement des commentaires a échoué', + 'delete-comment-failed': 'Erreur: la suppression du commentaire a échoué', + 'fetch-posts-failed': 'Erreur: le chargement des publications a échoué', + 'fetch-post-failed': 'Erreur: le chargement de la publication a échoué', + 'fetch-admins-failed': 'Erreur: le chargement des administrateurs a échoué', + 'fetch-user-invitation-failed': 'Erreur: le chargement de l\'invitation de utilisateur a échoué', + } satisfies typeof Shape diff --git a/canopeum_frontend/src/pages/Analytics.tsx b/canopeum_frontend/src/pages/Analytics.tsx index 7e97a6135..aae172a44 100644 --- a/canopeum_frontend/src/pages/Analytics.tsx +++ b/canopeum_frontend/src/pages/Analytics.tsx @@ -10,6 +10,7 @@ import { AuthenticationContext } from '@components/context/AuthenticationContext import { LanguageContext } from '@components/context/LanguageContext' import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import { type Coordinate, coordinateToString } from '@models/Coordinate' import type { SiteSummary, User } from '@services/api' import { assetFormatter } from '@utils/assetFormatter' @@ -20,6 +21,7 @@ const Analytics = () => { const { openAlertSnackbar } = useContext(SnackbarContext) const { currentUser } = useContext(AuthenticationContext) const { getApiClient } = useApiClient() + const { getErrorMessage } = useErrorHandling() const [siteSummaries, setSiteSummaries] = useState([]) const [adminList, setAdminList] = useState([]) @@ -27,11 +29,6 @@ const Analytics = () => { const [isModalOpen, setIsModalOpen] = useState(false) - const fetchSites = useCallback( - async () => setSiteSummaries(await getApiClient().summaryClient.all()), - [getApiClient], - ) - const fetchAdmins = useCallback( async () => setAdminList(await getApiClient().userClient.allForestStewards()), [getApiClient], @@ -138,13 +135,20 @@ const Analytics = () => { useEffect((): void => { if (currentUser?.role !== 'MegaAdmin') return - void fetchAdmins() + fetchAdmins().catch((error: unknown) => + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-admins-failed')), + { severity: 'error' }) + ) }, [currentUser?.role, fetchAdmins]) - useEffect( - (): void => void fetchSites(), - [fetchSites], - ) + useEffect(() => { + const fetchSites = async () => setSiteSummaries(await getApiClient().summaryClient.all()) + + fetchSites().catch((error: unknown) => + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-fertilizers-failed')), + { severity: 'error' }) + ) + }, [getApiClient, setSiteSummaries]) const renderBatches = () => siteSummaries.map(site => { diff --git a/canopeum_frontend/src/pages/AnalyticsSite.tsx b/canopeum_frontend/src/pages/AnalyticsSite.tsx index e1389b87f..e2f6d3899 100644 --- a/canopeum_frontend/src/pages/AnalyticsSite.tsx +++ b/canopeum_frontend/src/pages/AnalyticsSite.tsx @@ -8,7 +8,9 @@ import CreateBatchModal from '@components/analytics/batch-modal/CreateBatchModal import BatchTable from '@components/analytics/BatchTable' import SiteAdminTabs from '@components/analytics/SiteAdminTabs' import { LanguageContext } from '@components/context/LanguageContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { SiteSummaryDetail } from '@services/api' const AnalyticsSite = () => { @@ -16,6 +18,8 @@ const AnalyticsSite = () => { const { siteId: siteIdFromParams } = useParams() const { formatDate } = useContext(LanguageContext) const { getApiClient } = useApiClient() + const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const [siteSummary, setSiteSummary] = useState() const [lastModifiedBatchDate, setLastModifiedBatchDate] = useState() @@ -33,7 +37,11 @@ const AnalyticsSite = () => { const siteIdNumber = Number.parseInt(siteIdFromParams, 10) if (!siteIdNumber) return - void fetchSite(siteIdNumber) + fetchSite(siteIdNumber).catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-site-failed')), { severity: 'error' } + ) + ) }, [fetchSite, siteIdFromParams]) useEffect(() => { @@ -95,7 +103,12 @@ const AnalyticsSite = () => { { setIsCreateBatchOpen(false) - if (reason === 'create') void fetchSite(siteSummary.id) + if (reason === 'create') + fetchSite(siteSummary.id).catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-site-failed')), { severity: 'error' } + ) + ) }} open={isCreateBatchOpen} site={siteSummary} diff --git a/canopeum_frontend/src/pages/PostDetailsPage.tsx b/canopeum_frontend/src/pages/PostDetailsPage.tsx index a1410b063..f752d03ac 100644 --- a/canopeum_frontend/src/pages/PostDetailsPage.tsx +++ b/canopeum_frontend/src/pages/PostDetailsPage.tsx @@ -1,11 +1,13 @@ -import { useCallback, useEffect, useState } from 'react' +import { useCallback, useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { Link, useParams } from 'react-router-dom' import LoadingPage from './LoadingPage' +import { SnackbarContext } from '@components/context/SnackbarContext' import PostCard from '@components/social/PostCard' import { appRoutes } from '@constants/routes.constant' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { Post } from '@services/api' import usePostsStore from '@store/postsStore' @@ -14,6 +16,8 @@ const PostDetailsPage = () => { const { postId: postIdFromParams } = useParams() const { posts, setPosts } = usePostsStore() const { getApiClient } = useApiClient() + const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const [postId, setPostId] = useState() const [postDetail, setPostDetail] = useState() @@ -54,7 +58,11 @@ const PostDetailsPage = () => { return } - void fetchPost(postIdNumber) + fetchPost(postIdNumber).catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-post-failed')), { severity: 'error' } + ) + ) setPostId(postIdNumber) }, [fetchPost, postIdFromParams]) diff --git a/canopeum_frontend/src/pages/Register.tsx b/canopeum_frontend/src/pages/Register.tsx index 9b6ec87bc..7144cbc3c 100644 --- a/canopeum_frontend/src/pages/Register.tsx +++ b/canopeum_frontend/src/pages/Register.tsx @@ -4,8 +4,10 @@ import { Link, useSearchParams } from 'react-router-dom' import AuthPageLayout from '@components/auth/AuthPageLayout' import { AuthenticationContext } from '@components/context/AuthenticationContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import { appRoutes } from '@constants/routes.constant' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import type { UserInvitation } from '@services/api' import { RegisterUser } from '@services/api' import { storeToken } from '@utils/auth.utils' @@ -16,6 +18,8 @@ const Register = () => { const { authenticate } = useContext(AuthenticationContext) const { t: translate } = useTranslation() const { getApiClient } = useApiClient() + const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const [username, setUsername] = useState('') const [email, setEmail] = useState('') @@ -61,7 +65,12 @@ const Register = () => { const code = searchParams.get('code') if (!code) return - void fetchUserInvitation(code) + fetchUserInvitation(code).catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.fetch-user-invitation-failed')), + { severity: 'error' } + ) + ) }, [searchParams, fetchUserInvitation]) const validateUsername = () => { diff --git a/canopeum_frontend/src/pages/SiteSocialPage.tsx b/canopeum_frontend/src/pages/SiteSocialPage.tsx index 15b3ccfa2..f4f9718d7 100644 --- a/canopeum_frontend/src/pages/SiteSocialPage.tsx +++ b/canopeum_frontend/src/pages/SiteSocialPage.tsx @@ -6,6 +6,7 @@ import { useParams } from 'react-router-dom' import LoadingPage from './LoadingPage' import SiteAdminTabs from '@components/analytics/SiteAdminTabs' import { AuthenticationContext } from '@components/context/AuthenticationContext' +import { SnackbarContext } from '@components/context/SnackbarContext' import CreatePostWidget from '@components/CreatePostWidget' import AnnouncementCard from '@components/social/AnnouncementCard' import ContactCard from '@components/social/ContactCard' @@ -14,6 +15,7 @@ import SiteSocialHeader from '@components/social/SiteSocialHeader' import WidgetCard from '@components/social/WidgetCard' import WidgetDialog from '@components/social/WidgetDialog' import useApiClient from '@hooks/ApiClientHook' +import useErrorHandling from '@hooks/ErrorHandlingHook' import usePostsInfiniteScrolling from '@hooks/PostsInfiniteScrollingHook' import type { PageViewMode } from '@models/PageViewMode.type' import { type IWidget, PatchedWidget, type Post, type SiteSocial, Widget } from '@services/api' @@ -27,6 +29,8 @@ const SiteSocialPage = () => { const { currentUser } = useContext(AuthenticationContext) const { posts, addPost } = usePostsStore() const { getApiClient } = useApiClient() + const { openAlertSnackbar } = useContext(SnackbarContext) + const { getErrorMessage } = useErrorHandling() const scrollableContainerRef = useRef(null) const { @@ -46,6 +50,8 @@ const SiteSocialPage = () => { undefined, ]) + const fetchSiteDataError = 'errors.fetch-site-data-failed' + const siteId = siteIdParam ? Number.parseInt(siteIdParam, 10) || 0 : 0 @@ -80,7 +86,11 @@ const SiteSocialPage = () => { if (reason === 'delete' && data) { getApiClient().widgetClient.delete(siteId, data.id) - .then(() => void fetchSiteData(siteId)) + .then(() => fetchSiteData(siteId)).catch((error_: unknown) => + openAlertSnackbar( + getErrorMessage(error_, t(fetchSiteDataError)), { severity: 'error' } + ) + ) .catch(() => {/* empty */}) } @@ -90,7 +100,11 @@ const SiteSocialPage = () => { : getApiClient().widgetClient.update(siteId, data.id, new PatchedWidget(data)) response - .then(() => void fetchSiteData(siteId)) + .then(() => fetchSiteData(siteId)).catch((error_: unknown) => + openAlertSnackbar( + getErrorMessage(error_, t(fetchSiteDataError)), { severity: 'error' } + ) + ) .catch(() => {/* empty */}) } @@ -100,7 +114,11 @@ const SiteSocialPage = () => { const addNewPost = (newPost: Post) => addPost(newPost) useEffect((): void => { - void fetchSiteData(siteId) + fetchSiteData(siteId).catch((error_: unknown) => + openAlertSnackbar( + getErrorMessage(error_, t(fetchSiteDataError)), { severity: 'error' } + ) + ) setSiteIds([siteId]) }, [siteId, fetchSiteData, setSiteIds]) From e1ecd0733796a1ab8a74ef6b6e71f4e292c6e824 Mon Sep 17 00:00:00 2001 From: Maxime Beaulieu Date: Thu, 19 Dec 2024 13:53:11 -0500 Subject: [PATCH 2/2] fix linting --- .../src/components/MainLayout.tsx | 11 ++++------ canopeum_frontend/src/components/Navbar.tsx | 4 ++-- .../src/components/analytics/BatchActions.tsx | 17 +++++++++------- .../src/components/analytics/BatchTable.tsx | 17 +++++++++------- .../analytics/FertilizersSelector.tsx | 5 +++-- .../analytics/MulchLayersSelector.tsx | 2 +- .../analytics/SiteSummaryActions.tsx | 4 ++-- .../analytics/SupportSpeciesSelector.tsx | 2 +- .../analytics/TreeSpeciesSelector.tsx | 2 +- .../analytics/site-modal/SiteModal.tsx | 5 ++--- .../settings/AdminInvitationDialog.tsx | 6 +++--- .../src/components/settings/ManageAdmins.tsx | 4 ++-- .../components/social/PostCommentsDialog.tsx | 20 +++++++++---------- .../src/components/social/SharePostDialog.tsx | 15 +++++++------- .../social/site-modal/SiteContactModal.tsx | 4 ++-- .../src/hooks/PostsInfiniteScrollingHook.tsx | 14 +++++-------- canopeum_frontend/src/locale/fr/auth.ts | 2 +- canopeum_frontend/src/locale/fr/errors.ts | 5 ++--- canopeum_frontend/src/pages/Analytics.tsx | 10 ++++++---- canopeum_frontend/src/pages/AnalyticsSite.tsx | 9 ++++++--- .../src/pages/PostDetailsPage.tsx | 3 ++- canopeum_frontend/src/pages/Register.tsx | 2 +- .../src/pages/SiteSocialPage.tsx | 9 ++++++--- 23 files changed, 90 insertions(+), 82 deletions(-) diff --git a/canopeum_frontend/src/components/MainLayout.tsx b/canopeum_frontend/src/components/MainLayout.tsx index 2aab0dcad..aa2cc4db0 100644 --- a/canopeum_frontend/src/components/MainLayout.tsx +++ b/canopeum_frontend/src/components/MainLayout.tsx @@ -58,7 +58,6 @@ const MainLayout = () => { const { t: translate } = useTranslation() const { getErrorMessage } = useErrorHandling() - // Try authenticating user on app start if token was saved in storage useEffect(() => { const runInitAuth = async () => initAuth() @@ -66,13 +65,11 @@ const MainLayout = () => { runInitAuth().catch((error: unknown) => { const errorMessage = getErrorMessage( error, - translate('auth.user-token-not-found') + translate('auth.user-token-not-found'), ) - console.error(errorMessage); - }); - }, []); - - + console.error(errorMessage) + }) + }, []) return ( diff --git a/canopeum_frontend/src/components/Navbar.tsx b/canopeum_frontend/src/components/Navbar.tsx index 200f49761..b5f427297 100644 --- a/canopeum_frontend/src/components/Navbar.tsx +++ b/canopeum_frontend/src/components/Navbar.tsx @@ -60,9 +60,9 @@ const Navbar = () => { changeLanguage(newLanguage).catch((error: unknown) => { const errorMessage = getErrorMessage( error, - translate('errors.change-language-failed') + translate('errors.change-language-failed'), ) - openAlertSnackbar(errorMessage, { severity: 'error' }) + openAlertSnackbar(errorMessage, { severity: 'error' }) }) } diff --git a/canopeum_frontend/src/components/analytics/BatchActions.tsx b/canopeum_frontend/src/components/analytics/BatchActions.tsx index 2994ee4f3..103a30945 100644 --- a/canopeum_frontend/src/components/analytics/BatchActions.tsx +++ b/canopeum_frontend/src/components/analytics/BatchActions.tsx @@ -35,18 +35,21 @@ const BatchActions = ({ onEdit, onDelete, batchDetail }: Props) => { translate('analyticsSite.delete-batch.success', { batchName: batchDetail.name }), ) onDelete() - } const handleConfirmDeleteClose = (proceed: boolean) => { setConfirmDeleteOpen(false) - if (proceed) deleteBatch().catch((error: unknown) => - openAlertSnackbar( - getErrorMessage(error, translate('analyticsSite.delete-batch.error', - { batchName: batchDetail.name })), - { severity: 'error' }, + if (proceed) { + deleteBatch().catch((error: unknown) => + openAlertSnackbar( + getErrorMessage( + error, + translate('analyticsSite.delete-batch.error', { batchName: batchDetail.name }), + ), + { severity: 'error' }, + ) ) - ) + } } return ( diff --git a/canopeum_frontend/src/components/analytics/BatchTable.tsx b/canopeum_frontend/src/components/analytics/BatchTable.tsx index 87c3599a8..7afa28bbe 100644 --- a/canopeum_frontend/src/components/analytics/BatchTable.tsx +++ b/canopeum_frontend/src/components/analytics/BatchTable.tsx @@ -75,13 +75,16 @@ const BatchTable = (props: Props) => { setBatches(previous => previous.filter(b => b.id !== batch.id))} - onEdit={() => fetchBatch(props.siteId).catch((error: unknown) => - openAlertSnackbar( - getErrorMessage(error, t('errors.fetch-batch-failed', - { batchName: batch.name })), - { severity: 'error' }, - ) - )} + onEdit={() => + fetchBatch(props.siteId).catch((error: unknown) => + openAlertSnackbar( + getErrorMessage( + error, + t('errors.fetch-batch-failed', { batchName: batch.name }), + ), + { severity: 'error' }, + ) + )} /> diff --git a/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx b/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx index 138cda306..5938ea5e1 100644 --- a/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx +++ b/canopeum_frontend/src/components/analytics/FertilizersSelector.tsx @@ -46,8 +46,9 @@ const FertilizersSelector = ({ onChange, fertilizers }: Props) => { setOptions(fertilizerOptions) } fetchFertilizers().catch((error: unknown) => - openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-fertilizers-failed')), - { severity: 'error' }) + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-fertilizers-failed')), { + severity: 'error', + }) ) }, []) diff --git a/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx b/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx index 974ee3f61..38bcfeb55 100644 --- a/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx +++ b/canopeum_frontend/src/components/analytics/MulchLayersSelector.tsx @@ -47,7 +47,7 @@ const MulchLayersSelector = ({ onChange, mulchLayers }: Props) => { } fetchMulchLayers().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-mulch-layers-failed')) + getErrorMessage(error, translate('errors.fetch-mulch-layers-failed')), ) ) }, []) diff --git a/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx b/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx index 2fafd9e29..b6c72c46a 100644 --- a/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx +++ b/canopeum_frontend/src/components/analytics/SiteSummaryActions.tsx @@ -113,9 +113,9 @@ const SiteSummaryActions = ({ siteSummary, admins, onSiteChange, onSiteEdit }: P return } - deleteSite().catch((error: unknown)=> + deleteSite().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.delete-site-failed')) + getErrorMessage(error, translate('errors.delete-site-failed')), ) ) } diff --git a/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx b/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx index 6807aa7e7..a461095e1 100644 --- a/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx +++ b/canopeum_frontend/src/components/analytics/SupportSpeciesSelector.tsx @@ -45,7 +45,7 @@ const SupportSpeciesSelector = ({ onChange, species }: Props) => { } fetchTreeSpecies().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-support-species-failed')) + getErrorMessage(error, translate('errors.fetch-support-species-failed')), ) ) }, []) diff --git a/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx b/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx index 27c2ce3f4..00a105c9e 100644 --- a/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx +++ b/canopeum_frontend/src/components/analytics/TreeSpeciesSelector.tsx @@ -48,7 +48,7 @@ const TreeSpeciesSelector = ( } fetchTreeSpecies().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-tree-species-failed')) + getErrorMessage(error, translate('errors.fetch-tree-species-failed')), ) ) }, [setAvailableSpecies, setOptions]) diff --git a/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx b/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx index 9d64bf649..2c13aeb6f 100644 --- a/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx +++ b/canopeum_frontend/src/components/analytics/site-modal/SiteModal.tsx @@ -97,10 +97,9 @@ const SiteModal = ({ open, handleClose, siteId }: Props) => { const fetchSiteTypes = async () => setAvailableSiteTypes(await getApiClient().siteClient.types()) - fetchSiteTypes().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, t('errors.fetch-site-types-failed')) + getErrorMessage(error, t('errors.fetch-site-types-failed')), ) ) }, []) @@ -110,7 +109,7 @@ const SiteModal = ({ open, handleClose, siteId }: Props) => { fetchSite().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, t('errors.fetch-site-failed')) + getErrorMessage(error, t('errors.fetch-site-failed')), ) ) }, []) diff --git a/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx b/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx index 58ab553e3..3a843574b 100644 --- a/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx +++ b/canopeum_frontend/src/components/settings/AdminInvitationDialog.tsx @@ -1,5 +1,5 @@ import { Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material' -import { useCallback, useContext, useEffect, useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { SnackbarContext } from '@components/context/SnackbarContext' @@ -38,7 +38,7 @@ const AdminInvitationDialog = ({ open, handleClose }: Props) => { fetchAllSites().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-all-sites-failed')) + getErrorMessage(error, translate('errors.fetch-all-sites-failed')), ) ) }, []) @@ -94,7 +94,7 @@ const AdminInvitationDialog = ({ open, handleClose }: Props) => { ) .catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.copy-to-clipboard-failed')) + getErrorMessage(error, translate('errors.copy-to-clipboard-failed')), ) ) } diff --git a/canopeum_frontend/src/components/settings/ManageAdmins.tsx b/canopeum_frontend/src/components/settings/ManageAdmins.tsx index cdff85ec6..28311cd3b 100644 --- a/canopeum_frontend/src/components/settings/ManageAdmins.tsx +++ b/canopeum_frontend/src/components/settings/ManageAdmins.tsx @@ -1,4 +1,4 @@ -import { useCallback, useContext, useEffect, useState } from 'react' +import { useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { SnackbarContext } from '@components/context/SnackbarContext' @@ -32,7 +32,7 @@ const ManageAdmins = () => { fetchSiteAdmins().catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-support-species-failed')) + getErrorMessage(error, translate('errors.fetch-support-species-failed')), ) ) }, [setSiteAdminList, setIsLoadingAdmins]) diff --git a/canopeum_frontend/src/components/social/PostCommentsDialog.tsx b/canopeum_frontend/src/components/social/PostCommentsDialog.tsx index cb8d71162..1e8686d59 100644 --- a/canopeum_frontend/src/components/social/PostCommentsDialog.tsx +++ b/canopeum_frontend/src/components/social/PostCommentsDialog.tsx @@ -48,14 +48,13 @@ const PostCommentsDialog = ({ open, postId, siteId, handleClose }: Props) => { const fetchComments = async () => setComments(await getApiClient().commentClient.all(postId)) fetchComments() - .then(() => setCommentsLoaded(true)) - .catch((error: unknown) => { - openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-comments-failed')), - { severity: 'error' }) - setCommentsLoaded(false) - }) - - + .then(() => setCommentsLoaded(true)) + .catch((error: unknown) => { + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-comments-failed')), { + severity: 'error', + }) + setCommentsLoaded(false) + }) }, [postId, open, commentsLoaded, getApiClient]) useEffect(() => { @@ -140,8 +139,9 @@ const PostCommentsDialog = ({ open, postId, siteId, handleClose }: Props) => { if (!proceedWithDelete || !commentToDelete) return deleteComment(commentToDelete).catch((error: unknown) => - openAlertSnackbar(getErrorMessage(error, translate('errors.delete-comment-failed')), - { severity: 'error' }) + openAlertSnackbar(getErrorMessage(error, translate('errors.delete-comment-failed')), { + severity: 'error', + }) ) } diff --git a/canopeum_frontend/src/components/social/SharePostDialog.tsx b/canopeum_frontend/src/components/social/SharePostDialog.tsx index 47f75f4de..d8c32bbad 100644 --- a/canopeum_frontend/src/components/social/SharePostDialog.tsx +++ b/canopeum_frontend/src/components/social/SharePostDialog.tsx @@ -29,14 +29,15 @@ const SharePostDialog = ({ onClose, open, post }: Props) => { if (!shareUrl) return navigator.clipboard.writeText(shareUrl) - .then(() => - openAlertSnackbar(`${translate('generic.copied-clipboard')}!`, { severity: 'info' })) - .catch((error: unknown) => - openAlertSnackbar( - getErrorMessage(error, translate('errors.copy-to-clibboard-failed')), { severity: 'error' } + .then(() => + openAlertSnackbar(`${translate('generic.copied-clipboard')}!`, { severity: 'info' }) + ) + .catch((error: unknown) => + openAlertSnackbar( + getErrorMessage(error, translate('errors.copy-to-clibboard-failed')), + { severity: 'error' }, + ) ) - ) - } return ( diff --git a/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx b/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx index 24776a3ca..0bc4cd30e 100644 --- a/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx +++ b/canopeum_frontend/src/components/social/site-modal/SiteContactModal.tsx @@ -38,7 +38,6 @@ const SiteContactModal = ({ contact, isOpen, handleClose }: Props) => { const { openAlertSnackbar } = useContext(SnackbarContext) const { getErrorMessage } = useErrorHandling() - const handleSubmitSiteContact = () => getApiClient().contactClient.update(contact.id, editedContact as PatchedContact).then( () => { @@ -49,7 +48,8 @@ const SiteContactModal = ({ contact, isOpen, handleClose }: Props) => { }, ).catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, t('social.contact.feedback.edit-error')), { severity: 'error' } + getErrorMessage(error, t('social.contact.feedback.edit-error')), + { severity: 'error' }, ) ) diff --git a/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx b/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx index 601901c83..fbc560a32 100644 --- a/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx +++ b/canopeum_frontend/src/hooks/PostsInfiniteScrollingHook.tsx @@ -76,13 +76,8 @@ const usePostsInfiniteScrolling = () => { return } - fetchPostsPage().then(() => - isMounted.current = true - ) - .catch(() => - isMounted.current = false - ) - + fetchPostsPage().then(() => isMounted.current = true) + .catch(() => isMounted.current = false) }, [fetchPostsPage, siteIds]) // The scrollable container should be the parent container with overflow y auto/scroll @@ -101,8 +96,9 @@ const usePostsInfiniteScrolling = () => { setIsLoadingMore(true) fetchPostsPage().catch((error: unknown) => - openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-posts-failed')), - { severity: 'error' }) + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-posts-failed')), { + severity: 'error', + }) ) } diff --git a/canopeum_frontend/src/locale/fr/auth.ts b/canopeum_frontend/src/locale/fr/auth.ts index 3270a487e..694883ce2 100644 --- a/canopeum_frontend/src/locale/fr/auth.ts +++ b/canopeum_frontend/src/locale/fr/auth.ts @@ -1,7 +1,7 @@ import type Shape from '../en/auth' export default { - 'user-token-not-found': 'Token de l\'usager absent du stockage', + 'user-token-not-found': "Token de l'usager absent du stockage", 'keep-password': 'Garder le même mot de passe', 'change-password': 'Change de mot de passe', 'log-in-header-text': 'Connectez-vous à votre compte', diff --git a/canopeum_frontend/src/locale/fr/errors.ts b/canopeum_frontend/src/locale/fr/errors.ts index 2f445e332..e79476b0d 100644 --- a/canopeum_frontend/src/locale/fr/errors.ts +++ b/canopeum_frontend/src/locale/fr/errors.ts @@ -12,7 +12,7 @@ export default { 'fetch-fertilizers-failed': 'Erreur: le chargement des engrais a échoué', 'fetch-mulch-layers-failed': 'Erreur: le chargement des couches de paillis a échoué', 'fetch-support-species-failed': 'Erreur: le chargement des espèces de support a échoué', - 'fetch-tree-species-failed': 'Erreur: le chargement des espèces d\'arbres a échoué', + 'fetch-tree-species-failed': "Erreur: le chargement des espèces d'arbres a échoué", 'fetch-site-types-failed': 'Erreur: le chargement des types de sites a échoué', 'fetch-site-failed': 'Erreur: le chargement du site a échoué', 'fetch-site-data-failed': 'Erreur: le chargement des données du site a échoué', @@ -24,6 +24,5 @@ export default { 'fetch-posts-failed': 'Erreur: le chargement des publications a échoué', 'fetch-post-failed': 'Erreur: le chargement de la publication a échoué', 'fetch-admins-failed': 'Erreur: le chargement des administrateurs a échoué', - 'fetch-user-invitation-failed': 'Erreur: le chargement de l\'invitation de utilisateur a échoué', - + 'fetch-user-invitation-failed': "Erreur: le chargement de l'invitation de utilisateur a échoué", } satisfies typeof Shape diff --git a/canopeum_frontend/src/pages/Analytics.tsx b/canopeum_frontend/src/pages/Analytics.tsx index aae172a44..8386f3330 100644 --- a/canopeum_frontend/src/pages/Analytics.tsx +++ b/canopeum_frontend/src/pages/Analytics.tsx @@ -136,8 +136,9 @@ const Analytics = () => { if (currentUser?.role !== 'MegaAdmin') return fetchAdmins().catch((error: unknown) => - openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-admins-failed')), - { severity: 'error' }) + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-admins-failed')), { + severity: 'error', + }) ) }, [currentUser?.role, fetchAdmins]) @@ -145,8 +146,9 @@ const Analytics = () => { const fetchSites = async () => setSiteSummaries(await getApiClient().summaryClient.all()) fetchSites().catch((error: unknown) => - openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-fertilizers-failed')), - { severity: 'error' }) + openAlertSnackbar(getErrorMessage(error, translate('errors.fetch-fertilizers-failed')), { + severity: 'error', + }) ) }, [getApiClient, setSiteSummaries]) diff --git a/canopeum_frontend/src/pages/AnalyticsSite.tsx b/canopeum_frontend/src/pages/AnalyticsSite.tsx index e2f6d3899..b4a6c1e3b 100644 --- a/canopeum_frontend/src/pages/AnalyticsSite.tsx +++ b/canopeum_frontend/src/pages/AnalyticsSite.tsx @@ -39,7 +39,8 @@ const AnalyticsSite = () => { fetchSite(siteIdNumber).catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-site-failed')), { severity: 'error' } + getErrorMessage(error, translate('errors.fetch-site-failed')), + { severity: 'error' }, ) ) }, [fetchSite, siteIdFromParams]) @@ -103,12 +104,14 @@ const AnalyticsSite = () => { { setIsCreateBatchOpen(false) - if (reason === 'create') + if (reason === 'create') { fetchSite(siteSummary.id).catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-site-failed')), { severity: 'error' } + getErrorMessage(error, translate('errors.fetch-site-failed')), + { severity: 'error' }, ) ) + } }} open={isCreateBatchOpen} site={siteSummary} diff --git a/canopeum_frontend/src/pages/PostDetailsPage.tsx b/canopeum_frontend/src/pages/PostDetailsPage.tsx index f752d03ac..a1a272ffb 100644 --- a/canopeum_frontend/src/pages/PostDetailsPage.tsx +++ b/canopeum_frontend/src/pages/PostDetailsPage.tsx @@ -60,7 +60,8 @@ const PostDetailsPage = () => { fetchPost(postIdNumber).catch((error: unknown) => openAlertSnackbar( - getErrorMessage(error, translate('errors.fetch-post-failed')), { severity: 'error' } + getErrorMessage(error, translate('errors.fetch-post-failed')), + { severity: 'error' }, ) ) setPostId(postIdNumber) diff --git a/canopeum_frontend/src/pages/Register.tsx b/canopeum_frontend/src/pages/Register.tsx index 78c985e9e..d97f5ab1f 100644 --- a/canopeum_frontend/src/pages/Register.tsx +++ b/canopeum_frontend/src/pages/Register.tsx @@ -94,7 +94,7 @@ const Register = () => { fetchUserInvitation(code).catch((error: unknown) => openAlertSnackbar( getErrorMessage(error, translate('errors.fetch-user-invitation-failed')), - { severity: 'error' } + { severity: 'error' }, ) ) }, [searchParams, fetchUserInvitation]) diff --git a/canopeum_frontend/src/pages/SiteSocialPage.tsx b/canopeum_frontend/src/pages/SiteSocialPage.tsx index f4f9718d7..1af999e1b 100644 --- a/canopeum_frontend/src/pages/SiteSocialPage.tsx +++ b/canopeum_frontend/src/pages/SiteSocialPage.tsx @@ -88,7 +88,8 @@ const SiteSocialPage = () => { getApiClient().widgetClient.delete(siteId, data.id) .then(() => fetchSiteData(siteId)).catch((error_: unknown) => openAlertSnackbar( - getErrorMessage(error_, t(fetchSiteDataError)), { severity: 'error' } + getErrorMessage(error_, t(fetchSiteDataError)), + { severity: 'error' }, ) ) .catch(() => {/* empty */}) @@ -102,7 +103,8 @@ const SiteSocialPage = () => { response .then(() => fetchSiteData(siteId)).catch((error_: unknown) => openAlertSnackbar( - getErrorMessage(error_, t(fetchSiteDataError)), { severity: 'error' } + getErrorMessage(error_, t(fetchSiteDataError)), + { severity: 'error' }, ) ) .catch(() => {/* empty */}) @@ -116,7 +118,8 @@ const SiteSocialPage = () => { useEffect((): void => { fetchSiteData(siteId).catch((error_: unknown) => openAlertSnackbar( - getErrorMessage(error_, t(fetchSiteDataError)), { severity: 'error' } + getErrorMessage(error_, t(fetchSiteDataError)), + { severity: 'error' }, ) ) setSiteIds([siteId])