diff --git a/components/CSVLink.tsx b/components/CSVLink.tsx index 9f1ff97..2d85df7 100644 --- a/components/CSVLink.tsx +++ b/components/CSVLink.tsx @@ -88,11 +88,8 @@ export const toCSV = (data, headers, separator, enclosingCharacter) => { const CSVLink = ({filename, data, children, ...rest}) => { - console.log("DATA", data); const buildURI = ((data, uFEFF, headers, separator, enclosingCharacter) => { - console.log("DATA2", data); const csv = toCSV(data, headers, separator, enclosingCharacter); - console.log("CSV", csv); const type = isSafari() ? 'application/csv' : 'text/csv'; const blob = new Blob([uFEFF ? '\uFEFF' : '', csv], {type}); const dataURI = `data:${type};charset=utf-8,${uFEFF ? '\uFEFF' : ''}${csv}`; diff --git a/components/admin/AccessResults.tsx b/components/admin/AccessResults.tsx index 4f7620b..38352e5 100644 --- a/components/admin/AccessResults.tsx +++ b/components/admin/AccessResults.tsx @@ -1,17 +1,15 @@ import {useTranslation} from 'next-i18next'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; -import {Container, Row, Col} from 'reactstrap'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; +import {Container} from 'reactstrap'; import Switch from '@components/Switch'; const AccessResults = () => { const {t} = useTranslation(); - - const election = useElection(); - const dispatch = useElectionDispatch(); + const [election, dispatch] = useElection(); const toggle = () => { dispatch({ - type: 'set', + type: ElectionTypes.SET, field: 'restrictResult', value: !election.hideResults, }); diff --git a/components/admin/AdminModalEmail.tsx b/components/admin/AdminModalEmail.tsx index 36697f8..5ad3a25 100644 --- a/components/admin/AdminModalEmail.tsx +++ b/components/admin/AdminModalEmail.tsx @@ -19,7 +19,7 @@ interface AdminModalEmailInterface { const AdminModalEmail = ({isOpen, toggle, electionRef, adminToken}: AdminModalEmailInterface) => { const {t} = useTranslation(); const [email, setEmail] = useState(undefined); - const election = useElection(); + const [election, _] = useElection(); const adminUrl = electionRef && adminToken ? getUrlAdmin(electionRef, adminToken) : null; @@ -28,7 +28,6 @@ const AdminModalEmail = ({isOpen, toggle, electionRef, adminToken}: AdminModalEm } const handleSubmit = () => { - console.log(election); sendAdminMail(email, election.name, adminUrl); toggle(); }; diff --git a/components/admin/CandidateField.tsx b/components/admin/CandidateField.tsx index a7d1ade..711c10e 100644 --- a/components/admin/CandidateField.tsx +++ b/components/admin/CandidateField.tsx @@ -6,8 +6,7 @@ import Image from 'next/image'; import {useTranslation} from 'next-i18next'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faPlus, faTrashCan} from '@fortawesome/free-solid-svg-icons'; -import {Row, Col} from 'reactstrap'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; import whiteAvatar from '../../public/avatar.svg'; import CandidateModalSet from './CandidateModalSet'; import CandidateModalDel from './CandidateModalDel'; @@ -26,9 +25,8 @@ const CandidateField = ({ ...props }: CandidateProps) => { const {t} = useTranslation(); + const [election, dispatch] = useElection(); - const election = useElection(); - const dispatch = useElectionDispatch(); const candidate = election.candidates[position]; const image = candidate && candidate.image ? candidate.image : defaultAvatar; const active = candidate && candidate.active === true; @@ -37,7 +35,7 @@ const CandidateField = ({ const [modalSet, setModalSet] = useState(false); const addCandidate = () => { - dispatch({type: 'candidate-push', value: 'default'}); + dispatch({type: ElectionTypes.CANDIDATE_PUSH, value: 'default'}); }; const toggleSet = () => setModalSet((m) => !m); diff --git a/components/admin/CandidateModalDel.tsx b/components/admin/CandidateModalDel.tsx index 478d99b..6b97511 100644 --- a/components/admin/CandidateModalDel.tsx +++ b/components/admin/CandidateModalDel.tsx @@ -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 { faTrashCan, @@ -6,23 +6,17 @@ import { faArrowLeft, } from '@fortawesome/free-solid-svg-icons'; import {useTranslation} from 'next-i18next'; -import Image from 'next/image'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; 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 {t} = useTranslation(); + const [election, dispatch] = useElection(); - const election = useElection(); - const dispatch = useElectionDispatch(); const candidate = election.candidates[position]; const removeCandidate = () => { - dispatch({type: 'candidate-rm', position: position}); + dispatch({type: ElectionTypes.CANDIDATE_RM, position: position}); toggle(); }; diff --git a/components/admin/CandidateModalSet.tsx b/components/admin/CandidateModalSet.tsx index cd1de8a..37253f4 100644 --- a/components/admin/CandidateModalSet.tsx +++ b/components/admin/CandidateModalSet.tsx @@ -3,7 +3,7 @@ import {Row, Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap'; import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons'; import {useTranslation} from 'next-i18next'; import Image from 'next/image'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; import Button from '@components/Button'; import {upload} from '@services/imgpush'; import {IMGPUSH_URL} from '@services/constants'; @@ -11,8 +11,7 @@ import defaultAvatar from '../../public/default-avatar.svg'; const CandidateModal = ({isOpen, position, toggle}) => { const {t} = useTranslation(); - const election = useElection(); - const dispatch = useElectionDispatch(); + const [election, dispatch] = useElection(); const candidate = election.candidates[position]; const [state, setState] = useState(candidate); const image = state.image && state.image != '' ? state.image : defaultAvatar; @@ -35,19 +34,19 @@ const CandidateModal = ({isOpen, position, toggle}) => { const save = () => { dispatch({ - type: 'candidate-set', + type: ElectionTypes.CANDIDATE_SET, position: position, field: 'image', value: state.image, }); dispatch({ - type: 'candidate-set', + type: ElectionTypes.CANDIDATE_SET, position: position, field: 'name', value: state.name, }); dispatch({ - type: 'candidate-set', + type: ElectionTypes.CANDIDATE_SET, position: position, field: 'description', value: state.description, diff --git a/components/admin/CandidatesField.tsx b/components/admin/CandidatesField.tsx index dfb8b0d..5a37208 100644 --- a/components/admin/CandidatesField.tsx +++ b/components/admin/CandidatesField.tsx @@ -5,15 +5,14 @@ import {faArrowRight} from '@fortawesome/free-solid-svg-icons'; import {MAX_NUM_CANDIDATES} from '@services/constants'; import Alert from '@components/Alert'; import Button from '@components/Button'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; import CandidateField from './CandidateField'; const CandidatesField = ({onSubmit}) => { const {t} = useTranslation(); const submitReference = useRef(null); - const election = useElection(); - const dispatch = useElectionDispatch(); + const [election, dispatch] = useElection(); const candidates = election.candidates; const [error, setError] = useState(null); const disabled = candidates.filter((c) => c.name !== '').length < 2; @@ -22,7 +21,7 @@ const CandidatesField = ({onSubmit}) => { useEffect(() => { // Initialize the list with at least two candidates if (candidates.length < 2) { - dispatch({type: 'candidate-push', value: 'default'}); + dispatch({type: ElectionTypes.CANDIDATE_PUSH, value: 'default'}); } if (candidates.length > MAX_NUM_CANDIDATES) { setError('error.too-many-candidates'); @@ -37,7 +36,6 @@ const CandidatesField = ({onSubmit}) => { const handleKeyPress = (e: KeyboardEvent) => { - console.log(e.key) if (e.key == "Enter" && !disabled) { onSubmit(); } diff --git a/components/admin/ConfirmField.tsx b/components/admin/ConfirmField.tsx index e9142e2..1c425fd 100644 --- a/components/admin/ConfirmField.tsx +++ b/components/admin/ConfirmField.tsx @@ -29,7 +29,7 @@ import {gradeColors} from '@services/grades'; const TitleField = () => { const {t} = useTranslation(); - const election = useElection(); + const [election, _] = useElection(); return ( @@ -44,7 +44,7 @@ const TitleField = () => { const CandidatesField = () => { const {t} = useTranslation(); - const election = useElection(); + const [election, _] = useElection(); return ( @@ -112,8 +112,8 @@ const submitElection = ( const ConfirmField = ({onSubmit, onSuccess, onFailure, goToCandidates, goToParams}) => { const {t} = useTranslation(); - const election = useElection(); const router = useRouter(); + const [election, _] = useElection(); const handleSubmit = () => { diff --git a/components/admin/GradeField.tsx b/components/admin/GradeField.tsx index 0c52207..07271b2 100644 --- a/components/admin/GradeField.tsx +++ b/components/admin/GradeField.tsx @@ -8,7 +8,7 @@ import { faCheck, faRotateLeft, } 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 {useSortable} from '@dnd-kit/sortable'; @@ -18,10 +18,9 @@ export interface GradeInterface { } export default ({value}: GradeInterface) => { - const election = useElection(); - const grade = election.grades.filter(g => g.value === value)[0]; - const dispatch = useElectionDispatch(); + const [election, dispatch] = useElection(); + const grade = election.grades.filter(g => g.value === value)[0]; const activeGrade = election.grades.filter(g => g.active) const numGrades = activeGrade.length; const gradeIdx = activeGrade.map(g => g.value).indexOf(value); @@ -39,7 +38,7 @@ export default ({value}: GradeInterface) => { return } dispatch({ - type: 'grade-set', + type: ElectionTypes.GRADE_SET, position: value, field: 'active', value: !grade.active, diff --git a/components/admin/GradeModalAdd.tsx b/components/admin/GradeModalAdd.tsx index a471214..fcd783c 100644 --- a/components/admin/GradeModalAdd.tsx +++ b/components/admin/GradeModalAdd.tsx @@ -1,17 +1,16 @@ -import {useState, useEffect, useRef} from 'react'; -import {Row, Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap'; +import {useState, useEffect} from 'react'; +import {Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap'; import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons'; import {useTranslation} from 'next-i18next'; -import Image from 'next/image'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; import Button from '@components/Button'; import {GradeItem} from '@services/type'; const GradeModal = ({isOpen, toggle}) => { const {t} = useTranslation(); - const election = useElection(); - const dispatch = useElectionDispatch(); + + const [election, dispatch] = useElection(); const [grade, setGrade] = useState({name: "", description: "", value: -1, active: true}); useEffect(() => { @@ -22,7 +21,7 @@ const GradeModal = ({isOpen, toggle}) => { const save = () => { dispatch({ - type: 'set', + type: ElectionTypes.SET, field: 'grades', value: [...election.grades, grade], }); diff --git a/components/admin/Grades.tsx b/components/admin/Grades.tsx index 1a1917b..b9a7cd7 100644 --- a/components/admin/Grades.tsx +++ b/components/admin/Grades.tsx @@ -5,16 +5,11 @@ import {useState, useEffect} from 'react'; import {useTranslation} from 'next-i18next'; import {Container, Row, Col} from 'reactstrap'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import { - faPlus, - faPen, - faXmark, - faCheck, -} from '@fortawesome/free-solid-svg-icons'; +import {faPlus} from '@fortawesome/free-solid-svg-icons'; import {DndContext} from '@dnd-kit/core'; import {arrayMove, SortableContext} from '@dnd-kit/sortable'; -import {DEFAULT_GRADES, GRADE_COLORS} from '@services/constants'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {DEFAULT_GRADES} from '@services/constants'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; import GradeField from './GradeField'; import GradeModalAdd from './GradeModalAdd'; import {gradeColors} from '@services/grades'; @@ -26,7 +21,7 @@ const AddField = () => { const [modal, setModal] = useState(false); const toggle = () => setModal((m) => !m); - const election = useElection(); + const [election, _] = useElection(); const numGrades = election.grades.filter(g => g.active).length; const disabled = numGrades >= gradeColors.length; @@ -44,16 +39,18 @@ const AddField = () => { ); }; + const Grades = () => { const {t} = useTranslation(); const defaultEndDate = new Date(); defaultEndDate.setUTCDate(defaultEndDate.getUTCDate() + 15); - const [endDate, setEndDate] = useState(defaultEndDate); + const [election, dispatch] = useElection(); + const grades = election.grades; useEffect(() => { if (election.grades.length < 2) { dispatch({ - type: 'set', + type: ElectionTypes.SET, field: 'grades', value: DEFAULT_GRADES.map((g, i) => ({ name: t(g), @@ -64,9 +61,6 @@ const Grades = () => { } }, []); - const election = useElection(); - const grades = election.grades; - const dispatch = useElectionDispatch(); const handleDragEnd = (event) => { /** @@ -81,7 +75,7 @@ const Grades = () => { const newGrades = arrayMove(grades, activeIdx, overIdx); newGrades.forEach((g, i) => g.value = i); dispatch({ - type: "set", + type: ElectionTypes.SET, field: "grades", value: newGrades, }); diff --git a/components/admin/LimitDate.tsx b/components/admin/LimitDate.tsx index 71ca44f..844e9cd 100644 --- a/components/admin/LimitDate.tsx +++ b/components/admin/LimitDate.tsx @@ -3,7 +3,7 @@ import {useTranslation} from 'next-i18next'; import {Container, Row, Col} from 'reactstrap'; import DatePicker from '@components/DatePicker'; import Switch from '@components/Switch'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; const LimitDate = () => { const {t} = useTranslation(); @@ -11,15 +11,14 @@ const LimitDate = () => { defaultEndDate.setUTCDate(defaultEndDate.getUTCDate() + 15); 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 = () => { dispatch({ - type: 'set', - field: 'endVote', + type: ElectionTypes.SET, + field: 'dateEnd', value: hasDate ? null : endDate, }); }; diff --git a/components/admin/Order.tsx b/components/admin/Order.tsx index 4db3cf1..8b0ad31 100644 --- a/components/admin/Order.tsx +++ b/components/admin/Order.tsx @@ -4,17 +4,16 @@ import {useTranslation} from 'next-i18next'; import {Container} from 'reactstrap'; import Switch from '@components/Switch'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; const Order = () => { const {t} = useTranslation(); - const election = useElection(); - const dispatch = useElectionDispatch(); + const [election, dispatch] = useElection(); const toggle = () => { dispatch({ - type: 'set', + type: ElectionTypes.SET, field: 'randomOrder', value: !election.randomOrder, }); diff --git a/components/admin/ParamsField.tsx b/components/admin/ParamsField.tsx index e633838..f2d318e 100644 --- a/components/admin/ParamsField.tsx +++ b/components/admin/ParamsField.tsx @@ -12,7 +12,7 @@ import {useElection} from '@services/ElectionContext'; const ParamsField = ({onSubmit}) => { 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; return ( diff --git a/components/admin/Private.tsx b/components/admin/Private.tsx index 3f04090..602ab8d 100644 --- a/components/admin/Private.tsx +++ b/components/admin/Private.tsx @@ -1,35 +1,31 @@ /** * A field to set the privacy and add emails */ -import {useState} from 'react'; import {useTranslation} from 'next-i18next'; -import {Container, Row, Col} from 'reactstrap'; +import {Container} from 'reactstrap'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faCircleInfo} from '@fortawesome/free-solid-svg-icons'; import Switch from '@components/Switch'; import ListInput from '@components/ListInput'; -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; import {validateMail} from '@services/mail'; const Private = () => { const {t} = useTranslation(); - const [emails, setEmails] = useState([]); - - const election = useElection(); - const dispatch = useElectionDispatch(); + const [election, dispatch] = useElection(); const toggle = () => { dispatch({ - type: 'set', + type: ElectionTypes.SET, field: 'restricted', value: !election.restricted, }); }; - const handleEmails = (emails) => { + const handleEmails = (emails: Array) => { dispatch({ - type: 'set', + type: ElectionTypes.SET, field: 'emails', value: emails, }); diff --git a/components/admin/Title.tsx b/components/admin/Title.tsx index 707fe70..55f26cf 100644 --- a/components/admin/Title.tsx +++ b/components/admin/Title.tsx @@ -1,11 +1,10 @@ /** * This component manages the title of the election */ -import {useElection, useElectionDispatch} from '@services/ElectionContext'; +import {useElection} from '@services/ElectionContext'; const TitleField = () => { const election = useElection(); - const dispatch = useElectionDispatch(); }; export default TitleField; diff --git a/pages/admin/settings/index.tsx b/pages/admin/[pid]/[tid].tsx similarity index 87% rename from pages/admin/settings/index.tsx rename to pages/admin/[pid]/[tid].tsx index 474d37d..236fa9d 100644 --- a/pages/admin/settings/index.tsx +++ b/pages/admin/[pid]/[tid].tsx @@ -1,8 +1,8 @@ -import { useState, useEffect } from 'react'; +import {useState, useEffect} from 'react'; import Head from 'next/head'; -import { useRouter } from 'next/router'; -import { useTranslation } from 'next-i18next'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; +import {useRouter} from 'next/router'; +import {useTranslation} from 'next-i18next'; +import {serverSideTranslations} from 'next-i18next/serverSideTranslations'; import { Collapse, Container, @@ -19,7 +19,11 @@ import { ModalBody, ModalFooter, } 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 "react-multi-email/style.css"; // import {toast, ToastContainer} from "react-toastify"; @@ -42,54 +46,52 @@ import { GetStaticProps } from 'next'; // import Loader from "@components/wait"; // import CandidatesField from "@components/admin/CandidatesField"; // import ConfirmModal from "@components/admin/ConfirmModal"; -// import config from "../../next-i18next.config.js"; -// Error messages -// const AT_LEAST_2_CANDIDATES_ERROR = "Please add at least 2 candidates."; -// const NO_TITLE_ERROR = "Please add a title."; -// -// const isValidDate = (date) => date instanceof Date && !isNaN(date); -// const getOnlyValidDate = (date) => (isValidDate(date) ? date : new Date()); -// -// // Convert a Date object into YYYY-MM-DD -// const dateToISO = (date) => -// getOnlyValidDate(date).toISOString().substring(0, 10); -// -// // 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 seconds = (date) => getOnlyValidDate(date).getSeconds() * 1000; -// const ms = (date) => getOnlyValidDate(date).getMilliseconds(); -// const time = (date) => -// hours(getOnlyValidDate(date)) + -// minutes(getOnlyValidDate(date)) + -// seconds(getOnlyValidDate(date)) + -// ms(getOnlyValidDate(date)); -// -// // Retrieve the time part from a timestamp and remove the day. Return a int. -// const timeMinusDate = (date) => time(getOnlyValidDate(date)); -// -// // Retrieve the day and remove the time. Return a Date -// const dateMinusTime = (date) => -// new Date(getOnlyValidDate(date).getTime() - time(getOnlyValidDate(date))); -// -// const displayClockOptions = () => -// Array(24) -// .fill(1) -// .map((x, i) => ( -// -// )); +export async function getServerSideProps({query, locale}) { + const {pid, tid: token} = query; + const electionRef = pid.replaceAll("-", ""); + + const [payload, translations] = await Promise.all([ + getElection(electionRef), + serverSideTranslations(locale, ["resource"]), + ]); + + if ("msg" in payload) { + return {props: {err: payload.msg, ...translations}}; + } + + const grades = payload.grades.map((g, i) => ({...g, active: true})); + + const candidates: Array = payload.candidates.map(c => ({...c, active: true})) + const description = JSON.parse(payload.description) + const randomOrder = description["randomOrder"] + + const context: ElectionContextInterface = { + name: payload.name, + description: description["description"], + ref: payload.ref, + dateStart: payload.date_start, + dateEnd: payload.date_end, + hideResults: payload.hide_results, + forceClose: payload.force_close, + restricted: payload.restricted, + randomOrder, + emails: [], + grades, + candidates + } -export const getStaticProps: GetStaticProps = async ({ locale }) => ({ - props: { - ...(await serverSideTranslations(locale, ['resource'])), - }, -}); + return { + props: { + context, + token: token || "", + ...translations, + }, + }; +} -const CreateElection = (props) => { - // const {t} = useTranslation(); +const CreateElection = ({context, token}) => { + const {t} = useTranslation(); // // default value : start at the last hour // const now = new Date(); diff --git a/pages/ballot/[pid]/[[...tid]].tsx b/pages/ballot/[pid]/[[...tid]].tsx index 59e06a6..3f33618 100644 --- a/pages/ballot/[pid]/[[...tid]].tsx +++ b/pages/ballot/[pid]/[[...tid]].tsx @@ -29,7 +29,7 @@ export async function getServerSideProps({query: {pid, tid}, locale}) { serverSideTranslations(locale, ['resource']), ]); - if (typeof election === 'string' || election instanceof String) { + if ("msg" in election) { return {notFound: true} } diff --git a/pages/result/[pid]/[[...tid]].tsx b/pages/result/[pid]/[[...tid]].tsx index 139068c..439d607 100644 --- a/pages/result/[pid]/[[...tid]].tsx +++ b/pages/result/[pid]/[[...tid]].tsx @@ -7,13 +7,10 @@ import {useRouter} from 'next/router'; import Link from 'next/link'; import { Container, - Row, - Col, Collapse, Card, CardHeader, CardBody, - Table, Button, } from 'reactstrap'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; @@ -23,7 +20,6 @@ import { faChevronUp, faGear, } from '@fortawesome/free-solid-svg-icons'; -// import dynamic from 'next/dynamic' import ErrorMessage from '@components/Error'; import CSVLink from '@components/CSVLink'; import Logo from '@components/Logo'; @@ -40,22 +36,6 @@ import arrowLink from '../../../public/arrowL.svg' 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: () => placeholder component... -// }) - - export async function getServerSideProps({query, locale}) { const {pid, tid: token} = query; const electionRef = pid.replaceAll("-", ""); @@ -94,7 +74,6 @@ export async function getServerSideProps({query, locale}) { meritProfiles: payload.merit_profile, } - console.log("GRADES", payload.grades, grades, result.grades) return { props: { diff --git a/services/ElectionContext.tsx b/services/ElectionContext.tsx index 5b9fb0a..e3db50e 100644 --- a/services/ElectionContext.tsx +++ b/services/ElectionContext.tsx @@ -14,10 +14,18 @@ export interface ElectionContextInterface { forceClose: boolean; restricted: boolean; randomOrder: boolean; - endVote: string; emails: Array; + dateEnd: string; + dateStart?: string; + ref?: string; } +const defaultGrade: GradeItem = { + name: '', + description: '', + value: -1, + active: false, +}; const defaultCandidate: CandidateItem = { name: '', image: '', @@ -34,17 +42,58 @@ const defaultElection: ElectionContextInterface = { hideResults: true, forceClose: false, restricted: false, - endVote: null, + dateEnd: null, emails: [], }; -type DispatchType = Dispatch>; +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 -const ElectionContext = createContext(defaultElection); -// Store the dispatch function that can modify an election -// const ElectionDispatchContext = createContext(null); -const ElectionDispatchContext = createContext(null); +export type SetAction = { + type: ElectionTypes.SET; + field: string; + value: any; +} +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; +const ElectionContext = createContext<[ElectionContextInterface, DispatchType]>([defaultElection, () => {}]); export function ElectionProvider({children}) { /** @@ -58,17 +107,15 @@ export function ElectionProvider({children}) { if (!router.isReady) return; dispatch({ - type: 'set', + type: ElectionTypes.SET, field: 'name', value: router.query.name || '', }); }, [router.isReady]); return ( - - - {children} - + + {children} ); } @@ -80,14 +127,8 @@ export function useElection() { 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 */ @@ -95,17 +136,13 @@ function electionReducer(election: ElectionContextInterface, action) { case 'set': { return {...election, [action.field]: action.value}; } - case 'commit': { - throw Error('Not implemented yet'); - } - case 'remove': { - throw Error('Not implemented yet'); - } case 'candidate-push': { + if (typeof action.value === "string" && action.value !== "default") { + throw Error("Unexpected action") + } const candidate = action.value === 'default' ? {...defaultCandidate} : action.value; const candidates = [...election.candidates, candidate]; - console.log("NONACTIVE", candidates.filter(c => !c.active).length) if (candidates.filter(c => !c.active).length === 0) { return { ...election, candidates: [...candidates, {...defaultCandidate}] @@ -142,9 +179,7 @@ function electionReducer(election: ElectionContextInterface, action) { return {...election, candidates}; } case 'grade-push': { - const grade = - action.value === 'default' ? {...defaultCandidate} : action.value; - const grades = [...election.grades, grade]; + const grades = [...election.grades, action.value]; return {...election, grades}; } case 'grade-rm': { diff --git a/services/api.ts b/services/api.ts index 6f0f9ad..2377743 100644 --- a/services/api.ts +++ b/services/api.ts @@ -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; + msg: string; + type: string; + ctx: any; +} + +export interface ErrorPayload { + detail: Array; +} + +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; + candidates: Array; +} + +export interface ElectionCreatedPayload extends ElectionPayload { + invites: Array; + admin: string; + num_voters: number; +} + + +export interface ResultsPayload extends ElectionPayload { + status: number; + ranking: {[key: string]: number}; + merit_profile: {[key: number]: Array}; +} + + +export interface VotePayload { + id: string; + candidate: CandidatePayload; + grade: GradePayload; +} + +export interface BallotPayload { + votes: Array; + election: ElectionPayload; + token: string; +} + export const createElection = async ( name: string, candidates: Array, @@ -79,11 +150,7 @@ export const createElection = async ( }; -export const getResults = async ( - pid: string, - successCallback = null, - failureCallback = null -): Promise => { +export const getResults = async (pid: string): Promise => { /** * Fetch results from external API */ @@ -102,32 +169,29 @@ export const getResults = async ( const payload = await response.json() return {...payload, status: response.status}; } catch (error) { - return new Promise(() => "API errors") + return {status: 400, msg: "Unknown API error"} } }; -export const getElection = async ( - pid: string, -): Promise => { +export const getElection = async (pid: string): Promise => { /** * Fetch data from external API */ - const detailsEndpoint = new URL( - api.routesServer.getElection.replace(new RegExp(':slug', 'g'), pid), - api.urlServer - ); + const path = api.routesServer.getElection.replace(new RegExp(':slug', 'g'), pid); + const endpoint = new URL(path, api.urlServer); + try { - const res = await fetch(detailsEndpoint.href); + const response = await fetch(endpoint.href); - if (!res.ok) { - return res.text() + if (response.status != 200) { + const payload = await response.json(); + return {status: response.status, msg: payload}; } - - const payload: ElectionPayload = await res.json(); - return payload; + const payload = await response.json() + return {...payload, status: response.status}; } 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; - msg: string; - type: string; - ctx: any; -} - -export interface ErrorPayload { - detail: Array; -} - -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; - candidates: Array; -} - -export interface ElectionCreatedPayload extends ElectionPayload { - invites: Array; - admin: string; - num_voters: number; -} - - -export interface ResultsPayload extends ElectionPayload { - status: number; - ranking: {[key: string]: number}; - merit_profile: {[key: number]: Array}; -} - - -export interface VotePayload { - id: string; - candidate: CandidatePayload; - grade: GradePayload; -} - -export interface BallotPayload { - votes: Array; - election: ElectionPayload; - token: string; -} diff --git a/services/type.ts b/services/type.ts index b2b8800..fe56354 100644 --- a/services/type.ts +++ b/services/type.ts @@ -2,6 +2,7 @@ export interface Candidate { name: string, image?: string, description?: string + id?: number; } export interface CandidateItem extends Candidate { @@ -9,9 +10,10 @@ export interface CandidateItem extends Candidate { } export interface Grade { - name: string, - value: number, - description?: string + name: string; + value: number; + description?: string; + id?: number; } export interface GradeItem extends Grade { diff --git a/services/utils.ts b/services/utils.ts index 7e165ae..459ee49 100644 --- a/services/utils.ts +++ b/services/utils.ts @@ -32,6 +32,5 @@ export const displayRef = (ref: string): string => { export const isEnded = (date: string): boolean => { const dateEnd = new Date(date); const now = new Date(); - console.log(dateEnd, now) return +dateEnd < +now; }