From 0c38cddbfeb20bb070ca6990011649db55ea045f Mon Sep 17 00:00:00 2001 From: Pierre-Louis Guhur Date: Sat, 3 Dec 2022 16:34:21 +0100 Subject: [PATCH] fix: refactor app --- components/Bulles.tsx | 175 ------------------------- components/DatePicker.tsx | 33 +++-- components/PatternedBackground.tsx | 2 +- components/admin/AccessResults.tsx | 26 ++-- components/admin/CandidateModalSet.tsx | 42 ++++-- components/admin/ConfirmField.tsx | 74 ++++++++--- components/admin/Private.tsx | 8 +- components/admin/Title.tsx | 19 ++- components/admin/TitleModal.tsx | 105 +++++++++++++++ pages/admin/[pid]/[tid].tsx | 52 +++++++- public/locales/en/resource.json | 15 ++- public/locales/fr/resource.json | 13 ++ services/ElectionContext.tsx | 36 ++++- services/context.tsx | 4 +- styles/scss/_admin.scss | 4 + 15 files changed, 360 insertions(+), 248 deletions(-) delete mode 100644 components/Bulles.tsx create mode 100644 components/admin/TitleModal.tsx diff --git a/components/Bulles.tsx b/components/Bulles.tsx deleted file mode 100644 index bd78823..0000000 --- a/components/Bulles.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from 'react'; -// import Plot from 'react-plotly.js'; - -function Bulles(props) { - // récupération des résultats de l'élection et stockage en tableau - const votesBrut = Object.values(props)[0]; - - // déclaration et initialisation des mentions et couleurs - const mentionsBrut = [ - 'Passable', - 'Assez bien', - 'Bien', - 'Très bien', - 'Excellent', - ]; - const couleursBrut = ['#BB9C42', '#AABA44', '#DCDF44', '#B3D849', '#61AD45']; - - //----------- Traitement des données -----------// - - // fonction d'inversement des éléments de tableau - function inverse(obj) { - var retobj = {}; - for (var key in obj) { - retobj[obj[key]] = key; - } - return retobj; - } - - // fonction de réduction d'amplitude permettant de conserver une représentation ordinale du nombre de votes sans décalage visuel trop important - /* - Pattern de calcul : - - Soient Ai, Bi, Ci, Di, Ei les nombres de votes initiaux fournis dans le tableau classé par ordre mélioratif de mention (de Passable à Excellent). Il vient : - A = 1 - B = <{[1 + (Bi/Ai)] / 40} * A> - C = <{[1 + (Ci/Bi)] / 40} * B> - D = <{[1 + (Di/Ci)] / 40} * C> - E = <{[1 + (Ei/Di)] / 40} * D> - */ - function redAmpli(tab) { - var nvTab = []; - nvTab[0] = 100; - - for (i = 1; i < tab.length; i++) { - nvTab[i] = (1 + tab[i] / tab[i - 1] / 40) * nvTab[i - 1]; - } - return nvTab; - } - - // déclaration de l'objet votes-mention et votes-couleur - var votesMentionNonOrdonnes = {}; - var votesCouleurNonOrdonnes = {}; - - // initialisation votes-mention ordonnés croissants - for (var i = 0; i < mentionsBrut.length; i++) { - votesMentionNonOrdonnes[votesBrut[i]] = mentionsBrut[i]; - votesCouleurNonOrdonnes[votesBrut[i]] = couleursBrut[i]; - } - - // déclaration des mentions-votes par ordre croissant - var votesMentionOrdonnes = inverse(votesMentionNonOrdonnes); - var votesCouleurOrdonnes = inverse(votesCouleurNonOrdonnes); - - // vérification du nombre de votes classés par ordre croissant et passés initialement en propriétés au composant - console.log( - 'Les données transmises au composant concernant le nombre de votes par mention sont : ' - ); - console.log(votesBrut); - - // vérification des mentions destinées à être associées aux votes et ordonnées initialement par ordre mélioratif - console.log( - 'Les mentions des votes sont classées initialement par ordre mélioratif de la façon suivante :' - ); - console.log(mentionsBrut); - - // vérification du nombre de votes classés par ordre croissant - console.log( - 'Les mentions-votes classées par ordre croissant de votes sont : ' - ); - console.log(votesMentionOrdonnes); - - // séparation des mentions et des votes - const mentions = Object.keys(votesMentionOrdonnes); - const votes = Object.values(votesMentionOrdonnes); - const couleurs = Object.keys(votesCouleurOrdonnes); - - // vérification des mentions et des votes prêts à être traités pour la représentation graphique - console.log( - 'La liste des mentions issue du classement par ordre croissant de votes est :' - ); - console.log(mentions); - console.log( - 'La liste du nombre de votes correspondant, classée par ordre croissant, est :' - ); - console.log(votes); - - // déclaration et initialisation des rayons de bulle pour la représentation graphique - var rayons = []; - rayons = redAmpli(votes); - - // vérification des rayons - console.log( - 'La liste des rayons à représenter graphiquement est la suivante :' - ); - console.log(rayons); - - // déclaration et initialisation des textes des bulles - const texteBulle1 = (mentions[0] + '
' + votes[0] + ' votes').toString(); - const texteBulle2 = (mentions[1] + '
' + votes[1] + ' votes').toString(); - const texteBulle3 = (mentions[2] + '
' + votes[2] + ' votes').toString(); - const texteBulle4 = (mentions[3] + '
' + votes[3] + ' votes').toString(); - const texteBulle5 = (mentions[4] + '
' + votes[4] + ' votes').toString(); - - //---------------------------------------------// - - //----------- Affichage des données -----------// - const [loading, setLoading] = React.useState(true); - React.useEffect(() => { - setTimeout(() => setLoading(false), 3000); - }); - return ( -
TBD
- - //
- // {!loading ? ( - // - // %{text}' + - // '', - // text: [texteBulle1, texteBulle2, texteBulle3, texteBulle4, texteBulle5], - // showlegend: false, - // mode: 'markers', - // marker: { - // color: [couleurs[0], couleurs[1], couleurs[2], couleurs[3], couleurs[4]], - // size: rayons - // } - // } - // ]} - // layout={{ - // width: 600, - // height: 600, - // title: 'Nombre de voix par candidat', - // xaxis: { - // showgrid: false, - // showticklabels: false, - // showline: false, - // zeroline: false, - // range: [0, 1] - // }, - // yaxis: { - // showgrid: false, - // showticklabels: false, - // showline: false, - // zeroline: false, - // range: [0, 1] - // } - // }} - // config={{ - // displayModeBar: false // this is the line that hides the bar. - // }} - // /> - // - // ) : ( - // - // )} - //
- ); -} - -export default Bulles; diff --git a/components/DatePicker.tsx b/components/DatePicker.tsx index 51dd6b7..0761cd9 100644 --- a/components/DatePicker.tsx +++ b/components/DatePicker.tsx @@ -1,12 +1,13 @@ -import { useState, forwardRef, ReactNode } from 'react'; -import { Button, Row, Col } from 'reactstrap'; -import { useTranslation } from 'next-i18next'; +import {useState, forwardRef, ReactNode} from 'react'; +import {Button, Row, Col} from 'reactstrap'; +import {useTranslation} from 'next-i18next'; import { faCalendarDays, faChevronDown, } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import DatePicker from 'react-datepicker'; +import {AppTypes, useAppContext} from '@services/context'; interface InputProps { children?: ReactNode; @@ -15,11 +16,27 @@ interface InputProps { } export type ButtonRef = HTMLButtonElement; -const CustomDatePicker = ({ date, setDate, className = '', ...props }) => { - const { t } = useTranslation(); +const CustomDatePicker = ({date, setDate, className = '', ...props}) => { + const {t} = useTranslation(); + + const [_, dispatchApp] = useAppContext(); + + const handleChange = (date) => { + const now = new Date(); + + if (+date < +now) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.date-past") + }) + } else { + setDate(date) + } + } const ExampleCustomInput = forwardRef( - ({ value, onClick }, ref) => ( + ({value, onClick}, ref) => (
- +
+ +
diff --git a/components/admin/ConfirmField.tsx b/components/admin/ConfirmField.tsx index 69fb83f..505eda4 100644 --- a/components/admin/ConfirmField.tsx +++ b/components/admin/ConfirmField.tsx @@ -14,13 +14,12 @@ import LimitDate from './LimitDate'; import Grades from './Grades'; import Private from './Private'; import Order from './Order'; -import {useElection, ElectionContextInterface} from '@services/ElectionContext'; +import {useElection, ElectionContextInterface, hasEnoughCandidates, hasEnoughGrades, checkName, canBeFinished} from '@services/ElectionContext'; import {createElection, ElectionCreatedPayload} from '@services/api'; import {getUrlVote, getUrlResults} from '@services/routes'; import {GradeItem, CandidateItem} from '@services/type'; import {sendInviteMails} from '@services/mail'; -import {gradeColors} from '@services/grades'; - +import {AppTypes, useAppContext} from '@services/context'; const submitElection = ( @@ -68,20 +67,55 @@ const ConfirmField = ({onSubmit, onSuccess, onFailure}) => { const {t} = useTranslation(); const router = useRouter(); const [election, _] = useElection(); + const [app, dispatchApp] = useAppContext(); const handleSubmit = () => { + if (!checkName(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.uncorrect-name") + }) + return + } + + if (!hasEnoughGrades(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.not-enough-grades") + }) + return + } + + if (!hasEnoughCandidates(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.not-enough-candidates") + }) + return + } + + if (!canBeFinished(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.cant-be-finished") + }) + return + } onSubmit(); submitElection(election, onSuccess, onFailure, router); } - const numCandidates = election.candidates.filter(c => c.active && c.name != "").length; - const numGrades = election.grades.filter(g => g.active && g.name != "").length; const disabled = ( - !election.name || election.name == "" || - numCandidates < 2 || - numGrades < 2 || numGrades > gradeColors.length + !checkName(election) || + !hasEnoughCandidates(election) || + !hasEnoughGrades(election) || + !canBeFinished(election) ) return ( @@ -112,17 +146,19 @@ const ConfirmField = ({onSubmit, onSuccess, onFailure}) => { - +
+ +
); diff --git a/components/admin/Private.tsx b/components/admin/Private.tsx index 2f8f2b8..627e0b8 100644 --- a/components/admin/Private.tsx +++ b/components/admin/Private.tsx @@ -9,17 +9,23 @@ import Switch from '@components/Switch'; import ListInput from '@components/ListInput'; import {ElectionTypes, useElection} from '@services/ElectionContext'; import {validateMail} from '@services/mail'; +import {AppTypes, useAppContext} from '@services/context'; const Private = () => { const {t} = useTranslation(); + const [_, dispatchApp] = useAppContext(); const [election, dispatch] = useElection(); const isEditable = !election.ref || election.ref === ""; const toggle = () => { if (!isEditable) { - return; + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.cant-set-ongoing"), + }) } dispatch({ type: ElectionTypes.SET, diff --git a/components/admin/Title.tsx b/components/admin/Title.tsx index a6e1929..4039f5e 100644 --- a/components/admin/Title.tsx +++ b/components/admin/Title.tsx @@ -1,21 +1,28 @@ /** * This component manages the title of the election */ +import {faPen} from '@fortawesome/free-solid-svg-icons'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {useElection} from '@services/ElectionContext'; import {useTranslation} from 'next-i18next'; +import {useState} from 'react'; import {Col, Container, Row} from 'reactstrap'; +import TitleModal from './TitleModal'; const TitleField = () => { const {t} = useTranslation(); const [election, _] = useElection(); + const [modal, setModal] = useState(false); + const toggle = () => setModal((m) => !m); + return ( - - - -
{t('admin.confirm-question')}
- -
+ +
+
{t('admin.confirm-question')}
+ +

{election.name}

+
); }; diff --git a/components/admin/TitleModal.tsx b/components/admin/TitleModal.tsx new file mode 100644 index 0000000..1338b9a --- /dev/null +++ b/components/admin/TitleModal.tsx @@ -0,0 +1,105 @@ +import {useState, useEffect} from 'react'; +import {Label, Input, Modal, ModalBody, Form} from 'reactstrap'; +import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons'; +import {useTranslation} from 'next-i18next'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; +import Button from '@components/Button'; +import {checkName} from '@services/ElectionContext'; +import {AppTypes, useAppContext} from '@services/context'; +import {useRouter} from 'next/router'; + + +const TitleModal = ({isOpen, toggle}) => { + const {t} = useTranslation(); + const router = useRouter(); + const [election, dispatch] = useElection(); + const [_, dispatchApp] = useAppContext(); + const [name, setName] = useState(election.name); + const disabled = name === ""; + + useEffect(() => { + setName(election.name); + }, [election.name]) + + const save = (e) => { + e.preventDefault() + + if (name === "") { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.empty-name") + }) + return + } + + dispatch({ + type: ElectionTypes.SET, + field: 'name', + value: name, + }); + + toggle(); + }; + + const handleName = (e) => { + setName(e.target.value); + }; + + return ( + +
+

{t('admin.set-title')}

+ +
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+
+ ); +}; +export default TitleModal; diff --git a/pages/admin/[pid]/[tid].tsx b/pages/admin/[pid]/[tid].tsx index ecddbd2..2bf3e15 100644 --- a/pages/admin/[pid]/[tid].tsx +++ b/pages/admin/[pid]/[tid].tsx @@ -6,7 +6,7 @@ import {serverSideTranslations} from 'next-i18next/serverSideTranslations'; import {Container, Row, Col} from 'reactstrap'; import {faArrowRight} from '@fortawesome/free-solid-svg-icons'; import {getElection, updateElection} from '@services/api'; -import {ElectionContextInterface, ElectionProvider, ElectionTypes, useElection, isClosed, canViewResults} from '@services/ElectionContext'; +import {ElectionContextInterface, ElectionProvider, ElectionTypes, useElection, isClosed, canViewResults, checkName, hasEnoughGrades, hasEnoughCandidates, canBeFinished} from '@services/ElectionContext'; import {CandidateItem, GradeItem} from '@services/type'; import {gradeColors} from '@services/grades'; import TitleField from '@components/admin/Title'; @@ -20,6 +20,7 @@ import Private from '@components/admin/Private'; import Blur from '@components/Blur'; import {getUrlResults, getUrlVote, RESULTS, VOTE} from '@services/routes'; import {sendInviteMails} from '@services/mail'; +import {AppTypes, useAppContext} from '@services/context'; export async function getServerSideProps({query, locale}) { @@ -78,6 +79,7 @@ const Spinner = () => { const HeaderRubbon = () => { const {t} = useTranslation(); const [election, dispatch] = useElection(); + const [_, dispatchApp] = useAppContext(); const router = useRouter(); const [waiting, setWaiting] = useState(false); @@ -119,6 +121,11 @@ const HeaderRubbon = () => { ); } setWaiting(false) + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "success", + message: t("success.election-closed") + }) } } @@ -172,6 +179,7 @@ const HeaderRubbon = () => { const CreateElection = ({context, token}) => { const {t} = useTranslation(); const [election, dispatch] = useElection(); + const [_, dispatchApp] = useAppContext(); const router = useRouter(); const [waiting, setWaiting] = useState(false); @@ -180,6 +188,42 @@ const CreateElection = ({context, token}) => { }, []) const handleSubmit = async () => { + if (!checkName(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.uncorrect-name") + }) + return + } + + if (!hasEnoughGrades(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.not-enough-grades") + }) + return + } + + if (!hasEnoughCandidates(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.not-enough-candidates") + }) + return + } + + if (!canBeFinished(election)) { + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "error", + message: t("error.cant-be-finished") + }) + return + } + const candidates = election.candidates.filter(c => c.active).map((c: CandidateItem) => ({name: c.name, description: c.description, image: c.image})) const grades = election.grades.filter(c => c.active).map((g: GradeItem, i: number) => ({name: g.name, value: i})) setWaiting(true) @@ -212,6 +256,12 @@ const CreateElection = ({context, token}) => { ); } setWaiting(false) + + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: "success", + message: t("success.election-updated") + }) } } diff --git a/public/locales/en/resource.json b/public/locales/en/resource.json index bb4c9b7..a04b391 100644 --- a/public/locales/en/resource.json +++ b/public/locales/en/resource.json @@ -51,17 +51,27 @@ "common.the-params": "The parameters", "common.welcome": "Welcome!", "error.at-least-2-candidates": "At least two candidates are required.", + "error.cant-set-ongoing": "You can not set this parameter for an ongoing election", "error.ended-election": "The election has ended", "error.catch22": "Unknown error...", "error.help": "Ask for our help", "error.no-title": "Please add a title to your election.", + "error.twice-same-names": "Two items have the same name.", + "error.empty-name": "The name is empty", + "error.date-past": "The date is in the past", + "error.cant-be-finished": "The election will never be closed. Add invites, access to results, or a ending date.", + "error.not-enough-grades": "Not enough grades", + "error.not-enough-candidates": "Not enough candidates", + "error.uncorrect-name": "The title is incorrect", "grades.very-good": "Very good", "grades.good": "Good", "grades.passable": "Passable", "grades.inadequate": "Inadequate", "grades.mediocre": "Mediocre", "admin.admin-title": "Voting management panel", + "admin.close-election": "Close the election", "admin.date-limit": "Set a deadline for voting", + "admin.set-title": "Set the question of the vote", "admin.step-candidate": "Candidates", "admin.step-params": "Parameters", "admin.step-confirm": "Confirm", @@ -89,7 +99,7 @@ "admin.params-title": "Your parameters", "admin.access-results": "Immediate access to the results", "admin.access-results-desc": "No one can access to the results as long as the vote is not closed.", - "admin.limit-duration": "Limit the length of the vote", + "admin.limit-duration": "Limit the duration of the vote", "admin.limit-duration-desc": "", "admin.photo": "Picture", "admin.optional": "optional", @@ -107,7 +117,6 @@ "admin.confirm-submit": "Start the vote", "admin.confirm-title": "Confirmer votre vote", "admin.confirm-edit": "Save your modifications", - "admin.success-election": "The vote has been created with success!", "admin.success-emails": "The voting link has been sent by emails to the participants.", "admin.success-copy-vote": "Copy the voting link", "admin.success-copy-result": "Copy the result link", @@ -126,6 +135,8 @@ "result.how-to-interpret": "How to understand the results", "result.will-close": "Will close in", "result.share": "Share results", + "success.election-closed": "The vote has been created with success!", + "success.election-updated": "The vote has been updated with success!", "vote.discover-mj": "Discover majority judgment", "vote.discover-mj-desc": "Developed by French researchers, majority judgment is a voting system that improves voter expressiveness and provides the best consensus.", "vote.form": "Your opinion is important to us", diff --git a/public/locales/fr/resource.json b/public/locales/fr/resource.json index 32ce9d7..cebf3a5 100644 --- a/public/locales/fr/resource.json +++ b/public/locales/fr/resource.json @@ -51,17 +51,28 @@ "common.vote": "Voter", "common.welcome": "Bienvenue !", "error.help": "Besoin d'aide ?", + "error.cant-set-ongoing": "Vous ne pouvez pas modifier ce paramètre pour une élection déjà démarrée.", "error.at-least-2-candidates": "Ajoutez au moins deux candidats.", "error.no-title": "Ajoutez un titre à l'élection.", "error.ended-election": "L'élection est terminée", + "error.twice-same-names": "Deux éléments ont le même nom", + "error.empty-name": "Le nom est vite", + "error.wrong-name": "Le titre de l'élection est incorrecte", + "error.cant-be-finished": "L'élection ne se terminera jamais. Ajoutez une date limite, un accès aux résultats, ou des invitations.", "error.catch22": "Erreur inconnue...", + "error.date-past": "La date est dans le passé", + "error.uncorrect-name": "The title is incorrect", + "error.not-enough-grades": "Il manque des mentions", + "error.not-enough-candidates": "Il manque des candidats", "grades.very-good": "Très bien", "grades.good": "Bien", "grades.passable": "Passable", "grades.inadequate": "Insuffisant", "grades.mediocre": "Médiocre", "admin.admin-title": "Administration du vote", + "admin.close-election": "Clôturer l'élection", "admin.date-limit": "Fixer une date limite pour le vote", + "admin.set-title": "Changer la question de l'élection", "admin.step-candidate": "Les candidats", "admin.step-params": "Paramètres du vote", "admin.step-confirm": "Confirmation", @@ -126,6 +137,8 @@ "result.share": "Partager les résultats", "result.will-close": "Se termine dans", "result.how-to-interpret": "Comment interpréter les résultats", + "success.election-closed": "L'élection est désormais clôturée.", + "success.election-updated": "L'élection a été mise à jour.", "vote.discover-mj": "Découvrez le jugement majoritaire", "vote.discover-mj-desc": "Créé par des chercheurs français, le jugement majoritaire est un mode de scrutin qui améliore l'expressivité des électeurs et fournit le meilleur consensus.", "vote.go-to-results": "Voir les résultats", diff --git a/services/ElectionContext.tsx b/services/ElectionContext.tsx index f5e0e5c..8f23274 100644 --- a/services/ElectionContext.tsx +++ b/services/ElectionContext.tsx @@ -4,6 +4,7 @@ import {createContext, useContext, useReducer, useEffect, Dispatch, SetStateAction} from 'react'; import {useRouter} from 'next/router'; import {CandidateItem, GradeItem} from './type'; +import {gradeColors} from '@services/grades'; export interface ElectionContextInterface { name: string; @@ -33,7 +34,7 @@ const defaultElection: ElectionContextInterface = { candidates: [{...defaultCandidate}, {...defaultCandidate}], grades: [], randomOrder: true, - hideResults: true, + hideResults: false, forceClose: false, restricted: false, dateEnd: null, @@ -105,11 +106,14 @@ export function ElectionProvider({children}) { useEffect(() => { if (!router.isReady) return; - dispatch({ - type: ElectionTypes.SET, - field: 'name', - value: router.query.name || '', - }); + if (election.name === "" && router.query.name !== "") { + dispatch({ + type: ElectionTypes.SET, + field: 'name', + value: router.query.name, + }); + } + }, [router.isReady]); return ( @@ -220,3 +224,23 @@ export const canViewResults = (election: ElectionContextInterface) => { const isOver = +dateEnd < (+now); return election.forceClose || !election.hideResults || isOver; } + +export const hasEnoughCandidates = (election: ElectionContextInterface) => { + + const numCandidates = election.candidates.filter(c => c.active && c.name != "").length; + return numCandidates > 1; +} + +export const hasEnoughGrades = (election: ElectionContextInterface) => { + + const numGrades = election.grades.filter(g => g.active && g.name != "").length; + return numGrades > 1 && numGrades <= gradeColors.length +} + +export const checkName = (election: ElectionContextInterface) => { + return election.name && election.name !== "" +} + +export const canBeFinished = (election: ElectionContextInterface) => { + return election.restricted || election.forceClose || election.dateEnd || !election.hideResults; +} diff --git a/services/context.tsx b/services/context.tsx index 10408b5..0c05090 100644 --- a/services/context.tsx +++ b/services/context.tsx @@ -82,7 +82,6 @@ export function AppProvider({children}) { const [app, dispatch] = useReducer(reducer, defaultApp); const removeToast = (i: number) => { - console.log(i) dispatch({ type: AppTypes.TOAST_RM, position: i @@ -100,7 +99,6 @@ export function AppProvider({children}) { } }) - console.log("TOASTS", app.toasts) }, [app.toasts]); @@ -110,7 +108,7 @@ export function AppProvider({children}) { {app.toasts.filter(t => t.display).map((toast, i) => (
diff --git a/styles/scss/_admin.scss b/styles/scss/_admin.scss index b113da3..f379684 100644 --- a/styles/scss/_admin.scss +++ b/styles/scss/_admin.scss @@ -91,6 +91,10 @@ background-image: url('../../public/back.svg'); background-size: auto 100%; background-repeat: no-repeat; + top: 0; + bottom: 0; + left: 0; + right: 0; } @include media-breakpoint-up('xxl') { .waiting {