fix: background

pull/89/head
Pierre-Louis Guhur 1 year ago
parent 9454f90b34
commit fe61b09bb4

@ -0,0 +1,6 @@
const Blur = () => {
return <div id="blur_background"></div>
}
export default Blur;

@ -0,0 +1,57 @@
/**
* A modal to details a candidate
*/
import {
Button,
Col,
Container,
Row,
Modal,
ModalHeader,
ModalBody,
} from 'reactstrap';
import Image from 'next/image';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faXmark} from '@fortawesome/free-solid-svg-icons';
import {CandidatePayload} from '@services/api';
import defaultAvatar from '../public/avatarBlue.svg';
interface CandidateModal {
isOpen: boolean;
toggle: Function;
candidate: CandidatePayload;
}
const CandidateModal = ({isOpen, toggle, candidate}) => {
return (
< Modal
isOpen={isOpen}
toggle={toggle}
keyboard={true}
className="modal_candidate"
centered={true}
>
<div className="w-100 h-100 p-4 bg-white">
<div className="d-flex justify-content-between mb-4">
<Image
src={candidate && candidate.image ? candidate.image : defaultAvatar}
height={96}
width={96}
alt={candidate && candidate.name}
className="rounded-circle bg-light"
/>
<FontAwesomeIcon onClick={toggle} icon={faXmark} />
</div>
<div className="px-2">
<h5>{candidate && candidate.name}</h5>
<p>{candidate && candidate.description}</p>
</div>
</div>
</Modal >
)
};
export default CandidateModal;

