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}) => {
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}`;

@ -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,
});

@ -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();
};

@ -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);

@ -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();
};

@ -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,

@ -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<HTMLInputElement>) => {
console.log(e.key)
if (e.key == "Enter" && !disabled) {
onSubmit();
}

@ -29,7 +29,7 @@ import {gradeColors} from '@services/grades';
const TitleField = () => {
const {t} = useTranslation();
const election = useElection();
const [election, _] = useElection();
return (
<Container className="bg-white p-4">
<Row>
@ -44,7 +44,7 @@ const TitleField = () => {
const CandidatesField = () => {
const {t} = useTranslation();
const election = useElection();
const [election, _] = useElection();
return (
<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 {t} = useTranslation();
const election = useElection();
const router = useRouter();
const [election, _] = useElection();
const handleSubmit = () => {

@ -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,

@ -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<GradeItem>({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],
});

@ -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,
});

@ -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,
});
};

@ -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,
});

@ -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 (

@ -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<string>) => {
dispatch({
type: 'set',
type: ElectionTypes.SET,
field: 'emails',
value: emails,
});

@ -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;

@ -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) => (
// <option value={i} key={i}>
// {i}h00
// </option>
// ));
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<CandidateItem> = 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();

@ -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}
}

@ -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: () => <a>placeholder component...</a>
// })
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: {

@ -14,10 +14,18 @@ export interface ElectionContextInterface {
forceClose: boolean;
restricted: boolean;
randomOrder: boolean;
endVote: string;
emails: Array<string>;
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<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
const ElectionContext = createContext<ElectionContextInterface>(defaultElection);
// Store the dispatch function that can modify an election
// const ElectionDispatchContext = createContext<DispatchType | null>(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<ElectionActionTypes>;
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 (
<ElectionContext.Provider value={election}>
<ElectionDispatchContext.Provider value={dispatch}>
{children}
</ElectionDispatchContext.Provider>
<ElectionContext.Provider value={[election, dispatch]}>
{children}
</ElectionContext.Provider>
);
}
@ -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': {

@ -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 (
name: string,
candidates: Array<Candidate>,
@ -79,11 +150,7 @@ export const createElection = async (
};
export const getResults = async (
pid: string,
successCallback = null,
failureCallback = null
): Promise<ResultsPayload | HTTPPayload> => {
export const getResults = async (pid: string): Promise<ResultsPayload | HTTPPayload> => {
/**
* 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<ElectionPayload | string> => {
export const getElection = async (pid: string): Promise<ElectionPayload | HTTPPayload> => {
/**
* 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<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,
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 {

@ -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;
}

Loading…
Cancel
Save