fix: type and compile issues

pull/89/head
Pierre-Louis Guhur 1 year ago
parent 15183fe3d4
commit 318cc306d1

@ -1,5 +1,6 @@
import { UncontrolledAlert } from 'reactstrap';
import { useTranslation } from 'next-i18next';
import { CONTACT_MAIL } from '@services/constants';
const AlertDismissible = ({ msg, color }) => {
const { t } = useTranslation();

@ -1,41 +0,0 @@
import * as React from 'react';
import * as d3 from 'd3';
function drawChart(svgRef: React.RefObject<SVGSVGElement>) {
const data = [12, 5, 6, 6, 9, 10];
const h = 120;
const w = 250;
const svg = d3.select(svgRef.current);
svg
.attr('width', w)
.attr('height', h)
.style('margin-top', 50)
.style('margin-left', 50);
svg
.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', (d, i) => i * 40)
.attr('y', (d, i) => h - 10 * d)
.attr('width', 20)
.attr('height', (d, i) => d * 10)
.attr('fill', 'steelblue');
}
const Chart: React.FunctionComponent = () => {
const svg = React.useRef<SVGSVGElement>(null);
React.useEffect(() => {
drawChart(svg);
}, [svg]);
return (
<div id="chart">
<svg ref={svg} />
</div>
);
};
export default Chart;

@ -1,17 +0,0 @@
import React, { useRef, useState, useEffect } from 'react';
import D3Chart from './D3Chart';
const ChartWrapper = () => {
const chartArea = useRef(null);
const [chart, setChart] = useState(null);
useEffect(() => {
if (!chart) {
setChart(new D3Chart(chartArea.current));
}
}, [chart]);
return <div ref={chartArea}></div>;
};
export default ChartWrapper;

@ -27,7 +27,7 @@ const CopyField = (props) => {
type="text"
style={{ display: 'none' }}
className="form-control"
ref={ref}
// ref={ref}
value={value}
readOnly
onClick={handleClickOnField}

@ -6,11 +6,11 @@ import { faArrowLeft, faCheck } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const { Row, Col, Container } = require('reactstrap');
const Step = ({ name, position, active, check }) => {
const Step = ({ name, position, active, check, onClick }) => {
const { t } = useTranslation();
const disabled = !active && !check;
return (
<Col className="col-auto">
<Col className="col-auto" role={onClick ? 'button' : ''} onClick={onClick}>
<Row
className={`align-items-center creation-step ${
active ? 'active' : ''
@ -27,7 +27,25 @@ const Step = ({ name, position, active, check }) => {
export const creationSteps = ['candidate', 'params', 'confirm'];
export const ProgressSteps = ({ step, className, ...props }) => {
interface GoToStep {
(): void;
}
interface ProgressStepsProps {
step: string;
goToParams: GoToStep;
goToCandidates: GoToStep;
className?: string;
[props: string]: any;
}
export const ProgressSteps = ({
step,
goToParams,
goToCandidates,
className = '',
...props
}: ProgressStepsProps) => {
const { t } = useTranslation();
if (!creationSteps.includes(step)) {
@ -35,6 +53,8 @@ export const ProgressSteps = ({ step, className, ...props }) => {
}
const stepId = creationSteps.indexOf(step);
const gotosteps = [goToCandidates, goToParams];
return (
<Row className={`w-100 m-5 d-flex ${className}`} {...props}>
<Col className="col-lg-3 col-6 mb-3">
@ -43,7 +63,9 @@ export const ProgressSteps = ({ step, className, ...props }) => {
<Col className="col-auto">
<FontAwesomeIcon icon={faArrowLeft} />
</Col>
<Col className="col-auto">{t('admin.candidates-back-step')}</Col>
<Col role="button" onClick={goToCandidates} className="col-auto">
{t('admin.candidates-back-step')}
</Col>
</Row>
)}
</Col>
@ -56,6 +78,7 @@ export const ProgressSteps = ({ step, className, ...props }) => {
check={i < stepId}
key={i}
position={i + 1}
onClick={gotosteps[i]}
/>
))}
</Row>

@ -1,21 +1,12 @@
import PropTypes from 'prop-types';
import Image from 'next/image';
import logoWithText from '../public/logos/logo.svg';
import logo from '../public/logos/logo-footer.svg';
import { useTranslation } from 'next-i18next';
const Logo = ({ title, ...props }) => {
const Logo = ({ title = undefined, ...props }) => {
const { t } = useTranslation();
const src = title ? logoWithText : logo;
return <Image src={src} alt={t('logo.alt')} className="d-block" {...props} />;
};
Logo.propTypes = {
title: PropTypes.bool,
};
Logo.defaultProps = {
title: true,
};
export default Logo;

@ -1,23 +0,0 @@
/**
* A toggle button using bootstrap
*/
const Toggle = ({ active, children }) => {
return (
<button
type="button"
className={`btn btn-toggle ${active ? 'active' : ''}`}
data-toggle="button"
aria-pressed="false"
autocomplete="off"
>
{children}
</button>
);
};
Toggle.defaultProps = {
active: false,
};
export default Toggle;

@ -8,11 +8,23 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons';
import { Row, Col } from 'reactstrap';
import { useElection, useElectionDispatch } from './ElectionContext';
import defaultAvatar from '../../public/avatar.svg';
import whiteAvatar from '../../public/avatar.svg';
import CandidateModalSet from './CandidateModalSet';
import CandidateModalDel from './CandidateModalDel';
const CandidateField = ({ position, className = '', ...inputProps }) => {
interface CandidateProps {
position: number;
className?: string;
defaultAvatar?: any;
[props: string]: any;
}
const CandidateField = ({
position,
className = '',
defaultAvatar = whiteAvatar,
...props
}: CandidateProps) => {
const { t } = useTranslation();
const election = useElection();
@ -38,25 +50,27 @@ const CandidateField = ({ position, className = '', ...inputProps }) => {
} p-2 my-3 border border-dashed border-2 border-light border-opacity-25 align-items-center ${
active ? 'active' : ''
}`}
{...inputProps}
{...props}
>
<Col onClick={toggleSet} className="cursor-pointer col-auto me-auto">
<Col onClick={toggleSet} className="cursor-pointer col-10 me-auto">
<Row className="gx-3">
<Col className="col-auto">
<Col className="col-2 justify-content-start align-items-center d-flex">
<Image
src={image}
width={24}
height={24}
className={image == defaultAvatar ? 'default-avatar' : ''}
className={`${
image == defaultAvatar ? 'default-avatar' : ''
} bg-primary`}
alt={t('common.thumbnail')}
/>
</Col>
<Col className="col-auto fw-bold">
<Col className="col-10 fw-bold">
{candidate.name ? candidate.name : t('admin.add-candidate')}
</Col>
</Row>
</Col>
<Col className="col-auto cursor-pointer">
<Col role="button" className="col-2 text-end">
{active ? (
<FontAwesomeIcon
icon={faTrashCan}

@ -122,7 +122,7 @@ const CandidateModal = ({ isOpen, position, toggle }) => {
tabIndex={position + 1}
value={state.name}
onChange={handleName}
maxLength="250"
// maxLength="250"
autoFocus
required
/>
@ -138,7 +138,7 @@ const CandidateModal = ({ isOpen, position, toggle }) => {
placeholder={t('admin.candidate-desc-placeholder')}
onChange={handleDescription}
value={state.description}
maxLength="250"
// maxLength="250"
/>
</div>
<Row className="mt-5 mb-3">

@ -6,6 +6,7 @@ import {
faCheck,
faArrowLeft,
faPen,
faArrowRight,
} from '@fortawesome/free-solid-svg-icons';
import {
Button,
@ -21,15 +22,19 @@ import {
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useElection } from './ElectionContext';
import CandidateField from './CandidateField';
import AccessResults from './AccessResults';
import LimitDate from './LimitDate';
import Grades from './Grades';
import Private from './Private';
const TitleField = () => {
const { t } = useTranslation();
const election = useElection();
return (
<Container className="bg-white container-fluid p-4">
<Container className="bg-white p-4">
<Row>
<Col className="col-auto me-auto">
<h4 className="text-dark">{t('admin.access-results')}</h4>
<h5 className="text-dark">{t('admin.confirm-question')}</h5>
</Col>
</Row>
<h4 className="text-primary">{election.title}</h4>
@ -41,17 +46,17 @@ const CandidatesField = () => {
const { t } = useTranslation();
const election = useElection();
return (
<Container className="bg-white container-fluid p-4">
<Container className="bg-white p-4">
<Row>
<Col className="col-auto me-auto">
<h4 className="text-dark">{t('admin.access-results')}</h4>
<h5 className="text-dark">{t('admin.confirm-candidates')}</h5>
</Col>
<Col className="col-auto d-flex align-items-center">
<FontAwesomeIcon icon={faPen} />
</Col>
</Row>
{election.candidates.map((_, i) => (
<CandidateField position={i} />
<CandidateField position={i} key={i} className="text-primary" />
))}
</Container>
);
@ -62,15 +67,39 @@ const ConfirmField = ({ onSubmit, goToCandidates, goToParams }) => {
const election = useElection();
return (
<Container className="params flex-grow-1 my-5 mt-5 flex-column d-flex justify-content-between">
<Container
fluid="xl"
className="my-5 flex-column d-flex justify-content-center"
>
<Row>
<Col className="col-md-auto col-12">
<h4 className="mb-3">{t('common.the-vote')}</h4>
<Col className="col-lg-3 col-12">
<Container className="py-4">
<h4>{t('common.the-vote')}</h4>
</Container>
<TitleField />
<CandidatesField />
</Col>
<Col className="col-md-auto col-12"></Col>
<Col className="col-lg-9 col-12">
<Container className="py-4">
<h4>{t('common.the-params')}</h4>
</Container>
<AccessResults />
<LimitDate />
<Grades />
<Private />
</Col>
</Row>
<div className="my-5 d-flex justify-content-center">
<Button
outline={true}
color="secondary"
onClick={onSubmit}
icon={faArrowRight}
position="right"
>
{t('admin.confirm-submit')}
</Button>
</div>
</Container>
);
};

@ -1,26 +0,0 @@
/**
* This component manages the date for ending the election
*/
import { useState } from 'react';
import { Row } from 'reactstrap';
import { useTranslation } from 'next-i18next';
import { useElection, useElectionDispatch } from './ElectionContext';
import Toggle from '@components/Toggle';
const DateField = () => {
const election = useElection();
const dispatch = useElectionDispatch();
const [toggle, setToggle] = useState(false);
const { t } = useTranslation();
return (
<Row>
<Col className="col-auto me-auto">{t('admin.date-limit')}</Col>
<Col className="col-auto">
<Toggle onChange={setToggle((t) => !t)} />
</Col>
</Row>
);
};
export default DateField;

@ -42,18 +42,21 @@ const GradeField = ({ value }) => {
};
return (
<Row style={style} onClick={toggle} className="p-2 m-1 rounded-1">
<Col
className={`${
grade.active ? '' : 'text-decoration-line-through'
} col-auto fw-bold`}
>
<div
style={style}
onClick={toggle}
className="p-2 m-1 fw-bold rounded-1 d-flex justify-content-between gap-3"
>
<div className={grade.active ? '' : 'text-decoration-line-through'}>
{grade.name}
</Col>
<Col onClick={handleActive} className="col-auto">
<FontAwesomeIcon icon={grade.active ? faXmark : faRotateLeft} />
</Col>
</Row>
</div>
<div>
<FontAwesomeIcon
onClick={handleActive}
icon={grade.active ? faXmark : faRotateLeft}
/>
</div>
</div>
);
};

@ -66,10 +66,14 @@ const Grades = () => {
<p className="text-muted">{t('admin.grades-desc')}</p>
</Col>
<Col className="col-auto d-flex align-items-center">
{grades.map((_, i) => (
<GradeField value={i} key={i} />
))}
{/* <AddField /> */}
<Row className="gx-1">
{grades.map((_, i) => (
<Col className="col-auto">
<GradeField value={i} key={i} />
</Col>
))}
{/* <AddField /> */}
</Row>
</Col>
</Row>
</Container>

@ -1,75 +0,0 @@
/* eslint react/prop-types: 0 */
import React, { Component } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
class HelpButton extends Component {
constructor(props) {
super(props);
this.state = {
tooltipOpen: false,
};
}
showTooltip = () => {
this.setState({
tooltipOpen: true,
});
};
hideTooltip = () => {
this.setState({
tooltipOpen: false,
});
};
render() {
return (
<span className={this.props.className}>
<span>
{this.state.tooltipOpen ? (
<span
style={{
position: 'absolute',
zIndex: 10,
fontSize: '12px',
color: '#000',
backgroundColor: '#fff',
display: 'inline-block',
borderRadius: '0.25rem',
boxShadow: '-5px 0 5px rgba(0,0,0,0.5)',
maxWidth: '200px',
padding: '10px',
marginLeft: '-215px',
marginTop: '-25px',
}}
>
<span
style={{
position: 'absolute',
width: 0,
height: 0,
borderTop: '10px solid transparent',
borderBottom: '10px solid transparent',
borderLeft: '10px solid #fff',
marginLeft: '190px',
marginTop: '15px',
}}
></span>
{this.props.children}
</span>
) : (
<span />
)}
</span>
<FontAwesomeIcon
icon={faQuestionCircle}
onMouseOver={this.showTooltip}
onMouseOut={this.hideTooltip}
/>
</span>
);
}
}
export default HelpButton;

@ -11,7 +11,7 @@ const ParamsField = ({ onSubmit }) => {
const { t } = useTranslation();
return (
<Container className="params flex-grow-1 my-5 mt-5 flex-column d-flex justify-content-between">
<Container className="params flex-grow-1 my-5 flex-column d-flex justify-content-between">
<div className="d-flex flex-column">
<AccessResults />
<LimitDate />

@ -1,24 +0,0 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import i18n from '../../i18n';
const Helloasso = (props) => {
const locale =
i18n.language.substring(0, 2).toLowerCase() === 'fr' ? 'fr' : 'en';
const linkHelloAssoBanner =
locale === 'fr'
? 'https://www.helloasso.com/associations/mieux-voter/formulaires/1/widget'
: 'https://www.helloasso.com/associations/mieux-voter/formulaires/1/widget/en';
return (
<a href={linkHelloAssoBanner} target="_blank" rel="noopener noreferrer">
<img
src={'/banner/' + locale + '/helloasso.png'}
alt="support us on helloasso"
style={{ width: props.width }}
/>
</a>
);
};
export default Helloasso;

@ -1,43 +0,0 @@
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import { faPaypal } from '@fortawesome/free-brands-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const Paypal = () => {
const { t } = useTranslation();
// FIXME generate a xx_XX string for locale version
const { locale } = useRouter();
let localeShort = locale.substring(0, 2);
let localeComplete =
localeShort.toLowerCase() + '_' + localeShort.toUpperCase();
if (localeComplete === 'en_EN') {
localeComplete = 'en_US';
}
const pixelLink = `https://www.paypal.com/${localeComplete}/i/scr/pixel.gif`;
return (
<div className="d-inline-block m-auto">
<form
action="https://www.paypal.com/cgi-bin/webscr"
method="post"
target="_top"
>
<button
type="submit"
className="btn btn-primary"
title={t('PayPal - The safer, easier way to pay online!')}
>
{' '}
<FontAwesomeIcon icon={faPaypal} className="mr-2" />
{t('Support us !')}
</button>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="KB2Z7L9KARS7C" />
<img alt="" border="0" src={pixelLink} width="1" height="1" />
</form>
</div>
);
};
export default Paypal;

@ -1,42 +0,0 @@
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import { faPaypal } from '@fortawesome/free-brands-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
const PaypalNoLogo = () => {
const { t } = useTranslation();
// FIXME generate a xx_XX string for locale version
const { locale } = useRouter();
let localeShort = locale.substring(0, 2);
let localeComplete =
localeShort.toLowerCase() + '_' + localeShort.toUpperCase();
if (localeComplete === 'en_EN') {
localeComplete = 'en_US';
}
const pixelLink = `https://www.paypal.com/${localeComplete}/i/scr/pixel.gif`;
return (
<div className="d-inline-block m-auto">
<form
action="https://www.paypal.com/cgi-bin/webscr"
method="post"
target="_top"
>
<button
type="submit"
className="addButton"
title={t('PayPal - The safer, easier way to pay online!')}
>
{t('Support us !')}
<img src="/arrow-blue.svg" className="mr-2" />
</button>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="KB2Z7L9KARS7C" />
<img alt="" border="0" src={pixelLink} width="1" height="1" />
</form>
</div>
);
};
export default PaypalNoLogo;

@ -5,26 +5,14 @@ import Logo from '@components/Logo';
import LanguageSelector from '@components/layouts/LanguageSelector';
const Footer = () => {
const linkStyle = { whiteSpace: 'nowrap' };
const { t } = useTranslation();
// const [bboxLink1, link1] = useBbox();
// const [bboxLink2, link2] = useBbox();
// const [bboxLink3, link3] = useBbox();
// const [bboxLink4, link4] = useBbox();
// const [bboxLink5, link5] = useBbox();
//<Col className="col-
const menu = [
{
component: <Logo title={false} />,
},
{
component: (
<Link href="/" style={linkStyle}>
{t('menu.majority-judgment')}
</Link>
),
component: <Link href="/">{t('menu.majority-judgment')}</Link>,
},
{
component: (
@ -32,29 +20,20 @@ const Footer = () => {
href="https://mieuxvoter.fr/"
target="_blank"
rel="noopener noreferrer"
style={linkStyle}
>
{t('menu.whoarewe')}
</Link>
),
},
{
component: (
<Link href="/faq" style={linkStyle}>
{t('menu.faq')}
</Link>
),
component: <Link href="/faq">{t('menu.faq')}</Link>,
},
{
component: (
<Link href="/" style={linkStyle}>
{t('menu.news')}
</Link>
),
component: <Link href="/">{t('menu.news')}</Link>,
},
{
component: (
<a href="mailto:app@mieuxvoter.fr?subject=[HELP]" style={linkStyle}>
<a href="mailto:app@mieuxvoter.fr?subject=[HELP]">
{t('menu.contact-us')}
</a>
),
@ -72,7 +51,7 @@ const Footer = () => {
<footer>
<Row>
<Col className="col-auto me-auto">
<Row>
<Row className="gx-3">
{menu.map((item, i) => (
<Col key={i} className="col-auto d-flex align-items-center">
{item.component}

@ -87,7 +87,7 @@ const Header = () => {
</NavItem>
<NavItem>
<LanguageSelector style={{ width: '80px' }} />
<LanguageSelector />
</NavItem>
</div>

@ -22,7 +22,6 @@ const LanguageSelector = () => {
customLabels={{ GB: 'English', FR: 'Francais' }}
className="menu-flags"
selectedSize={14}
selectedSize={14}
/>
);
};

@ -1,11 +1,11 @@
import React from 'react';
import HeaderDesktopResult from './HeaderDesktopResult';
import HeaderMobileResult from './HeaderMobileResult';
import { useMediaQuery } from 'react-responsive';
// import { useMediaQuery } from 'react-responsive';
export default function HeaderResult() {
const isMobile = useMediaQuery({ query: '(max-width: 800px)' });
if (isMobile) return <HeaderMobileResult />;
else return <HeaderDesktopResult />;
// const isMobile = useMediaQuery({ query: '(max-width: 800px)' });
//
// if (isMobile) return <HeaderMobileResult />;
return <HeaderDesktopResult />;
}

@ -1,20 +0,0 @@
/* eslint react/prop-types: 0 */
import { useState } from 'react';
import { useRef } from 'react';
import { useEffect } from 'react';
export const useBbox = () => {
const ref = useRef();
const [bbox, setBbox] = useState({});
const set = () =>
setBbox(ref && ref.current ? ref.current.getBoundingClientRect() : {});
useEffect(() => {
set();
window.addEventListener('resize', set);
return () => window.removeEventListener('resize', set);
}, []);
return [bbox, ref];
};

93
package-lock.json generated

@ -20,20 +20,18 @@
"bootstrap-scss": "^5.2.2",
"clipboard": "^2.0.10",
"dotenv": "^8.6.0",
"embla-carousel-react": "^7.0.4",
"eslint-config-next": "^13.0.0",
"framer-motion": "^7.6.4",
"highcharts-react-official": "^3.1.0",
"i18next": "^22.0.3",
"mailgun.js": "^3.3.2",
"next": "^13.0.0",
"next-i18next": "^12.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-datepicker": "^4.8.0",
"react-dom": "^18.2.0",
"react-flags-select": "^2.2.3",
"react-i18next": "^12.0.0",
"react-toastify": "^9.1.0",
"reactstrap": "^9.1.4",
"sass": "^1.32.13",
"typescript": "^4.8.4"
@ -1476,14 +1474,6 @@
"node": ">=6"
}
},
"node_modules/clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -1659,6 +1649,22 @@
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
"peer": true
},
"node_modules/embla-carousel": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-7.0.4.tgz",
"integrity": "sha512-2/EO9Zh6yT1EiTNkCUhbYfAqe6PVODSYFZQxTeuLGFYYNTKTkHvalfuFVIdEDmtxbbGepuYtLqCmk6yzJfJeyw=="
},
"node_modules/embla-carousel-react": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-7.0.4.tgz",
"integrity": "sha512-pSnxrvsLMt3TwVS8uqYMZGfJvRLy+3rcg3JOImVDgvNEtEbbzXoL8tR34Dph6Jh38e/Z9IZW7jVX0igu/haLyQ==",
"dependencies": {
"embla-carousel": "7.0.4"
},
"peerDependencies": {
"react": "^18.1.0"
}
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -2636,21 +2642,6 @@
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
},
"node_modules/highcharts": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.3.1.tgz",
"integrity": "sha512-8UgVcLmgpiYwnsII0Ht76O+GRutfbrLslZFH3c53fXgl3aZ6NRB4mW5qsIyfsUExMny/n9JqYO/BFNejOKC6AA==",
"peer": true
},
"node_modules/highcharts-react-official": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.1.0.tgz",
"integrity": "sha512-CkWJHrVMOc6CT8KFu1dR+a0w5OxCVKKgZUNWtEi5TmR0xqBDIDe+RyM652MAN/jBYppxMo6TCUVlRObCyWAn0Q==",
"peerDependencies": {
"highcharts": ">=6.0.0",
"react": ">=16.8.0"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -3803,18 +3794,6 @@
"react-dom": "^16.8.0 || ^17 || ^18"
}
},
"node_modules/react-toastify": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.0.tgz",
"integrity": "sha512-63i/5SROvfYz9yzdkmxIrpndtggTdif/5ZpswY3VH+s2/S1Hxo/hiv+oGXnPH6UO2pJBOgfcLNeyVh7okRmnhg==",
"dependencies": {
"clsx": "^1.1.1"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@ -5465,11 +5444,6 @@
"shallow-clone": "^3.0.0"
}
},
"clsx": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -5599,6 +5573,19 @@
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
"peer": true
},
"embla-carousel": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-7.0.4.tgz",
"integrity": "sha512-2/EO9Zh6yT1EiTNkCUhbYfAqe6PVODSYFZQxTeuLGFYYNTKTkHvalfuFVIdEDmtxbbGepuYtLqCmk6yzJfJeyw=="
},
"embla-carousel-react": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-7.0.4.tgz",
"integrity": "sha512-pSnxrvsLMt3TwVS8uqYMZGfJvRLy+3rcg3JOImVDgvNEtEbbzXoL8tR34Dph6Jh38e/Z9IZW7jVX0igu/haLyQ==",
"requires": {
"embla-carousel": "7.0.4"
}
},
"emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -6316,18 +6303,6 @@
"resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
"integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="
},
"highcharts": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-10.3.1.tgz",
"integrity": "sha512-8UgVcLmgpiYwnsII0Ht76O+GRutfbrLslZFH3c53fXgl3aZ6NRB4mW5qsIyfsUExMny/n9JqYO/BFNejOKC6AA==",
"peer": true
},
"highcharts-react-official": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/highcharts-react-official/-/highcharts-react-official-3.1.0.tgz",
"integrity": "sha512-CkWJHrVMOc6CT8KFu1dR+a0w5OxCVKKgZUNWtEi5TmR0xqBDIDe+RyM652MAN/jBYppxMo6TCUVlRObCyWAn0Q==",
"requires": {}
},
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -7070,14 +7045,6 @@
"warning": "^4.0.2"
}
},
"react-toastify": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.0.tgz",
"integrity": "sha512-63i/5SROvfYz9yzdkmxIrpndtggTdif/5ZpswY3VH+s2/S1Hxo/hiv+oGXnPH6UO2pJBOgfcLNeyVh7okRmnhg==",
"requires": {
"clsx": "^1.1.1"
}
},
"react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",

@ -22,20 +22,18 @@
"bootstrap-scss": "^5.2.2",
"clipboard": "^2.0.10",
"dotenv": "^8.6.0",
"embla-carousel-react": "^7.0.4",
"eslint-config-next": "^13.0.0",
"framer-motion": "^7.6.4",
"highcharts-react-official": "^3.1.0",
"i18next": "^22.0.3",
"mailgun.js": "^3.3.2",
"next": "^13.0.0",
"next-i18next": "^12.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-datepicker": "^4.8.0",
"react-dom": "^18.2.0",
"react-flags-select": "^2.2.3",
"react-i18next": "^12.0.0",
"react-toastify": "^9.1.0",
"reactstrap": "^9.1.4",
"sass": "^1.32.13",
"typescript": "^4.8.4"

@ -26,7 +26,7 @@ import config from '../../../next-i18next.config.js';
import { AnimatePresence, motion } from 'framer-motion';
export async function getServerSideProps({ query: { pid }, locale }) {
let [details, translations] = await Promise.all([
getDetails(pid),
getDetails(pid, console.log, console.log),
serverSideTranslations(locale, [], config),
]);
@ -63,7 +63,7 @@ const ConfirmElection = ({
const { t } = useTranslation();
if (err) {
return <Error value={apiErrors(err, t)} />;
return <Error msg={apiErrors(err, t)} />;
}
const origin =

@ -54,7 +54,11 @@ const CreateElectionForm = () => {
return (
<ElectionProvider>
<ProgressSteps step={step} />
<ProgressSteps
step={step}
goToCandidates={() => setStepId(0)}
goToParams={() => setStepId(1)}
/>
{Step}
</ElectionProvider>
);

@ -11,7 +11,6 @@ import {
Input,
Label,
InputGroup,
InputGroupAddon,
Button,
Card,
CardBody,
@ -19,7 +18,6 @@ import {
ModalHeader,
ModalBody,
ModalFooter,
CustomInput,
} from 'reactstrap';
import { GetStaticProps } from 'next';
// import {ReactMultiEmail, isEmail} from "react-multi-email";

@ -1,7 +1,6 @@
import Link from 'next/link';
import { Container, Row, Col } from 'reactstrap';
import { useTranslation } from 'next-i18next';
import Paypal from '@components/banner/Paypal';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import config from '../next-i18next.config.js';
import { GetStaticProps } from 'next';
@ -39,7 +38,7 @@ const FAQ = () => {
»).
</p>
<div style={{ maxWidth: '445px' }}>
<video width="100%" height="250" controls="controls">
<video width="100%" height="250" controls={true}>
<source
src="/video/Le_Jugement_Majoritaire_en_1_minute.mp4"
type="video/mp4"
@ -253,7 +252,6 @@ const FAQ = () => {
Nous en sommes ravis ! Vous pouvez nous aider en faisant un don à
lassociation ici :
</p>
<Paypal btnColor="btn-success" className="mt-1" />
</Col>
</Row>
</Container>

@ -47,7 +47,6 @@ const StartForm = () => {
name="title"
value={title ? title : ''}
onChange={(e) => setTitle(e.target.value)}
maxLength="250"
/>
<p className="pt-0 mt-0 mr-0 maxLength">250</p>
@ -145,7 +144,13 @@ const ExperienceRow = () => {
<Col>
<Button color="primary" className="p-4 fs-5">
{t('home.experience-call-to-action')}
<Image src={arrowRight} width={22} height={22} className="mr-2" />
<Image
src={arrowRight}
width={22}
height={22}
className="mr-2"
alt="icon arrow right"
/>
</Button>
</Col>
</Row>
@ -164,7 +169,7 @@ const ShareRow = () => {
rel="noopener noreferrer"
href="https://www.facebook.com/mieuxvoter.fr/"
>
<Image height={22} width={22} src={facebook} />
<Image height={22} width={22} src={facebook} alt="icon facebook" />
</a>
</Col>
<Col className="col-auto">
@ -173,7 +178,7 @@ const ShareRow = () => {
rel="noopener noreferrer"
href="https://twitter.com/mieux_voter"
>
<Image height={22} width={22} src={twitter} />
<Image height={22} width={22} src={twitter} alt="icon twitter" />
</a>
</Col>
</Row>

@ -16,9 +16,7 @@ import {
Button,
} from 'reactstrap';
import { getResults, getDetails, apiErrors } from '@services/api';
import { grades } from '@services/grades';
import { translateGrades } from '@services/grades';
import Facebook from '@components/banner/Facebook';
import Error from '@components/Error';
import config from '../../../next-i18next.config.js';
import Footer from '@components/layouts/Footer';
@ -68,7 +66,7 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => {
const newstart = new Date(finish * 1000).toLocaleDateString('fr-FR');
if (err && err !== '') {
return <Error value={apiErrors(err, t)} />;
return <Error msg={apiErrors(err, t)} />;
}
const router = useRouter();
@ -96,7 +94,8 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => {
const [collapseProfiles, setCollapseProfiles] = useState(false);
const [collapseGraphics, setCollapseGraphics] = useState(false);
const sum = (seq) => Object.values(seq).reduce((a, b) => a + b, 0);
const sum = (seq: Array<number>) =>
Object.values(seq).reduce((a, b) => a + b, 0);
const numVotes =
candidates && candidates.length > 0 ? sum(candidates[0].profile) : 1;
const gradeIds =

@ -20,7 +20,7 @@ import { getDetails, castBallot, apiErrors } from '@services/api';
import Error from '@components/Error';
import { translateGrades } from '@services/grades';
import Footer from '@components/layouts/Footer';
// import useEmblaCarousel from 'embla-carousel-react'
import useEmblaCarousel from 'embla-carousel-react';
import {
DotButton,
PrevButton,
@ -68,7 +68,7 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
const { t } = useTranslation();
if (err) {
return <Error value={apiErrors(err, t)}></Error>;
return <Error msg={apiErrors(err, t)}></Error>;
}
const [judgments, setJudgments] = useState([]);
@ -166,7 +166,9 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
content={t('common.application')}
/>
</Head>
<ToastContainer />
{
// <ToastContainer />
}
<Container className="homePage">
<section>
<div className="sectionOneHomeForm pb-5">
@ -430,9 +432,7 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
<Col className="text-center">
{judgments.length !== candidates.length ? (
<VoteButtonWithConfirm
className="btn btn-transparent my-3"
action={handleSubmitWithoutAllRate}
onClick={toggle}
/>
) : (
<Button type="submit" className="mt-5 btn btn-transparent">
@ -607,11 +607,7 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
<Row className="btn-background mx-0">
<Col className="text-center">
{judgments.length !== candidates.length ? (
<VoteButtonWithConfirm
className="btn btn-transparent my-3"
action={handleSubmitWithoutAllRate}
onClick={toggle}
/>
<VoteButtonWithConfirm action={handleSubmitWithoutAllRate} />
) : (
<Button type="submit" className="my-3 btn btn-transparent">
<FontAwesomeIcon icon={faCheck} className="mr-2" />

@ -19,7 +19,7 @@ export async function getServerSideProps({ query: { pid }, locale }) {
]);
if (typeof details === 'string' || details instanceof String) {
return { props: { err: res.slice(1, -1), ...translations } };
return { props: { err: details.slice(1, -1), ...translations } };
}
if (!details.candidates || !Array.isArray(details.candidates)) {
@ -42,7 +42,7 @@ export async function getServerSideProps({ query: { pid }, locale }) {
const VoteSuccess = ({ title, invitationOnly, pid, err }) => {
const { t } = useTranslation();
if (err && err !== '') {
return <Error value={apiErrors(err, t)} />;
return <Error msg={apiErrors(err, t)} />;
}
return (

@ -37,6 +37,7 @@
"common.grades": "Grades",
"common.days": "days",
"common.the-vote": "The vote",
"common.the-params": "The parameters",
"error.help": "Ask for our help",
"error.at-least-2-candidates": "At least two candidates are required.",
"error.no-title": "Please add a title to your election.",
@ -75,5 +76,7 @@
"admin.private-desc": "Only participants who received an invite by email will be able to vote",
"admin.private-tip": "You can copy-paste a list of emails from a spreadsheet.",
"admin.private-placeholder": "Add here the emails of the participants.",
"admin.confirm-question": "Question of your vote"
"admin.confirm-question": "Question of your vote",
"admin.confirm-candidates": "Candidates",
"admin.confirm-submit": "Start the vote"
}

@ -37,6 +37,7 @@
"common.grades": "Mentions",
"common.days": "jours",
"common.the-vote": "Le vote",
"common.the-params": "Les paramètres",
"error.help": "Besoin d'aide ?",
"error.at-least-2-candidates": "Ajoutez au moins deux candidats.",
"error.no-title": "Ajoutez un titre à l'élection.",
@ -75,6 +76,7 @@
"admin.private-desc": "Uniquement les personnes invités par mail pourront participé au vote",
"admin.private-tip": "Vous pouvez copier-coller une liste d'emails depuis un tableur.",
"admin.private-placeholder": "Ajoutez ici les emails des participants.",
"admin.confirm-question": "Question de votre vote"
"admin.confirm-question": "Question de votre vote",
"admin.confirm-candidates": "Candidats",
"admin.confirm-submit": "Démarrer le vote"
}

@ -1,14 +1,14 @@
const api = {
urlServer:
process.env.NEXT_PUBLIC_SERVER_URL || "https://demo.mieuxvoter.fr/api/",
process.env.NEXT_PUBLIC_SERVER_URL || 'https://demo.mieuxvoter.fr/api/',
feedbackForm:
process.env.NEXT_PUBLIC_FEEDBACK_FORM ||
"https://docs.google.com/forms/d/e/1FAIpQLScuTsYeBXOSJAGSE_AFraFV7T2arEYua7UCM4NRBSCQQfRB6A/viewform",
'https://docs.google.com/forms/d/e/1FAIpQLScuTsYeBXOSJAGSE_AFraFV7T2arEYua7UCM4NRBSCQQfRB6A/viewform',
routesServer: {
setElection: "election/",
getElection: "election/get/:slug/",
getResults: "election/results/:slug",
voteElection: "election/vote/",
setElection: 'election/',
getElection: 'election/get/:slug/',
getResults: 'election/results/:slug',
voteElection: 'election/vote/',
},
};
@ -19,17 +19,17 @@ const sendInviteMail = (res) => {
const { id, title, mails, tokens, locale } = res;
if (!mails || !mails.length) {
throw new Error("No emails are provided.");
throw new Error('No emails are provided.');
}
if (mails.length !== tokens.length) {
throw new Error("The number of emails differ from the number of tokens");
throw new Error('The number of emails differ from the number of tokens');
}
const origin =
typeof window !== "undefined" && window.location.origin
typeof window !== 'undefined' && window.location.origin
? window.location.origin
: "http://localhost";
: 'http://localhost';
const urlVote = (pid, token) => new URL(`/vote/${pid}/${token}`, origin);
const urlResult = (pid) => new URL(`/result/${pid}`, origin);
@ -41,10 +41,10 @@ const sendInviteMail = (res) => {
};
});
const req = fetch("/.netlify/functions/send-invite-email/", {
method: "POST",
const req = fetch('/.netlify/functions/send-invite-email/', {
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify({
recipientVariables,
@ -79,9 +79,9 @@ const createElection = (
const onInvitationOnly = mails && mails.length > 0;
fetch(endpoint.href, {
method: "POST",
method: 'POST',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
},
body: JSON.stringify({
title,
@ -91,7 +91,7 @@ const createElection = (
elector_emails: mails || [],
start_at: start,
finish_at: finish,
select_language: locale || "en",
select_language: locale || 'en',
front_url: window.location.origin,
restrict_results: restrictResult,
send_mail: false,
@ -113,13 +113,17 @@ const createElection = (
.catch(failureCallback || console.log);
};
const getResults = (pid, successCallback, failureCallback) => {
const getResults = (
pid: string,
successCallback = null,
failureCallback = null
) => {
/**
* Fetch results from external API
*/
const endpoint = new URL(
api.routesServer.getResults.replace(new RegExp(":slug", "g"), pid),
api.routesServer.getResults.replace(new RegExp(':slug', 'g'), pid),
api.urlServer
);
@ -134,13 +138,17 @@ const getResults = (pid, successCallback, failureCallback) => {
.catch(failureCallback || ((err) => err));
};
const getDetails = (pid, successCallback, failureCallback) => {
const getDetails = (
pid: string,
successCallback = null,
failureCallback = null
) => {
/**
* Fetch data from external API
*/
const detailsEndpoint = new URL(
api.routesServer.getElection.replace(new RegExp(":slug", "g"), pid),
api.routesServer.getElection.replace(new RegExp(':slug', 'g'), pid),
api.urlServer
);
return fetch(detailsEndpoint.href)
@ -155,7 +163,13 @@ const getDetails = (pid, successCallback, failureCallback) => {
.then((res) => res);
};
const castBallot = (judgments, pid, token, callbackSuccess, callbackError) => {
const castBallot = (
judgments: Array<number>,
pid: string,
token: string,
callbackSuccess = null,
callbackError = null
) => {
/**
* Save a ballot on the remote database
*/
@ -166,55 +180,55 @@ const castBallot = (judgments, pid, token, callbackSuccess, callbackError) => {
election: pid,
grades_by_candidate: judgments,
};
if (token && token !== "") {
payload["token"] = token;
if (token && token !== '') {
payload['token'] = token;
}
fetch(endpoint.href, {
method: "POST",
headers: { "Content-Type": "application/json" },
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
.then(callbackSuccess || ((res) => res))
.catch(callbackError || console.log);
};
export const UNKNOWN_ELECTION_ERROR = "E1:";
export const ONGOING_ELECTION_ERROR = "E2:";
export const NO_VOTE_ERROR = "E3:";
export const ELECTION_NOT_STARTED_ERROR = "E4:";
export const ELECTION_FINISHED_ERROR = "E5:";
export const INVITATION_ONLY_ERROR = "E6:";
export const UNKNOWN_TOKEN_ERROR = "E7:";
export const USED_TOKEN_ERROR = "E8:";
export const WRONG_ELECTION_ERROR = "E9:";
export const UNKNOWN_ELECTION_ERROR = 'E1:';
export const ONGOING_ELECTION_ERROR = 'E2:';
export const NO_VOTE_ERROR = 'E3:';
export const ELECTION_NOT_STARTED_ERROR = 'E4:';
export const ELECTION_FINISHED_ERROR = 'E5:';
export const INVITATION_ONLY_ERROR = 'E6:';
export const UNKNOWN_TOKEN_ERROR = 'E7:';
export const USED_TOKEN_ERROR = 'E8:';
export const WRONG_ELECTION_ERROR = 'E9:';
export const apiErrors = (error, t) => {
if (error.includes(UNKNOWN_ELECTION_ERROR)) {
return t("error.e1");
return t('error.e1');
}
if (error.includes(ONGOING_ELECTION_ERROR)) {
return t("error.e2");
return t('error.e2');
}
if (error.includes(NO_VOTE_ERROR)) {
return t("error.e3");
return t('error.e3');
}
if (error.includes(ELECTION_NOT_STARTED_ERROR)) {
return t("error.e4");
return t('error.e4');
}
if (error.includes(ELECTION_FINISHED_ERROR)) {
return t("error.e5");
return t('error.e5');
}
if (error.includes(INVITATION_ONLY_ERROR)) {
return t("error.e6");
return t('error.e6');
}
if (error.includes(USED_TOKEN_ERROR)) {
return t("error.e7");
return t('error.e7');
}
if (error.includes(WRONG_ELECTION_ERROR)) {
return t("error.e8");
return t('error.e8');
} else {
return t("error.catch22");
return t('error.catch22');
}
};

@ -3,24 +3,24 @@
*/
export const MAX_NUM_CANDIDATES = process.env.MAX_NUM_CANDIDATES || 1000;
export const CONTACT_MAIL = process.env.CONTACT_MAIL || "app@mieuxvoter.fr";
export const DEFAULT_GRADES = process.env.DEFAULT_GRADES ? process.env.DEFAULT_GRADES.split(",") : ['grades.very-good', 'grades.good', 'grades.passable', 'grades.inadequate', 'grades.mediocre'];
export const IMGPUSH_URL = process.env.IMGPUSH_URL || 'https://imgpush.mieuxvoter.fr';
export const GRADE_CLASSES = [
"to-reject",
"insufficient",
"passable",
"fair",
"good",
"very-good",
"excellent"
];
export const CONTACT_MAIL = process.env.CONTACT_MAIL || 'app@mieuxvoter.fr';
export const DEFAULT_GRADES = process.env.DEFAULT_GRADES
? process.env.DEFAULT_GRADES.split(',')
: [
'grades.very-good',
'grades.good',
'grades.passable',
'grades.inadequate',
'grades.mediocre',
];
export const IMGPUSH_URL =
process.env.IMGPUSH_URL || 'https://imgpush.mieuxvoter.fr';
export const GRADE_COLORS = [
"#3A9918",
"#A0CF1C",
"#D3D715",
"#C2B113",
"#C27C13",
"#C23D13",
"#F2F0FF",
'#3A9918',
'#A0CF1C',
'#D3D715',
'#C2B113',
'#C27C13',
'#C23D13',
'#F2F0FF',
];

@ -1,37 +0,0 @@
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);
/**
* Extract only the time from a date.
* Date can be a string or a Date.
* Return result in timestamp seconds.
*/
const extractTime = (date) => {
if (typeof date === "string") {
date = Date.parse(date);
}
if (!isValidDate) {
throw Error("The date is not valid.")
}
return date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds();
}
/**
* Extract only the day from a date.
* Return result in timestamp seconds.
*/
const extractDay = (date) => {
if (typeof date === "string") {
date = Date.parse(date);
}
if (!isValidDate) {
throw Error("The date is not valid.")
}
return date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds();
}

@ -30,11 +30,8 @@
}
.default-avatar {
background-color: rgba(255, 255, 255, 0.16);
width: inherit !important;
position: inherit !important;
padding: 4px;
margin-right: 10px;
}
.candidate * > input[type="file"] {

Loading…
Cancel
Save