@ -3,39 +3,71 @@ import {useTranslation} from 'next-i18next';
import {CSSProperties, useEffect, useState} from 'react';
import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {Container} from 'reactstrap';
import Button from '@components/Button';
import ButtonCopy from '@components/ButtonCopy';
import Share from '@components/Share';
import ErrorMessage from '@components/Error';
import AdminModalEmail from '@components/admin/AdminModalEmail';
import {ElectionPayload, ErrorPayload} from '@services/api';
import {BallotPayload, ErrorPayload} from '@services/api';
import {useAppContext} from '@services/context';
import {getUrlVote, getUrlResults} from '@services/routes';
import {getUrlResults} from '@services/routes';
import urne from '../public/urne.svg'
import star from '../public/star.svg'
import {Container} from 'reactstrap';
export interface WaitingBallotInterface {
election?: ElectionPayload;
ballot?: BallotPayload;
error?: ErrorPayload;
}
interface InfoElectionInterface extends WaitingBallotInterface {
display: string;
const ButtonResults = ({election}) => {
const {t} = useTranslation();
const dateEnd = new Date(election.date_end);
const now = new Date();
const isEnded = +dateEnd > +now;
if (!election.hideResults || isEnded) {
return (
<Button className="" icon={faArrowRight} position="right">
{t('vote.go-to-results')}
</Button>
)
} else {
return null;
}
}
const InfoElection = ({election, error, display}: InfoElectionInterface) => {
const DiscoverMajorityJudgment = () => {
const {t} = useTranslation();
return (
<div className="bg-secondary p-4 text-white">
<h5>{t('vote.discover-mj')}</h5>
<h5>{t('vote.discover-mj-desc')}</h5>
<a href="https://mieuxvoter.fr/le-jugement-majoritaire">
<div className="d-flex">
<div className="me-2">{t('common.about')}</div>
<FontAwesomeIcon icon={faArrowRight} />
</div>
</a>
</div>)
}
interface InfoInterface extends WaitingBallotInterface {
display: string;
}
const [modal, setModal] = useState(false);
const toggleModal = () => setModal(m => !m);
const Info = ({ballot, error, display}: InfoInterface) => {
const {t} = useTranslation();
if (!election) return null;
if (!ballot) return null;
const urlVote = getUrlVote(election.id)
const urlResults = getUrlResults(election.id)
if (error) {
return <ErrorMessage msg={error.detail[0].msg} />
}
return (
<div style={{
@ -44,60 +76,26 @@ const InfoElection = ({election, error, display}: InfoElectionInterface) => {
}}
className="d-flex flex-column align-items-center"
>
{error && error.detail ?
<ErrorMessage msg={error.detail[0].msg} /> : null}
{election && election.id ?
<>
<h4 className="text-center">
{t('admin.success-election')}
</h4>
{election && election.restricted ?
<h5 className="text-center">
{t('admin.success-emails')}
</h5>
: <div className="d-grid w-100">
<ButtonCopy
text={t('admin.success-copy-vote')}
content={urlVote}
/>
<ButtonCopy
text={t('admin.success-copy-result')}
content={urlResults}
/>
</div>}
<div className="d-grid w-100">
<Button
customIcon={<FontAwesomeIcon icon={faArrowRight} />}
position="right"
color="secondary"
outline={true}
onClick={toggleModal}
className="mt-3 py-3 px-4"
>
{t('admin.go-to-admin')}
</Button>
</div>
<Share title={t('common.share-short')} />
<AdminModalEmail
toggle={toggleModal}
isOpen={modal}
electionId={election.id}
adminToken={election.admin}
/>
</> : null}
</div>
<h4 className="text-center">
{t('vote.success-ballot')}
</h4>
<ButtonResults election={ballot.election} />
<Container className="justify-content-between">
<DiscoverMajorityJudgment />
<SupportBetterVote />
</Container>
</div >
)
}
export default ({election, error}: WaitingBallotInterface) => {
export default ({ballot, error}: WaitingBallotInterface) => {
const {setApp} = useAppContext();
const [urneProperties, setUrne] = useState<CSSProperties>({width: 0, height: 0, marginBottom: 0});
const [starProperties, setStar] = useState<CSSProperties>({width: 0, height: 0, marginLeft: 100, marginBottom: 0});
const [urneContainerProperties, setUrneContainer] = useState<CSSProperties>({height: "100vh"});
const [electionProperties, setElection] = useState<CSSProperties>({display: "none"});
const [ballotProperties, setBallot] = useState<CSSProperties>({display: "none"});
useEffect(() => {
@ -121,7 +119,7 @@ export default ({election, error}: WaitingBallotInterface) => {
}, 1000);
const timer2 = setTimeout(() => {
// setElection({display: "block"});
// setBallot({display: "block"});
setUrneContainer(urneContainer => ({
...urneContainer,
height: "50vh",
@ -141,7 +139,7 @@ export default ({election, error}: WaitingBallotInterface) => {
}, 3000);
const timer3 = setTimeout(() => {
setElection({display: "grid"});
setBallot({display: "grid"});
}, 4500);
return () => {
@ -200,6 +198,6 @@ export default ({election, error}: WaitingBallotInterface) => {
/>
</div>
</div>
<InfoElection election={election} error={error} display={electionProperties.display} />
<Info ballot={ballot} error={error} display={ballotProperties.display} />
</Container >)
}

@ -0,0 +1,205 @@
import Image from 'next/image';
import {useTranslation} from 'next-i18next';
import {CSSProperties, useEffect, useState} from 'react';
import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import Button from '@components/Button';
import ButtonCopy from '@components/ButtonCopy';
import Share from '@components/Share';
import ErrorMessage from '@components/Error';
import AdminModalEmail from '@components/admin/AdminModalEmail';
import {ElectionPayload, ErrorPayload} from '@services/api';
import {useAppContext} from '@services/context';
import {getUrlVote, getUrlResults} from '@services/routes';
import urne from '../public/urne.svg'
import star from '../public/star.svg'
import {Container} from 'reactstrap';
export interface WaitingBallotInterface {
election?: ElectionPayload;
error?: ErrorPayload;
}
interface InfoElectionInterface extends WaitingBallotInterface {
display: string;
}
const InfoElection = ({election, error, display}: InfoElectionInterface) => {
const {t} = useTranslation();
const [modal, setModal] = useState(false);
const toggleModal = () => setModal(m => !m);
if (!election) return null;
const urlVote = getUrlVote(election.id)
const urlResults = getUrlResults(election.id)
return (
<div style={{
display: display,
transition: "display 2s",
}}
className="d-flex flex-column align-items-center"
>
{error && error.detail ?
<ErrorMessage msg={error.detail[0].msg} /> : null}
{election && election.id ?
<>
<h4 className="text-center">
{t('admin.success-election')}
</h4>
{election && election.restricted ?
<h5 className="text-center">
{t('admin.success-emails')}
</h5>
: <div className="d-grid w-100">
<ButtonCopy
text={t('admin.success-copy-vote')}
content={urlVote}
/>
<ButtonCopy
text={t('admin.success-copy-result')}
content={urlResults}
/>
</div>}
<div className="d-grid w-100">
<Button
customIcon={<FontAwesomeIcon icon={faArrowRight} />}
position="right"
color="secondary"
outline={true}
onClick={toggleModal}
className="mt-3 py-3 px-4"
>
{t('admin.go-to-admin')}
</Button>
</div>
<Share title={t('common.share-short')} />
<AdminModalEmail
toggle={toggleModal}
isOpen={modal}
electionId={election.id}
adminToken={election.admin}
/>
</> : null}
</div>
)
}
export default ({election, error}: WaitingBallotInterface) => {
const {setApp} = useAppContext();
const [urneProperties, setUrne] = useState<CSSProperties>({width: 0, height: 0, marginBottom: 0});
const [starProperties, setStar] = useState<CSSProperties>({width: 0, height: 0, marginLeft: 100, marginBottom: 0});
const [urneContainerProperties, setUrneContainer] = useState<CSSProperties>({height: "100vh"});
const [electionProperties, setElection] = useState<CSSProperties>({display: "none"});
useEffect(() => {
setApp({footer: false});
setUrne(urne => ({
...urne,
width: 300,
height: 300,
}));
const timer = setTimeout(() => {
setStar(star => ({
...star,
width: 150,
height: 150,
marginLeft: 150,
marginBottom: 300,
}
));
}, 1000);
const timer2 = setTimeout(() => {
// setElection({display: "block"});
setUrneContainer(urneContainer => ({
...urneContainer,
height: "50vh",
}));
setStar(star => ({
...star,
width: 100,
height: 100,
marginLeft: 100,
marginBottom: 200,
}));
setUrne(urne => ({
...urne,
width: 200,
height: 200,
}));
}, 3000);
const timer3 = setTimeout(() => {
setElection({display: "grid"});
}, 4500);
return () => {
clearTimeout(timer);
clearTimeout(timer2);
clearTimeout(timer3);
};
}, [])
return (<Container
className="d-flex h-100 w-100 align-items-center flex-column"
style={{
maxWidth: "400px"
}}
>
<div
style={{
transition: "width 2s, height 2s",
height: urneContainerProperties.height,
}}
className="d-flex align-items-center"
>
<div
className="position-relative"
style={{
transition: "width 2s, height 2s, margin-bottom 2s",
zIndex: 2,
marginTop: urneProperties.marginBottom,
height: urneProperties.height,
width: urneProperties.width,
}}
>
<Image
src={urne}
alt="urne"
fill={true}
/>
</div>
<div
className="position-absolute"
style={{
transition: "width 2s, height 2s, margin-left 2s, margin-bottom 2s",
marginLeft: starProperties.marginLeft,
marginBottom: starProperties.marginBottom,
height: starProperties.height,
width: starProperties.width,
}}
>
<Image
src={star}
fill={true}
alt="urne"
/>
</div>
</div>
<InfoElection election={election} error={error} display={electionProperties.display} />
</Container >)
}

@ -1,4 +1,4 @@
import {MouseEvent} from 'react'
import {MouseEvent, useState} from 'react'
import {useRouter} from 'next/router';
import {useTranslation} from 'next-i18next';
import Button from '@components/Button';
@ -9,17 +9,21 @@ import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import CandidateCard from '@components/ballot/CandidateCard'
import TitleBar from '@components/ballot/TitleBar'
import GradeInput from '@components/ballot/GradeInput'
import {CandidatePayload} from '@services/api';
import CandidateModal from '@components/CandidateModalGet';
const BallotDesktop = () => {
const {t} = useTranslation();
const [ballot, dispatch] = useBallot();
const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length;
const router = useRouter();
const [candidate, setCandidate] = useState(null);
const handleSubmit = (event: MouseEvent) => {
event.preventDefault();
@ -37,15 +41,19 @@ const BallotDesktop = () => {
// });
};
console.log(candidate)
return (
<div className="w-100 h-100 d-none d-lg-block">
<div className="w-100 h-100 d-none d-md-block">
<TitleBar election={ballot.election} />
<Container className="w-100 h-100 d-flex flex-column justify-content-center align-items-center" style={{maxWidth: "750px"}}>
<h1 className="mb-5">{ballot.election.name}</h1>
{ballot.election.candidates.map((candidate, candidateId) => {
return (
<div key={candidateId} className="bg-white justify-content-between d-flex my-4 py-2 w-100 px-3">
<CandidateCard candidate={candidate} />
<div key={candidateId} className="bg-white justify-content-between d-flex my-2 py-2 w-100 px-3">
<CandidateCard
onClick={() => setCandidate(candidate)}
candidate={candidate}
/>
<div className="d-flex">
{ballot.election.grades.map((_, gradeId) => {
console.assert(gradeId < numGrades);
@ -57,6 +65,7 @@ const BallotDesktop = () => {
</div>
);
})}
<CandidateModal isOpen={candidate !== null} toggle={() => setCandidate(null)} candidate={candidate} />
<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}

@ -43,7 +43,7 @@ const BallotMobile = () => {
}
return (
<div className="w-100 h-100 d-block d-lg-none">
<div className="w-100 h-100 d-block d-md-none">
<TitleName name={ballot.election.name} />
<div className="w-100 d-flex">

@ -2,13 +2,17 @@ import Image from 'next/image';
import {useTranslation} from 'next-i18next';
import defaultAvatar from '../../public/avatarBlue.svg';
import {CandidatePayload} from '@services/api';
import {MouseEventHandler} from 'react';
interface CandidateCardInterface {
candidate: CandidatePayload;
onClick: MouseEventHandler;
}
const CandidateCard = ({candidate}: CandidateCardInterface) => {
const CandidateCard = ({candidate, onClick}: CandidateCardInterface) => {
const {t} = useTranslation();
return (<div className="d-flex align-items-center flex-fill">
return (<div
onClick={onClick}
className="d-flex align-items-center flex-fill">
<Image
src={defaultAvatar}
width={32}

@ -36,14 +36,14 @@ const GradeInput = ({gradeId, candidateId}: GradeInputInterface) => {
return (<>
<div
className={`justify-content-center d-none d-lg-flex my-1 rounded-1 px-2 py-1 fs-5 text-white ms-3`}
className={`justify-content-center d-none d-md-flex my-1 rounded-1 px-2 py-1 fs-5 text-white ms-3`}
onClick={handleClick}
style={{backgroundColor: color, boxShadow: active ? `0px 2px 0px ${color}` : "0px 2px 0px #8F88BA"}}
>
<GradeName name={grade.name} active={active} />
</div >
<div
className={`d-flex d-lg-none my-1 justify-content-center py-2 text-white`}
className={`d-flex d-md-none my-1 justify-content-center py-2 text-white`}
onClick={handleClick}
style={{
backgroundColor: color,

@ -15,16 +15,25 @@ const TitleBar = ({election}: TitleBarInterface) => {
const router = useRouter();
const locale = getLocaleShort(router);
return (
<div className="w-100 bg-light p-2 text-black justify-content-center d-flex ">
<div className="me-2">
<FontAwesomeIcon icon={faCalendarDays} />
</div>
<div>
{` ${t("vote.open-until")} ${new Date(election.date_end).toLocaleDateString(locale, {dateStyle: "long"})}`}
const dateEnd = new Date(election.date_end);
const farAway = new Date();
farAway.setFullYear(farAway.getFullYear() + 1);
const isFarAway = +dateEnd > +farAway;
if (!isFarAway) {
return (
<div className="w-100 bg-light p-2 text-black justify-content-center d-flex ">
<div className="me-2">
<FontAwesomeIcon icon={faCalendarDays} />
</div>
<div>
{` ${t("vote.open-until")} ${new Date(election.date_end).toLocaleDateString(locale, {dateStyle: "long"})}`}
</div>
</div>
</div>
)
)
} else {
return null;
}
};
export default TitleBar

@ -3,12 +3,13 @@ import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import CandidatesField from '@components/admin/CandidatesField';
import ParamsField from '@components/admin/ParamsField';
import ConfirmField from '@components/admin/ConfirmField';
import WaitingBallot from '@components/WaitingBallot';
import WaitingElection from '@components/WaitingElection';
import PatternedBackground from '@components/PatternedBackground';
import {
ElectionProvider,
} from '@services/ElectionContext';
import {ProgressSteps, creationSteps} from '@components/CreationSteps';
import Blur from '@components/Blur'
import {GetStaticProps} from 'next';
import {ElectionPayload, ErrorPayload} from '@services/api';
@ -19,6 +20,8 @@ export const getStaticProps: GetStaticProps = async ({locale}) => ({
},
});
const CreateElectionForm = () => {
/**
* Manage the steps for creating an election
@ -60,13 +63,16 @@ const CreateElectionForm = () => {
}
if (wait) {
return <PatternedBackground>
<WaitingBallot election={payload} error={error} />
</PatternedBackground>
return (<> <Blur />
<PatternedBackground>
<WaitingElection election={payload} error={error} />
</PatternedBackground>
</>)
}
return (
<ElectionProvider>
<Blur />
<ProgressSteps
step={step}
goToCandidates={() => setStepId(0)}

@ -1,13 +1,15 @@
import {useEffect} from 'react';
import {useEffect, useState} from 'react';
import Head from 'next/head';
import {useRouter} from 'next/router';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {useTranslation} from 'next-i18next';
import {getElection, castBallot, apiErrors, ElectionPayload, CandidatePayload, GradePayload} from '@services/api';
import {getElection, castBallot, ElectionPayload, BallotPayload, ErrorPayload} from '@services/api';
import BallotDesktop from '@components/ballot/BallotDesktop'
import BallotMobile from '@components/ballot/BallotMobile'
import Blur from '@components/Blur'
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import {ENDED_VOTE} from '@services/routes';
import WaitingBallot from '@components/WaitingBallot';
import PatternedBackground from '@components/PatternedBackground';
const shuffle = (array) => array.sort(() => Math.random() - 0.5);
@ -66,9 +68,12 @@ interface VoteInterface {
const VoteBallot = ({election, token}: VoteInterface) => {
const {t} = useTranslation();
const router = useRouter();
const [ballot, dispatch] = useBallot();
const [voting, setVoting] = useState(false);
const [payload, setPayload] = useState<BallotPayload | null>(null);
const [error, setError] = useState<ErrorPayload | null>(null);
useEffect(() => {
dispatch({
type: BallotTypes.ELECTION,
@ -80,61 +85,34 @@ const VoteBallot = ({election, token}: VoteInterface) => {
return <div>"Loading..."</div>;
}
const numGrades = ballot.election.grades.length;
const colSizeCandidateLg = 4;
const colSizeCandidateMd = 6;
const colSizeCandidateXs = 12;
const colSizeGradeLg = Math.floor((12 - colSizeCandidateLg) / numGrades);
const colSizeGradeMd = Math.floor((12 - colSizeCandidateMd) / numGrades);
const colSizeGradeXs = Math.floor((12 - colSizeCandidateXs) / numGrades);
const handleSubmitWithoutAllRate = () => {
alert(t('You have to judge every candidate/proposal!'));
};
const handleSubmit = (event) => {
const handleSubmit = async (event) => {
event.preventDefault();
// const gradesById = {};
// judgments.forEach((c) => {
// gradesById[c.id] = c.value;
// });
// const gradesByCandidate = [];
// Object.keys(gradesById).forEach((id) => {
// gradesByCandidate.push(gradesById[id]);
// });
// castBallot(gradesByCandidate, election.id.toString(), token, () => {
router.push(`/confirm/${election.id}`);
// });
setVoting(true);
try {
const res = await castBallot(
ballot.votes,
ballot.election.ref, ballot.election.restricted, token)
if (res.status !== 200) {
console.error(res);
const msg = await res.json();
setError(msg)
}
else {
const msg = await res.json();
setPayload(msg)
}
} catch (err) {
console.error(err);
setError(err.message)
}
};
// const [viewportRef, embla] = useEmblaCarousel({skipSnaps: false});
// const [prevBtnEnabled, setPrevBtnEnabled] = useState(false);
// const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
// const [selectedIndex, setSelectedIndex] = useState(0);
// const [scrollSnaps, setScrollSnaps] = useState([]);
// const scrollPrev = useCallback(() => embla && embla.scrollPrev(), [embla]);
// const scrollNext = useCallback(() => embla && embla.scrollNext(), [embla]);
// const scrollTo = useCallback(
// (index) => embla && embla.scrollTo(index),
// [embla]
// );
// const onSelect = useCallback(() => {
// if (!embla) return;
// setSelectedIndex(embla.selectedScrollSnap());
// setPrevBtnEnabled(embla.canScrollPrev());
// setNextBtnEnabled(embla.canScrollNext());
// }, [embla, setSelectedIndex]);
// useEffect(() => {
// if (!embla) return;
// onSelect();
// setScrollSnaps(embla.scrollSnapList());
// embla.on('select', onSelect);
// }, [embla, setScrollSnaps, onSelect]);
if (voting) {
return <PatternedBackground>
<WaitingBallot ballot={payload} error={error} />
</PatternedBackground>
}
return (
<form className="w-100 h-100" onSubmit={handleSubmit} autoComplete="off">
@ -149,6 +127,7 @@ const VoteBallot = ({election, token}: VoteInterface) => {
/>
</Head>
<Blur />
<BallotDesktop />
<BallotMobile />
</form >

@ -26,6 +26,7 @@
"menu.faq": "FAQ",
"menu.news": "News",
"menu.contact-us": "Contact us",
"common.about": "Read more",
"common.about-mj": "Read more about Better Vote",
"common.error": "Oh no... An error has occured...",
"common.better-vote": "Better Vote",
@ -106,9 +107,11 @@
"admin.success-copy-result": "Copy the result link",
"admin.success-copy-admin": "Copy the admin link",
"admin.go-to-admin": "Manage the vote",
"vote.go-to-results": "Show results",
"vote.home-desc": "Participate in the vote and discover majority judgment",
"vote.home-start": "I participate",
"vote.open-until": "Vote open until",
"vote.more-details": "More details...",
"vote.submit": "Cast my ballot"
"vote.submit": "Cast my ballot",
"vote.success-ballot": "Your vote has successfully been taken into account!"
}

@ -26,6 +26,7 @@
"menu.faq": "FAQ",
"menu.news": "Actualités",
"menu.contact-us": "Nous contacter",
"common.about": "En savoir plus...",
"common.about-mj": "En savoir plus sur Mieux voter",
"common.error": "Oh non ! Une erreur s'est produite...",
"common.better-vote": "Mieux Voter",
@ -106,9 +107,13 @@
"admin.success-copy-result": "Copier le lien des résultats",
"admin.success-copy-admin": "Copier le lien d'administration",
"admin.go-to-admin": "Administrez le vote",
"vote.discover-mj": "Découvrez le jugement majoritaire",
"vote.discover-mj-desc": "Créé par des chercheurs français, le jugement majoritaire est un mode de scrutin qui améliore l'expressivité des électeurs et fournit le meilleur consensus.",
"vote.go-to-results": "Voir les résultats",
"vote.home-desc": "Participez au vote et découvrez le jugement majoritaire.",
"vote.home-start": "Je participe",
"vote.open-until": "Vote ouvert jusqu'au",
"vote.more-details": "Cliquez ici pour en savoir plus",
"vote.submit": "Déposer mon bulletin de vote"
"vote.submit": "Déposer mon bulletin de vote",
"vote.success-ballot": "Votre vote a bien été pris en compte !"
}

@ -3,13 +3,9 @@
*/
import {createContext, useContext, useReducer, Dispatch} from 'react';
import {ElectionPayload} from './api';
import {Vote} from './type';
export interface Vote {
candidateId: number;
gradeId: number;
}
export interface BallotContextInterface {
election: ElectionPayload | null;
votes: Array<Vote>;
@ -24,6 +20,7 @@ const defaultBallot: BallotContextInterface = {
export enum BallotTypes {
ELECTION = 'ELECTION',
VOTE = 'VOTE',
COMMIT = 'COMMIT',
}
export type ElectionAction = {
@ -37,7 +34,12 @@ export type VoteAction = {
gradeId: number;
}
export type BallotActionTypes = ElectionAction | VoteAction;
export type BallotAction = {
type: BallotTypes.COMMIT;
token?: string;
}
export type BallotActionTypes = ElectionAction | VoteAction | BallotAction;
function reducer(ballot: BallotContextInterface, action: BallotActionTypes) {
@ -65,6 +67,10 @@ function reducer(ballot: BallotContextInterface, action: BallotActionTypes) {
}
return {...ballot, votes};
}
case BallotTypes.COMMIT: {
throw Error("Not implemented")
return ballot;
}
default: {
return ballot
}

@ -1,4 +1,4 @@
import {Candidate, Grade} from './type';
import {Candidate, Grade, Vote} from './type';
export const api = {
urlServer:
@ -10,7 +10,7 @@ export const api = {
setElection: 'elections',
getElection: 'elections/:slug',
getResults: 'results/:slug',
voteElection: 'votes',
voteElection: 'ballots',
},
};
@ -131,11 +131,10 @@ export const getElection = async (
export const castBallot = (
judgments: Array<number>,
pid: string,
token: string,
callbackSuccess = null,
callbackError = null
votes: Array<Vote>,
electionRef: string,
restricted: boolean,
token?: string,
) => {
/**
* Save a ballot on the remote database
@ -144,20 +143,30 @@ export const castBallot = (
const endpoint = new URL(api.routesServer.voteElection, api.urlServer);
const payload = {
election: pid,
grades_by_candidate: judgments,
election_ref: electionRef,
votes: votes,
};
if (token && token !== '') {
payload['token'] = token;
}
fetch(endpoint.href, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
})
.then(callbackSuccess || ((res) => res))
.catch(callbackError || console.log);
if (!restricted) {
return fetch(endpoint.href, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload),
})
}
else {
if (!token) {
throw Error("Missing token")
}
return fetch(endpoint.href, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
"Authorization": `Bearer ${token}`
},
body: JSON.stringify(payload),
})
}
};
export const UNKNOWN_ELECTION_ERROR = 'E1:';
@ -243,3 +252,15 @@ export interface ResultsPayload extends ElectionPayload {
votes: {[key: string]: Array<number>};
}
export interface VotePayload {
id: string;
candidate: CandidatePayload;
grade: GradePayload;
}
export interface BallotPayload {
votes: Array<VotePayload>;
election: ElectionPayload;
token: string;
}

@ -17,3 +17,9 @@ export interface Grade {
export interface GradeItem extends Grade {
active: boolean;
}
export interface Vote {
candidateId: number;
gradeId: number;
}

@ -7,13 +7,12 @@
font-weight: 500;
font-size: 16px;
}
}
.creation-step-icon {
width: 24px;
height: 24px;
background-color: black; // , 0.2);
width: 24px;
height: 24px;
background-color: black; // , 0.2);
}
.creation-step.active {
@ -35,7 +34,7 @@ background-color: black; // , 0.2);
padding: 4px;
}
.candidate * > input[type="file"] {
.candidate * > input[type='file'] {
display: none;
}
.candidate * > .inputfile {
@ -53,7 +52,7 @@ background-color: black; // , 0.2);
.modal
> *
:is(input[type="text"], input[type="text"]:focus, input[type="text"]::placeholder) {
:is(input[type='text'], input[type='text']:focus, input[type='text']::placeholder) {
background: white;
border-radius: 0px;
color: black !important;
@ -62,10 +61,10 @@ background-color: black; // , 0.2);
margin-bottom: 20px;
}
.modal> * input[type="text"]:focus {
.modal > * input[type='text']:focus {
opacity: 0.8;
}
.modal> * input[type="text"]:placeholder {
.modal > * input[type='text']:placeholder {
opacity: 0.4;
}
@ -74,27 +73,27 @@ background-color: black; // , 0.2);
}
.desktop_step {
width: 24px;
height: 24px;}
width: 24px;
height: 24px;
}
.mobile_step {
width: 32px;
height: 32px;}
width: 32px;
height: 32px;
}
.disabled {
opacity: 0.6;
opacity: 0.6;
}
/* Waiting page */
.waiting {
background-color: #2400fd;
background-image: url("/back.svg");
background-size: auto 100%;
// background-position: top right -260px;
background-repeat: no-repeat;
background-image: url('../../public/back.svg');
background-size: auto 100%;
background-repeat: no-repeat;
}
@include media-breakpoint-up("xxl") {
@include media-breakpoint-up('xxl') {
.waiting {
background-size: 100% auto;
background-size: 100% auto;
}
}

@ -1,23 +1,24 @@
@import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=DM+Serif+Display:ital@0;1&display=swap");
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&family=DM+Serif+Display:ital@0;1&display=swap');
body {
background-color: $mv-blue-color;
// background-color: $mv-blue-color;
// background-color: #110081;
font-weight: 500;
}
h1,
h2,
h3 {
font-family: "DM Serif Display", serif !important;
font-family: 'DM Serif Display', serif !important;
}
body,
h4,
h5,
p,
button {
font-family: "DM Sans", sans-serif !important;
font-family: 'DM Sans', sans-serif !important;
}
::placeholder {
font-family: "DM Sans", sans-serif !important;
font-family: 'DM Sans', sans-serif !important;
}
.rowNoMargin {
margin-left: 0px !important;
@ -51,8 +52,30 @@ main > .div {
width: 100%;
}
main {
background: url("../../public/back.svg"), rgb(10, 0, 76, 0.64);
/* main {
// background: url("../../public/back.svg"), rgb(10, 0, 76, 0.64);
} */
$blur-radius: 30px;
#blur_background {
position: fixed;
top: -$blur-radius;
right: -$blur-radius;
bottom: -$blur-radius;
left: -$blur-radius;
z-index: -1;
background: url('../../public/back.svg'), #110081;
background-size: auto 100%;
background-repeat: no-repeat;
// filter: url('/media/blur.svg#blur');
-webkit-filter: blur($blur-radius);
filter: blur($blur-radius);
}
@include media-breakpoint-up('xxl') {
#blur_background {
background-size: 100% auto;
}
}
header {
@ -150,7 +173,7 @@ li.sortable {
/* Create the indicator (the dot/circle - hidden when not checked) */
.checkround:after {
content: "";
content: '';
position: absolute;
display: none;
}
@ -225,7 +248,7 @@ li.sortable {
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:before {
content: "";
content: '';
display: none;
}
@ -377,15 +400,14 @@ ol.result > li {
}
.text-muted {
color: #8F88BA!important;
// opacity: 0.5;
color: #8f88ba !important;
// opacity: 0.5;
}
.text-bg-light {
background: #F2F0FF!important;
.text-bg-light {
background: #f2f0ff !important;
}
.hide {
display: none;
}
@ -393,19 +415,19 @@ ol.result > li {
box-shadow: unset !important;
}
.list-input input[type="text"]:focus {
outline: none;
.list-input input[type='text']:focus {
outline: none;
}
a, a:hover {
text-decoration: none;
a,
a:hover {
text-decoration: none;
}
.bg-blue {
background-color: $mv-blue-color!important;
background-color: $mv-blue-color !important;
}
.no-shadow {
box-shadow: unset!important;
box-shadow: unset !important;
}

@ -1,11 +1,10 @@
@import "../../node_modules/bootstrap/scss/mixins/banner";
@include bsBanner("");
@import '../../node_modules/bootstrap/scss/mixins/banner';
@include bsBanner('');
// scss-docs-start import-stack
// Configuration
@import '../../node_modules/bootstrap/scss/functions';
@import "../../node_modules/bootstrap/scss/variables";
@import '../../node_modules/bootstrap/scss/variables';
@import '../../node_modules/bootstrap/scss/maps';
@import '../../node_modules/bootstrap/scss/mixins';
@import '../../node_modules/bootstrap/scss/utilities';

@ -1,4 +1,4 @@
@use "sass:map";
@use 'sass:map';
.btn {
//width: 165px;
@ -17,7 +17,7 @@
border-style: solid;
border-radius: 0px;
}
*[class*="btn-outline-"] {
*[class*='btn-outline-'] {
box-shadow: 0px 4px 0px;
border-width: 2px;
}
@ -42,21 +42,21 @@
transition: 0.5s;
margin: auto;
}
*.btn[class*="-secondary"] {
*.btn[class*='-secondary'] {
/* width: fit-content;*/
background-color: transparent;
border-color: white;
color: white;
}
*.btn[class*="-primary"] {
*.btn[class*='-primary'] {
border: 2px solid $mv-blue-color;
box-shadow: 0px 5px 0px #7a64f9;
}
*.btn[class*="-info"] {
*.btn[class*='-info'] {
background-color: #4a2fef;
border: 2px solid #4a2fef;
}
.btn:not([class*="btn-outline-"]):hover {
.btn:not([class*='btn-outline-']):hover {
background-color: rgb(255, 255, 255, 0.2);
color: inherit;
border-color: inherit;
@ -64,19 +64,18 @@
box-shadow: unset;
}
.btn.btn-danger:hover {
color: map.get($theme-colors, "danger");
border-color: map.get($theme-colors, "danger");
color: map.get($theme-colors, 'danger');
border-color: map.get($theme-colors, 'danger');
border-width: 2px;
box-shadow: unset;
}
.btn_menu {
position: absolute;
top: 20px;
right: 20px;
position: absolute;
top: 20px;
right: 20px;
}
.btn_shadow{
box-shadow: 0px 2px 0px #8F88BA;
.btn_shadow {
box-shadow: 0px 2px 0px #8f88ba;
}

@ -2,7 +2,7 @@
border-color: $mv-blue-color;
border-style: solid;
border-width: 2px 2px 0 0;
content: "";
content: '';
display: block;
height: 12px;
position: absolute;
@ -28,7 +28,7 @@
border: $datepicker__triangle-size solid transparent;
height: 0;
width: 1px;
content: "";
content: '';
z-index: -1;
border-width: $datepicker__triangle-size;
left: -$datepicker__triangle-size;

@ -13,7 +13,7 @@ $datepicker__navigation-disabled-color: lighten(
$datepicker__border-radius: 2px !default;
$datepicker__day-margin: 0 !default;
$datepicker__font-size: 0.8rem !default;
$datepicker__font-family: "DM Sans", sans-serif !default;
$datepicker__font-family: 'DM Sans', sans-serif !default;
$datepicker__item-size: 42px !default;
$datepicker__margin: 0.4rem !default;
$datepicker__navigation-button-size: 32px !default;

@ -1,5 +1,5 @@
@import "./_datepicker-variables.scss";
@import "./_datepicker-mixins.scss";
@import './_datepicker-variables.scss';
@import './_datepicker-mixins.scss';
.react-datepicker-wrapper {
display: inline-block;
@ -46,7 +46,7 @@
.react-datepicker-popper {
z-index: 1;
&[data-placement^="bottom"] {
&[data-placement^='bottom'] {
padding-top: $datepicker__triangle-size + 2px;
.react-datepicker__triangle {
@ -54,15 +54,15 @@
}
}
&[data-placement="bottom-end"],
&[data-placement="top-end"] {
&[data-placement='bottom-end'],
&[data-placement='top-end'] {
.react-datepicker__triangle {
left: auto;
right: 50px;
}
}
&[data-placement^="top"] {
&[data-placement^='top'] {
padding-bottom: $datepicker__triangle-size + 2px;
.react-datepicker__triangle {
@ -70,7 +70,7 @@
}
}
&[data-placement^="right"] {
&[data-placement^='right'] {
padding-left: $datepicker__triangle-size;
.react-datepicker__triangle {
@ -79,7 +79,7 @@
}
}
&[data-placement^="left"] {
&[data-placement^='left'] {
padding-right: $datepicker__triangle-size;
.react-datepicker__triangle {
@ -541,7 +541,7 @@
text-align: center;
display: table-cell;
vertical-align: middle;
content: "\00d7";
content: '\00d7';
}
}

@ -31,7 +31,7 @@ footer a:hover {
}
}
@include media-breakpoint-down("md") {
@include media-breakpoint-down('md') {
footer {
display: none !important;
}

@ -52,7 +52,7 @@
background-color: transparent !important;
border: none !important;
box-shadow: none !important;
font-family: "DM Sans", sans-serif !important;
font-family: 'DM Sans', sans-serif !important;
}
.navbar-accordion button {
background-color: transparent !important;
@ -71,7 +71,7 @@
height: 0.25rem;
margin-left: 0.255em;
vertical-align: 0.255em;
content: "";
content: '';
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;

@ -63,7 +63,7 @@ $desktop: 1680px;
.sectionTwoHome {
background: $mv-dark-blue-color;
padding: 10%;
background-image: url("/vote.svg");
background-image: url('/vote.svg');
background-position: top 46% right;
background-repeat: no-repeat;
}
@ -99,25 +99,25 @@ $desktop: 1680px;
display: flex;
}
@include media-breakpoint-up("md") {
@include media-breakpoint-up('md') {
.sectionOneHomeForm {
background-image: url("/chevron-bicolore.svg");
background-image: url('/chevron-bicolore.svg');
background-size: contain;
background-position: top right -260px;
background-repeat: no-repeat;
}
}
@include media-breakpoint-up("lg") {
@include media-breakpoint-up('lg') {
.sectionOneHomeForm {
background-position: top right -150px;
}
}
@include media-breakpoint-up("xl") {
@include media-breakpoint-up('xl') {
.sectionOneHomeForm {
background-position: top right -50px;
}
}
@include media-breakpoint-down("sm") {
@include media-breakpoint-down('sm') {
.sectionOneHomeForm {
background-image: unset;
}
@ -144,7 +144,7 @@ $desktop: 1680px;
margin: 40px 13% 60px;
}
}
@include media-breakpoint-up("md") {
@include media-breakpoint-up('md') {
.sectionOneHomeForm,
.sectionOneHomeContent {
min-height: 100vh;
@ -153,7 +153,7 @@ $desktop: 1680px;
margin-bottom: 70px;
}
}
@include media-breakpoint-down("sm") {
@include media-breakpoint-down('sm') {
.sectionOneHomeContent {
align-content: center;

@ -5,7 +5,7 @@
.addCandidatePage {
max-width: 100% !important;
background-color: #17048e;
background-image: url("/back.svg");
background-image: url('/back.svg');
background-size: 100%;
background-position: center;
padding: 0px;
@ -186,7 +186,7 @@
.addButton span {
margin-left: 20px;
}
input[type="file"] {
input[type='file'] {
display: none;
}
.inputfile {

@ -1,6 +1,6 @@
.resultPage {
background-image: url("/background-woman-left.svg"),
url("/background-woman-right.svg");
background-image: url('/background-woman-left.svg'),
url('/background-woman-right.svg');
background-position: left bottom, right bottom;
background-color: #1d0a93;
min-height: 100vh;

@ -11,6 +11,5 @@
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3.5' fill='%23fff'/%3e%3c/svg%3e");
}
.form-switch input {
outline: none;
outline: none;
}

@ -12,7 +12,7 @@
max-width: 100vw;
min-height: 100vh;
background-color: #17048e;
background-image: url("/back.svg");
background-image: url('/back.svg');
background-size: 100%;
background-position: center;
margin: 0px !important;
@ -23,9 +23,6 @@
width: fit-content;
margin: auto;
}
.modalVote > .modal-content {
background: transparent;
}
.modalVote > .modal-content > div > .modal-header {
border-bottom: none;
}
@ -34,7 +31,7 @@
font-size: 36px;
line-height: 56px;
margin: auto;
font-family: "DM Serif Display", serif !important;
font-family: 'DM Serif Display', serif !important;
font-weight: normal;
}
.candidateGrade {
@ -161,54 +158,6 @@
.checkmark > .badge {
width: fit-content;
}
.embla {
overflow: hidden;
}
.embla__container {
display: flex;
}
.embla__slide {
position: relative;
flex: 0 0 100%;
}
.embla__nav {
justify-content: center;
}
.embla__btn {
width: 32px;
height: 32px;
display: flex;
}
.embla__dots {
display: flex;
list-style: none;
justify-content: center;
padding-top: 10px;
}
.embla__dot {
background-color: #0a004c4d;
cursor: pointer;
position: relative;
padding: 0;
outline: 0;
border: 0;
width: 30px;
height: 30px;
margin-right: 7.5px;
margin-left: 7.5px;
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
}
.embla__dot.is-selected {
background-color: white;
opacity: 1;
color: #0a004c;
}
.btn-background {
background-color: #291797;
width: 100%;
@ -247,10 +196,12 @@
}
}
// EDIT
.candidate-vote {
width: 232px;
position: relative;
transition: left 1s;
}
.modal_candidate > .modal-content {
border: unset;
}

@ -14,14 +14,14 @@ $modal-header-border-width: 0px;
$badge-border-radius: 0px;
$theme-colors: (
"primary": $mv-blue-color,
"secondary": $mv-dark-blue-color,
"light": $mv-light-color,
"dark": $mv-dark-color,
"danger": #990000,
"success": #009900,
"info": #2b8299,
"warning": #ff6e11,
'primary': $mv-blue-color,
'secondary': $mv-dark-blue-color,
'light': $mv-light-color,
'dark': $mv-dark-color,
'danger': #990000,
'success': #009900,
'info': #2b8299,
'warning': #ff6e11,
);
// $grade-colors: (
@ -33,13 +33,13 @@ $theme-colors: (
// "very-good": #A0CF1C,
// "excellent": #3A9918,
// );
$font-size-base: 1rem;
$font-size-base: 1rem;
$h1-font-size: $font-size-base * 2.5;
$h2-font-size: $font-size-base * 2;
$h3-font-size: $font-size-base * 1.75;
$h4-font-size: $font-size-base * 1.5;
$h5-font-size: $font-size-base * 1.25;
$h1-font-size: $font-size-base * 2.5;
$h2-font-size: $font-size-base * 2;
$h3-font-size: $font-size-base * 1.75;
$h4-font-size: $font-size-base * 1.5;
$h5-font-size: $font-size-base * 1.25;
$font-sizes: (
1: $h1-font-size,
@ -47,21 +47,19 @@ $font-sizes: (
3: $h3-font-size,
4: $h4-font-size,
5: $font-size-base * 0.9,
6: $font-size-base * 0.75
6: $font-size-base * 0.75,
);
@import "_bootstrap.scss";
@import "app.scss";
@import "_button.scss";
@import "_badge.scss";
@import "_datepicker.scss";
@import "_header.scss";
@import "_footer.scss";
@import "_modal.scss";
@import "_homePage.scss";
@import "_vote.scss";
@import "_resultVote.scss";
@import "_admin.scss";
@import "_switch.scss";
@import '_bootstrap.scss';
@import 'app.scss';
@import '_button.scss';
@import '_badge.scss';
@import '_datepicker.scss';
@import '_header.scss';
@import '_footer.scss';
@import '_modal.scss';
@import '_homePage.scss';
@import '_vote.scss';
@import '_resultVote.scss';
@import '_admin.scss';
@import '_switch.scss';

Loading…
Cancel
Save