fix: download csv

pull/89/head
Pierre-Louis Guhur 1 year ago
parent 9a41f72e70
commit 9e7e2c690f

@ -0,0 +1,125 @@
import React from 'react';
// import dynamic from 'next/dynamic'
// import {buildURI} from 'react-csv';
// /**
// * See https://github.com/react-csv/react-csv/issues/87
// */
export const isSafari = () => /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
export const isJsons = ((array) => Array.isArray(array) && array.every(
row => (typeof row === 'object' && !(row instanceof Array))
));
export const isArrays = ((array) => Array.isArray(array) && array.every(
row => Array.isArray(row)
));
export const jsonsHeaders = ((array) => Array.from(
array.map(json => Object.keys(json))
.reduce((a, b) => new Set([...a, ...b]), [])
));
export const jsons2arrays = (jsons, headers) => {
headers = headers || jsonsHeaders(jsons);
// allow headers to have custom labels, defaulting to having the header data key be the label
let headerLabels = headers;
let headerKeys = headers;
if (isJsons(headers)) {
headerLabels = headers.map((header) => header.label);
headerKeys = headers.map((header) => header.key);
}
const data = jsons.map((object) => headerKeys.map((header) => getHeaderValue(header, object)));
return [headerLabels, ...data];
};
export const getHeaderValue = (property, obj) => {
const foundValue = property
.replace(/\[([^\]]+)]/g, ".$1")
.split(".")
.reduce(function (o, p, i, arr) {
// if at any point the nested keys passed do not exist, splice the array so it doesnt keep reducing
const value = o[p];
if (value === undefined || value === null) {
arr.splice(1);
} else {
return value;
}
}, obj);
// if at any point the nested keys passed do not exist then looks for key `property` in object obj
return (foundValue === undefined) ? ((property in obj) ? obj[property] : '') : foundValue;
}
export const elementOrEmpty = (element) =>
(typeof element === 'undefined' || element === null) ? '' : element;
export const joiner = ((data, separator = ',', enclosingCharacter = '"') => {
return data
.filter(e => e)
.map(
row => row
.map((element) => elementOrEmpty(element))
.map(column => `${enclosingCharacter}${column}${enclosingCharacter}`)
.join(separator)
)
.join(`\n`);
});
export const arrays2csv = ((data, headers, separator, enclosingCharacter) =>
joiner(headers ? [headers, ...data] : data, separator, enclosingCharacter)
);
export const jsons2csv = ((data, headers, separator, enclosingCharacter) =>
joiner(jsons2arrays(data, headers), separator, enclosingCharacter)
);
export const string2csv = ((data, headers, separator) =>
(headers) ? `${headers.join(separator)}\n${data}` : data.replace(/"/g, '""')
);
export const toCSV = (data, headers, separator, enclosingCharacter) => {
if (isJsons(data)) return jsons2csv(data, headers, separator, enclosingCharacter);
if (isArrays(data)) return arrays2csv(data, headers, separator, enclosingCharacter);
if (typeof data === 'string') return string2csv(data, headers, separator);
throw new TypeError(`Data should be a "String", "Array of arrays" OR "Array of objects" `);
};
const CSVLink = ({filename, data, children}) => {
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}`;
const URL = window.URL || window.webkitURL;
return (typeof URL.createObjectURL === 'undefined')
? dataURI
: URL.createObjectURL(blob);
});
const isNodeEnvironment = typeof window === 'undefined';
const uFEFF = true;
const headers = undefined;
const separator = ",";
const enclosingCharacter = '"';
const href = isNodeEnvironment ? '' : buildURI(data, uFEFF, headers, separator, enclosingCharacter)
return (
<a
download={filename}
target="_blank"
href={href}
>
{children}
</a>
);
}
export default CSVLink;

@ -1,5 +1,4 @@
import Link from 'next/link';
import {Container, Row, Col} from 'reactstrap';
import {Container} from 'reactstrap';
import {useTranslation} from 'next-i18next';
import {CONTACT_MAIL} from '@services/constants';
import Button from '@components/Button'

@ -0,0 +1,206 @@
import {useEffect, useLayoutEffect, useRef, useState} from 'react';
import {GradeResultInterface, MeritProfileInterface} from '@services/type';
import {getMajorityGrade} from '@services/majorityJudgment';
interface ParamsInterface {
numVotes: number;
outgaugeThreshold: number;
}
interface GradeBarInterface {
grade: GradeResultInterface;
size: number;
index: number;
params: ParamsInterface;
}
const GradeBar = ({index, grade, size, params}: GradeBarInterface) => {
const width = `${size * 100}%`
const textWidth = Math.floor(100 * size)
const left = `${(size * 100) / 2}%`;
const top = index % 2 ? "20px" : "-20px";
if (size < 0.001) {
return null;
}
return (
<div
className="h-100"
style={{flexBasis: width, backgroundColor: grade.color, minHeight: "20px"}}>
{
/* size < params.outgaugeThreshold ? (
<span
style={{
left: left,
top: top,
display: "relative",
backgroundColor: grade.color,
}}
>
{textWidth}%
</span>
) : (
<span>
{Math.floor(100 * size)}%
</span>
)
*/ }
</div>)
}
const DashedMedian = () => {
return <div className="position-relative d-flex justify-content-center"
style={{top: "60px", height: "50px"}}
>
<div
className="border h-100 border-1 border-dark border-opacity-75 border-dashed"
>
</div>
</div>
}
const MajorityGrade = ({grade, left}) => {
const spanRef = useRef<HTMLDivElement>();
const [width, setWidth] = useState(0)
useLayoutEffect(() => {
if (spanRef && spanRef.current) {
setWidth(spanRef.current.offsetWidth);
}
}, []);
useEffect(() => {
if (spanRef && spanRef.current) {
setWidth(spanRef.current.offsetWidth);
}
}, []);
return (
<>
<span
ref={spanRef}
style={{
color: 'white',
backgroundColor: grade.color,
left: `calc(${left * 100}% - ${width / 2}px)`,
top: "-20px",
}}
className="p-2 position-relative fw-bold rounded-1 text-center"
>
{grade.name}
</span>
<span style={{
position: "relative",
width: 0,
height: 0,
left: `calc(${left * 100}% - ${width + 6}px)`,
top: "20px",
borderLeftWidth: 6,
borderRightWidth: 6,
borderTopWidth: 12,
borderStyle: 'solid',
backgroundColor: 'transparent',
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderTopColor: grade.color,
color: "transparent",
}}></span>
</>
);
}
interface MeritProfileBarInterface {
profile: MeritProfileInterface;
grades: Array<GradeResultInterface>;
}
const MeritProfileBar = ({profile, grades}: MeritProfileBarInterface) => {
const gradesByValue: {[key: number]: GradeResultInterface} = {}
grades.forEach(g => gradesByValue[g.value] = g)
const numVotes = Object.values(profile).reduce((a, b) => a + b, 0)
const values = grades.map(g => g.value).sort();
const normalized = values.map(value => value in profile ? profile[value] / numVotes : 0)
// low values means great grade
// find the majority grade
const majorityIdx = getMajorityGrade(profile)
const majorityGrade = grades[majorityIdx]
const proponentSizes = normalized.filter((_, i) => values[i] < majorityGrade.value)
const proponentWidth = proponentSizes.reduce((a, b) => a + b, 0)
const opponentSizes = normalized.filter((_, i) => values[i] > majorityGrade.value)
const opponentWidth = opponentSizes.reduce((a, b) => a + b, 0)
// is proponent higher than opposant?
const proponentMajority = proponentWidth > opponentWidth;
// for mobile phone, we outgauge earlier than on desktop
const innerWidth = typeof window !== 'undefined' && window.innerWidth ? window.innerWidth : 1000;
const params: ParamsInterface = {
outgaugeThreshold: (innerWidth <= 760) ? 0.05 : 0.03,
numVotes,
}
return (
<>
<DashedMedian />
<MajorityGrade
grade={majorityGrade}
left={proponentWidth + normalized[majorityIdx] / 2}
/>
<div className='d-flex'>
<div
className={`d-flex border border-${proponentMajority ? 2 : 1} border-success`}
style={{flexBasis: `${proponentWidth * 100}%`}}
>
{values.filter(v => v < majorityGrade.value).map(v => {
const index = values.indexOf(v);
const size = proponentWidth < 1e-3 ? 0 : normalized[index] / proponentWidth;
return (
<GradeBar index={index} params={params} grade={grades[v]} key={index} size={size} />
)
})}
</div>
<div className="border border-2 border-primary"
style={{flexBasis: `${normalized[majorityIdx] * 100}%`}}
>
{values.filter(v => v === majorityGrade.value).map(v => {
const index = values.indexOf(v);
return (
<GradeBar index={index} params={params} grade={grades[v]} key={index} size={1} />
)
})}
</div>
<div
className={`d-flex border border-${proponentMajority ? 1 : 2} border-danger`}
style={{flexBasis: `${opponentWidth * 100}%`}}
>
{values.filter(v => v > majorityGrade.value).map(v => {
const index = values.indexOf(v);
const size = opponentWidth < 1e-3 ? 0 : normalized[index] / opponentWidth;
return (
<GradeBar index={index} params={params} grade={grades[v]} key={index} size={size} />
)
})}
</div>
</div>
{ /* <div className='median dash'> </div> */}
</>
)
}
export default MeritProfileBar;

@ -11,7 +11,7 @@ import ErrorMessage from '@components/Error';
import AdminModalEmail from '@components/admin/AdminModalEmail';
import {BallotPayload, ErrorPayload} from '@services/api';
import {useAppContext} from '@services/context';
import {displayRef} from '@services/utils';
import {displayRef, isEnded} from '@services/utils';
import {RESULTS} from '@services/routes';
import Logo from './Logo';
import {FORM_FEEDBACK} from '@services/constants';
@ -29,11 +29,8 @@ export interface WaitingBallotInterface {
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) {
if (!election.hideResults || isEnded(election.date_end)) {
return (
<Link href={`${RESULTS}/${displayRef(election.ref)}`}>
<Button className="" icon={faArrowRight} position="right">

287
package-lock.json generated

@ -27,9 +27,10 @@
"eslint-config-next": "^13.0.0",
"framer-motion": "^7.6.4",
"i18next": "^22.0.6",
"next": "^13.0.0",
"next": "^13.0.5",
"next-i18next": "^12.1.0",
"react": "^18.2.0",
"react-csv": "^2.2.2",
"react-datepicker": "^4.8.0",
"react-dom": "^18.2.0",
"react-flags-select": "^2.2.3",
@ -749,9 +750,9 @@
}
},
"node_modules/@next/env": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.1.tgz",
"integrity": "sha512-gK60YoFae3s8qi5UgIzbvxOhsh5gKyEaiKH5+kLBUYXLlrPyWJR2xKBj2WqvHkO7wDX7/Hed3DAqjSpU4ijIvQ=="
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.5.tgz",
"integrity": "sha512-F3KLtiDrUslAZhTYTh8Zk5ZaavbYwLUn3NYPBnOjAXU8hWm0QVGVzKIOuURQ098ofRU4e9oglf3Sj9pFx5nI5w=="
},
"node_modules/@next/eslint-plugin-next": {
"version": "13.0.1",
@ -762,9 +763,9 @@
}
},
"node_modules/@next/swc-android-arm-eabi": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.1.tgz",
"integrity": "sha512-M28QSbohZlNXNn//HY6lV2T3YaMzG58Jwr0YwOdVmOQv6i+7lu6xe3GqQu4kdqInqhLrBXnL+nabFuGTVSHtTg==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.5.tgz",
"integrity": "sha512-YO691dxHlviy6H0eghgwqn+5kU9J3iQnKERHTDSppqjjGDBl6ab4wz9XfI5AhljjkaTg3TknHoIEWFDoZ4Ve8g==",
"cpu": [
"arm"
],
@ -777,9 +778,9 @@
}
},
"node_modules/@next/swc-android-arm64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.1.tgz",
"integrity": "sha512-szmO/i6GoHcPXcbhUKhwBMETWHNXH3ITz9wfxwOOFBNKdDU8pjKsHL88lg28aOiQYZSU1sxu1v1p9KY5kJIZCg==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.5.tgz",
"integrity": "sha512-ugbwffkUmp8cd2afehDC8LtQeFUxElRUBBngfB5UYSWBx18HW4OgzkPFIY8jUBH16zifvGZWXbICXJWDHrOLtw==",
"cpu": [
"arm64"
],
@ -792,9 +793,9 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.1.tgz",
"integrity": "sha512-O1RxCaiDNOjGZmdAp6SQoHUITt9aVDQXoR3lZ/TloI/NKRAyAV4u0KUUofK+KaZeHOmVTnPUaQuCyZSc3i1x5Q==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.5.tgz",
"integrity": "sha512-mshlh8QOtOalfZbc17uNAftWgqHTKnrv6QUwBe+mpGz04eqsSUzVz1JGZEdIkmuDxOz00cK2NPoc+VHDXh99IQ==",
"cpu": [
"arm64"
],
@ -807,9 +808,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.1.tgz",
"integrity": "sha512-8E6BY/VO+QqQkthhoWgB8mJMw1NcN9Vhl2OwEwxv8jy2r3zjeU+WNRxz4y8RLbcY0R1h+vHlXuP0mLnuac84tQ==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.5.tgz",
"integrity": "sha512-SfigOKW4Z2UB3ruUPyvrlDIkcJq1hiw1wvYApWugD+tQsAkYZKEoz+/8emCmeYZ6Gwgi1WHV+z52Oj8u7bEHPg==",
"cpu": [
"x64"
],
@ -822,9 +823,9 @@
}
},
"node_modules/@next/swc-freebsd-x64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.1.tgz",
"integrity": "sha512-ocwoOxm2KVwF50RyoAT+2RQPLlkyoF7sAqzMUVgj+S6+DTkY3iwH+Zpo0XAk2pnqT9qguOrKnEpq9EIx//+K7Q==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.5.tgz",
"integrity": "sha512-0NJg8HZr4yG8ynmMGFXQf+Mahvq4ZgBmUwSlLXXymgxEQgH17erH/LoR69uITtW+KTsALgk9axEt5AAabM4ucg==",
"cpu": [
"x64"
],
@ -837,9 +838,9 @@
}
},
"node_modules/@next/swc-linux-arm-gnueabihf": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.1.tgz",
"integrity": "sha512-yO7e3zITfGol/N6lPQnmIRi0WyuILBMXrvH6EdmWzzqMDJFfTCII6l+B6gMO5WVDCTQUGQlQRNZ7sFqWR4I71g==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.5.tgz",
"integrity": "sha512-Cye+h3oDT3NDWjACMlRaolL8fokpKie34FlPj9nfoW7bYKmoMBY1d4IO/GgBF+5xEl7HkH0Ny/qex63vQ0pN+A==",
"cpu": [
"arm"
],
@ -852,9 +853,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.1.tgz",
"integrity": "sha512-OEs6WDPDI8RyM8SjOqTDMqMBfOlU97VnW6ZMXUvzUTyH0K9c7NF+cn7UMu+I4tKFN0uJ9WQs/6TYaFBGkgoVVA==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.5.tgz",
"integrity": "sha512-5BfDS/VoRDR5QUGG9oedOCEZGmV2zxUVFYLUJVPMSMeIgqkjxWQBiG2BUHZI6/LGk9yvHmjx7BTvtBCLtRg6IQ==",
"cpu": [
"arm64"
],
@ -867,9 +868,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.1.tgz",
"integrity": "sha512-y5ypFK0Y3urZSFoQxbtDqvKsBx026sz+Fm+xHlPWlGHNZrbs3Q812iONjcZTo09QwRMk5X86iMWBRxV18xMhaw==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.5.tgz",
"integrity": "sha512-xenvqlXz+KxVKAB1YR723gnVNszpsCvKZkiFFaAYqDGJ502YuqU2fwLsaSm/ASRizNcBYeo9HPLTyc3r/9cdMQ==",
"cpu": [
"arm64"
],
@ -882,9 +883,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.1.tgz",
"integrity": "sha512-XDIHEE6SU8VCF+dUVntD6PDv6RK31N0forx9kucZBYirbe8vCZ+Yx8hYgvtIaGrTcWtGxibxmND0pIuHDq8H5g==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.5.tgz",
"integrity": "sha512-9Ahi1bbdXwhrWQmOyoTod23/hhK05da/FzodiNqd6drrMl1y7+RujoEcU8Dtw3H1mGWB+yuTlWo8B4Iba8hqiQ==",
"cpu": [
"x64"
],
@ -897,9 +898,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.1.tgz",
"integrity": "sha512-yxIOuuz5EOx0F1FDtsyzaLgnDym0Ysxv8CWeJyDTKKmt9BVyITg6q/cD+RP9bEkT1TQi+PYXIMATSz675Q82xw==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.5.tgz",
"integrity": "sha512-V+1mnh49qmS9fOZxVRbzjhBEz9IUGJ7AQ80JPWAYQM5LI4TxfdiF4APLPvJ52rOmNeTqnVz1bbKtVOso+7EZ4w==",
"cpu": [
"x64"
],
@ -912,9 +913,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.1.tgz",
"integrity": "sha512-+ucLe2qgQzP+FM94jD4ns6LDGyMFaX9k3lVHqu/tsQCy2giMymbport4y4p77mYcXEMlDaHMzlHgOQyHRniWFA==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.5.tgz",
"integrity": "sha512-wRE9rkp7I+/3Jf2T9PFIJOKq3adMWYEFkPOA7XAkUfYbQHlDJm/U5cVCWUsKByyQq5RThwufI91sgd19MfxRxg==",
"cpu": [
"arm64"
],
@ -927,9 +928,9 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.1.tgz",
"integrity": "sha512-Krr/qGN7OB35oZuvMAZKoXDt2IapynIWLh5A5rz6AODb7f/ZJqyAuZSK12vOa2zKdobS36Qm4IlxxBqn9c00MA==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.5.tgz",
"integrity": "sha512-Q1XQSLEhFuFhkKFdJIGt7cYQ4T3u6P5wrtUNreg5M+7P+fjSiC8+X+Vjcw+oebaacsdl0pWZlK+oACGafush1w==",
"cpu": [
"ia32"
],
@ -942,9 +943,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.1.tgz",
"integrity": "sha512-t/0G33t/6VGWZUGCOT7rG42qqvf/x+MrFp1CU+8CN6PrjSSL57R5bqkXfubV9t4eCEnUxVP+5Hn3MoEXEebtEw==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.5.tgz",
"integrity": "sha512-t5gRblrwwiNZP6cT7NkxlgxrFgHWtv9ei5vUraCLgBqzvIsa7X+PnarZUeQCXqz6Jg9JSGGT9j8lvzD97UqeJQ==",
"cpu": [
"x64"
],
@ -1049,9 +1050,9 @@
}
},
"node_modules/@swc/helpers": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.11.tgz",
"integrity": "sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==",
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
"integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
"dependencies": {
"tslib": "^2.4.0"
}
@ -3574,16 +3575,15 @@
"dev": true
},
"node_modules/next": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/next/-/next-13.0.1.tgz",
"integrity": "sha512-ErCNBPIeZMKFn6hX+ZBSlqZVgJIeitEqhGTuQUNmYXJ07/A71DZ7AJI8eyHYUdBb686LUpV1/oBdTq9RpzRVPg==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/next/-/next-13.0.5.tgz",
"integrity": "sha512-awpc3DkphyKydwCotcBnuKwh6hMqkT5xdiBK4OatJtOZurDPBYLP62jtM2be/4OunpmwIbsS0Eyv+ZGU97ciEg==",
"dependencies": {
"@next/env": "13.0.1",
"@swc/helpers": "0.4.11",
"@next/env": "13.0.5",
"@swc/helpers": "0.4.14",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.1.0",
"use-sync-external-store": "1.2.0"
"styled-jsx": "5.1.0"
},
"bin": {
"next": "dist/bin/next"
@ -3592,19 +3592,19 @@
"node": ">=14.6.0"
},
"optionalDependencies": {
"@next/swc-android-arm-eabi": "13.0.1",
"@next/swc-android-arm64": "13.0.1",
"@next/swc-darwin-arm64": "13.0.1",
"@next/swc-darwin-x64": "13.0.1",
"@next/swc-freebsd-x64": "13.0.1",
"@next/swc-linux-arm-gnueabihf": "13.0.1",
"@next/swc-linux-arm64-gnu": "13.0.1",
"@next/swc-linux-arm64-musl": "13.0.1",
"@next/swc-linux-x64-gnu": "13.0.1",
"@next/swc-linux-x64-musl": "13.0.1",
"@next/swc-win32-arm64-msvc": "13.0.1",
"@next/swc-win32-ia32-msvc": "13.0.1",
"@next/swc-win32-x64-msvc": "13.0.1"
"@next/swc-android-arm-eabi": "13.0.5",
"@next/swc-android-arm64": "13.0.5",
"@next/swc-darwin-arm64": "13.0.5",
"@next/swc-darwin-x64": "13.0.5",
"@next/swc-freebsd-x64": "13.0.5",
"@next/swc-linux-arm-gnueabihf": "13.0.5",
"@next/swc-linux-arm64-gnu": "13.0.5",
"@next/swc-linux-arm64-musl": "13.0.5",
"@next/swc-linux-x64-gnu": "13.0.5",
"@next/swc-linux-x64-musl": "13.0.5",
"@next/swc-win32-arm64-msvc": "13.0.5",
"@next/swc-win32-ia32-msvc": "13.0.5",
"@next/swc-win32-x64-msvc": "13.0.5"
},
"peerDependencies": {
"fibers": ">= 3.1.0",
@ -4076,6 +4076,11 @@
"node": ">=0.10.0"
}
},
"node_modules/react-csv": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
"integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
},
"node_modules/react-datepicker": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.8.0.tgz",
@ -4774,14 +4779,6 @@
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
"node_modules/use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@ -5410,9 +5407,9 @@
}
},
"@next/env": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.1.tgz",
"integrity": "sha512-gK60YoFae3s8qi5UgIzbvxOhsh5gKyEaiKH5+kLBUYXLlrPyWJR2xKBj2WqvHkO7wDX7/Hed3DAqjSpU4ijIvQ=="
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.0.5.tgz",
"integrity": "sha512-F3KLtiDrUslAZhTYTh8Zk5ZaavbYwLUn3NYPBnOjAXU8hWm0QVGVzKIOuURQ098ofRU4e9oglf3Sj9pFx5nI5w=="
},
"@next/eslint-plugin-next": {
"version": "13.0.1",
@ -5423,81 +5420,81 @@
}
},
"@next/swc-android-arm-eabi": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.1.tgz",
"integrity": "sha512-M28QSbohZlNXNn//HY6lV2T3YaMzG58Jwr0YwOdVmOQv6i+7lu6xe3GqQu4kdqInqhLrBXnL+nabFuGTVSHtTg==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.5.tgz",
"integrity": "sha512-YO691dxHlviy6H0eghgwqn+5kU9J3iQnKERHTDSppqjjGDBl6ab4wz9XfI5AhljjkaTg3TknHoIEWFDoZ4Ve8g==",
"optional": true
},
"@next/swc-android-arm64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.1.tgz",
"integrity": "sha512-szmO/i6GoHcPXcbhUKhwBMETWHNXH3ITz9wfxwOOFBNKdDU8pjKsHL88lg28aOiQYZSU1sxu1v1p9KY5kJIZCg==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-13.0.5.tgz",
"integrity": "sha512-ugbwffkUmp8cd2afehDC8LtQeFUxElRUBBngfB5UYSWBx18HW4OgzkPFIY8jUBH16zifvGZWXbICXJWDHrOLtw==",
"optional": true
},
"@next/swc-darwin-arm64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.1.tgz",
"integrity": "sha512-O1RxCaiDNOjGZmdAp6SQoHUITt9aVDQXoR3lZ/TloI/NKRAyAV4u0KUUofK+KaZeHOmVTnPUaQuCyZSc3i1x5Q==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.5.tgz",
"integrity": "sha512-mshlh8QOtOalfZbc17uNAftWgqHTKnrv6QUwBe+mpGz04eqsSUzVz1JGZEdIkmuDxOz00cK2NPoc+VHDXh99IQ==",
"optional": true
},
"@next/swc-darwin-x64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.1.tgz",
"integrity": "sha512-8E6BY/VO+QqQkthhoWgB8mJMw1NcN9Vhl2OwEwxv8jy2r3zjeU+WNRxz4y8RLbcY0R1h+vHlXuP0mLnuac84tQ==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.5.tgz",
"integrity": "sha512-SfigOKW4Z2UB3ruUPyvrlDIkcJq1hiw1wvYApWugD+tQsAkYZKEoz+/8emCmeYZ6Gwgi1WHV+z52Oj8u7bEHPg==",
"optional": true
},
"@next/swc-freebsd-x64": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.1.tgz",
"integrity": "sha512-ocwoOxm2KVwF50RyoAT+2RQPLlkyoF7sAqzMUVgj+S6+DTkY3iwH+Zpo0XAk2pnqT9qguOrKnEpq9EIx//+K7Q==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.5.tgz",
"integrity": "sha512-0NJg8HZr4yG8ynmMGFXQf+Mahvq4ZgBmUwSlLXXymgxEQgH17erH/LoR69uITtW+KTsALgk9axEt5AAabM4ucg==",
"optional": true
},
"@next/swc-linux-arm-gnueabihf": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.1.tgz",
"integrity": "sha512-yO7e3zITfGol/N6lPQnmIRi0WyuILBMXrvH6EdmWzzqMDJFfTCII6l+B6gMO5WVDCTQUGQlQRNZ7sFqWR4I71g==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.5.tgz",
"integrity": "sha512-Cye+h3oDT3NDWjACMlRaolL8fokpKie34FlPj9nfoW7bYKmoMBY1d4IO/GgBF+5xEl7HkH0Ny/qex63vQ0pN+A==",
"optional": true
},
"@next/swc-linux-arm64-gnu": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.1.tgz",
"integrity": "sha512-OEs6WDPDI8RyM8SjOqTDMqMBfOlU97VnW6ZMXUvzUTyH0K9c7NF+cn7UMu+I4tKFN0uJ9WQs/6TYaFBGkgoVVA==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.5.tgz",
"integrity": "sha512-5BfDS/VoRDR5QUGG9oedOCEZGmV2zxUVFYLUJVPMSMeIgqkjxWQBiG2BUHZI6/LGk9yvHmjx7BTvtBCLtRg6IQ==",
"optional": true
},
"@next/swc-linux-arm64-musl": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.1.tgz",
"integrity": "sha512-y5ypFK0Y3urZSFoQxbtDqvKsBx026sz+Fm+xHlPWlGHNZrbs3Q812iONjcZTo09QwRMk5X86iMWBRxV18xMhaw==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.5.tgz",
"integrity": "sha512-xenvqlXz+KxVKAB1YR723gnVNszpsCvKZkiFFaAYqDGJ502YuqU2fwLsaSm/ASRizNcBYeo9HPLTyc3r/9cdMQ==",
"optional": true
},
"@next/swc-linux-x64-gnu": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.1.tgz",
"integrity": "sha512-XDIHEE6SU8VCF+dUVntD6PDv6RK31N0forx9kucZBYirbe8vCZ+Yx8hYgvtIaGrTcWtGxibxmND0pIuHDq8H5g==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.5.tgz",
"integrity": "sha512-9Ahi1bbdXwhrWQmOyoTod23/hhK05da/FzodiNqd6drrMl1y7+RujoEcU8Dtw3H1mGWB+yuTlWo8B4Iba8hqiQ==",
"optional": true
},
"@next/swc-linux-x64-musl": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.1.tgz",
"integrity": "sha512-yxIOuuz5EOx0F1FDtsyzaLgnDym0Ysxv8CWeJyDTKKmt9BVyITg6q/cD+RP9bEkT1TQi+PYXIMATSz675Q82xw==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.5.tgz",
"integrity": "sha512-V+1mnh49qmS9fOZxVRbzjhBEz9IUGJ7AQ80JPWAYQM5LI4TxfdiF4APLPvJ52rOmNeTqnVz1bbKtVOso+7EZ4w==",
"optional": true
},
"@next/swc-win32-arm64-msvc": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.1.tgz",
"integrity": "sha512-+ucLe2qgQzP+FM94jD4ns6LDGyMFaX9k3lVHqu/tsQCy2giMymbport4y4p77mYcXEMlDaHMzlHgOQyHRniWFA==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.5.tgz",
"integrity": "sha512-wRE9rkp7I+/3Jf2T9PFIJOKq3adMWYEFkPOA7XAkUfYbQHlDJm/U5cVCWUsKByyQq5RThwufI91sgd19MfxRxg==",
"optional": true
},
"@next/swc-win32-ia32-msvc": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.1.tgz",
"integrity": "sha512-Krr/qGN7OB35oZuvMAZKoXDt2IapynIWLh5A5rz6AODb7f/ZJqyAuZSK12vOa2zKdobS36Qm4IlxxBqn9c00MA==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.5.tgz",
"integrity": "sha512-Q1XQSLEhFuFhkKFdJIGt7cYQ4T3u6P5wrtUNreg5M+7P+fjSiC8+X+Vjcw+oebaacsdl0pWZlK+oACGafush1w==",
"optional": true
},
"@next/swc-win32-x64-msvc": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.1.tgz",
"integrity": "sha512-t/0G33t/6VGWZUGCOT7rG42qqvf/x+MrFp1CU+8CN6PrjSSL57R5bqkXfubV9t4eCEnUxVP+5Hn3MoEXEebtEw==",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.5.tgz",
"integrity": "sha512-t5gRblrwwiNZP6cT7NkxlgxrFgHWtv9ei5vUraCLgBqzvIsa7X+PnarZUeQCXqz6Jg9JSGGT9j8lvzD97UqeJQ==",
"optional": true
},
"@nicolo-ribaudo/eslint-scope-5-internals": {
@ -5571,9 +5568,9 @@
}
},
"@swc/helpers": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.11.tgz",
"integrity": "sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==",
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz",
"integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==",
"requires": {
"tslib": "^2.4.0"
}
@ -7387,29 +7384,28 @@
"dev": true
},
"next": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/next/-/next-13.0.1.tgz",
"integrity": "sha512-ErCNBPIeZMKFn6hX+ZBSlqZVgJIeitEqhGTuQUNmYXJ07/A71DZ7AJI8eyHYUdBb686LUpV1/oBdTq9RpzRVPg==",
"requires": {
"@next/env": "13.0.1",
"@next/swc-android-arm-eabi": "13.0.1",
"@next/swc-android-arm64": "13.0.1",
"@next/swc-darwin-arm64": "13.0.1",
"@next/swc-darwin-x64": "13.0.1",
"@next/swc-freebsd-x64": "13.0.1",
"@next/swc-linux-arm-gnueabihf": "13.0.1",
"@next/swc-linux-arm64-gnu": "13.0.1",
"@next/swc-linux-arm64-musl": "13.0.1",
"@next/swc-linux-x64-gnu": "13.0.1",
"@next/swc-linux-x64-musl": "13.0.1",
"@next/swc-win32-arm64-msvc": "13.0.1",
"@next/swc-win32-ia32-msvc": "13.0.1",
"@next/swc-win32-x64-msvc": "13.0.1",
"@swc/helpers": "0.4.11",
"version": "13.0.5",
"resolved": "https://registry.npmjs.org/next/-/next-13.0.5.tgz",
"integrity": "sha512-awpc3DkphyKydwCotcBnuKwh6hMqkT5xdiBK4OatJtOZurDPBYLP62jtM2be/4OunpmwIbsS0Eyv+ZGU97ciEg==",
"requires": {
"@next/env": "13.0.5",
"@next/swc-android-arm-eabi": "13.0.5",
"@next/swc-android-arm64": "13.0.5",
"@next/swc-darwin-arm64": "13.0.5",
"@next/swc-darwin-x64": "13.0.5",
"@next/swc-freebsd-x64": "13.0.5",
"@next/swc-linux-arm-gnueabihf": "13.0.5",
"@next/swc-linux-arm64-gnu": "13.0.5",
"@next/swc-linux-arm64-musl": "13.0.5",
"@next/swc-linux-x64-gnu": "13.0.5",
"@next/swc-linux-x64-musl": "13.0.5",
"@next/swc-win32-arm64-msvc": "13.0.5",
"@next/swc-win32-ia32-msvc": "13.0.5",
"@next/swc-win32-x64-msvc": "13.0.5",
"@swc/helpers": "0.4.14",
"caniuse-lite": "^1.0.30001406",
"postcss": "8.4.14",
"styled-jsx": "5.1.0",
"use-sync-external-store": "1.2.0"
"styled-jsx": "5.1.0"
}
},
"next-i18next": {
@ -7703,6 +7699,11 @@
"loose-envify": "^1.1.0"
}
},
"react-csv": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/react-csv/-/react-csv-2.2.2.tgz",
"integrity": "sha512-RG5hOcZKZFigIGE8LxIEV/OgS1vigFQT4EkaHeKgyuCbUAu9Nbd/1RYq++bJcJJ9VOqO/n9TZRADsXNDR4VEpw=="
},
"react-datepicker": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.8.0.tgz",
@ -8162,12 +8163,6 @@
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
"dev": true
},
"use-sync-external-store": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
"requires": {}
},
"v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",

@ -29,9 +29,10 @@
"eslint-config-next": "^13.0.0",
"framer-motion": "^7.6.4",
"i18next": "^22.0.6",
"next": "^13.0.0",
"next": "^13.0.5",
"next-i18next": "^12.1.0",
"react": "^18.2.0",
"react-csv": "^2.2.2",
"react-datepicker": "^4.8.0",
"react-dom": "^18.2.0",
"react-flags-select": "^2.2.3",

@ -1,11 +1,11 @@
import { Html, Head, Main, NextScript } from 'next/document';
import {Html, Head, Main, NextScript} from 'next/document';
export default function Document() {
return (
<Html>
<Head>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin={true} />
<link
href="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"
rel="stylesheet"

@ -11,6 +11,7 @@ import Blur from '@components/Blur'
import {getElection, castBallot, ElectionPayload, BallotPayload, ErrorPayload} from '@services/api';
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import {ENDED_VOTE} from '@services/routes';
import {isEnded} from '@services/utils';
import WaitingBallot from '@components/WaitingBallot';
import PatternedBackground from '@components/PatternedBackground';
@ -21,7 +22,7 @@ export async function getServerSideProps({query: {pid, tid}, locale}) {
if (!pid) {
return {notFound: true}
}
const electionRef = pid.replace("-", "");
const electionRef = pid.replaceAll("-", "");
const [election, translations] = await Promise.all([
getElection(electionRef),
@ -32,11 +33,12 @@ export async function getServerSideProps({query: {pid, tid}, locale}) {
return {notFound: true}
}
const dateEnd = new Date(election.date_end)
if (dateEnd.getDate() > new Date().getDate()) {
if (isEnded(election.date_end)) {
return {
redirect: ENDED_VOTE,
permanent: false
redirect: {
destination: `${ENDED_VOTE}/${pid}/${tid || ""}`,
permanent: false
}
}
}

@ -0,0 +1,50 @@
import Link from 'next/link'
import {useTranslation} from 'next-i18next';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
import {Container} from 'reactstrap';
import ErrorMessage from '@components/Error';
import {RESULTS} from '@services/routes'
import {displayRef} from '@services/utils'
import Blur from '@components/Blur'
import Button from '@components/Button';
export const getServerSideProps = async ({query: {pid, tid}, locale}) => {
return {
props: {
...(await serverSideTranslations(locale, ['resource'])),
token: tid,
electionRef: pid.replaceAll("-", "")
},
}
}
const End = ({electionRef, token}) => {
const {t} = useTranslation();
return (
<>
<Blur />
<div className="w-100 h-100 d-flex flex-column justify-content-center align-items-center">
<ErrorMessage msg={t('error.ended-election')} />
<Container className="full-height-container">
<Link
className="d-grid w-100 mt-5"
href={`${RESULTS}/${displayRef(electionRef)}/${token ? token : ""}`}>
<Button
color="secondary"
outline={true}
type="submit"
icon={faArrowRight}
position="right"
>
{t('vote.go-to-results')}</Button>
</Link>
</Container>
</div>
</>
);
};
export default End;

@ -1,18 +1,17 @@
import Link from 'next/link';
import { Container, Row, Col } from 'reactstrap';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import config from '../next-i18next.config.js';
import { GetStaticProps } from 'next';
import {Container, Row, Col} from 'reactstrap';
import {useTranslation} from 'next-i18next';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {GetStaticProps} from 'next';
export const getStaticProps: GetStaticProps = async ({ locale }) => ({
export const getStaticProps: GetStaticProps = async ({locale}) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
...(await serverSideTranslations(locale, ['resource'])),
},
});
const FAQ = () => {
const { t } = useTranslation();
const {t} = useTranslation();
return (
<Container>
<Row>
@ -37,7 +36,7 @@ const FAQ = () => {
lโ€™รฉlectorat (celui qui obtient la meilleure mention ยซ majoritaire
ยป).
</p>
<div style={{ maxWidth: '445px' }}>
<div style={{maxWidth: '445px'}}>
<video width="100%" height="250" controls={true}>
<source
src="/video/Le_Jugement_Majoritaire_en_1_minute.mp4"

@ -1,18 +1,17 @@
import Link from 'next/link';
import { Container, Row, Col } from 'reactstrap';
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import config from '../next-i18next.config.js';
import { GetStaticProps } from 'next';
import {Container, Row, Col} from 'reactstrap';
import {useTranslation} from 'next-i18next';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {GetStaticProps} from 'next';
export const getStaticProps: GetStaticProps = async ({ locale }) => ({
export const getStaticProps: GetStaticProps = async ({locale}) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
...(await serverSideTranslations(locale, ['resource'])),
},
});
const LegalNotices = (props) => {
const { t } = useTranslation();
const {t} = useTranslation();
return (
<Container>
<Row>

@ -1,18 +1,17 @@
import { Col, Container, Row } from 'reactstrap';
import { useTranslation } from 'next-i18next';
import {Col, Container, Row} from 'reactstrap';
import {useTranslation} from 'next-i18next';
import Link from 'next/link';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import config from '../next-i18next.config.js';
import { GetStaticProps } from 'next';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {GetStaticProps} from 'next';
export const getStaticProps: GetStaticProps = async ({ locale }) => ({
export const getStaticProps: GetStaticProps = async ({locale}) => ({
props: {
...(await serverSideTranslations(locale, [], config)),
...(await serverSideTranslations(locale, ['resource'])),
},
});
const PrivacyPolicy = (props) => {
const { t } = useTranslation();
const {t} = useTranslation();
return (
<Container>
<Row>

@ -1,25 +0,0 @@
import dynamic from 'next/dynamic';
// import Plot from 'react-plotly.js';
import React, { Component } from 'react';
class BarChart extends Component {
render() {
return <div></div>;
}
}
export default BarChart;
// <Plot
// data={[
// {
// type: 'bar',
// x: ['Taubira', 'Hidalgo', 'Mรฉlenchon'],
// y: [29, 150, 85]
// }
// ]}
// layout={{width: 1000, height: 500, title: 'Nombre de voix par candidat'}}
// config={{
// displayModeBar: false // this is the line that hides the bar.
// }}
// />

@ -23,10 +23,15 @@ 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';
import {getResults, getElection, apiErrors, ResultsPayload, CandidatePayload, GradePayload} from '@services/api';
import MeritProfile from '@components/MeritProfile';
import {getResults} from '@services/api';
import {GradeResultInterface, ResultInterface, MeritProfileInterface, CandidateResultInterface} from '@services/type';
import {getUrlAdmin} from '@services/routes';
import {displayRef} from '@services/utils';
import {getMajorityGrade} from '@services/majorityJudgment';
import avatarBlue from '../../../public/avatarBlue.svg'
import calendar from '../../../public/calendar.svg'
@ -35,50 +40,38 @@ import arrowLink from '../../../public/arrowL.svg'
import {getGradeColor} from '@services/grades';
interface GradeInterface extends GradePayload {
color: string;
}
interface CandidateInterface extends CandidatePayload {
majorityGrade: GradeInterface;
rank: number;
}
interface ElectionInterface {
name: string;
description: string;
ref: string;
dateStart: string;
dateEnd: string;
hideResults: boolean;
forceClose: boolean;
restricted: boolean;
grades: Array<GradeInterface>;
candidates: Array<CandidateInterface>;
}
// /**
// * 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>
// })
interface ResultInterface extends ElectionInterface {
ranking: {[key: string]: number};
meritProfile: {[key: number]: Array<number>};
}
export async function getServerSideProps({query, locale}) {
const {pid, tid: token} = query;
const electionRef = pid.replace("-", "");
const electionRef = pid.replaceAll("-", "");
const [payload, translations] = await Promise.all([
getResults(electionRef),
serverSideTranslations(locale, ["resource"]),
]);
if (typeof payload === 'string' || payload instanceof String) {
return {props: {err: payload, ...translations}};
if ("msg" in payload) {
return {props: {err: payload.msg, ...translations}};
}
const numGrades = payload.grades.length;
const grades = payload.grades.map((g, i) => ({...g, color: getGradeColor(i, numGrades)}));
const gradesByValue: {[key: number]: GradeInterface} = {}
const gradesByValue: {[key: number]: GradeResultInterface} = {}
grades.forEach(g => gradesByValue[g.value] = g)
const result: ResultInterface = {
@ -93,13 +86,15 @@ export async function getServerSideProps({query, locale}) {
grades: grades,
candidates: payload.candidates.map(c => ({
...c,
rank: payload.ranking[c.id],
meritProfile: payload.merit_profile[c.id],
rank: payload.ranking[c.id] + 1,
majorityGrade: gradesByValue[getMajorityGrade(payload.merit_profile[c.id])]
})),
ranking: payload.ranking,
meritProfile: payload.merit_profile,
meritProfiles: payload.merit_profile,
}
console.log("GRADES", payload.grades, grades, result.grades)
return {
props: {
@ -112,11 +107,11 @@ export async function getServerSideProps({query, locale}) {
const getNumVotes = (result: ResultInterface) => {
const sum = (seq: Array<number>) =>
const sum = (seq: MeritProfileInterface) =>
Object.values(seq).reduce((a, b) => a + b, 0);
const anyCandidateId = result.candidates[0].id;
const numVotes = sum(result.meritProfile[anyCandidateId]);
Object.values(result.meritProfile).forEach(v => {
const numVotes = sum(result.meritProfiles[anyCandidateId]);
Object.values(result.meritProfiles).forEach(v => {
if (sum(v) !== numVotes) {
throw Error("The election does not contain the same number of votes for each candidate")
}
@ -124,41 +119,54 @@ const getNumVotes = (result: ResultInterface) => {
return numVotes;
}
const WillClose = ({delay}) => {
const {t} = useTranslation();
if (delay < 365) {
return <div>{t('result.closed')}</div>
}
else if (delay < 0) {
return <div>{`${t('result.has-closed')} ${delay} ${t('common.days')}`}</div>
} else if (delay > 365) {
return <div>{t('result.opened')}</div>
} else {
return <div>{`${t('result.will-close')} ${delay} ${t('common.days')}`}</div>
}
}
interface ResultBanner {
result: ResultsPayload;
result: ResultInterface;
}
const ResultBanner = ({result}) => {
const {t} = useTranslation();
const dateEnd = new Date(result.date_end);
const dateEnd = new Date(result.dateEnd);
const now = new Date();
const closedSince = +dateEnd - (+now);
const numVotes = getNumVotes(result)
return (<div className="w-100 bg-white p-5 justify-content-between align-items-center">
return (<div className="w-100 bg-white p-5 d-flex justify-content-between align-items-center">
<div className="text-muted">
<div className="d-flex align-items-center">
<Image alt="Calendar" src={calendar} />
<p>{closedSince > 0 ? `${t('result.has-closed')} {closedSince}` : `${t('result.will-close')} {closedSince}`} {t('common.days')}</p>
<Image alt="Calendar" src={calendar} className="me-2" />
<WillClose delay={closedSince} />
</div>
<div className="d-flex align-items-center">
<Image src={avatarBlue} alt="Avatar" />
<p>{`${numVotes} ${t('common.participants')}`}</p>
<div className="d-flex align-items-center" >
<Image src={avatarBlue} alt="Avatar" className="me-2" />
<div>{numVotes} {numVotes > 1 ? t('common.participants') : t('common.participant')}</div>
</div>
</div>
<h3>{result.name}</h3>
<h4 className="text-black">{result.name}</h4>
<div className="text-muted">
<div className="d-flex align-items-center">
<Image alt="Download" src={arrowUpload} />
<p>{t('result.download')}</p>
<Image alt="Download" src={arrowUpload} className="me-2" />
<div>{t('result.download')}</div>
</div>
<div className="d-flex align-items-center">
<Image src={arrowLink} alt="Share" />
<p>{t('result.share')}</p>
<Image src={arrowLink} alt="Share" className="me-2" />
<div>{t('result.share')}</div>
</div>
</div>
</div >
@ -166,14 +174,28 @@ const ResultBanner = ({result}) => {
}
const BottomButtonsMobile = () => {
const BottomButtonsMobile = ({result}) => {
const {t} = useTranslation();
const values = result.grades.map(v => v.value).sort()
// const data = result.candidates.map(c => [c.name]);
const data = result.candidates.map(c => {
const grades = {}
result.grades.forEach(g => grades[g.name] = g.value in c.meritProfile ? c.meritProfile[g.value].toString() : "0")
return {name: c.name, ...grades}
});
console.log(data)
return (
<div className="d-block d-md-none mt-5">
<Button className="cursorPointer btn-result btn-validation mb-5 btn btn-secondary">
<Image alt="Download" src={arrowUpload} />
<p>{t('result.download')}</p>
</Button>
<div className="d-block d-md-none mt-5" role="button">
<CSVLink
filename={`results-${displayRef(result.ref)}.csv`}
data={data}>
<Button className="cursorPointer btn-result btn-validation mb-5 btn btn-secondary">
<Image alt="Download" src={arrowUpload} />
<p>{t('result.download')}</p>
</Button>
</CSVLink>
<Button className="cursorPointer btn-result btn-validation mb-5 btn btn-secondary">
<Image src={arrowLink} alt="Share" />
<p>{t('result.share')}</p>