Skip to content
Snippets Groups Projects
Commit 3a37a4e3 authored by Bastien DUMONT's avatar Bastien DUMONT :angel:
Browse files

Merge branch 'feat/US827-custom-alert' into 'dev'

Feat/us827 custom alert

See merge request web-et-numerique/llle_project/backoffice-client!64
parents f2f45719 72cb11d7
No related branches found
No related tags found
6 merge requests!96Deploy OpenShift v2,!95MEP fix liens undefined,!91MEP: removed Meilisearch,!79Fix: nginx unprivileged image,!77Back-office SGE before canary release,!64Feat/us827 custom alert
Pipeline #42763 passed
Showing
with 106 additions and 67 deletions
...@@ -2,8 +2,9 @@ NODE_TLS_REJECT_UNAUTHORIZED = '0' ...@@ -2,8 +2,9 @@ NODE_TLS_REJECT_UNAUTHORIZED = '0'
HTTPS=true HTTPS=true
SSL_CRT_FILE=cert.pem SSL_CRT_FILE=cert.pem
SSL_KEY_FILE=key.pem SSL_KEY_FILE=key.pem
# Common
HOSTNAME= # Common settings
HOSTNAME=localhost
ADMIN_ROLE= ADMIN_ROLE=
DEBUG_MODE= DEBUG_MODE=
MOCK_OAUTH2= MOCK_OAUTH2=
...@@ -22,4 +23,7 @@ LOGOUT_URL= ...@@ -22,4 +23,7 @@ LOGOUT_URL=
# Access to the database # Access to the database
DATABASE_USER= DATABASE_USER=
DATABASE_PASSWORD= DATABASE_PASSWORD=
DATABASE_NAME= DATABASE_NAME=
\ No newline at end of file
SGE_API_TOKEN=
MEILI_MASTER_KEY=
\ No newline at end of file
image: docker:git default:
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/node:14.19.3-alpine
services: services:
- docker:dind - name: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/docker:20.10.9-dind
alias: docker
variables: variables:
DOCKER_DRIVER: overlay2 DEPENDENCY_PROXY: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/
DOCKER_TLS_CERTDIR: ''
stages: stages:
- build
- quality - quality
- build
build-test: build-test:
stage: build stage: build
...@@ -40,13 +40,15 @@ build: ...@@ -40,13 +40,15 @@ build:
sonarqube: sonarqube:
stage: quality stage: quality
only: only:
- dev
- merge_requests - merge_requests
when: manual image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/sonarsource/sonar-scanner-cli:4
image: registry.forge.grandlyon.com/apoyen2/sonnar-scanner-gl:master variables:
before_script: SONAR_USER_HOME: '${CI_PROJECT_DIR}/.sonar' # Defines the location of the analysis task cache
- export NODE_PATH=$NODE_PATH:`npm root -g` GIT_DEPTH: '0' # T
- npm install -g typescript cache:
key: '${CI_JOB_NAME}'
paths:
- .sonar/cache
script: script:
- > - >
sonar-scanner sonar-scanner
......
...@@ -24,5 +24,9 @@ ...@@ -24,5 +24,9 @@
"source.fixAll.eslint": true "source.fixAll.eslint": true
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode", "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"
}
} }
...@@ -16,11 +16,12 @@ You can then clone the app repository and install dependencies: ...@@ -16,11 +16,12 @@ You can then clone the app repository and install dependencies:
```sh ```sh
$ git clone https://forge.grandlyon.com/web-et-numerique/llle_project/backoffice-client.git $ git clone https://forge.grandlyon.com/web-et-numerique/llle_project/backoffice-client.git
$ cd backoffice-client $ cd backoffice-client
yarn
``` ```
## Local usage ## 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 In order to launch the projet in local with the backend working launch the following command
......
...@@ -52,7 +52,8 @@ services: ...@@ -52,7 +52,8 @@ services:
PMA_HOST: database-agent PMA_HOST: database-agent
backend: 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: depends_on:
database-agent: database-agent:
condition: service_healthy condition: service_healthy
......
import { AxiosRequestConfig } from 'axios'
export const getAxiosXSRFHeader = (token: string): AxiosRequestConfig => ({
headers: {
'XSRF-TOKEN': token,
},
})
...@@ -25,6 +25,7 @@ import DowloadModal from './DowloadModal' ...@@ -25,6 +25,7 @@ import DowloadModal from './DowloadModal'
import { ConsentService } from '../../services/consent.service' import { ConsentService } from '../../services/consent.service'
import { UserContextProps, UserContext } from '../../hooks/userContext' import { UserContextProps, UserContext } from '../../hooks/userContext'
import { IConsent } from '../../models/consent.model' import { IConsent } from '../../models/consent.model'
import { getAxiosXSRFHeader } from '../../axios.config'
const Consents: React.FC = () => { const Consents: React.FC = () => {
const [gridApi, setGridApi] = useState<GridApi | null>(null) const [gridApi, setGridApi] = useState<GridApi | null>(null)
...@@ -87,12 +88,14 @@ const Consents: React.FC = () => { ...@@ -87,12 +88,14 @@ const Consents: React.FC = () => {
headerName: 'Nom', headerName: 'Nom',
initialWidth: 200, initialWidth: 200,
filter: true, filter: true,
cellStyle: { 'text-transform': 'uppercase' },
}, },
{ {
field: 'firstname', field: 'firstname',
headerName: 'Prénom', headerName: 'Prénom',
initialWidth: 200, initialWidth: 200,
filter: true, filter: true,
cellStyle: { 'text-transform': 'capitalize' },
}, },
]) ])
const handleChangePage = useCallback( const handleChangePage = useCallback(
...@@ -131,13 +134,13 @@ const Consents: React.FC = () => { ...@@ -131,13 +134,13 @@ const Consents: React.FC = () => {
if (newSearch) { if (newSearch) {
consentsData = await consentService.searchConsent( consentsData = await consentService.searchConsent(
newSearch, newSearch,
user.xsrftoken getAxiosXSRFHeader(user.xsrftoken)
) )
} else { } else {
const consentPagination = await consentService.getConsents( const consentPagination = await consentService.getConsents(
rowsPerPage, rowsPerPage,
page, page,
user.xsrftoken getAxiosXSRFHeader(user.xsrftoken)
) )
consentsData = consentPagination && consentPagination.rows consentsData = consentPagination && consentPagination.rows
} }
...@@ -235,7 +238,7 @@ const Consents: React.FC = () => { ...@@ -235,7 +238,7 @@ const Consents: React.FC = () => {
const consentsPaginationData = await consentService.getConsents( const consentsPaginationData = await consentService.getConsents(
rowsPerPage, rowsPerPage,
page, page,
user.xsrftoken getAxiosXSRFHeader(user.xsrftoken)
) )
if (consentsPaginationData) { if (consentsPaginationData) {
setConsents(consentsPaginationData.rows) setConsents(consentsPaginationData.rows)
...@@ -257,12 +260,12 @@ const Consents: React.FC = () => { ...@@ -257,12 +260,12 @@ const Consents: React.FC = () => {
<div className={styles.content + ' content'}> <div className={styles.content + ' content'}>
<div className={styles.searchField}> <div className={styles.searchField}>
<div className={styles.inputGroup}> <div className={styles.inputGroup}>
<label htmlFor="search">N° PDL / Nom: </label> <label htmlFor="search">Recherche</label>
<input <input
value={search} value={search}
name="search" name="search"
type="text" type="text"
placeholder="N° PDL / Nom" placeholder="N°PDL, Nom, Prénom..."
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
handleSearchChange(e.target.value) handleSearchChange(e.target.value)
} }
......
...@@ -7,12 +7,7 @@ ...@@ -7,12 +7,7 @@
height: $small-nav-height !important; height: $small-nav-height !important;
} }
.content { .content {
padding-top: $small-nav-height !important;
margin-top: 1rem !important;
background: #121212;
min-height: 100vh;
background: $dark-bg; background: $dark-bg;
min-height: 100vh;
} }
.searchField { .searchField {
max-width: 750px; max-width: 750px;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
height: 3rem; height: 3rem;
margin: auto; margin: auto;
max-width: 250px; max-width: 250px;
@media screen and(min-width: $width-desktop) { @media screen and (min-width: $width-desktop) {
position: relative; position: relative;
left: -2rem; left: -2rem;
} }
...@@ -16,10 +16,8 @@ ...@@ -16,10 +16,8 @@
@include text-large(); @include text-large();
color: white; color: white;
font-weight: 600; font-weight: 600;
margin: 0 0.4rem;
min-width: 150px; min-width: 150px;
text-align: center; text-align: center;
margin: auto;
} }
.arrow { .arrow {
cursor: pointer; cursor: pointer;
......
import React from 'react'
import { stateToHTML } from 'draft-js-export-html' 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 { Editor, EditorState } from 'react-draft-wysiwyg'
import CustomLink from './CustomLink'
import './customEditor.scss' import './customEditor.scss'
import CustomLink from './CustomLink'
interface CustomEditorProps { interface CustomEditorProps {
baseState: EditorState baseState: EditorState
......
import React from 'react' import { ContentState, EditorState, Modifier } from 'draft-js'
import { EditorState, Modifier, ContentState } from 'draft-js'
import htmlToDraft from 'html-to-draftjs' import htmlToDraft from 'html-to-draftjs'
import { useState } from 'react' import React, { useState } from 'react'
interface EcolyoLinkProps { interface EcolyoLinkProps {
onChange?: (editorState: EditorState) => void onChange?: (editorState: EditorState) => void
......
...@@ -19,6 +19,7 @@ import MonthlyNews from '../MonthlyNews/MonthlyNews' ...@@ -19,6 +19,7 @@ import MonthlyNews from '../MonthlyNews/MonthlyNews'
import Loader from '../Loader/Loader' import Loader from '../Loader/Loader'
import Modal from '../Modal/Modal' import Modal from '../Modal/Modal'
import './editing.scss' import './editing.scss'
import { getAxiosXSRFHeader } from '../../axios.config'
export type ContentItems = export type ContentItems =
| 'monthlyInfo' | 'monthlyInfo'
...@@ -58,7 +59,11 @@ const Editing: React.FC = () => { ...@@ -58,7 +59,11 @@ const Editing: React.FC = () => {
const handleSaveSubject = async (): Promise<void> => { const handleSaveSubject = async (): Promise<void> => {
if (user) { if (user) {
await newsletterService.saveMailSubject(date, subject, user.xsrftoken) await newsletterService.saveMailSubject(
date,
subject,
getAxiosXSRFHeader(user.xsrftoken)
)
setIsTouched(false) setIsTouched(false)
} }
} }
...@@ -69,7 +74,7 @@ const Editing: React.FC = () => { ...@@ -69,7 +74,7 @@ const Editing: React.FC = () => {
date, date,
info, info,
imageURL, imageURL,
user.xsrftoken getAxiosXSRFHeader(user.xsrftoken)
) )
setIsTouched(false) setIsTouched(false)
} }
...@@ -81,14 +86,19 @@ const Editing: React.FC = () => { ...@@ -81,14 +86,19 @@ const Editing: React.FC = () => {
date, date,
title, title,
content, content,
user.xsrftoken getAxiosXSRFHeader(user.xsrftoken)
) )
setIsTouched(false) setIsTouched(false)
} }
} }
const handleSavePoll = async (): Promise<void> => { const handleSavePoll = async (): Promise<void> => {
if (user) { if (user) {
await newsletterService.savePoll(date, question, link, user.xsrftoken) await newsletterService.savePoll(
date,
question,
link,
getAxiosXSRFHeader(user.xsrftoken)
)
setIsTouched(false) setIsTouched(false)
} }
} }
...@@ -98,25 +108,37 @@ const Editing: React.FC = () => { ...@@ -98,25 +108,37 @@ const Editing: React.FC = () => {
const handleDeleteMailSubject = async (): Promise<void> => { const handleDeleteMailSubject = async (): Promise<void> => {
if (user) { if (user) {
await newsletterService.deleteMailSubject(date, user.xsrftoken) await newsletterService.deleteMailSubject(
date,
getAxiosXSRFHeader(user.xsrftoken)
)
setRefreshData(true) setRefreshData(true)
} }
} }
const handleDeleteMonthlyInfo = async (): Promise<void> => { const handleDeleteMonthlyInfo = async (): Promise<void> => {
if (user) { if (user) {
await newsletterService.deleteMonthlyInfo(date, user.xsrftoken) await newsletterService.deleteMonthlyInfo(
date,
getAxiosXSRFHeader(user.xsrftoken)
)
setRefreshData(true) setRefreshData(true)
} }
} }
const handleDeleteMonthlyNews = async (): Promise<void> => { const handleDeleteMonthlyNews = async (): Promise<void> => {
if (user) { if (user) {
await newsletterService.deleteMonthlyNews(date, user.xsrftoken) await newsletterService.deleteMonthlyNews(
date,
getAxiosXSRFHeader(user.xsrftoken)
)
setRefreshData(true) setRefreshData(true)
} }
} }
const handleDeletePoll = async (): Promise<void> => { const handleDeletePoll = async (): Promise<void> => {
if (user) { if (user) {
await newsletterService.deletePoll(date, user.xsrftoken) await newsletterService.deletePoll(
date,
getAxiosXSRFHeader(user.xsrftoken)
)
setRefreshData(true) setRefreshData(true)
} }
} }
...@@ -213,14 +235,23 @@ const Editing: React.FC = () => { ...@@ -213,14 +235,23 @@ const Editing: React.FC = () => {
async function getCurrentMonthlyNews() { async function getCurrentMonthlyNews() {
if (user) { if (user) {
const mailSubject: IMailSubject | null = const mailSubject: IMailSubject | null =
await newsletterService.getSingleMailSubject(date, user.xsrftoken) await newsletterService.getSingleMailSubject(
date,
getAxiosXSRFHeader(user.xsrftoken)
)
const montlhyInfo: IMonthlyInfo | null = const montlhyInfo: IMonthlyInfo | null =
await newsletterService.getSingleMonthlyInfo(date, user.xsrftoken) await newsletterService.getSingleMonthlyInfo(
date,
getAxiosXSRFHeader(user.xsrftoken)
)
const montlhyNews: IMonthlyNews | null = const montlhyNews: IMonthlyNews | null =
await newsletterService.getSingleMonthlyNews(date, user.xsrftoken) await newsletterService.getSingleMonthlyNews(
date,
getAxiosXSRFHeader(user.xsrftoken)
)
const poll: IPoll | null = await newsletterService.getSinglePoll( const poll: IPoll | null = await newsletterService.getSinglePoll(
date, date,
user.xsrftoken getAxiosXSRFHeader(user.xsrftoken)
) )
if (mailSubject) { if (mailSubject) {
setSubject(mailSubject.subject) setSubject(mailSubject.subject)
......
...@@ -3,22 +3,18 @@ ...@@ -3,22 +3,18 @@
@import '../../styles/config/breakpoints'; @import '../../styles/config/breakpoints';
.header { .header {
position: fixed; padding: 1.5rem;
z-index: 1500;
width: inherit; width: inherit;
background: radial-gradient( background: radial-gradient(
74.83% 76.97% at 50% 13.64%, 74.83% 76.97% at 50% 13.64%,
#343641 0%, #343641 0%,
#1b1c22 100% #1b1c22 100%
); );
height: $navigator-height;
box-shadow: 0px 5px 5px rgb(0 0 0 / 0%), 0px 3px 14px rgb(0 0 0 / 0%), 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%); 0px 8px 10px rgb(0 0 0 / 15%);
padding: 1.7rem;
} }
.content { .content {
padding: 1rem; padding: 1rem;
margin-top: $navigator-height;
} }
.subtitle { .subtitle {
margin: 1rem 0; margin: 1rem 0;
...@@ -28,7 +24,7 @@ hr { ...@@ -28,7 +24,7 @@ hr {
} }
.buttons { .buttons {
display: flex; display: flex;
@media screen and(max-width: $width-tablet) { @media screen and (max-width: $width-tablet) {
flex-direction: column; flex-direction: column;
button { button {
width: 100%; width: 100%;
......
...@@ -4,6 +4,7 @@ import Modal from '../Modal/Modal' ...@@ -4,6 +4,7 @@ import Modal from '../Modal/Modal'
import Pagination from '@material-ui/lab/Pagination' import Pagination from '@material-ui/lab/Pagination'
import SingleImage from './SingleImage' import SingleImage from './SingleImage'
import { UserContext, UserContextProps } from '../../hooks/userContext' import { UserContext, UserContextProps } from '../../hooks/userContext'
import { getAxiosXSRFHeader } from '../../axios.config'
interface ImagePickerProps { interface ImagePickerProps {
imageURL: string imageURL: string
...@@ -53,7 +54,7 @@ const ImagePicker: React.FC<ImagePickerProps> = ({ ...@@ -53,7 +54,7 @@ const ImagePicker: React.FC<ImagePickerProps> = ({
if (user) { if (user) {
const newsletterService = new NewsletterService() const newsletterService = new NewsletterService()
const images = await newsletterService.getEcogestureImages( const images = await newsletterService.getEcogestureImages(
user.xsrftoken getAxiosXSRFHeader(user.xsrftoken)
) )
//Split array depending on page numbers //Split array depending on page numbers
setpageCount(Math.ceil(images.length / imagePerPage)) setpageCount(Math.ceil(images.length / imagePerPage))
......
...@@ -11,8 +11,8 @@ const SingleImage: React.FC<SingleImageProps> = ({ ...@@ -11,8 +11,8 @@ const SingleImage: React.FC<SingleImageProps> = ({
selectedImage, selectedImage,
setSelectedImageURL, setSelectedImageURL,
}: SingleImageProps) => { }: SingleImageProps) => {
const selectImage = (imageURL: string) => { const selectImage = (targetImage: string) => {
setSelectedImageURL(imageURL) setSelectedImageURL(targetImage)
} }
return ( return (
<img <img
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
@media screen and(max-width: $width-tablet) { @media screen and (max-width: $width-tablet) {
.ecogesture-image { .ecogesture-image {
max-width: 80px; max-width: 80px;
max-height: 80px; max-height: 80px;
......
...@@ -14,9 +14,7 @@ const Layout: React.FC<LayoutProps> = ({ children }: LayoutProps) => { ...@@ -14,9 +14,7 @@ const Layout: React.FC<LayoutProps> = ({ children }: LayoutProps) => {
const test: boolean = const test: boolean =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent navigator.userAgent
) ) || window.innerWidth < 768
? true
: window.innerWidth < 768
? true ? true
: false : false
setisMobile(test) setisMobile(test)
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
height: 100vh; height: 100vh;
z-index: 1501; z-index: 1501;
@media screen and(max-width: $width-tablet) { @media screen and (max-width: $width-tablet) {
width: 0; width: 0;
display: none; display: none;
} }
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
box-shadow: 0px 5px 5px rgb(0 0 0 / 20%), 0px 3px 14px rgb(0 0 0 / 12%), 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%); 0px 8px 10px rgb(0 0 0 / 14%);
background: $dark-bg; background: $dark-bg;
@media screen and(max-width: $width-tablet) { @media screen and (max-width: $width-tablet) {
margin-left: 0; margin-left: 0;
padding-bottom: $navbar-height; padding-bottom: $navbar-height;
} }
......
.mailSubject { .mailSubject {
margin: 2rem 0;
.title { .title {
margin: 1rem 0; margin: 1rem 0;
} }
......
...@@ -5,6 +5,7 @@ import './menu.scss' ...@@ -5,6 +5,7 @@ import './menu.scss'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { UserContext } from '../../hooks/userContext' import { UserContext } from '../../hooks/userContext'
import { useAuth } from '../../hooks/useAuth' import { useAuth } from '../../hooks/useAuth'
import { Route } from '../../models/route.model'
const Menu: React.FC = () => { const Menu: React.FC = () => {
const { user } = useContext(UserContext) const { user } = useContext(UserContext)
...@@ -15,7 +16,7 @@ const Menu: React.FC = () => { ...@@ -15,7 +16,7 @@ const Menu: React.FC = () => {
<img src={logo} alt="Ecolyo logo" className="logo" /> <img src={logo} alt="Ecolyo logo" className="logo" />
</div> </div>
<div className="menu-list"> <div className="menu-list">
{routes.map((route: any, index: number) => { {routes.map((route: Route, index: number) => {
return ( return (
<NavLink key={index} to={route.path} activeClassName="active"> <NavLink key={index} to={route.path} activeClassName="active">
{route.label} {route.label}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment