diff --git a/template.env b/.env.template similarity index 79% rename from template.env rename to .env.template index bd65fa3b21069576144c633ac447fbd68eb6690b..ea19ce6ac4fa544be1331404692e841bc037339e 100644 --- a/template.env +++ b/.env.template @@ -2,8 +2,9 @@ NODE_TLS_REJECT_UNAUTHORIZED = '0' HTTPS=true SSL_CRT_FILE=cert.pem SSL_KEY_FILE=key.pem -# Common -HOSTNAME= + +# Common settings +HOSTNAME=localhost ADMIN_ROLE= DEBUG_MODE= MOCK_OAUTH2= @@ -22,4 +23,7 @@ LOGOUT_URL= # Access to the database DATABASE_USER= DATABASE_PASSWORD= -DATABASE_NAME= \ No newline at end of file +DATABASE_NAME= + +SGE_API_TOKEN= +MEILI_MASTER_KEY= \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 173b511564402f55d2ccb74eecae0029d39a38b8..d8f2bbe9fdce89115cc75b10c7173555ece49ae5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,15 +1,15 @@ -image: docker:git - -services: - - docker:dind +default: + image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:14.19.3-alpine + services: + - name: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/docker:20.10.9-dind + alias: docker variables: - DOCKER_DRIVER: overlay2 - DOCKER_TLS_CERTDIR: '' + DEPENDENCY_PROXY: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/ stages: - - build - quality + - build build-test: stage: build @@ -40,13 +40,15 @@ build: sonarqube: stage: quality only: - - dev - merge_requests - when: manual - image: registry.forge.grandlyon.com/apoyen2/sonnar-scanner-gl:master - before_script: - - export NODE_PATH=$NODE_PATH:`npm root -g` - - npm install -g typescript + image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/sonarsource/sonar-scanner-cli:4 + variables: + SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar' # Defines the location of the analysis task cache + GIT_DEPTH: '0' # T + cache: + key: '${CI_JOB_NAME}' + paths: + - .sonar/cache script: - > sonar-scanner diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a18744cb31bd103c38d9ca494c666d51d4981ce..1adc76b2940150ae9d2131cf287d205b80852562 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,9 @@ "source.fixAll.eslint": true }, "editor.defaultFormatter": "esbenp.prettier-vscode", - "peacock.color": "#2aa63d" + "peacock.color": "#2aa63d", + "sonarlint.connectedMode.project": { + "connectionId": "sonarqube-forge-grandlyon", + "projectKey": "web-et-numerique-llle-project-backoffice-client" + } } diff --git a/README.md b/README.md index 5546d2b27a43cd8b0b548679cd46d9b57739894d..c587f93a48227ac98d822b85e174b1adb4467039 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,12 @@ You can then clone the app repository and install dependencies: ```sh $ git clone https://forge.grandlyon.com/web-et-numerique/llle_project/backoffice-client.git $ cd backoffice-client +yarn ``` ## Local usage -Before launching the application, ensure you've properly filled the .env file according to the template. If needed please refer to a team member. +Before launching the application, ensure you've properly filled the **.env** file according to the .env.template. If needed please refer to a team member. In order to launch the projet in local with the backend working launch the following command diff --git a/docker-compose.local.yml b/docker-compose.local.yml index d8cbe45f9fa16e0140c8e098cc138914750721a8..eb54fa9669d09769c1b7f5bea110eab257a10837 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -52,7 +52,8 @@ services: PMA_HOST: database-agent backend: - image: registry.forge.grandlyon.com/web-et-numerique/llle_project/backoffice-server:test-US821 + # --When using yarn local-up build backoffice service image with: docker build . -t backoffice-server + image: backoffice-server depends_on: database-agent: condition: service_healthy diff --git a/src/axios.config.ts b/src/axios.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..15a9dfbbff15abe3f8a2583753675f228533e8ab --- /dev/null +++ b/src/axios.config.ts @@ -0,0 +1,7 @@ +import { AxiosRequestConfig } from 'axios' + +export const getAxiosXSRFHeader = (token: string): AxiosRequestConfig => ({ + headers: { + 'XSRF-TOKEN': token, + }, +}) diff --git a/src/components/Consents/Consents.tsx b/src/components/Consents/Consents.tsx index 5d06fdfb111a439a18f42bd232d9812f33ce190e..4b7eb3cbb54a4cae8d1c8003fe0ac8e001eb5b51 100644 --- a/src/components/Consents/Consents.tsx +++ b/src/components/Consents/Consents.tsx @@ -25,6 +25,7 @@ import DowloadModal from './DowloadModal' import { ConsentService } from '../../services/consent.service' import { UserContextProps, UserContext } from '../../hooks/userContext' import { IConsent } from '../../models/consent.model' +import { getAxiosXSRFHeader } from '../../axios.config' const Consents: React.FC = () => { const [gridApi, setGridApi] = useState<GridApi | null>(null) @@ -87,12 +88,14 @@ const Consents: React.FC = () => { headerName: 'Nom', initialWidth: 200, filter: true, + cellStyle: { 'text-transform': 'uppercase' }, }, { field: 'firstname', headerName: 'Prénom', initialWidth: 200, filter: true, + cellStyle: { 'text-transform': 'capitalize' }, }, ]) const handleChangePage = useCallback( @@ -131,13 +134,13 @@ const Consents: React.FC = () => { if (newSearch) { consentsData = await consentService.searchConsent( newSearch, - user.xsrftoken + getAxiosXSRFHeader(user.xsrftoken) ) } else { const consentPagination = await consentService.getConsents( rowsPerPage, page, - user.xsrftoken + getAxiosXSRFHeader(user.xsrftoken) ) consentsData = consentPagination && consentPagination.rows } @@ -235,7 +238,7 @@ const Consents: React.FC = () => { const consentsPaginationData = await consentService.getConsents( rowsPerPage, page, - user.xsrftoken + getAxiosXSRFHeader(user.xsrftoken) ) if (consentsPaginationData) { setConsents(consentsPaginationData.rows) @@ -257,12 +260,12 @@ const Consents: React.FC = () => { <div className={styles.content + ' content'}> <div className={styles.searchField}> <div className={styles.inputGroup}> - <label htmlFor="search">N° PDL / Nom: </label> + <label htmlFor="search">Recherche</label> <input value={search} name="search" type="text" - placeholder="N° PDL / Nom" + placeholder="N°PDL, Nom, Prénom..." onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleSearchChange(e.target.value) } diff --git a/src/components/Consents/consents.module.scss b/src/components/Consents/consents.module.scss index b2787aa883ca0ad2c5ed3702c11b9bd69721001c..2a5ef40504b1e9a05f7ab5decf7292ee2c548b29 100644 --- a/src/components/Consents/consents.module.scss +++ b/src/components/Consents/consents.module.scss @@ -7,12 +7,7 @@ height: $small-nav-height !important; } .content { - padding-top: $small-nav-height !important; - margin-top: 1rem !important; - background: #121212; - min-height: 100vh; background: $dark-bg; - min-height: 100vh; } .searchField { max-width: 750px; diff --git a/src/components/DateSelector/dateSelector.scss b/src/components/DateSelector/dateSelector.scss index 8154e6a42a9ec43b4573ea03fc3b32134af75eb1..5d079413075a9e6fbacf55fc964571cb02b1baca 100644 --- a/src/components/DateSelector/dateSelector.scss +++ b/src/components/DateSelector/dateSelector.scss @@ -8,7 +8,7 @@ height: 3rem; margin: auto; max-width: 250px; - @media screen and(min-width: $width-desktop) { + @media screen and (min-width: $width-desktop) { position: relative; left: -2rem; } @@ -16,10 +16,8 @@ @include text-large(); color: white; font-weight: 600; - margin: 0 0.4rem; min-width: 150px; text-align: center; - margin: auto; } .arrow { cursor: pointer; diff --git a/src/components/Editing/CustomEditor.tsx b/src/components/Editing/CustomEditor.tsx index fe837c1bf73e1a210926a1c03004c792c3efd39a..3fae4902f7ef9457baccbedd9812491550e54430 100644 --- a/src/components/Editing/CustomEditor.tsx +++ b/src/components/Editing/CustomEditor.tsx @@ -1,9 +1,8 @@ -import React from 'react' import { stateToHTML } from 'draft-js-export-html' -import { useCallback, useMemo, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { Editor, EditorState } from 'react-draft-wysiwyg' -import CustomLink from './CustomLink' import './customEditor.scss' +import CustomLink from './CustomLink' interface CustomEditorProps { baseState: EditorState diff --git a/src/components/Editing/CustomLink.tsx b/src/components/Editing/CustomLink.tsx index aeb17571b94e23b935ef7989350112c34f8f894c..46bb1d970f6dfce8fdc6d52023c836f0944778a0 100644 --- a/src/components/Editing/CustomLink.tsx +++ b/src/components/Editing/CustomLink.tsx @@ -1,7 +1,6 @@ -import React from 'react' -import { EditorState, Modifier, ContentState } from 'draft-js' +import { ContentState, EditorState, Modifier } from 'draft-js' import htmlToDraft from 'html-to-draftjs' -import { useState } from 'react' +import React, { useState } from 'react' interface EcolyoLinkProps { onChange?: (editorState: EditorState) => void diff --git a/src/components/Editing/Editing.tsx b/src/components/Editing/Editing.tsx index 30637e6161271c6277826f0c700afabe4d82484d..4de59e72423eb5da231fed147d99c0586f1b790d 100644 --- a/src/components/Editing/Editing.tsx +++ b/src/components/Editing/Editing.tsx @@ -19,6 +19,7 @@ import MonthlyNews from '../MonthlyNews/MonthlyNews' import Loader from '../Loader/Loader' import Modal from '../Modal/Modal' import './editing.scss' +import { getAxiosXSRFHeader } from '../../axios.config' export type ContentItems = | 'monthlyInfo' @@ -58,7 +59,11 @@ const Editing: React.FC = () => { const handleSaveSubject = async (): Promise<void> => { if (user) { - await newsletterService.saveMailSubject(date, subject, user.xsrftoken) + await newsletterService.saveMailSubject( + date, + subject, + getAxiosXSRFHeader(user.xsrftoken) + ) setIsTouched(false) } } @@ -69,7 +74,7 @@ const Editing: React.FC = () => { date, info, imageURL, - user.xsrftoken + getAxiosXSRFHeader(user.xsrftoken) ) setIsTouched(false) } @@ -81,14 +86,19 @@ const Editing: React.FC = () => { date, title, content, - user.xsrftoken + getAxiosXSRFHeader(user.xsrftoken) ) setIsTouched(false) } } const handleSavePoll = async (): Promise<void> => { if (user) { - await newsletterService.savePoll(date, question, link, user.xsrftoken) + await newsletterService.savePoll( + date, + question, + link, + getAxiosXSRFHeader(user.xsrftoken) + ) setIsTouched(false) } } @@ -98,25 +108,37 @@ const Editing: React.FC = () => { const handleDeleteMailSubject = async (): Promise<void> => { if (user) { - await newsletterService.deleteMailSubject(date, user.xsrftoken) + await newsletterService.deleteMailSubject( + date, + getAxiosXSRFHeader(user.xsrftoken) + ) setRefreshData(true) } } const handleDeleteMonthlyInfo = async (): Promise<void> => { if (user) { - await newsletterService.deleteMonthlyInfo(date, user.xsrftoken) + await newsletterService.deleteMonthlyInfo( + date, + getAxiosXSRFHeader(user.xsrftoken) + ) setRefreshData(true) } } const handleDeleteMonthlyNews = async (): Promise<void> => { if (user) { - await newsletterService.deleteMonthlyNews(date, user.xsrftoken) + await newsletterService.deleteMonthlyNews( + date, + getAxiosXSRFHeader(user.xsrftoken) + ) setRefreshData(true) } } const handleDeletePoll = async (): Promise<void> => { if (user) { - await newsletterService.deletePoll(date, user.xsrftoken) + await newsletterService.deletePoll( + date, + getAxiosXSRFHeader(user.xsrftoken) + ) setRefreshData(true) } } @@ -213,14 +235,23 @@ const Editing: React.FC = () => { async function getCurrentMonthlyNews() { if (user) { const mailSubject: IMailSubject | null = - await newsletterService.getSingleMailSubject(date, user.xsrftoken) + await newsletterService.getSingleMailSubject( + date, + getAxiosXSRFHeader(user.xsrftoken) + ) const montlhyInfo: IMonthlyInfo | null = - await newsletterService.getSingleMonthlyInfo(date, user.xsrftoken) + await newsletterService.getSingleMonthlyInfo( + date, + getAxiosXSRFHeader(user.xsrftoken) + ) const montlhyNews: IMonthlyNews | null = - await newsletterService.getSingleMonthlyNews(date, user.xsrftoken) + await newsletterService.getSingleMonthlyNews( + date, + getAxiosXSRFHeader(user.xsrftoken) + ) const poll: IPoll | null = await newsletterService.getSinglePoll( date, - user.xsrftoken + getAxiosXSRFHeader(user.xsrftoken) ) if (mailSubject) { setSubject(mailSubject.subject) diff --git a/src/components/Editing/editing.scss b/src/components/Editing/editing.scss index 82c9a2f6d355c2971345c6476438f3e022f2caac..e2c8832d1fb4a5cd2225123ea730f4d5fc3b96fa 100644 --- a/src/components/Editing/editing.scss +++ b/src/components/Editing/editing.scss @@ -3,22 +3,18 @@ @import '../../styles/config/breakpoints'; .header { - position: fixed; - z-index: 1500; + padding: 1.5rem; width: inherit; background: radial-gradient( 74.83% 76.97% at 50% 13.64%, #343641 0%, #1b1c22 100% ); - height: $navigator-height; box-shadow: 0px 5px 5px rgb(0 0 0 / 0%), 0px 3px 14px rgb(0 0 0 / 0%), 0px 8px 10px rgb(0 0 0 / 15%); - padding: 1.7rem; } .content { padding: 1rem; - margin-top: $navigator-height; } .subtitle { margin: 1rem 0; @@ -28,7 +24,7 @@ hr { } .buttons { display: flex; - @media screen and(max-width: $width-tablet) { + @media screen and (max-width: $width-tablet) { flex-direction: column; button { width: 100%; diff --git a/src/components/ImagePicker/ImagePicker.tsx b/src/components/ImagePicker/ImagePicker.tsx index c3625b96c00879676eece3126c4054fe91e9310f..cce9b48afe6d38d06a536ec26bfc6963314ce060 100644 --- a/src/components/ImagePicker/ImagePicker.tsx +++ b/src/components/ImagePicker/ImagePicker.tsx @@ -4,6 +4,7 @@ import Modal from '../Modal/Modal' import Pagination from '@material-ui/lab/Pagination' import SingleImage from './SingleImage' import { UserContext, UserContextProps } from '../../hooks/userContext' +import { getAxiosXSRFHeader } from '../../axios.config' interface ImagePickerProps { imageURL: string @@ -53,7 +54,7 @@ const ImagePicker: React.FC<ImagePickerProps> = ({ if (user) { const newsletterService = new NewsletterService() const images = await newsletterService.getEcogestureImages( - user.xsrftoken + getAxiosXSRFHeader(user.xsrftoken) ) //Split array depending on page numbers setpageCount(Math.ceil(images.length / imagePerPage)) diff --git a/src/components/ImagePicker/SingleImage.tsx b/src/components/ImagePicker/SingleImage.tsx index b217b1b192e1e16509615eadd41b5c5ac070a42e..94d2f5c6ba7ffd3de888f8cbdde5c3ae3d236f5b 100644 --- a/src/components/ImagePicker/SingleImage.tsx +++ b/src/components/ImagePicker/SingleImage.tsx @@ -11,8 +11,8 @@ const SingleImage: React.FC<SingleImageProps> = ({ selectedImage, setSelectedImageURL, }: SingleImageProps) => { - const selectImage = (imageURL: string) => { - setSelectedImageURL(imageURL) + const selectImage = (targetImage: string) => { + setSelectedImageURL(targetImage) } return ( <img diff --git a/src/components/ImagePicker/imagePicker.scss b/src/components/ImagePicker/imagePicker.scss index 2d1b5855e199da348c0bd4f4afc418f1c42dc837..249a99175ee8f037e6ea5cc5732257f2394792fd 100644 --- a/src/components/ImagePicker/imagePicker.scss +++ b/src/components/ImagePicker/imagePicker.scss @@ -5,7 +5,7 @@ display: flex; flex-wrap: wrap; justify-content: center; - @media screen and(max-width: $width-tablet) { + @media screen and (max-width: $width-tablet) { .ecogesture-image { max-width: 80px; max-height: 80px; diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 9dd09a7d1959a2f32d3f0f26b735934c25c34b9e..440e00c2e43f574869bf7b6311361cab24bbd4b1 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -14,9 +14,7 @@ const Layout: React.FC<LayoutProps> = ({ children }: LayoutProps) => { const test: boolean = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent - ) - ? true - : window.innerWidth < 768 + ) || window.innerWidth < 768 ? true : false setisMobile(test) diff --git a/src/components/Layout/layout.module.scss b/src/components/Layout/layout.module.scss index 3ca31348c757458d367f2e5f6f6ef21410d9b2c0..c62142238a2faf974cdd9d32f7d9fcaad7784c95 100644 --- a/src/components/Layout/layout.module.scss +++ b/src/components/Layout/layout.module.scss @@ -14,7 +14,7 @@ height: 100vh; z-index: 1501; - @media screen and(max-width: $width-tablet) { + @media screen and (max-width: $width-tablet) { width: 0; display: none; } @@ -35,7 +35,7 @@ box-shadow: 0px 5px 5px rgb(0 0 0 / 20%), 0px 3px 14px rgb(0 0 0 / 12%), 0px 8px 10px rgb(0 0 0 / 14%); background: $dark-bg; - @media screen and(max-width: $width-tablet) { + @media screen and (max-width: $width-tablet) { margin-left: 0; padding-bottom: $navbar-height; } diff --git a/src/components/MailSuject/mailSubject.scss b/src/components/MailSuject/mailSubject.scss index 87f0d6d66f181188531c6bb386e2c99e0c16b380..de327758893e42b30477e68dba7af7e83629ab7a 100644 --- a/src/components/MailSuject/mailSubject.scss +++ b/src/components/MailSuject/mailSubject.scss @@ -1,5 +1,4 @@ .mailSubject { - margin: 2rem 0; .title { margin: 1rem 0; } diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index 10b2d90fb14a4bc9e3cb223948a6c1cc23a159b6..05b3b1689ea8b3b7342b31355a8c772f465c306d 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -5,6 +5,7 @@ import './menu.scss' import { NavLink } from 'react-router-dom' import { UserContext } from '../../hooks/userContext' import { useAuth } from '../../hooks/useAuth' +import { Route } from '../../models/route.model' const Menu: React.FC = () => { const { user } = useContext(UserContext) @@ -15,7 +16,7 @@ const Menu: React.FC = () => { <img src={logo} alt="Ecolyo logo" className="logo" /> </div> <div className="menu-list"> - {routes.map((route: any, index: number) => { + {routes.map((route: Route, index: number) => { return ( <NavLink key={index} to={route.path} activeClassName="active"> {route.label} diff --git a/src/components/Menu/menu.scss b/src/components/Menu/menu.scss index 4520ef89dcb6cbf57cc69b8d28d16fbd7be6fed3..e6917fe46f2dcd36c62247699aea73b2f5f75742 100644 --- a/src/components/Menu/menu.scss +++ b/src/components/Menu/menu.scss @@ -8,11 +8,7 @@ padding: 1.875rem; box-shadow: 0px 5px 5px rgb(0 0 0 / 20%), 0px 3px 14px rgb(0 0 0 / 12%), 0px 8px 10px rgb(0 0 0 / 14%); - border-top: unset; - border-right: unset; - background-color: $grey-dark; - position: relative; - z-index: 1501; + background-color: $grey-light; .logo-container { display: flex; @@ -28,13 +24,15 @@ color: $text-grey; font-size: 1rem; + &.active { + color: $gold; + font-weight: 700; + } + &:hover { color: $gold; } } - .active { - color: $gold; - } .menu-list { margin-top: 1.5rem; } diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index a41458c379d7249eb4181c7c12c5d1e080de9e62..ad45dd3f53172f92e9408679e62610c62ad7da53 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -1,10 +1,10 @@ import React, { useContext } from 'react' import { NavLink } from 'react-router-dom' -import editing from '../../assets/icons/editing.svg' -import settings from '../../assets/icons/settings.svg' -import './navbar.scss' -import { UserContext } from '../../hooks/userContext' +import routes from '../../constants/routes.json' import { useAuth } from '../../hooks/useAuth' +import { UserContext } from '../../hooks/userContext' +import { Route } from '../../models/route.model' +import './navbar.scss' const Navbar: React.FC = () => { const { user } = useContext(UserContext) @@ -12,14 +12,11 @@ const Navbar: React.FC = () => { return ( <div className="navbar"> <div className="menu-list"> - <NavLink to={'/editing'} activeClassName="active"> - <img src={editing} className="navbar-icon" alt="Editing icon" /> - Edition - </NavLink> - <NavLink to={'/settings'} activeClassName="active"> - <img src={settings} className="navbar-icon" alt="Settings icon" /> - Paramètres - </NavLink> + {routes.map((route: Route, index: number) => ( + <NavLink key={index} to={route.path} activeClassName="active"> + {route.label} + </NavLink> + ))} </div> {user && ( <button className="btnValid logButton" onClick={logoutUser}> diff --git a/src/components/PartnersInfo/PartnersInfo.tsx b/src/components/PartnersInfo/PartnersInfo.tsx deleted file mode 100644 index 0988b3d7f27f1e8e3f2f68ad3b4dad51540e7c6d..0000000000000000000000000000000000000000 --- a/src/components/PartnersInfo/PartnersInfo.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import React, { useCallback, useContext, useEffect, useState } from 'react' -import { IPartnersInfo } from '../../models/partnersInfo.model' -import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' -import './partnersInfo.scss' -import { PartnersInfoService } from '../../services/partnersInfo.service' -import { UserContext, UserContextProps } from '../../hooks/userContext' -import Loader from '../Loader/Loader' -import { CheckboxType } from '../../enum/checkboxType.enum' - -const PartnersInfo: React.FC = () => { - const [refreshData, setRefreshData] = useState(false) - const [isLoading, setIsLoading] = useState<boolean>(false) - const [partnersInfo, setPartnersInfo] = useState<IPartnersInfo>({ - grdf_failure: false, - enedis_failure: false, - egl_failure: false, - notification_activated: false, - }) - const { user }: Partial<UserContextProps> = useContext(UserContext) - - const handleCheckboxChange = (value: boolean, type: CheckboxType): void => { - switch (type) { - case CheckboxType.GRDF: - setPartnersInfo((prevPartnersInfo) => ({ - ...prevPartnersInfo, - grdf_failure: value, - })) - break - case CheckboxType.ENEDIS: - setPartnersInfo((prevPartnersInfo) => ({ - ...prevPartnersInfo, - enedis_failure: value, - })) - break - case CheckboxType.EGL: - setPartnersInfo((prevPartnersInfo) => ({ - ...prevPartnersInfo, - egl_failure: value, - })) - break - case CheckboxType.NOTIFICATION: - setPartnersInfo((prevPartnersInfo) => ({ - ...prevPartnersInfo, - notification_activated: value, - })) - break - default: - throw new Error('Unknown checkbox type') - } - } - - const handleCancel = useCallback(() => { - setRefreshData(true) - }, [setRefreshData]) - - const resetFields = useCallback(() => { - setPartnersInfo({ - grdf_failure: false, - enedis_failure: false, - egl_failure: false, - notification_activated: false, - }) - }, [setPartnersInfo]) - - useEffect(() => { - let subscribed = true - resetFields() - setIsLoading(true) - - async function getPartnersInfo() { - if (user) { - const partnersInfoService = new PartnersInfoService() - const partnersInfoResp: IPartnersInfo | null = - await partnersInfoService.getPartnersInfo() - if (partnersInfoResp) { - setPartnersInfo({ - grdf_failure: partnersInfoResp.grdf_failure, - enedis_failure: partnersInfoResp.enedis_failure, - egl_failure: partnersInfoResp.egl_failure, - notification_activated: partnersInfoResp.notification_activated, - }) - } - } - setIsLoading(false) - } - if (subscribed) { - getPartnersInfo() - } - return () => { - subscribed = false - setRefreshData(false) - } - }, [user, refreshData, setPartnersInfo, resetFields]) - - const handleSave = async (): Promise<void> => { - if (user) { - const partnersInfoService = new PartnersInfoService() - await partnersInfoService.savePartnersInfo(partnersInfo, user.xsrftoken) - } - } - - return ( - <> - {isLoading ? ( - <Loader /> - ) : ( - <div className="partnersInfo"> - <h2>État des services des partenaires</h2> - <div> - <p className="title">Affichage de la pop-up dans Ecolyo</p> - <div className="switch_div"> - Pop-up active - <input - type="checkbox" - id="switch_notification" - onChange={(event) => { - handleCheckboxChange( - event.currentTarget.checked, - CheckboxType.NOTIFICATION - ) - }} - checked={partnersInfo.notification_activated} - /> - <label htmlFor="switch_notification"></label> - </div> - <p className="title">Services concernés</p> - <div className="switch_div"> - Panne Enedis - <input - type="checkbox" - id="switch_enedis" - onChange={(event) => { - handleCheckboxChange( - event.currentTarget.checked, - CheckboxType.ENEDIS - ) - }} - checked={partnersInfo.enedis_failure} - /> - <label htmlFor="switch_enedis"></label> - </div> - <div className="switch_div"> - Panne EGL - <input - type="checkbox" - id="switch_egl" - onChange={(event) => { - handleCheckboxChange( - event.currentTarget.checked, - CheckboxType.EGL - ) - }} - checked={partnersInfo.egl_failure} - /> - <label htmlFor="switch_egl"></label> - </div> - <div className="switch_div"> - Panne GRDF - <input - type="checkbox" - id="switch_grdf" - onChange={(event) => { - handleCheckboxChange( - event.currentTarget.checked, - CheckboxType.GRDF - ) - }} - checked={partnersInfo.grdf_failure} - /> - <label htmlFor="switch_grdf"></label> - </div> - <div className="buttons"> - <button className="btnCancel" onClick={handleCancel}> - Annuler - </button> - <button className="btnValid" onClick={handleSave}> - Sauvegarder - </button> - </div> - </div> - </div> - )} - </> - ) -} - -export default PartnersInfo diff --git a/src/components/PartnersInfo/partnersInfo.scss b/src/components/PartnersInfo/partnersInfo.scss deleted file mode 100644 index 27de6e115e2ddca455b1fe3003e36429588a5e3f..0000000000000000000000000000000000000000 --- a/src/components/PartnersInfo/partnersInfo.scss +++ /dev/null @@ -1,59 +0,0 @@ -.partnersInfo { - margin: 2rem 0; - .title { - margin: 1rem 0; - } - h2 { - margin-bottom: 1rem; - } - - .switch_div { - display: inline-block; - padding: 1rem 1rem; - min-width: 135px; - } - - input[type='checkbox'] { - width: 0; - height: 0; - visibility: hidden; - margin-bottom: 15px; - } - - label { - display: block; - width: 50px; - height: 20px; - background-color: grey; - border-radius: 15px; - position: relative; - cursor: pointer; - transition: 0.5s; - box-shadow: 0 0 20px #80808050; - } - - label::after { - content: ''; - width: 17px; - height: 17px; - background-color: #e8f5f7; - position: absolute; - border-radius: 13px; - top: 2px; - left: 2px; - transition: 0.5s; - } - - input:checked + label:after { - left: calc(100% - 3px); - transform: translateX(-100%); - } - - input:checked + label { - background-color: #e3b82a; - } - - label:active:after { - width: 34px; - } -} diff --git a/src/components/Prices/PriceSection.tsx b/src/components/Prices/PriceSection.tsx index 51aee446ba0f63f104866e7e90a2bbae3276853d..9be61bd3338810e16f7ceb88a89d11e032b6aa6e 100644 --- a/src/components/Prices/PriceSection.tsx +++ b/src/components/Prices/PriceSection.tsx @@ -13,6 +13,7 @@ import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' import timezone from 'dayjs/plugin/timezone' import PriceRow from './PriceRow' +import { getAxiosXSRFHeader } from '../../axios.config' dayjs.extend(utc) dayjs.extend(timezone) @@ -61,7 +62,10 @@ const PriceSection: React.FC<PriceSectionProps> = ({ ...priceToSave, price: parseFloat(priceToSave.price as string), } - await priceService.savePrice(formattedPrice, user.xsrftoken) + await priceService.savePrice( + formattedPrice, + getAxiosXSRFHeader(user.xsrftoken) + ) setRefreshData(true) } }, [priceToSave, user]) diff --git a/src/components/Routes/Routes.tsx b/src/components/Routes/Routes.tsx index df33cea60c8a036d70bd5e546de4ef32b92cc5e7..9e022e2e1f8c83a69de6a5624d7657f32b20db33 100644 --- a/src/components/Routes/Routes.tsx +++ b/src/components/Routes/Routes.tsx @@ -15,10 +15,10 @@ const Routes: React.FC = () => { <Switch> {user && <Redirect path="/login" to="/editing" />} <Route path="/login" component={Login} /> - <PrivateRoute path="/editing" component={Editing} exact /> - <PrivateRoute path="/prices" component={Prices} exact /> - <PrivateRoute path="/settings" component={Settings} exact /> - <PrivateRoute path="/consents" component={Consents} exact /> + <PrivateRoute exact path="/editing" component={Editing} /> + <PrivateRoute exact path="/prices" component={Prices} /> + <PrivateRoute exact path="/settings" component={Settings} /> + <PrivateRoute exact path="/consents" component={Consents} /> <Redirect path="*" to="/editing" /> </Switch> ) diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx index e7aaaa50a843cd9a3149dce1c607254c80183825..62b9b30413e8d2ab272777bd113696f38eba8b0a 100644 --- a/src/components/Settings/Settings.tsx +++ b/src/components/Settings/Settings.tsx @@ -1,17 +1,270 @@ -import React from 'react' -import PartnersInfo from '../PartnersInfo/PartnersInfo' - -const Settings: React.FC = () => { - return ( - <> - <div className="header"> - <p className="title pagetitle">Paramètres de l'appli</p> - </div> - - <div className="content"> - <PartnersInfo /> - </div> - </> - ) -} -export default Settings +import React, { useCallback, useContext, useEffect, useState } from 'react' +import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css' +import { getAxiosXSRFHeader } from '../../axios.config' +import { CheckboxType } from '../../enum/checkboxType.enum' +import { UserContext, UserContextProps } from '../../hooks/userContext' +import { ICustomPopup } from '../../models/cutomPopup.model' +import { IPartnersInfo } from '../../models/partnersInfo.model' +import { CustomPopupService } from '../../services/customPopup.service' +import { PartnersInfoService } from '../../services/partnersInfo.service' +import Loader from '../Loader/Loader' +import './settings.scss' + +const Settings: React.FC = () => { + const [refreshData, setRefreshData] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [partnersInfo, setPartnersInfo] = useState<IPartnersInfo>({ + grdf_failure: false, + enedis_failure: false, + egl_failure: false, + notification_activated: false, + }) + const [customPopup, setCustomPopup] = useState<ICustomPopup>({ + popupEnabled: false, + title: '', + description: '', + }) + const { user }: Partial<UserContextProps> = useContext(UserContext) + + const isPartnerNotificationOn = () => + partnersInfo.enedis_failure || + partnersInfo.egl_failure || + partnersInfo.grdf_failure + + const handleCheckboxChange = (value: boolean, type: CheckboxType): void => { + switch (type) { + case CheckboxType.GRDF: + setPartnersInfo((prevPartnersInfo) => ({ + ...prevPartnersInfo, + grdf_failure: value, + })) + break + case CheckboxType.ENEDIS: + setPartnersInfo((prevPartnersInfo) => ({ + ...prevPartnersInfo, + enedis_failure: value, + })) + break + case CheckboxType.EGL: + setPartnersInfo((prevPartnersInfo) => ({ + ...prevPartnersInfo, + egl_failure: value, + })) + break + case CheckboxType.CUSTOM: + setCustomPopup((prev) => ({ + ...prev, + popupEnabled: value, + })) + break + default: + throw new Error('Unknown checkbox type') + } + } + + const handlePopupChange = ( + event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, + field: 'title' | 'description' + ) => { + setCustomPopup((prev) => ({ + ...prev, + [field]: event.target.value, + })) + } + + const handleCancel = useCallback(() => { + setRefreshData(true) + }, [setRefreshData]) + + const resetFields = useCallback(() => { + setPartnersInfo({ + grdf_failure: false, + enedis_failure: false, + egl_failure: false, + notification_activated: false, + }) + }, [setPartnersInfo]) + + useEffect(() => { + let subscribed = true + resetFields() + setIsLoading(true) + + async function getSettings() { + if (user) { + const partnersInfoService = new PartnersInfoService() + const customPopupService = new CustomPopupService() + const partnersInfoData = await partnersInfoService.getPartnersInfo() + const customPopupData = await customPopupService.getCustomPopupInfo() + + if (partnersInfoData) { + setPartnersInfo({ + ...partnersInfoData, + }) + } + if (customPopupData) { + setCustomPopup({ + ...customPopupData, + }) + } + } + setIsLoading(false) + } + if (subscribed) { + getSettings() + } + return () => { + subscribed = false + setRefreshData(false) + } + }, [user, refreshData, setPartnersInfo, setCustomPopup, resetFields]) + + const handleSave = async (): Promise<void> => { + if (user) { + const partnersInfoService = new PartnersInfoService() + const customPopupService = new CustomPopupService() + const updatedPartnersInfo = { + egl_failure: partnersInfo.egl_failure, + enedis_failure: partnersInfo.enedis_failure, + grdf_failure: partnersInfo.grdf_failure, + notification_activated: isPartnerNotificationOn(), + } + await partnersInfoService.savePartnersInfo( + updatedPartnersInfo, + getAxiosXSRFHeader(user.xsrftoken) + ) + await customPopupService.saveCustomPopup( + customPopup, + getAxiosXSRFHeader(user.xsrftoken) + ) + } + } + + return ( + <> + <div className="header"> + <p className="title pagetitle">Paramètres de l'appli</p> + </div> + + <div className="content"> + {isLoading && <Loader />} + {!isLoading && ( + <> + <h1>Création de Pop-up</h1> + <div className="partnersInfo"> + <h2 className="title">Affichage des pop-up de panne</h2> + <p>Services concernés</p> + <div className="switch_div"> + <span>Panne Enedis</span> + <input + type="checkbox" + id="switch_enedis" + onChange={(event) => { + handleCheckboxChange( + event.currentTarget.checked, + CheckboxType.ENEDIS + ) + }} + checked={partnersInfo.enedis_failure} + /> + <label htmlFor="switch_enedis"></label> + </div> + <div className="switch_div"> + <span>Panne EGL</span> + <input + type="checkbox" + id="switch_egl" + onChange={(event) => { + handleCheckboxChange( + event.currentTarget.checked, + CheckboxType.EGL + ) + }} + checked={partnersInfo.egl_failure} + /> + <label htmlFor="switch_egl"></label> + </div> + <div className="switch_div"> + <span>Panne GRDF</span> + <input + type="checkbox" + id="switch_grdf" + onChange={(event) => { + handleCheckboxChange( + event.currentTarget.checked, + CheckboxType.GRDF + ) + }} + checked={partnersInfo.grdf_failure} + /> + <label htmlFor="switch_grdf"></label> + </div> + </div> + + <div className="customInfo"> + <h2 className="title">Affichage de pop-up personnalisée</h2> + <div className="switch_div"> + <span>Pop-up active</span> + <input + type="checkbox" + id="switch_popup" + checked={customPopup.popupEnabled} + onChange={(event) => { + handleCheckboxChange( + event.currentTarget.checked, + CheckboxType.CUSTOM + ) + }} + /> + <label htmlFor="switch_popup"></label> + </div> + {customPopup.popupEnabled && ( + <> + <div className="popupTitle"> + <label htmlFor="title">Titre</label> + <input + type="text" + name="title" + id="title" + placeholder="Titre" + value={customPopup.title} + onChange={(event) => handlePopupChange(event, 'title')} + /> + </div> + <div className="popupDescription"> + <label htmlFor="description">Description</label> + <textarea + name="description" + id="description" + placeholder="Description" + rows={5} + maxLength={250} + value={customPopup.description} + onChange={(event) => + handlePopupChange(event, 'description') + } + /> + <p className="count"> + {customPopup.description.length} / 250 + </p> + </div> + </> + )} + </div> + + <div className="buttons"> + <button className="btnCancel" onClick={handleCancel}> + Annuler + </button> + <button className="btnValid" onClick={handleSave}> + Sauvegarder + </button> + </div> + </> + )} + </div> + </> + ) +} + +export default Settings diff --git a/src/components/Settings/settings.scss b/src/components/Settings/settings.scss new file mode 100644 index 0000000000000000000000000000000000000000..1d4a66f96fb9de7d94787998e7baa0e30e0fac26 --- /dev/null +++ b/src/components/Settings/settings.scss @@ -0,0 +1,115 @@ +@import '../../styles/config/colors'; + +h2.title { + margin: 1rem 0; +} + +.partnersInfo { + margin: 2rem 0; + h1 { + margin-bottom: 1rem; + } + + p { + color: $text-grey; + } +} + +.popupTitle { + margin-bottom: 1.5rem; +} + +.popupTitle, +.popupDescription { + display: flex; + flex-direction: column; + gap: 0.5rem; + + label { + text-transform: uppercase; + font-weight: 700; + } + + input, + textarea { + background: inherit; + border-radius: 4px; + border: 1px solid $text-chart; + max-width: 600px; + padding: 1rem; + color: $text-grey; + font-size: 1rem; + } + + .count { + color: $text-dark; + max-width: 600px; + height: 19px; + font-weight: 400; + font-size: 0.8rem; + display: flex; + justify-content: flex-end; + } +} + +.buttons { + position: fixed; + bottom: 1rem; + display: flex; + transform: translate(-25%); + left: 50%; +} + +.switch_div { + display: inline-block; + padding: 1rem 1rem; + min-width: 135px; + + span { + color: $text-dark; + } + + input[type='checkbox'] { + width: 0; + height: 0; + visibility: hidden; + margin-bottom: 15px; + } + + label { + display: block; + width: 50px; + height: 20px; + background-color: grey; + border-radius: 15px; + position: relative; + cursor: pointer; + transition: 0.5s; + box-shadow: 0 0 20px #80808050; + } + + label::after { + content: ''; + width: 17px; + height: 17px; + background-color: #e8f5f7; + position: absolute; + border-radius: 13px; + top: 2px; + left: 2px; + transition: 0.5s; + } + + input:checked + label:after { + left: calc(100% - 3px); + transform: translateX(-100%); + } + + input:checked + label { + background-color: #e3b82a; + } + + label:active:after { + width: 34px; + } +} diff --git a/src/constants/routes.json b/src/constants/routes.json index 05020de39aaefadabbf9a746f1ab8e59bbf3a1f4..edb9f99927426d2b57ab70ebd8d3246c72a2fb22 100644 --- a/src/constants/routes.json +++ b/src/constants/routes.json @@ -1,18 +1,18 @@ [ { - "label": "Edition", - "path": "/editing" + "label": "Consentements", + "path": "/consents" }, { - "label": "Prix", - "path": "/prices" + "label": "Edition", + "path": "/editing" }, { "label": "Paramètres", "path": "/settings" }, { - "label": "Consentements", - "path": "/consents" + "label": "Prix", + "path": "/prices" } ] diff --git a/src/enum/checkboxType.enum.ts b/src/enum/checkboxType.enum.ts index 6363fcefdaaa845a8bb343bc6bd363a032f9ae1d..c5b6eb0c3b51539358ca82c8690be73a79889bdb 100644 --- a/src/enum/checkboxType.enum.ts +++ b/src/enum/checkboxType.enum.ts @@ -1,6 +1,6 @@ export enum CheckboxType { - NOTIFICATION = 0, - GRDF = 1, - ENEDIS = 2, - EGL = 3, + GRDF, + ENEDIS, + EGL, + CUSTOM, } diff --git a/src/models/cutomPopup.model.ts b/src/models/cutomPopup.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..9af8abd26f9f4609fd74d77fefc1692368dc365b --- /dev/null +++ b/src/models/cutomPopup.model.ts @@ -0,0 +1,5 @@ +export interface ICustomPopup { + description: string + popupEnabled: boolean + title: string +} diff --git a/src/models/route.model.ts b/src/models/route.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..116f14c77ce207f187cf2d5eeab39152c04ee74d --- /dev/null +++ b/src/models/route.model.ts @@ -0,0 +1,6 @@ +export interface Route { + label: string + path: string +} + +export interface Routes extends Array<Route> {} diff --git a/src/services/consent.service.ts b/src/services/consent.service.ts index b06742880732d31bee3fc3b4b94047d6dfdff1a3..fdfd233af9668048591cd4543c1030ba4894b72c 100644 --- a/src/services/consent.service.ts +++ b/src/services/consent.service.ts @@ -2,7 +2,7 @@ import { ConsentPaginationEntity, IConsentPagination, } from './../models/consent.model' -import axios from 'axios' +import axios, { AxiosRequestConfig } from 'axios' import { ConsentEntity, IConsent } from '../models/consent.model' import { toast } from 'react-toastify' import { DateTime } from 'luxon' @@ -11,18 +11,17 @@ export class ConsentService { /** * Search for consents * @param search - * @param token + * @param axiosHeaders */ public searchConsent = async ( search: string, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<IConsent[] | null> => { try { - const { data } = await axios.get(`/api/admin/consent?search=${search}`, { - headers: { - 'XSRF-TOKEN': token, - }, - }) + const { data } = await axios.get( + `/api/admin/consent?search=${search}`, + axiosHeaders + ) const consentEntities = data as ConsentEntity[] return consentEntities.map((entity) => this.parseConsent(entity)) } catch (e: any) { @@ -42,21 +41,17 @@ export class ConsentService { * Gets consents * @param limit * @param page - * @param token + * @param axiosHeaders */ public getConsents = async ( limit: number, page: number, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<IConsentPagination | null> => { try { const { data } = await axios.get( `/api/admin/consent?limit=${limit}&page=${page}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) const consentPagination = data as ConsentPaginationEntity return this.parseConsentPagination(consentPagination) diff --git a/src/services/customPopup.service.ts b/src/services/customPopup.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..63580b78d0b9aac0e1e186185584ccc71d0a92e7 --- /dev/null +++ b/src/services/customPopup.service.ts @@ -0,0 +1,42 @@ +import axios, { AxiosRequestConfig } from 'axios' +import { toast } from 'react-toastify' +import { ICustomPopup } from '../models/cutomPopup.model' + +export class CustomPopupService { + /** + * Save the customPopup info + * @param customPopup + * @param axiosHeaders + */ + public saveCustomPopup = async ( + customPopup: ICustomPopup, + axiosHeaders: AxiosRequestConfig + ): Promise<void> => { + try { + await axios.put( + `/api/admin/customPopup`, + { + ...customPopup, + }, + axiosHeaders + ) + toast.success('Pop-up personnalisée enregistrée !') + } catch (e) { + toast.error("Erreur lors de l'enregistement de la pop-up personnalisée") + console.error(e) + } + } + + /** + * Gets the custom pop-up information + */ + public getCustomPopupInfo = async (): Promise<ICustomPopup | null> => { + try { + const { data } = await axios.get(`/api/common/customPopup`) + return data as ICustomPopup + } catch (e) { + console.error('error', e) + return null + } + } +} diff --git a/src/services/newsletter.service.ts b/src/services/newsletter.service.ts index f0676360c98dccd24d497d1b8c9a95bd78a4882f..e0ef1123fbb0eaab866e3ba91ae563b4aa6593a6 100644 --- a/src/services/newsletter.service.ts +++ b/src/services/newsletter.service.ts @@ -1,4 +1,4 @@ -import axios from 'axios' +import axios, { AxiosRequestConfig } from 'axios' import { IMailSubject } from '../models/mailSubject.model' import { IMonthlyNews } from '../models/monthlyNews.model' import { IMonthlyInfo } from '../models/monthlyInfo.model' @@ -9,12 +9,12 @@ export class NewsletterService { * Saves a mail subject for selected month * @param date * @param subject - * @param token + * @param axiosHeaders */ public saveMailSubject = async ( date: Date, subject: string, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.put( @@ -24,11 +24,7 @@ export class NewsletterService { year: date.getFullYear(), subject: subject, }, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Mail subject succesfully saved !') } catch (e: any) { @@ -46,20 +42,16 @@ export class NewsletterService { /** * Gets the mail subject for selected month * @param date - * @param token + * @param axiosHeaders */ public getSingleMailSubject = async ( date: Date, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<IMailSubject | null> => { try { const { data } = await axios.get( `/api/admin/mailSubject/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) return data as IMailSubject } catch (e: any) { @@ -71,20 +63,16 @@ export class NewsletterService { /** * Deletes the mail subject for selected month * @param date - * @param token + * @param axiosHeaders */ public deleteMailSubject = async ( date: Date, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.delete( `/api/admin/mailSubject/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Mail subject succesfully deleted !') } catch (e: any) { @@ -108,7 +96,7 @@ export class NewsletterService { date: Date, info: string, image: string, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.put( @@ -119,11 +107,7 @@ export class NewsletterService { info: info, image: image, }, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Monthly info succesfully saved !') } catch (e: any) { @@ -141,20 +125,16 @@ export class NewsletterService { /** * Gets the information for selected month * @param date - * @param token + * @param axiosHeaders */ public getSingleMonthlyInfo = async ( date: Date, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<IMonthlyInfo | null> => { try { const { data } = await axios.get( `/api/admin/monthlyInfo/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) return data as IMonthlyInfo } catch (e: any) { @@ -166,20 +146,16 @@ export class NewsletterService { /** * Deletes a Monthly Info for selected month * @param date - * @param token + * @param axiosHeaders */ public deleteMonthlyInfo = async ( date: Date, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.delete( `/api/admin/monthlyInfo/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Monthly info succesfully deleted !') } catch (e: any) { @@ -204,7 +180,7 @@ export class NewsletterService { date: Date, title: string, content: string, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.put( @@ -215,11 +191,7 @@ export class NewsletterService { title: title, content: content, }, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Monthly news succesfully saved !') } catch (e: any) { @@ -237,20 +209,16 @@ export class NewsletterService { /** * Gets a news title and content for selected month * @param date - * @param token + * @param axiosHeaders */ public getSingleMonthlyNews = async ( date: Date, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<IMonthlyNews | null> => { try { const { data } = await axios.get( `/api/admin/monthlyNews/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) return data as IMonthlyNews } catch (e) { @@ -262,20 +230,16 @@ export class NewsletterService { /** * Deletes a Monthly News for selected month * @param date - * @param token + * @param axiosHeaders */ public deleteMonthlyNews = async ( date: Date, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.delete( `/api/admin/monthlyNews/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Monthly news succesfully deleted !') } catch (e: any) { @@ -300,7 +264,7 @@ export class NewsletterService { date: Date, question: string, link: string, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.put( @@ -311,11 +275,7 @@ export class NewsletterService { link: link, question: question, }, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Poll successfully saved !') } catch (e: any) { @@ -333,20 +293,16 @@ export class NewsletterService { /** * Gets a poll with question and link for selected month * @param date - * @param token + * @param axiosHeaders */ public getSinglePoll = async ( date: Date, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<IPoll | null> => { try { const { data } = await axios.get( `/api/admin/poll/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) return data as IPoll } catch (e) { @@ -358,17 +314,16 @@ export class NewsletterService { /** * Deletes a poll for selected month * @param date - * @param token + * @param axiosHeaders */ - public deletePoll = async (date: Date, token: string): Promise<void> => { + public deletePoll = async ( + date: Date, + axiosHeaders: AxiosRequestConfig + ): Promise<void> => { try { await axios.delete( `/api/admin/poll/${date.getFullYear()}/${date.getMonth() + 1}`, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) toast.success('Poll succesfully deleted !') } catch (e: any) { @@ -386,18 +341,16 @@ export class NewsletterService { /** * Gets the ecogesture images URLs */ - public getEcogestureImages = async (token: string): Promise<string[]> => { + public getEcogestureImages = async ( + axiosHeaders: AxiosRequestConfig + ): Promise<string[]> => { try { - const { data: imageNames } = await axios.get(`/api/admin/imageNames`, { - headers: { - 'XSRF-TOKEN': token, - }, - }) + const { data: imageNames } = await axios.get( + `/api/admin/imageNames`, + axiosHeaders + ) if (imageNames && imageNames !== null) { - const imageURLs = imageNames.map((image: string) => { - return `/assets/ecogesture/${image}` - }) - return imageURLs + return imageNames.map((image: string) => `/assets/ecogesture/${image}`) } return [] } catch (e) { diff --git a/src/services/partnersInfo.service.ts b/src/services/partnersInfo.service.ts index 0b930759c5f95db71a73fdee1911480391af9cb9..dfa14f863425b37accdd32cb8903f2f402ac8224 100644 --- a/src/services/partnersInfo.service.ts +++ b/src/services/partnersInfo.service.ts @@ -1,15 +1,15 @@ -import axios from 'axios' +import axios, { AxiosRequestConfig } from 'axios' import { IPartnersInfo } from '../models/partnersInfo.model' import { toast } from 'react-toastify' export class PartnersInfoService { /** * Save the partnersInfo * @param partnersInfo - * @param token + * @param axiosHeaders */ public savePartnersInfo = async ( partnersInfo: IPartnersInfo, - token: string + axiosHeaders: AxiosRequestConfig ): Promise<void> => { try { await axios.put( @@ -20,13 +20,9 @@ export class PartnersInfoService { egl_failure: partnersInfo.egl_failure, notification_activated: partnersInfo.notification_activated, }, - { - headers: { - 'XSRF-TOKEN': token, - }, - } + axiosHeaders ) - toast.success('Partners info succesfully saved !') + toast.success('Information des partenaires mises à jour !') } catch (e) { toast.error('Failed to save partners info') console.error(e) diff --git a/src/services/prices.service.ts b/src/services/prices.service.ts index 723b1360f9fbdee1744f598701b40cd5288e83f6..addd6f982b7a06d26d0a1fad279dfe37cceee5b1 100644 --- a/src/services/prices.service.ts +++ b/src/services/prices.service.ts @@ -1,19 +1,18 @@ -import axios from 'axios' +import axios, { AxiosRequestConfig } from 'axios' import { toast } from 'react-toastify' import { IPrice } from '../models/price.model' export class PricesService { /** * Save the partnersInfo * @param price - * @param token + * @param axiosHeaders */ - public savePrice = async (price: IPrice, token: string): Promise<void> => { + public savePrice = async ( + price: IPrice, + axiosHeaders: AxiosRequestConfig + ): Promise<void> => { try { - await axios.put(`/api/admin/prices`, price, { - headers: { - 'XSRF-TOKEN': token, - }, - }) + await axios.put(`/api/admin/prices`, price, axiosHeaders) toast.success('Price succesfully saved !') } catch (e) { toast.error('Failed to save price') diff --git a/src/styles/config/_colors.scss b/src/styles/config/_colors.scss index aed7855720574fed6bf2caab1dafe4b78065b8f5..2abf8f66da66f296c0319d29f77553bbd0a1c922 100644 --- a/src/styles/config/_colors.scss +++ b/src/styles/config/_colors.scss @@ -8,6 +8,8 @@ $gold: #e3b82a; $btn-gold: #f1c017; $dark-light: #1b1c22; $grey-dark: #25262b; +$grey-light: #32343d; $dark-bg: #121212; $text-grey: #e0e0e0; $text-dark: #a0a0a0; +$text-chart: #7b7b7b; diff --git a/src/styles/config/_typography.scss b/src/styles/config/_typography.scss index aff2788fa0806cc8fb29837b2b9e0594fe319a7e..b37644cf618bfd9645ff710b02f4634c548e698a 100644 --- a/src/styles/config/_typography.scss +++ b/src/styles/config/_typography.scss @@ -70,7 +70,7 @@ $main-spacing: 4px; @include baseButton(); display: inline-block; margin-left: auto; - background: transparent; + background: $grey-dark; color: $gold; border: 1px solid $text-dark; &:hover { diff --git a/src/utils/editorStateManagment.ts b/src/utils/editorStateManagment.ts index 2102519ec5f6e3c72880c9e1aa3390767c5b2edb..dd3ff893cd944156a5bc8945559c27a34d5b2ee1 100644 --- a/src/utils/editorStateManagment.ts +++ b/src/utils/editorStateManagment.ts @@ -6,6 +6,5 @@ export const convertStringToEditorState = (string: string): EditorState => { blocksFromHTML.contentBlocks, blocksFromHTML.entityMap ) - const editorState = EditorState.createWithContent(state) - return editorState + return EditorState.createWithContent(state) }