diff --git a/.env.template b/.env.template index 232b90483e02b29d1af95058c1b5990cf8c98710..e63fc8829c4b49a7475f767879551102f7b6464c 100644 --- a/.env.template +++ b/.env.template @@ -19,7 +19,6 @@ CLIENT_SECRET= AUTH_URL= TOKEN_URL= USERINFO_URL= -LOGOUT_URL= # Access to the database DATABASE_USER= diff --git a/.vscode/settings.json b/.vscode/settings.json index 9d92bf0ce597fc8b141b91daf40f22ebd99998b1..f4bf373fa08dbbcb94b583826378412b211a9be6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,7 +29,7 @@ "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.fixAll": true, - "source.organizeImports": true + "source.organizeImports": false }, "editor.defaultFormatter": "esbenp.prettier-vscode", "peacock.color": "#2aa63d", diff --git a/src/API.ts b/src/API.ts index b4f8feaa739fe930ca7ad890b017751a0fe16a30..8b3ec5b1f9bd5c854db3a3e0d346c3077b5d6a34 100644 --- a/src/API.ts +++ b/src/API.ts @@ -1,11 +1,27 @@ import axios from 'axios' +import { useQuery } from 'react-query' +import { toast } from 'react-toastify' import { User } from './models/user.model' -export const fetchWhoAmI = async () => { +const fetchWhoAmI = async () => { const { data } = await axios.get<User | null>('/api/common/WhoAmI') return data } +export const useWhoAmI = () => { + return useQuery({ + queryKey: ['WhoAmI'], + queryFn: fetchWhoAmI, + retry: false, + onError: () => { + console.log('error whoami') + toast.error('Accès refusé, veuillez vous connecter') + }, + refetchOnMount: false, + }) +} + export const fetchLogout = async () => { - await axios.get('/api/common/Logout') + console.log('fetch logout') + return await axios.get('/Logout') } diff --git a/src/App.tsx b/src/App.tsx index f3ba18986e26c21109d696c98b5eca30ac3b3d98..35fe4e0d3b67fb6b7fede86e31c980e4166d9bb1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,32 +2,19 @@ import { QueryClient, QueryClientProvider } from 'react-query' import { BrowserRouter } from 'react-router-dom' import { ToastContainer } from 'react-toastify' import 'react-toastify/dist/ReactToastify.css' -import { fetchWhoAmI } from './API' import Layout from './components/Layout/Layout' import Router from './components/Routes/Router' -import useFindUser from './hooks/useFindUser' -import { UserContext } from './hooks/userContext' // provide the default query function to your app with defaultOptions -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - queryFn: fetchWhoAmI, - }, - }, -}) +const queryClient = new QueryClient() function App() { - const { user, setUser, isLoading } = useFindUser() - return ( <BrowserRouter> <QueryClientProvider client={queryClient}> - <UserContext.Provider value={{ user, setUser, isLoading }}> - <Layout> - <Router /> - </Layout> - </UserContext.Provider> + <Layout> + <Router /> + </Layout> <ToastContainer theme="colored" /> </QueryClientProvider> </BrowserRouter> diff --git a/src/components/Consents/Consents.tsx b/src/components/Consents/Consents.tsx index 23cee71179f7453373205a13dea6a2984ca0e0bb..d14ab99daf8b7906513e4f875ae628b87b554386 100644 --- a/src/components/Consents/Consents.tsx +++ b/src/components/Consents/Consents.tsx @@ -13,15 +13,9 @@ import 'ag-grid-community/dist/styles/ag-grid.css' import 'ag-grid-community/dist/styles/ag-theme-alpine-dark.css' import { AgGridReact } from 'ag-grid-react' import { DateTime } from 'luxon' -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useWhoAmI } from '../../API' import { getAxiosXSRFHeader } from '../../axios.config' -import { UserContext, UserContextProps } from '../../hooks/userContext' import { IConsent } from '../../models/consent.model' import { ConsentService } from '../../services/consent.service' import DowloadModal from './DowloadModal' @@ -39,7 +33,7 @@ const Consents: React.FC = () => { const [page, setPage] = useState<number>(0) const [rowsPerPage, setRowsPerPage] = useState<number>(50) const [totalRows, setTotalRows] = useState<number>(50) - const { user }: Partial<UserContextProps> = useContext(UserContext) + const { data: user } = useWhoAmI() const consentService = useMemo(() => { return new ConsentService() }, []) diff --git a/src/components/Login/Login.tsx b/src/components/Login/Login.tsx index 1f20b820b0daabb90e23d18cdd77f0a46a5bfbf6..1d7b30169e14affef64bc1bed0ba6542d4066988 100644 --- a/src/components/Login/Login.tsx +++ b/src/components/Login/Login.tsx @@ -1,9 +1,10 @@ import React from 'react' -import { useAuth } from '../../hooks/useAuth' import './login.scss' const Login: React.FC = () => { - const { loginUser } = useAuth() + const loginUser = () => { + window.location.href = '/OAuth2Login' + } return ( <div className="login"> diff --git a/src/components/Newsletter/ImagePicker/ImagePicker.tsx b/src/components/Newsletter/ImagePicker/ImagePicker.tsx index fa7946a0c46f3de2284ccd612410514c889358a8..6a58e17e0546ce5183dae034790add36b2385d17 100644 --- a/src/components/Newsletter/ImagePicker/ImagePicker.tsx +++ b/src/components/Newsletter/ImagePicker/ImagePicker.tsx @@ -1,7 +1,7 @@ import Pagination from '@material-ui/lab/Pagination' -import React, { useContext, useEffect, useState } from 'react' +import React, { useEffect, useState } from 'react' +import { useWhoAmI } from '../../../API' import { getAxiosXSRFHeader } from '../../../axios.config' -import { UserContext, UserContextProps } from '../../../hooks/userContext' import { NewsletterService } from '../../../services/newsletter.service' import { EditorType } from '../CustomEditor' import Modal from '../Modal/Modal' @@ -16,12 +16,12 @@ const ImagePicker: React.FC<ImagePickerProps> = ({ imageURL, handleChange, }) => { + const { data: user } = useWhoAmI() const [imageNames, setImageNames] = useState<string[][]>([]) const [selectedImageURL, setSelectedImageURL] = useState<string>( imageURL && imageURL !== null ? imageURL : '' ) const [openModal, setOpenModal] = useState<boolean>(false) - const { user }: Partial<UserContextProps> = useContext(UserContext) const [currentPage, setCurrentPage] = useState(1) const [pageCount, setPageCount] = useState<number>(1) const [preSelectImage, setPreSelectImage] = useState<string>('') diff --git a/src/components/Newsletter/Newsletter.tsx b/src/components/Newsletter/Newsletter.tsx index 8d2fbf56dd7439e2a333b96d39dcee768fc2befc..63a192db82190b664b55720b738aa92bc463d0bc 100644 --- a/src/components/Newsletter/Newsletter.tsx +++ b/src/components/Newsletter/Newsletter.tsx @@ -1,12 +1,6 @@ -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { useWhoAmI } from '../../API' import { getAxiosXSRFHeader } from '../../axios.config' -import { UserContext, UserContextProps } from '../../hooks/userContext' import { IMailSubject } from '../../models/mailSubject.model' import { IMonthlyInfo } from '../../models/monthlyInfo.model' import { IMonthlyNews } from '../../models/monthlyNews.model' @@ -30,9 +24,9 @@ export type ContentItems = | '' const Newsletter: React.FC = () => { - /** - * Display previous month until the newsletter is sent on the 3rd day of the month - */ + const { data: user } = useWhoAmI() + + /** Display previous month until the newsletter is sent on the 3rd day of the month */ const getCurrentNewsletterDate = (): Date => { const today = new Date() const currentDay = today.getDate() @@ -57,7 +51,6 @@ const Newsletter: React.FC = () => { const [isLoading, setIsLoading] = useState<boolean>(false) const [warningModal, setWarningModal] = useState<boolean>(false) const [toDelete, setToDelete] = useState<ContentItems>('') - const { user }: Partial<UserContextProps> = useContext(UserContext) const newsletterService = useMemo(() => { return new NewsletterService() }, []) diff --git a/src/components/Popups/Popups.tsx b/src/components/Popups/Popups.tsx index f5a8a2ebec3bfeffeb8c51fc9af8191784a3ec02..5f868efb8241533a726f9b3e30f539ae242e64e8 100644 --- a/src/components/Popups/Popups.tsx +++ b/src/components/Popups/Popups.tsx @@ -1,9 +1,9 @@ import { DateTime } from 'luxon' -import React, { useCallback, useContext, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' +import { useWhoAmI } from '../../API' import { getAxiosXSRFHeader } from '../../axios.config' import { CheckboxType } from '../../enum/checkboxType.enum' -import { UserContext, UserContextProps } from '../../hooks/userContext' import { ICustomPopup, PopupDuration } from '../../models/customPopup.model' import { Option, @@ -34,6 +34,8 @@ const OPTIONS: Option[] = [ ] const Popups: React.FC = () => { + const { data: user } = useWhoAmI() + const [refreshData, setRefreshData] = useState(false) const [isLoading, setIsLoading] = useState(false) const [partnersInfo, setPartnersInfo] = useState<IPartnersInfo>({ @@ -54,16 +56,12 @@ const Popups: React.FC = () => { duration: 0, }) - const { user }: Partial<UserContextProps> = useContext(UserContext) - const isPartnerNotificationOn = () => partnersInfo.enedis_failure || partnersInfo.egl_failure || partnersInfo.grdf_failure - /** - * Only one type of popup can be enabled - */ + /** Only one type of popup can be enabled */ const isPageValid = () => !(isPartnerNotificationOn() && customPopup.popupEnabled) @@ -105,6 +103,7 @@ const Popups: React.FC = () => { })) } + // clean this const handleCancel = useCallback(() => { setRefreshData(true) }, [setRefreshData]) diff --git a/src/components/Prices/PriceSection.tsx b/src/components/Prices/PriceSection.tsx index 9b67c82694c71eb3b7f0d219a775a4ddbba44116..8e6d6d87aebd5f4865fb38681d96c4ee2ed7cbea 100644 --- a/src/components/Prices/PriceSection.tsx +++ b/src/components/Prices/PriceSection.tsx @@ -1,13 +1,13 @@ import dayjs from 'dayjs' import timezone from 'dayjs/plugin/timezone' import utc from 'dayjs/plugin/utc' -import React, { useCallback, useContext, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' +import { useWhoAmI } from '../../API' import arrowDown from '../../assets/icons/down-arrow.png' import { getAxiosXSRFHeader } from '../../axios.config' import { FluidType } from '../../enum/fluidTypes' import { FrequencyInMonth } from '../../enum/frequency.enum' -import { UserContext, UserContextProps } from '../../hooks/userContext' import { IPrice } from '../../models/price.model' import { PricesService } from '../../services/prices.service' import Loader from '../Loader/Loader' @@ -23,6 +23,7 @@ interface PriceSectionProps { } const PriceSection: React.FC<PriceSectionProps> = ({ fluid, frequency }) => { + const { data: user } = useWhoAmI() const [prices, setPrices] = useState<IPrice[]>([]) const [nextPrice, setNextPrice] = useState<IPrice>() const [isLoading, setIsLoading] = useState<boolean>(false) @@ -35,7 +36,6 @@ const PriceSection: React.FC<PriceSectionProps> = ({ fluid, frequency }) => { startDate: '', endDate: null, }) - const { user }: Partial<UserContextProps> = useContext(UserContext) const maxPerList = 8 const handlePriceSelection = useCallback((val: string) => { diff --git a/src/components/Routes/Router.tsx b/src/components/Routes/Router.tsx index 93dd73d40e8711841f61e415aa7e7be08c824b53..3fcb44293aecccf8944b07d338667742f6b83676 100644 --- a/src/components/Routes/Router.tsx +++ b/src/components/Routes/Router.tsx @@ -1,7 +1,6 @@ import { Suspense } from 'react' -import { useQuery } from 'react-query' import { Navigate, Route, Routes } from 'react-router-dom' -import { fetchWhoAmI } from '../../API' +import { useWhoAmI } from '../../API' import Consents from '../Consents/Consents' import Loader from '../Loader/Loader' import Login from '../Login/Login' @@ -39,15 +38,7 @@ export const routes = Object.keys(links).map(key => ({ })) const Router = () => { - // const { user } = useContext(UserContext) - const { - data: user, - error, - isFetching, - } = useQuery({ - queryKey: ['WhoAmI'], - queryFn: fetchWhoAmI, - }) + const { data: user, error, isFetching } = useWhoAmI() if (isFetching) return <Loader /> @@ -68,6 +59,7 @@ const Router = () => { {user.isAdmin && ( <Route path={links.consents.path} element={<Consents />} /> )} + <Route path="/login" element={<Login />} /> <Route path="*" element={<Navigate replace to={links.newsletter.path} />} diff --git a/src/components/SideBar/SideBar.tsx b/src/components/SideBar/SideBar.tsx index c47991180a6535ff5062faf24b6433705f0cc416..a00cda242156e115f1e6fa7282c129d25a26b59a 100644 --- a/src/components/SideBar/SideBar.tsx +++ b/src/components/SideBar/SideBar.tsx @@ -1,56 +1,64 @@ import React from 'react' +import { useMutation, useQueryClient } from 'react-query' +import { NavLink, useNavigate } from 'react-router-dom' +import { fetchLogout, useWhoAmI } from '../../API' +import logo from '../../assets/icons/ecolyo-logo.svg' +import { routes } from '../Routes/Router' import './sidebar.scss' const SideBar: React.FC = () => { - return <div /> - // const queryClient = useQueryClient() - // const { data: user } = useQuery({ - // queryKey: ['WhoAmI'], - // queryFn: fetchWhoAmI, - // }) + const queryClient = useQueryClient() + const navigate = useNavigate() + const { data: user } = useWhoAmI() - // const logout = queryClient.removeQueries('WhoAmI') + const { mutate: logout } = useMutation({ + mutationFn: fetchLogout, + onSuccess: () => { + queryClient.clear() + navigate('/login') + }, + }) - // if (!user) return <div /> + if (!user) return <div /> - // return ( - // <nav className="menu"> - // <div className="logo-container"> - // <img src={logo} alt="Ecolyo logo" className="logo" /> - // </div> - // <div className="menu-list"> - // {user && - // routes.map( - // route => - // (!route.adminOnly || user.isAdmin) && ( - // <NavLink - // key={route.label} - // to={route.path} - // className={({ isActive }) => (isActive ? 'active' : '')} - // > - // {route.label} - // </NavLink> - // ) - // )} - // {process.env.NODE_ENV === 'development' && ( - // <a href="/doc/" target="_blank"> - // Swagger doc - // </a> - // )} - // </div> - // <div className="bottom"> - // {user ? ( - // <button className="btnValid" onClick={() => logout}> - // Logout - // </button> - // ) : ( - // <NavLink to="/login" className="active"> - // Login - // </NavLink> - // )} - // </div> - // </nav> - // ) + return ( + <nav className="menu"> + <div className="logo-container"> + <img src={logo} alt="Ecolyo logo" className="logo" /> + </div> + <div className="menu-list"> + {user && + routes.map( + route => + (!route.adminOnly || user.isAdmin) && ( + <NavLink + key={route.label} + to={route.path} + className={({ isActive }) => (isActive ? 'active' : '')} + > + {route.label} + </NavLink> + ) + )} + {process.env.NODE_ENV === 'development' && ( + <a href="/doc/" target="_blank"> + Swagger doc + </a> + )} + </div> + <div className="bottom"> + {user ? ( + <button className="btnValid" onClick={() => logout()}> + Logout + </button> + ) : ( + <NavLink to="/login" className="active"> + Login + </NavLink> + )} + </div> + </nav> + ) } export default SideBar diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts deleted file mode 100644 index dd4cc745f85a855a0f2dc2fbe93c0bf93257cb3d..0000000000000000000000000000000000000000 --- a/src/hooks/useAuth.ts +++ /dev/null @@ -1,50 +0,0 @@ -import axios from 'axios' -import { useContext, useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { links } from '../components/Routes/Router' -import { UserContext } from './userContext' - -interface Auth { - loginUser: () => Promise<void> - error: unknown - logoutUser: () => void -} -export const useAuth = (): Auth => { - const navigate = useNavigate() - const { setUser } = useContext(UserContext) - const [error, setError] = useState<unknown>(null) - - //login user - const loginUser = async (): Promise<void> => { - try { - window.location.href = '/OAuth2Login' - await setUserContext() - } catch (e) { - setError(e) - } - } - - const logoutUser = async (): Promise<void> => { - try { - if (setUser) setUser(null) - window.location.href = '/Logout' - } catch (e) { - setError(e) - } - } - - //set user in context and push them home - const setUserContext = async (): Promise<void> => { - try { - const { data } = await axios.get(`/api/common/WhoAmI`) - if (data && setUser) { - setUser(data) - navigate(links.newsletter.path) - } - } catch (e) { - setError(e) - } - } - - return { loginUser, error, logoutUser } -} diff --git a/src/hooks/useFindUser.ts b/src/hooks/useFindUser.ts deleted file mode 100644 index e9fb3132867aebf3cd03d8f8a440fba0ef23d4aa..0000000000000000000000000000000000000000 --- a/src/hooks/useFindUser.ts +++ /dev/null @@ -1,33 +0,0 @@ -import axios from 'axios' -import { useEffect, useState } from 'react' -import { toast } from 'react-toastify' -import { User } from '../models/user.model' - -const useFindUser = () => { - const [user, setUser] = useState<User | null>(null) - const [isLoading, setLoading] = useState<boolean>(true) - - useEffect(() => { - async function findUser() { - try { - const { - data: { user }, - } = await axios.get<{ user: User | null }>(`/api/common/WhoAmI`) - if (user) { - setUser(user) - setLoading(false) - } - } catch (error) { - toast.error('Accès refusé, veuillez vous connecter') - } - } - findUser() - }, []) - return { - user, - setUser, - isLoading, - } -} - -export default useFindUser diff --git a/src/hooks/userContext.ts b/src/hooks/userContext.ts deleted file mode 100644 index e2f6936764e83d2e058302e7fb4a835aa859ff82..0000000000000000000000000000000000000000 --- a/src/hooks/userContext.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createContext, Dispatch, SetStateAction } from 'react' -import { User } from '../models/user.model' - -export interface UserContextProps { - user: User | null - setUser: Dispatch<SetStateAction<User | null>> - isLoading: boolean -} -export const UserContext = createContext<Partial<UserContextProps>>({})