fix: refactor election context

pull/89/head
Pierre-Louis Guhur 1 year ago
parent 170fb372c4
commit c2aca299dd

@ -88,11 +88,8 @@ export const toCSV = (data, headers, separator, enclosingCharacter) => {
const CSVLink = ({filename, data, children, ...rest}) => { const CSVLink = ({filename, data, children, ...rest}) => {
console.log("DATA", data);
const buildURI = ((data, uFEFF, headers, separator, enclosingCharacter) => { const buildURI = ((data, uFEFF, headers, separator, enclosingCharacter) => {
console.log("DATA2", data);
const csv = toCSV(data, headers, separator, enclosingCharacter); const csv = toCSV(data, headers, separator, enclosingCharacter);
console.log("CSV", csv);
const type = isSafari() ? 'application/csv' : 'text/csv'; const type = isSafari() ? 'application/csv' : 'text/csv';
const blob = new Blob([uFEFF ? '\uFEFF' : '', csv], {type}); const blob = new Blob([uFEFF ? '\uFEFF' : '', csv], {type});
const dataURI = `data:${type};charset=utf-8,${uFEFF ? '\uFEFF' : ''}${csv}`; const dataURI = `data:${type};charset=utf-8,${uFEFF ? '\uFEFF' : ''}${csv}`;

@ -1,17 +1,15 @@
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import {Container, Row, Col} from 'reactstrap'; import {Container} from 'reactstrap';
import Switch from '@components/Switch'; import Switch from '@components/Switch';
const AccessResults = () => { const AccessResults = () => {
const {t} = useTranslation(); const {t} = useTranslation();
const [election, dispatch] = useElection();
const election = useElection();
const dispatch = useElectionDispatch();
const toggle = () => { const toggle = () => {
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'restrictResult', field: 'restrictResult',
value: !election.hideResults, value: !election.hideResults,
}); });

@ -19,7 +19,7 @@ interface AdminModalEmailInterface {
const AdminModalEmail = ({isOpen, toggle, electionRef, adminToken}: AdminModalEmailInterface) => { const AdminModalEmail = ({isOpen, toggle, electionRef, adminToken}: AdminModalEmailInterface) => {
const {t} = useTranslation(); const {t} = useTranslation();
const [email, setEmail] = useState(undefined); const [email, setEmail] = useState(undefined);
const election = useElection(); const [election, _] = useElection();
const adminUrl = electionRef && adminToken ? getUrlAdmin(electionRef, adminToken) : null; const adminUrl = electionRef && adminToken ? getUrlAdmin(electionRef, adminToken) : null;
@ -28,7 +28,6 @@ const AdminModalEmail = ({isOpen, toggle, electionRef, adminToken}: AdminModalEm
} }
const handleSubmit = () => { const handleSubmit = () => {
console.log(election);
sendAdminMail(email, election.name, adminUrl); sendAdminMail(email, election.name, adminUrl);
toggle(); toggle();
}; };

@ -6,8 +6,7 @@ import Image from 'next/image';
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faPlus, faTrashCan} from '@fortawesome/free-solid-svg-icons'; import {faPlus, faTrashCan} from '@fortawesome/free-solid-svg-icons';
import {Row, Col} from 'reactstrap'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import {useElection, useElectionDispatch} from '@services/ElectionContext';
import whiteAvatar from '../../public/avatar.svg'; import whiteAvatar from '../../public/avatar.svg';
import CandidateModalSet from './CandidateModalSet'; import CandidateModalSet from './CandidateModalSet';
import CandidateModalDel from './CandidateModalDel'; import CandidateModalDel from './CandidateModalDel';
@ -26,9 +25,8 @@ const CandidateField = ({
...props ...props
}: CandidateProps) => { }: CandidateProps) => {
const {t} = useTranslation(); const {t} = useTranslation();
const [election, dispatch] = useElection();
const election = useElection();
const dispatch = useElectionDispatch();
const candidate = election.candidates[position]; const candidate = election.candidates[position];
const image = candidate && candidate.image ? candidate.image : defaultAvatar; const image = candidate && candidate.image ? candidate.image : defaultAvatar;
const active = candidate && candidate.active === true; const active = candidate && candidate.active === true;
@ -37,7 +35,7 @@ const CandidateField = ({
const [modalSet, setModalSet] = useState(false); const [modalSet, setModalSet] = useState(false);
const addCandidate = () => { const addCandidate = () => {
dispatch({type: 'candidate-push', value: 'default'}); dispatch({type: ElectionTypes.CANDIDATE_PUSH, value: 'default'});
}; };
const toggleSet = () => setModalSet((m) => !m); const toggleSet = () => setModalSet((m) => !m);

@ -1,4 +1,4 @@
import {Row, Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap'; import {Row, Col, Modal, ModalBody} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { import {
faTrashCan, faTrashCan,
@ -6,23 +6,17 @@ import {
faArrowLeft, faArrowLeft,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import Image from 'next/image'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import {useElection, useElectionDispatch} from '@services/ElectionContext';
import Button from '@components/Button'; import Button from '@components/Button';
import {upload} from '@services/imgpush';
import {IMGPUSH_URL} from '@services/constants';
import defaultAvatar from '../../public/default-avatar.svg';
import {useEffect} from 'react';
const CandidateModal = ({isOpen, position, toggle}) => { const CandidateModal = ({isOpen, position, toggle}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const [election, dispatch] = useElection();
const election = useElection();
const dispatch = useElectionDispatch();
const candidate = election.candidates[position]; const candidate = election.candidates[position];
const removeCandidate = () => { const removeCandidate = () => {
dispatch({type: 'candidate-rm', position: position}); dispatch({type: ElectionTypes.CANDIDATE_RM, position: position});
toggle(); toggle();
}; };

@ -3,7 +3,7 @@ import {Row, Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap';
import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons'; import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons';
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import Image from 'next/image'; import Image from 'next/image';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import Button from '@components/Button'; import Button from '@components/Button';
import {upload} from '@services/imgpush'; import {upload} from '@services/imgpush';
import {IMGPUSH_URL} from '@services/constants'; import {IMGPUSH_URL} from '@services/constants';
@ -11,8 +11,7 @@ import defaultAvatar from '../../public/default-avatar.svg';
const CandidateModal = ({isOpen, position, toggle}) => { const CandidateModal = ({isOpen, position, toggle}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection(); const [election, dispatch] = useElection();
const dispatch = useElectionDispatch();
const candidate = election.candidates[position]; const candidate = election.candidates[position];
const [state, setState] = useState(candidate); const [state, setState] = useState(candidate);
const image = state.image && state.image != '' ? state.image : defaultAvatar; const image = state.image && state.image != '' ? state.image : defaultAvatar;
@ -35,19 +34,19 @@ const CandidateModal = ({isOpen, position, toggle}) => {
const save = () => { const save = () => {
dispatch({ dispatch({
type: 'candidate-set', type: ElectionTypes.CANDIDATE_SET,
position: position, position: position,
field: 'image', field: 'image',
value: state.image, value: state.image,
}); });
dispatch({ dispatch({
type: 'candidate-set', type: ElectionTypes.CANDIDATE_SET,
position: position, position: position,
field: 'name', field: 'name',
value: state.name, value: state.name,
}); });
dispatch({ dispatch({
type: 'candidate-set', type: ElectionTypes.CANDIDATE_SET,
position: position, position: position,
field: 'description', field: 'description',
value: state.description, value: state.description,

@ -5,15 +5,14 @@ import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
import {MAX_NUM_CANDIDATES} from '@services/constants'; import {MAX_NUM_CANDIDATES} from '@services/constants';
import Alert from '@components/Alert'; import Alert from '@components/Alert';
import Button from '@components/Button'; import Button from '@components/Button';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import CandidateField from './CandidateField'; import CandidateField from './CandidateField';
const CandidatesField = ({onSubmit}) => { const CandidatesField = ({onSubmit}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const submitReference = useRef(null); const submitReference = useRef(null);
const election = useElection(); const [election, dispatch] = useElection();
const dispatch = useElectionDispatch();
const candidates = election.candidates; const candidates = election.candidates;
const [error, setError] = useState(null); const [error, setError] = useState(null);
const disabled = candidates.filter((c) => c.name !== '').length < 2; const disabled = candidates.filter((c) => c.name !== '').length < 2;
@ -22,7 +21,7 @@ const CandidatesField = ({onSubmit}) => {
useEffect(() => { useEffect(() => {
// Initialize the list with at least two candidates // Initialize the list with at least two candidates
if (candidates.length < 2) { if (candidates.length < 2) {
dispatch({type: 'candidate-push', value: 'default'}); dispatch({type: ElectionTypes.CANDIDATE_PUSH, value: 'default'});
} }
if (candidates.length > MAX_NUM_CANDIDATES) { if (candidates.length > MAX_NUM_CANDIDATES) {
setError('error.too-many-candidates'); setError('error.too-many-candidates');
@ -37,7 +36,6 @@ const CandidatesField = ({onSubmit}) => {
const handleKeyPress = (e: KeyboardEvent<HTMLInputElement>) => { const handleKeyPress = (e: KeyboardEvent<HTMLInputElement>) => {
console.log(e.key)
if (e.key == "Enter" && !disabled) { if (e.key == "Enter" && !disabled) {
onSubmit(); onSubmit();
} }

@ -29,7 +29,7 @@ import {gradeColors} from '@services/grades';
const TitleField = () => { const TitleField = () => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection(); const [election, _] = useElection();
return ( return (
<Container className="bg-white p-4"> <Container className="bg-white p-4">
<Row> <Row>
@ -44,7 +44,7 @@ const TitleField = () => {
const CandidatesField = () => { const CandidatesField = () => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection(); const [election, _] = useElection();
return ( return (
<Container className="bg-white p-4 mt-3 mt-md-0"> <Container className="bg-white p-4 mt-3 mt-md-0">
@ -112,8 +112,8 @@ const submitElection = (
const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParams}) => { const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParams}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection();
const router = useRouter(); const router = useRouter();
const [election, _] = useElection();
const handleSubmit = () => { const handleSubmit = () => {

@ -8,7 +8,7 @@ import {
faCheck, faCheck,
faRotateLeft, faRotateLeft,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import {getGradeColor, gradeColors} from '@services/grades'; import {getGradeColor, gradeColors} from '@services/grades';
import {useSortable} from '@dnd-kit/sortable'; import {useSortable} from '@dnd-kit/sortable';
@ -18,10 +18,9 @@ export interface GradeInterface {
} }
export default ({value}: GradeInterface) => { export default ({value}: GradeInterface) => {
const election = useElection(); const [election, dispatch] = useElection();
const grade = election.grades.filter(g => g.value === value)[0];
const dispatch = useElectionDispatch();
const grade = election.grades.filter(g => g.value === value)[0];
const activeGrade = election.grades.filter(g => g.active) const activeGrade = election.grades.filter(g => g.active)
const numGrades = activeGrade.length; const numGrades = activeGrade.length;
const gradeIdx = activeGrade.map(g => g.value).indexOf(value); const gradeIdx = activeGrade.map(g => g.value).indexOf(value);
@ -39,7 +38,7 @@ export default ({value}: GradeInterface) => {
return return
} }
dispatch({ dispatch({
type: 'grade-set', type: ElectionTypes.GRADE_SET,
position: value, position: value,
field: 'active', field: 'active',
value: !grade.active, value: !grade.active,

@ -1,17 +1,16 @@
import {useState, useEffect, useRef} from 'react'; import {useState, useEffect} from 'react';
import {Row, Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap'; import {Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap';
import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons'; import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons';
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import Image from 'next/image'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import {useElection, useElectionDispatch} from '@services/ElectionContext';
import Button from '@components/Button'; import Button from '@components/Button';
import {GradeItem} from '@services/type'; import {GradeItem} from '@services/type';
const GradeModal = ({isOpen, toggle}) => { const GradeModal = ({isOpen, toggle}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection();
const dispatch = useElectionDispatch(); const [election, dispatch] = useElection();
const [grade, setGrade] = useState<GradeItem>({name: "", description: "", value: -1, active: true}); const [grade, setGrade] = useState<GradeItem>({name: "", description: "", value: -1, active: true});
useEffect(() => { useEffect(() => {
@ -22,7 +21,7 @@ const GradeModal = ({isOpen, toggle}) => {
const save = () => { const save = () => {
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'grades', field: 'grades',
value: [...election.grades, grade], value: [...election.grades, grade],
}); });

@ -5,16 +5,11 @@ import {useState, useEffect} from 'react';
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import {Container, Row, Col} from 'reactstrap'; import {Container, Row, Col} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { import {faPlus} from '@fortawesome/free-solid-svg-icons';
faPlus,
faPen,
faXmark,
faCheck,
} from '@fortawesome/free-solid-svg-icons';
import {DndContext} from '@dnd-kit/core'; import {DndContext} from '@dnd-kit/core';
import {arrayMove, SortableContext} from '@dnd-kit/sortable'; import {arrayMove, SortableContext} from '@dnd-kit/sortable';
import {DEFAULT_GRADES, GRADE_COLORS} from '@services/constants'; import {DEFAULT_GRADES} from '@services/constants';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import GradeField from './GradeField'; import GradeField from './GradeField';
import GradeModalAdd from './GradeModalAdd'; import GradeModalAdd from './GradeModalAdd';
import {gradeColors} from '@services/grades'; import {gradeColors} from '@services/grades';
@ -26,7 +21,7 @@ const AddField = () => {
const [modal, setModal] = useState(false); const [modal, setModal] = useState(false);
const toggle = () => setModal((m) => !m); const toggle = () => setModal((m) => !m);
const election = useElection(); const [election, _] = useElection();
const numGrades = election.grades.filter(g => g.active).length; const numGrades = election.grades.filter(g => g.active).length;
const disabled = numGrades >= gradeColors.length; const disabled = numGrades >= gradeColors.length;
@ -44,16 +39,18 @@ const AddField = () => {
); );
}; };
const Grades = () => { const Grades = () => {
const {t} = useTranslation(); const {t} = useTranslation();
const defaultEndDate = new Date(); const defaultEndDate = new Date();
defaultEndDate.setUTCDate(defaultEndDate.getUTCDate() + 15); defaultEndDate.setUTCDate(defaultEndDate.getUTCDate() + 15);
const [endDate, setEndDate] = useState(defaultEndDate); const [election, dispatch] = useElection();
const grades = election.grades;
useEffect(() => { useEffect(() => {
if (election.grades.length < 2) { if (election.grades.length < 2) {
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'grades', field: 'grades',
value: DEFAULT_GRADES.map((g, i) => ({ value: DEFAULT_GRADES.map((g, i) => ({
name: t(g), name: t(g),
@ -64,9 +61,6 @@ const Grades = () => {
} }
}, []); }, []);
const election = useElection();
const grades = election.grades;
const dispatch = useElectionDispatch();
const handleDragEnd = (event) => { const handleDragEnd = (event) => {
/** /**
@ -81,7 +75,7 @@ const Grades = () => {
const newGrades = arrayMove(grades, activeIdx, overIdx); const newGrades = arrayMove(grades, activeIdx, overIdx);
newGrades.forEach((g, i) => g.value = i); newGrades.forEach((g, i) => g.value = i);
dispatch({ dispatch({
type: "set", type: ElectionTypes.SET,
field: "grades", field: "grades",
value: newGrades, value: newGrades,
}); });

@ -3,7 +3,7 @@ import {useTranslation} from 'next-i18next';
import {Container, Row, Col} from 'reactstrap'; import {Container, Row, Col} from 'reactstrap';
import DatePicker from '@components/DatePicker'; import DatePicker from '@components/DatePicker';
import Switch from '@components/Switch'; import Switch from '@components/Switch';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
const LimitDate = () => { const LimitDate = () => {
const {t} = useTranslation(); const {t} = useTranslation();
@ -11,15 +11,14 @@ const LimitDate = () => {
defaultEndDate.setUTCDate(defaultEndDate.getUTCDate() + 15); defaultEndDate.setUTCDate(defaultEndDate.getUTCDate() + 15);
const [endDate, setEndDate] = useState(defaultEndDate); const [endDate, setEndDate] = useState(defaultEndDate);
const election = useElection();
const dispatch = useElectionDispatch();
const hasDate = election.endVote !== null; const [election, dispatch] = useElection();
const hasDate = election.dateEnd !== null;
const toggle = () => { const toggle = () => {
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'endVote', field: 'dateEnd',
value: hasDate ? null : endDate, value: hasDate ? null : endDate,
}); });
}; };

@ -4,17 +4,16 @@
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import {Container} from 'reactstrap'; import {Container} from 'reactstrap';
import Switch from '@components/Switch'; import Switch from '@components/Switch';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
const Order = () => { const Order = () => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection(); const [election, dispatch] = useElection();
const dispatch = useElectionDispatch();
const toggle = () => { const toggle = () => {
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'randomOrder', field: 'randomOrder',
value: !election.randomOrder, value: !election.randomOrder,
}); });

@ -12,7 +12,7 @@ import {useElection} from '@services/ElectionContext';
const ParamsField = ({onSubmit}) => { const ParamsField = ({onSubmit}) => {
const {t} = useTranslation(); const {t} = useTranslation();
const election = useElection(); const [election, _] = useElection();
const checkDisability = election.restricted && (typeof election.emails === "undefined" || election.emails.length === 0) || election.grades.filter(g => g.active).length < 2; const checkDisability = election.restricted && (typeof election.emails === "undefined" || election.emails.length === 0) || election.grades.filter(g => g.active).length < 2;
return ( return (

@ -1,35 +1,31 @@
/** /**
* A field to set the privacy and add emails * A field to set the privacy and add emails
*/ */
import {useState} from 'react';
import {useTranslation} from 'next-i18next'; import {useTranslation} from 'next-i18next';
import {Container, Row, Col} from 'reactstrap'; import {Container} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCircleInfo} from '@fortawesome/free-solid-svg-icons'; import {faCircleInfo} from '@fortawesome/free-solid-svg-icons';
import Switch from '@components/Switch'; import Switch from '@components/Switch';
import ListInput from '@components/ListInput'; import ListInput from '@components/ListInput';
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {ElectionTypes, useElection} from '@services/ElectionContext';
import {validateMail} from '@services/mail'; import {validateMail} from '@services/mail';
const Private = () => { const Private = () => {
const {t} = useTranslation(); const {t} = useTranslation();
const [emails, setEmails] = useState([]); const [election, dispatch] = useElection();
const election = useElection();
const dispatch = useElectionDispatch();
const toggle = () => { const toggle = () => {
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'restricted', field: 'restricted',
value: !election.restricted, value: !election.restricted,
}); });
}; };
const handleEmails = (emails) => { const handleEmails = (emails: Array<string>) => {
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'emails', field: 'emails',
value: emails, value: emails,
}); });

@ -1,11 +1,10 @@
/** /**
* This component manages the title of the election * This component manages the title of the election
*/ */
import {useElection, useElectionDispatch} from '@services/ElectionContext'; import {useElection} from '@services/ElectionContext';
const TitleField = () => { const TitleField = () => {
const election = useElection(); const election = useElection();
const dispatch = useElectionDispatch();
}; };
export default TitleField; export default TitleField;

@ -1,8 +1,8 @@
import { useState, useEffect } from 'react'; import {useState, useEffect} from 'react';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import {useRouter} from 'next/router';
import { useTranslation } from 'next-i18next'; import {useTranslation} from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import { import {
Collapse, Collapse,
Container, Container,
@ -19,7 +19,11 @@ import {
ModalBody, ModalBody,
ModalFooter, ModalFooter,
} from 'reactstrap'; } from 'reactstrap';
import { GetStaticProps } from 'next'; import {GetStaticProps} from 'next';
import {getElection} from '@services/api';
import {getGradeColor} from '@services/grades';
import {ElectionContextInterface} from '@services/ElectionContext';
import {CandidateItem} from '@services/type';
// import {ReactMultiEmail, isEmail} from "react-multi-email"; // import {ReactMultiEmail, isEmail} from "react-multi-email";
// import "react-multi-email/style.css"; // import "react-multi-email/style.css";
// import {toast, ToastContainer} from "react-toastify"; // import {toast, ToastContainer} from "react-toastify";
@ -42,54 +46,52 @@ import { GetStaticProps } from 'next';
// import Loader from "@components/wait"; // import Loader from "@components/wait";
// import CandidatesField from "@components/admin/CandidatesField"; // import CandidatesField from "@components/admin/CandidatesField";
// import ConfirmModal from "@components/admin/ConfirmModal"; // import ConfirmModal from "@components/admin/ConfirmModal";
// import config from "../../next-i18next.config.js";
// Error messages export async function getServerSideProps({query, locale}) {
// const AT_LEAST_2_CANDIDATES_ERROR = "Please add at least 2 candidates."; const {pid, tid: token} = query;
// const NO_TITLE_ERROR = "Please add a title."; const electionRef = pid.replaceAll("-", "");
//
// const isValidDate = (date) => date instanceof Date && !isNaN(date); const [payload, translations] = await Promise.all([
// const getOnlyValidDate = (date) => (isValidDate(date) ? date : new Date()); getElection(electionRef),
// serverSideTranslations(locale, ["resource"]),
// // Convert a Date object into YYYY-MM-DD ]);
// const dateToISO = (date) =>
// getOnlyValidDate(date).toISOString().substring(0, 10); if ("msg" in payload) {
// return {props: {err: payload.msg, ...translations}};
// // Retrieve the current hour, minute, sec, ms, time into a timestamp }
// const hours = (date) => getOnlyValidDate(date).getHours() * 3600 * 1000;
// const minutes = (date) => getOnlyValidDate(date).getMinutes() * 60 * 1000; const grades = payload.grades.map((g, i) => ({...g, active: true}));
// const seconds = (date) => getOnlyValidDate(date).getSeconds() * 1000;
// const ms = (date) => getOnlyValidDate(date).getMilliseconds(); const candidates: Array<CandidateItem> = payload.candidates.map(c => ({...c, active: true}))
// const time = (date) => const description = JSON.parse(payload.description)
// hours(getOnlyValidDate(date)) + const randomOrder = description["randomOrder"]
// minutes(getOnlyValidDate(date)) +
// seconds(getOnlyValidDate(date)) + const context: ElectionContextInterface = {
// ms(getOnlyValidDate(date)); name: payload.name,
// description: description["description"],
// // Retrieve the time part from a timestamp and remove the day. Return a int. ref: payload.ref,
// const timeMinusDate = (date) => time(getOnlyValidDate(date)); dateStart: payload.date_start,
// dateEnd: payload.date_end,
// // Retrieve the day and remove the time. Return a Date hideResults: payload.hide_results,
// const dateMinusTime = (date) => forceClose: payload.force_close,
// new Date(getOnlyValidDate(date).getTime() - time(getOnlyValidDate(date))); restricted: payload.restricted,
// randomOrder,
// const displayClockOptions = () => emails: [],
// Array(24) grades,
// .fill(1) candidates
// .map((x, i) => ( }
// <option value={i} key={i}>
// {i}h00
// </option>
// ));
export const getStaticProps: GetStaticProps = async ({ locale }) => ({ return {
props: { props: {
...(await serverSideTranslations(locale, ['resource'])), context,
}, token: token || "",
}); ...translations,
},
};
}
const CreateElection = (props) => { const CreateElection = ({context, token}) => {
// const {t} = useTranslation(); const {t} = useTranslation();
// // default value : start at the last hour // // default value : start at the last hour
// const now = new Date(); // const now = new Date();

@ -29,7 +29,7 @@ export async function getServerSideProps({query: {pid, tid}, locale}) {
serverSideTranslations(locale, ['resource']), serverSideTranslations(locale, ['resource']),
]); ]);
if (typeof election === 'string' || election instanceof String) { if ("msg" in election) {
return {notFound: true} return {notFound: true}
} }

@ -7,13 +7,10 @@ import {useRouter} from 'next/router';
import Link from 'next/link'; import Link from 'next/link';
import { import {
Container, Container,
Row,
Col,
Collapse, Collapse,
Card, Card,
CardHeader, CardHeader,
CardBody, CardBody,
Table,
Button, Button,
} from 'reactstrap'; } from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
@ -23,7 +20,6 @@ import {
faChevronUp, faChevronUp,
faGear, faGear,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
// import dynamic from 'next/dynamic'
import ErrorMessage from '@components/Error'; import ErrorMessage from '@components/Error';
import CSVLink from '@components/CSVLink'; import CSVLink from '@components/CSVLink';
import Logo from '@components/Logo'; import Logo from '@components/Logo';
@ -40,22 +36,6 @@ import arrowLink from '../../../public/arrowL.svg'
import {getGradeColor} from '@services/grades'; import {getGradeColor} from '@services/grades';
// /**
// * See https://github.com/react-csv/react-csv/issues/87
// */
// const CSVDownload = dynamic(
// import('react-csv').then((m) => {
// const {
// CSVDownload
// } = m
// return CSVDownload
// }), {
// ssr: false,
// loading: () => <a>placeholder component...</a>
// })
export async function getServerSideProps({query, locale}) { export async function getServerSideProps({query, locale}) {
const {pid, tid: token} = query; const {pid, tid: token} = query;
const electionRef = pid.replaceAll("-", ""); const electionRef = pid.replaceAll("-", "");
@ -94,7 +74,6 @@ export async function getServerSideProps({query, locale}) {
meritProfiles: payload.merit_profile, meritProfiles: payload.merit_profile,
} }
console.log("GRADES", payload.grades, grades, result.grades)
return { return {
props: { props: {

@ -14,10 +14,18 @@ export interface ElectionContextInterface {
forceClose: boolean; forceClose: boolean;
restricted: boolean; restricted: boolean;
randomOrder: boolean; randomOrder: boolean;
endVote: string;
emails: Array<string>; emails: Array<string>;
dateEnd: string;
dateStart?: string;
ref?: string;
} }
const defaultGrade: GradeItem = {
name: '',
description: '',
value: -1,
active: false,
};
const defaultCandidate: CandidateItem = { const defaultCandidate: CandidateItem = {
name: '', name: '',
image: '', image: '',
@ -34,17 +42,58 @@ const defaultElection: ElectionContextInterface = {
hideResults: true, hideResults: true,
forceClose: false, forceClose: false,
restricted: false, restricted: false,
endVote: null, dateEnd: null,
emails: [], emails: [],
}; };
type DispatchType = Dispatch<SetStateAction<ElectionContextInterface>>; export enum ElectionTypes {
SET = 'set',
CANDIDATE_PUSH = 'candidate-push',
CANDIDATE_RM = 'candidate-rm',
CANDIDATE_SET = 'candidate-set',
GRADE_PUSH = 'grade-push',
GRADE_RM = 'grade-rm',
GRADE_SET = 'grade-set',
}
// Store data about an election export type SetAction = {
const ElectionContext = createContext<ElectionContextInterface>(defaultElection); type: ElectionTypes.SET;
// Store the dispatch function that can modify an election field: string;
// const ElectionDispatchContext = createContext<DispatchType | null>(null); value: any;
const ElectionDispatchContext = createContext(null); }
export type CandidatePushAction = {
type: ElectionTypes.CANDIDATE_PUSH;
value: string | CandidateItem;
}
export type CandidateRmAction = {
type: ElectionTypes.CANDIDATE_RM;
position: number;
}
export type CandidateSetAction = {
type: ElectionTypes.CANDIDATE_SET;
position: number;
field: string;
value: any;
}
export type GradePushAction = {
type: ElectionTypes.GRADE_PUSH;
value: GradeItem;
}
export type GradeRmAction = {
type: ElectionTypes.GRADE_RM;
position: number;
}
export type GradeSetAction = {
type: ElectionTypes.GRADE_SET;
position: number;
field: string;
value: any;
}
export type ElectionActionTypes = SetAction | CandidateRmAction | CandidateSetAction | CandidatePushAction | GradeRmAction | GradeSetAction | GradePushAction;
type DispatchType = Dispatch<ElectionActionTypes>;
const ElectionContext = createContext<[ElectionContextInterface, DispatchType]>([defaultElection, () => {}]);
export function ElectionProvider({children}) { export function ElectionProvider({children}) {
/** /**
@ -58,17 +107,15 @@ export function ElectionProvider({children}) {
if (!router.isReady) return; if (!router.isReady) return;
dispatch({ dispatch({
type: 'set', type: ElectionTypes.SET,
field: 'name', field: 'name',
value: router.query.name || '', value: router.query.name || '',
}); });
}, [router.isReady]); }, [router.isReady]);
return ( return (
<ElectionContext.Provider value={election}> <ElectionContext.Provider value={[election, dispatch]}>
<ElectionDispatchContext.Provider value={dispatch}> {children}
{children}
</ElectionDispatchContext.Provider>
</ElectionContext.Provider> </ElectionContext.Provider>
); );
} }
@ -80,14 +127,8 @@ export function useElection() {
return useContext(ElectionContext); return useContext(ElectionContext);
} }
export function useElectionDispatch() {
/**
* A simple hook to modify the election
*/
return useContext(ElectionDispatchContext);
}
function electionReducer(election: ElectionContextInterface, action) { function electionReducer(election: ElectionContextInterface, action: ElectionActionTypes): ElectionContextInterface {
/** /**
* Manage all types of action doable on an election * Manage all types of action doable on an election
*/ */
@ -95,17 +136,13 @@ function electionReducer(election: ElectionContextInterface, action) {
case 'set': { case 'set': {
return {...election, [action.field]: action.value}; return {...election, [action.field]: action.value};
} }
case 'commit': {
throw Error('Not implemented yet');
}
case 'remove': {
throw Error('Not implemented yet');
}
case 'candidate-push': { case 'candidate-push': {
if (typeof action.value === "string" && action.value !== "default") {
throw Error("Unexpected action")
}
const candidate = const candidate =
action.value === 'default' ? {...defaultCandidate} : action.value; action.value === 'default' ? {...defaultCandidate} : action.value;
const candidates = [...election.candidates, candidate]; const candidates = [...election.candidates, candidate];
console.log("NONACTIVE", candidates.filter(c => !c.active).length)
if (candidates.filter(c => !c.active).length === 0) { if (candidates.filter(c => !c.active).length === 0) {
return { return {
...election, candidates: [...candidates, {...defaultCandidate}] ...election, candidates: [...candidates, {...defaultCandidate}]
@ -142,9 +179,7 @@ function electionReducer(election: ElectionContextInterface, action) {
return {...election, candidates}; return {...election, candidates};
} }
case 'grade-push': { case 'grade-push': {
const grade = const grades = [...election.grades, action.value];
action.value === 'default' ? {...defaultCandidate} : action.value;
const grades = [...election.grades, grade];
return {...election, grades}; return {...election, grades};
} }
case 'grade-rm': { case 'grade-rm': {

@ -15,6 +15,77 @@ export const api = {
}; };
export interface GradePayload {
name: string;
description: string;
id: number;
value: number;
}
export interface CandidatePayload {
name: string;
description: string;
id: number;
image: string;
}
export interface ErrorMessage {
loc: Array<string>;
msg: string;
type: string;
ctx: any;
}
export interface ErrorPayload {
detail: Array<ErrorMessage>;
}
export interface HTTPPayload {
status: number;
msg: string;
}
export interface ElectionPayload {
name: string;
description: string;
ref: string;
date_start: string;
date_end: string;
hide_results: boolean;
force_close: boolean;
restricted: boolean;
grades: Array<GradePayload>;
candidates: Array<CandidatePayload>;
}
export interface ElectionCreatedPayload extends ElectionPayload {
invites: Array<string>;
admin: string;
num_voters: number;
}
export interface ResultsPayload extends ElectionPayload {
status: number;
ranking: {[key: string]: number};
merit_profile: {[key: number]: Array<number>};
}
export interface VotePayload {
id: string;
candidate: CandidatePayload;
grade: GradePayload;
}
export interface BallotPayload {
votes: Array<VotePayload>;
election: ElectionPayload;
token: string;
}
export const createElection = async ( export const createElection = async (
name: string, name: string,
candidates: Array<Candidate>, candidates: Array<Candidate>,
@ -79,11 +150,7 @@ export const createElection = async (
}; };
export const getResults = async ( export const getResults = async (pid: string): Promise<ResultsPayload | HTTPPayload> => {
pid: string,
successCallback = null,
failureCallback = null
): Promise<ResultsPayload | HTTPPayload> => {
/** /**
* Fetch results from external API * Fetch results from external API
*/ */
@ -102,32 +169,29 @@ export const getResults = async (
const payload = await response.json() const payload = await response.json()
return {...payload, status: response.status}; return {...payload, status: response.status};
} catch (error) { } catch (error) {
return new Promise(() => "API errors") return {status: 400, msg: "Unknown API error"}
} }
}; };
export const getElection = async ( export const getElection = async (pid: string): Promise<ElectionPayload | HTTPPayload> => {
pid: string,
): Promise<ElectionPayload | string> => {
/** /**
* Fetch data from external API * Fetch data from external API
*/ */
const detailsEndpoint = new URL( const path = api.routesServer.getElection.replace(new RegExp(':slug', 'g'), pid);
api.routesServer.getElection.replace(new RegExp(':slug', 'g'), pid), const endpoint = new URL(path, api.urlServer);
api.urlServer
);
try { try {
const res = await fetch(detailsEndpoint.href); const response = await fetch(endpoint.href);
if (!res.ok) { if (response.status != 200) {
return res.text() const payload = await response.json();
return {status: response.status, msg: payload};
} }
const payload = await response.json()
const payload: ElectionPayload = await res.json(); return {...payload, status: response.status};
return payload;
} catch (error) { } catch (error) {
return error; return {status: 400, msg: "Unknown API error"}
} }
}; };
@ -204,74 +268,3 @@ export const apiErrors = (error: string): string => {
} }
}; };
export interface GradePayload {
name: string;
description: string;
id: number;
value: number;
}
export interface CandidatePayload {
name: string;
description: string;
id: number;
image: string;
}
export interface ErrorMessage {
loc: Array<string>;
msg: string;
type: string;
ctx: any;
}
export interface ErrorPayload {
detail: Array<ErrorMessage>;
}
export interface HTTPPayload {
status: number;
msg: string;
}
export interface ElectionPayload {
name: string;
description: string;
ref: string;
date_start: string;
date_end: string;
hide_results: boolean;
force_close: boolean;
restricted: boolean;
grades: Array<GradePayload>;
candidates: Array<CandidatePayload>;
}
export interface ElectionCreatedPayload extends ElectionPayload {
invites: Array<string>;
admin: string;
num_voters: number;
}
export interface ResultsPayload extends ElectionPayload {
status: number;
ranking: {[key: string]: number};
merit_profile: {[key: number]: Array<number>};
}
export interface VotePayload {
id: string;
candidate: CandidatePayload;
grade: GradePayload;
}
export interface BallotPayload {
votes: Array<VotePayload>;
election: ElectionPayload;
token: string;
}

@ -2,6 +2,7 @@ export interface Candidate {
name: string, name: string,
image?: string, image?: string,
description?: string description?: string
id?: number;
} }
export interface CandidateItem extends Candidate { export interface CandidateItem extends Candidate {
@ -9,9 +10,10 @@ export interface CandidateItem extends Candidate {
} }
export interface Grade { export interface Grade {
name: string, name: string;
value: number, value: number;
description?: string description?: string;
id?: number;
} }
export interface GradeItem extends Grade { export interface GradeItem extends Grade {

@ -32,6 +32,5 @@ export const displayRef = (ref: string): string => {
export const isEnded = (date: string): boolean => { export const isEnded = (date: string): boolean => {
const dateEnd = new Date(date); const dateEnd = new Date(date);
const now = new Date(); const now = new Date();
console.log(dateEnd, now)
return +dateEnd < +now; return +dateEnd < +now;
} }

Loading…
Cancel
Save