Merge pull request #37 from MieuxVoter/feature/clement-release

Feature/clement release
pull/73/head
guhur 4 years ago committed by GitHub
commit ee68af410a

@ -6,6 +6,7 @@
"@babel/core": "7.4.3",
"@fortawesome/fontawesome-free": "^5.9.0",
"@fortawesome/fontawesome-svg-core": "^1.2.19",
"@fortawesome/free-brands-svg-icons": "^5.13.0",
"@fortawesome/free-solid-svg-icons": "^5.9.0",
"@fortawesome/react-fontawesome": "^0.1.4",
"@svgr/webpack": "4.1.0",
@ -62,6 +63,7 @@
"react-dev-utils": "^9.0.1",
"react-dom": "^16.8.6",
"react-flag-icon-css": "^1.0.25",
"react-flags-select": "^1.1.12",
"react-i18next": "^11.3.4",
"react-loader-spinner": "^3.1.14",
"react-multi-email": "^0.5.3",

@ -1,7 +1,7 @@
{
"Homepage": " Homepage ",
"Source code": "Quellcode",
"Who are we": "Wer wir sind",
"Who are we?": "Wer wir sind?",
"BetterVote": " BetterVote",
"Voting platform": "Wahlplattform",
"Majority Judgment": " Mehrheitswahl ",
@ -50,6 +50,9 @@
"You have to judge every candidate/proposal!": "Sie müssen jeden Kandidaten/Abstimmungsvorschlag bewerten!",
"Your participation was recorded with success!": " Ihre Teilnahme wurde gespeichert!",
"Thanks for your participation.": " Vielen Dank für Ihre Teilnahme.",
"Support us !" : "Unterstützen Sie uns!",
"PayPal - The safer, easier way to pay online!" : "PayPal - Die sicherere und einfachere Art, online zu bezahlen!",
"Number of votes:" : "Anzahl der Stimmen:"
"Unknown error. Try again please.": "__STRING_NOT_TRANSLATED__",
"Ending date:": "__STRING_NOT_TRANSLATED__",
"If you list voters' emails, only them will be able to access the election": "__STRING_NOT_TRANSLATED__",
@ -71,5 +74,4 @@
"You need a token to vote in this election": "__STRING_NOT_TRANSLATED__",
"You seem to have already voted.": "__STRING_NOT_TRANSLATED__",
"The parameters of the election are incorrect.": "__STRING_NOT_TRANSLATED__",
"Number of votes:": "__STRING_NOT_TRANSLATED__"
}

@ -1,7 +1,7 @@
{
"Homepage": "Homepage",
"Source code": "Source code",
"Who are we": "Who are we",
"Who are we?": "Who are we?",
"BetterVote": "BetterVote",
"Voting platform": "Voting platform",
"Majority Judgment": "Majority Judgment",
@ -78,5 +78,7 @@
"You need a token to vote in this election": "You need a token to vote in this election",
"You seem to have already voted.": "You seem to have already voted.",
"The parameters of the election are incorrect.": "The parameters of the election are incorrect.",
"Number of votes:": "Number of votes:"
"Support us !" : "Support us !",
"PayPal - The safer, easier way to pay online!" : "PayPal - The safer, easier way to pay online!",
"Number of votes:" : "Number of votes:"
}

@ -1,7 +1,7 @@
{
"Homepage": "Página de inicio",
"Source code": "Código fuente",
"Who are we": "Quiénes somos",
"Who are we?": "¿Quiénes somos?",
"BetterVote": "VotarMejor",
"Voting platform": "Plataforma de votación",
"Majority Judgment": "Juicio Mayoritario",
@ -78,5 +78,7 @@
"You need a token to vote in this election": "__STRING_NOT_TRANSLATED__",
"You seem to have already voted.": "__STRING_NOT_TRANSLATED__",
"The parameters of the election are incorrect.": "__STRING_NOT_TRANSLATED__",
"Number of votes:": "__STRING_NOT_TRANSLATED__"
"Support us !" : "¡Apóyanos!",
"PayPal - The safer, easier way to pay online!" : "PayPal - ¡La forma más segura y fácil de pagar en línea!",
"Number of votes:" : "Número de votos:"
}

@ -1,7 +1,7 @@
{
"Homepage": "Accueil",
"Source code": "Code source",
"Who are we": "Qui sommes-nous",
"Who are we?": "Qui sommes-nous ?",
"BetterVote": "MieuxVoter",
"Voting platform": "Plateforme de vote",
"Majority Judgment": "Jugement Majoritaire",
@ -78,5 +78,7 @@
"You need a token to vote in this election": "Vous avez besoin d'un jeton pour voter dans cette élection",
"You seem to have already voted.": "Il semble que vous ayez déjà voté.",
"The parameters of the election are incorrect.": "Les paramètres de l'élection sont incorrects.",
"Number of votes:": "Nombre de votes :"
"Support us !" : "Soutenez-nous !",
"PayPal - The safer, easier way to pay online!" : "PayPal - Le moyen le plus sûr et le plus simple de payer en ligne !",
"Number of votes:" : "Nombre de votes :"
}

@ -1,49 +1,60 @@
import React from 'react';
import {
Container,
Row,
Col,
} from "reactstrap";
import React from "react";
import { Container, Row, Col } from "reactstrap";
import { Link } from "react-router-dom";
import logoLine from "./logos/logo-line-white.svg";
import { withTranslation } from "react-i18next";
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 redirectError = (errorMsg, history) => {};
export const errorMessage = (error, t) => {
if (error.startsWith(UNKNOWN_ELECTION_ERROR)) {
return t('Oops... The election is unknown.');
return t("Oops... The election is unknown.");
} else if (error.startsWith(ONGOING_ELECTION_ERROR)) {
return t(
"The election is still going on. You can't access now to the results.",
"The election is still going on. You can't access now to the results."
);
} else if (error.startsWith(NO_VOTE_ERROR)) {
return t('No votes have been recorded yet. Come back later.');
return t("No votes have been recorded yet. Come back later.");
} else if (error.startsWith(ELECTION_NOT_STARTED_ERROR)) {
return t('The election has not started yet.');
return t("The election has not started yet.");
} else if (error.startsWith(ELECTION_FINISHED_ERROR)) {
return t('The election is over. You can\'t vote anymore');
return t("The election is over. You can't vote anymore");
} else if (error.startsWith(INVITATION_ONLY_ERROR)) {
return t('You need a token to vote in this election');
return t("You need a token to vote in this election");
} else if (error.startsWith(USED_TOKEN_ERROR)) {
return t('You seem to have already voted.');
return t("You seem to have already voted.");
} else if (error.startsWith(WRONG_ELECTION_ERROR)) {
return t('The parameters of the election are incorrect.');
return t("The parameters of the election are incorrect.");
}
};
export const Error = (props) => (
export const Error = props => (
<Container>
<Row>
<Col xs="12">
<h1>{props.value}</h1>
<Link to="/" className="d-block ml-auto mr-auto mb-4">
<img src={logoLine} alt="logo" height="128" />
</Link>
</Row>
<Row className="mt-4">
<Col className="text-center">
<h4>{props.value}</h4>
</Col>
</Row>
<Row className="mt-4">
<Col className="text-center">
<Link to="/" className="btn btn-secondary">
Back to home page
</Link>
</Col>
</Row>
</Container>

@ -9,7 +9,7 @@ const Helloasso = (props) => {
return (
<a href={linkHelloAssoBanner} target="_blank" rel="noopener noreferrer">
<img src={"/banner/"+locale+"/helloasso.png"} style={{width:props.width}} />
<img src={"/banner/"+locale+"/helloasso.png"} alt="support us on helloasso" style={{width:props.width}} />
</a>);
};

@ -0,0 +1,29 @@
import React from 'react';
import i18n from '../../i18n'
import {withTranslation} from 'react-i18next';
import { faPaypal } from "@fortawesome/free-brands-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
const Paypal = (props) => {
const {t} = props;
let localeStringShort=i18n.language.substring(0,2);
let localeStringComplete=localeStringShort.toLowerCase()+"_"+localeStringShort.toUpperCase();
if(localeStringComplete==="en_EN"){
localeStringComplete="en_US";
}
const pixelLink="https://www.paypal.com/"+localeStringComplete+"/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 "+props.btnColor} 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 withTranslation()(Paypal);

@ -1,10 +1,7 @@
import React, { Component, Fragment } from "react";
import {withTranslation} from 'react-i18next';
import { Link } from "react-router-dom";
import { FlagIcon } from '../flag'
import i18n from '../../i18n'
import Helloasso from "../banner/Helloasso";
import Paypal from "../banner/Paypal";
class Footer extends Component {
constructor(props) {
@ -17,23 +14,15 @@ class Footer extends Component {
render() {
const buttonStyle = {backgroundColor: "black", padding: "0px", border: "0px",};
const linkStyle = {whiteSpace: "nowrap"};
const {t} = this.props;
const countries = [
{"l": "en", "flag": "gb"},
{"l": "es", "flag": "es"},
{"l": "fr", "flag": "fr"},
{"l": "de", "flag": "de"},
];
return (
<footer className="text-center">
<Link to="/" style={linkStyle}>{t("Homepage")}</Link>
<span className="m-2">-</span>
<a href="https://github.com/MieuxVoter" target="_blank" rel="noopener noreferrer" style={linkStyle}>{t("Source code")}</a>
<span className="m-2">-</span>
<a href="https://mieuxvoter.fr/" target="_blank" rel="noopener noreferrer" style={linkStyle} >{t("Who are we")}</a>
<a href="https://mieuxvoter.fr/" target="_blank" rel="noopener noreferrer" style={linkStyle} >{t("Who are we?")}</a>
<span className="m-2">-</span>
{
countries.map(({l, flag}, i) => (
@ -46,7 +35,7 @@ class Footer extends Component {
))
}
<div className="mt-3">
<Helloasso width="150px" />
<Paypal btnColor="btn-primary"/>
</div>
</footer>
);

@ -7,6 +7,7 @@ import { withTranslation } from 'react-i18next';
import logo from "../../logos/logo-color.svg";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faRocket } from "@fortawesome/free-solid-svg-icons";
import LanguageSelector from "./LanguageSelector";
class Header extends Component {
state = {
@ -44,10 +45,13 @@ class Header extends Component {
<Nav className="ml-auto" navbar>
<NavItem>
<Link className="text-primary nav-link" to="/create-election/">
<FontAwesomeIcon icon={faRocket} className="mr-2" />
{t("Start an election")}
<FontAwesomeIcon icon={faRocket} className="mr-2" />
{t("Start an election")}
</Link>
</NavItem>
<NavItem style={{width:"150px"}}>
<LanguageSelector />
</NavItem>
</Nav>
</Collapse>
</Navbar>

@ -0,0 +1,30 @@
import React from 'react';
import ReactFlagsSelect from 'react-flags-select';
import 'react-flags-select/css/react-flags-select.css';
import i18n from '../../i18n'
const LanguageSelector = () => {
const selectHandler = (e) => {
let locale=e.toLowerCase();
if(locale==="gb")locale="en";
i18n.changeLanguage(locale);
};
let locale=i18n.language.substring(0,2).toUpperCase();
if(locale==="EN")locale="GB";
return (<ReactFlagsSelect onSelect={selectHandler}
countries={["GB", "FR", "ES", "DE"]}
showOptionLabel={false}
defaultCountry={locale}
selectedSize={15}
optionsSize={22}
showSelectedLabel={false}
/>);
};
export default LanguageSelector;

@ -38,6 +38,7 @@ import {AppContext} from '../../AppContext';
import HelpButton from '../form/HelpButton';
import ButtonWithConfirm from '../form/ButtonWithConfirm';
import Loader from '../wait';
import i18n from '../../i18n'
// Error messages
@ -61,90 +62,90 @@ const timeMinusDate = date => time(date);
const dateMinusTime = date => new Date(date.getTime() - time(date));
const DragHandle = sortableHandle(({children}) => (
<span className="input-group-text indexNumber">{children}</span>
<span className="input-group-text indexNumber">{children}</span>
));
const displayClockOptions = () =>
Array(24)
.fill(1)
.map((x, i) => (
<option value={i} key={i}>
{i}h00
</option>
));
Array(24)
.fill(1)
.map((x, i) => (
<option value={i} key={i}>
{i}h00
</option>
));
const SortableCandidate = sortableElement(({candidate, sortIndex, form, t}) => (
<li className="sortable">
<Row key={'rowCandidate' + sortIndex}>
<Col>
<InputGroup>
<InputGroupAddon addonType="prepend">
<DragHandle>
<span>{sortIndex + 1}</span>
</DragHandle>
</InputGroupAddon>
<Input
type="text"
value={candidate.label}
onChange={event => form.editCandidateLabel(event, sortIndex)}
onKeyPress={event =>
form.handleKeypressOnCandidateLabel(event, sortIndex)
}
placeholder={t('Candidate/proposal name...')}
tabIndex={sortIndex + 1}
innerRef={ref => (form.candidateInputs[sortIndex] = ref)}
maxLength="250"
/>
<ButtonWithConfirm className="btn btn-primary input-group-append border-light">
<div key="button">
<FontAwesomeIcon icon={faTrashAlt} />
</div>
<div key="modal-title">{t('Delete?')}</div>
<div key="modal-body">
{t('Are you sure to delete')}{' '}
{candidate.label !== '' ? (
<b>"{candidate.label}"</b>
) : (
<span>
<li className="sortable">
<Row key={'rowCandidate' + sortIndex}>
<Col>
<InputGroup>
<InputGroupAddon addonType="prepend">
<DragHandle>
<span>{sortIndex + 1}</span>
</DragHandle>
</InputGroupAddon>
<Input
type="text"
value={candidate.label}
onChange={event => form.editCandidateLabel(event, sortIndex)}
onKeyPress={event =>
form.handleKeypressOnCandidateLabel(event, sortIndex)
}
placeholder={t('Candidate/proposal name...')}
tabIndex={sortIndex + 1}
innerRef={ref => (form.candidateInputs[sortIndex] = ref)}
maxLength="250"
/>
<ButtonWithConfirm className="btn btn-primary input-group-append border-light">
<div key="button">
<FontAwesomeIcon icon={faTrashAlt} />
</div>
<div key="modal-title">{t('Delete?')}</div>
<div key="modal-body">
{t('Are you sure to delete')}{' '}
{candidate.label !== '' ? (
<b>"{candidate.label}"</b>
) : (
<span>
{t('the row')} {sortIndex + 1}
</span>
)}{' '}
?
</div>
<div
key="modal-confirm"
onClick={() => form.removeCandidate(sortIndex)}>
Oui
</div>
<div key="modal-cancel">Non</div>
</ButtonWithConfirm>
</InputGroup>
</Col>
<Col xs="auto" className="align-self-center pl-0">
<HelpButton>
{t(
'Write here your question or introduce simple your election (250 characters max.)',
)}
</HelpButton>
</Col>
</Row>
</li>
)}{' '}
?
</div>
<div
key="modal-confirm"
onClick={() => form.removeCandidate(sortIndex)}>
Oui
</div>
<div key="modal-cancel">Non</div>
</ButtonWithConfirm>
</InputGroup>
</Col>
<Col xs="auto" className="align-self-center pl-0">
<HelpButton>
{t(
'Write here your question or introduce simple your election (250 characters max.)',
)}
</HelpButton>
</Col>
</Row>
</li>
));
const SortableCandidatesContainer = sortableContainer(({items, form, t}) => {
return (
<ul className="sortable">
{items.map((candidate, index) => (
<SortableCandidate
key={`item-${index}`}
index={index}
sortIndex={index}
candidate={candidate}
form={form}
t={t}
/>
))}
</ul>
<ul className="sortable">
{items.map((candidate, index) => (
<SortableCandidate
key={`item-${index}`}
index={index}
sortIndex={index}
candidate={candidate}
form={form}
t={t}
/>
))}
</ul>
);
});
@ -155,7 +156,7 @@ class CreateElection extends Component {
// default value : start at the last hour
const now = new Date();
const start = new Date(
now.getTime() - minutes(now) - seconds(now) - ms(now),
now.getTime() - minutes(now) - seconds(now) - ms(now),
);
const {title} = queryString.parse(this.props.location.search);
@ -277,11 +278,12 @@ class CreateElection extends Component {
} = this.state;
const endpoint = resolve(
this.context.urlServer,
this.context.routesServer.setElection,
this.context.urlServer,
this.context.routesServer.setElection,
);
const {t} = this.props;
const locale=i18n.language.substring(0,2).toLowerCase()==="fr"?"fr":"en";
const check = this.checkFields();
if (!check.ok) {
@ -306,6 +308,8 @@ class CreateElection extends Component {
elector_emails: electorEmails,
start_at: start.getTime() / 1000,
finish_at: finish.getTime() / 1000,
select_language: locale,
front_url : window.location.origin
}),
})
.then(response => response.json())
@ -359,235 +363,235 @@ class CreateElection extends Component {
if (successCreate) return <Redirect to={redirectTo} />;
return (
<Container>
<ToastContainer />
{waiting ? <Loader /> : ''}
<form onSubmit={this.handleSubmit} autoComplete="off">
<Row>
<Col>
<h3>{t('Start an election')}</h3>
</Col>
</Row>
<hr />
<Row className="mt-4">
<Col xs="12">
<Label for="title">{t('Question of the election')}</Label>
</Col>
<Col>
<Input
placeholder={t('Write here the question of your election')}
tabIndex="1"
name="title"
id="title"
innerRef={this.focusInput}
autoFocus
value={title}
onChange={this.handleChangeTitle}
maxLength="250"
/>
</Col>
<Col xs="auto" className="align-self-center pl-0">
<HelpButton>
{t(
'Write here your question or introduce simple your election (250 characters max.)',
)}
<br />
<u>{t('For example:')}</u>{' '}
<em>
<Container>
<ToastContainer />
{waiting ? <Loader /> : ''}
<form onSubmit={this.handleSubmit} autoComplete="off">
<Row>
<Col>
<h3>{t('Start an election')}</h3>
</Col>
</Row>
<hr />
<Row className="mt-4">
<Col xs="12">
<Label for="title">{t('Question of the election')}</Label>
</Col>
<Col>
<Input
placeholder={t('Write here the question of your election')}
tabIndex="1"
name="title"
id="title"
innerRef={this.focusInput}
autoFocus
value={title}
onChange={this.handleChangeTitle}
maxLength="250"
/>
</Col>
<Col xs="auto" className="align-self-center pl-0">
<HelpButton>
{t(
'For the role of my representative, I judge this candidate...',
'Write here your question or introduce simple your election (250 characters max.)',
)}
</em>
</HelpButton>
</Col>
</Row>
<Row className="mt-4">
<Col xs="12">
<Label for="title">{t('Candidates/Proposals')}</Label>
</Col>
<Col xs="12">
<SortableCandidatesContainer
items={candidates}
onSortEnd={this.onCandidatesSortEnd}
form={this}
t={t}
useDragHandle
/>
</Col>
</Row>
<Row className="justify-content-between">
<Col xs="12" sm="6" md="5" lg="4">
<Button
color="secondary"
className="btn-block mt-2"
tabIndex={candidates.length + 2}
type="button"
onClick={event => this.addCandidate(event)}>
<FontAwesomeIcon icon={faPlus} className="mr-2" />
{t('Add a proposal')}
</Button>
</Col>
<Col
xs="12"
sm="6"
md="12"
className="text-center text-sm-right text-md-left">
<Button
color="link"
className="text-white mt-3 mb-1"
onClick={this.toggleAdvancedOptions}>
<FontAwesomeIcon icon={faCogs} className="mr-2" />
{t('Advanced options')}
</Button>
</Col>
</Row>
<Collapse isOpen={isAdvancedOptionsOpen}>
<Card>
<CardBody className="text-primary">
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Starting date:')}</Label>
</Col>
<Col xs="6" md="4" lg="3">
<input
className="form-control"
type="date"
value={dateToISO(start)}
onChange={e => {
this.setState({
start: new Date(
timeMinusDate(start) +
new Date(e.target.valueAsNumber).getTime(),
),
});
}}
/>
</Col>
<Col xs="6" md="5" lg="3">
<select
className="form-control"
value={start.getHours()}
onChange={e =>
this.setState({
start: new Date(
dateMinusTime(start).getTime() +
e.target.value * 3600000,
),
})
}>
{displayClockOptions()}
</select>
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Ending date:')}</Label>
</Col>
<Col xs="6" md="4" lg="3">
<input
className="form-control"
type="date"
value={dateToISO(finish)}
min={dateToISO(start)}
onChange={e => {
this.setState({
finish: new Date(
timeMinusDate(finish) +
new Date(e.target.valueAsNumber).getTime(),
),
});
}}
/>
</Col>
<Col xs="6" md="5" lg="3">
<select
className="form-control"
value={finish.getHours()}
onChange={e =>
this.setState({
finish: new Date(
dateMinusTime(finish).getTime() +
e.target.value * 3600000,
),
})
}>
{displayClockOptions()}
</select>
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Grades:')}</Label>
</Col>
<Col xs="10" sm="11" md="4" lg="3">
<select
className="form-control"
tabIndex={candidates.length + 3}
onChange={this.handleChangeNumGrades}
defaultValue="7">
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</Col>
<Col xs="auto" className="align-self-center pl-0 ">
<HelpButton>
{t(
'You can select here the number of grades for your election',
)}
<br />
<u>{t('For example:')}</u>{' '}
<em>
{' '}
{t('5 = Excellent, Very good, Good, Fair, Passable')}
</em>
</HelpButton>
</Col>
<Col
xs="12"
md="9"
lg="10"
className="offset-xs-0 offset-md-3 offset-lg-2">
{grades.map((mention, i) => {
return (
<span
key={i}
className="badge badge-light mr-2 mt-2 "
style={{
backgroundColor: mention.color,
color: '#fff',
opacity: i < numGrades ? 1 : 0.3,
}}>
{mention.label}
</span>
);
})}
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Participants:')}</Label>
</Col>
<Col xs="12" md="9" lg="10">
<ReactMultiEmail
placeholder={t("Add here participants' emails")}
emails={electorEmails}
onChange={_emails => {
this.setState({electorEmails: _emails});
}}
validateEmail={email => {
return isEmail(email); // return boolean
}}
getLabel={(email, index, removeEmail) => {
<br />
<u>{t('For example:')}</u>{' '}
<em>
{t(
'For the role of my representative, I judge this candidate...',
)}
</em>
</HelpButton>
</Col>
</Row>
<Row className="mt-4">
<Col xs="12">
<Label for="title">{t('Candidates/Proposals')}</Label>
</Col>
<Col xs="12">
<SortableCandidatesContainer
items={candidates}
onSortEnd={this.onCandidatesSortEnd}
form={this}
t={t}
useDragHandle
/>
</Col>
</Row>
<Row className="justify-content-between">
<Col xs="12" sm="6" md="5" lg="4">
<Button
color="secondary"
className="btn-block mt-2"
tabIndex={candidates.length + 2}
type="button"
onClick={event => this.addCandidate(event)}>
<FontAwesomeIcon icon={faPlus} className="mr-2" />
{t('Add a proposal')}
</Button>
</Col>
<Col
xs="12"
sm="6"
md="12"
className="text-center text-sm-right text-md-left">
<Button
color="link"
className="text-white mt-3 mb-1"
onClick={this.toggleAdvancedOptions}>
<FontAwesomeIcon icon={faCogs} className="mr-2" />
{t('Advanced options')}
</Button>
</Col>
</Row>
<Collapse isOpen={isAdvancedOptionsOpen}>
<Card>
<CardBody className="text-primary">
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Starting date:')}</Label>
</Col>
<Col xs="6" md="4" lg="3">
<input
className="form-control"
type="date"
value={dateToISO(start)}
onChange={e => {
this.setState({
start: new Date(
timeMinusDate(start) +
new Date(e.target.valueAsNumber).getTime(),
),
});
}}
/>
</Col>
<Col xs="6" md="5" lg="3">
<select
className="form-control"
value={start.getHours()}
onChange={e =>
this.setState({
start: new Date(
dateMinusTime(start).getTime() +
e.target.value * 3600000,
),
})
}>
{displayClockOptions()}
</select>
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Ending date:')}</Label>
</Col>
<Col xs="6" md="4" lg="3">
<input
className="form-control"
type="date"
value={dateToISO(finish)}
min={dateToISO(start)}
onChange={e => {
this.setState({
finish: new Date(
timeMinusDate(finish) +
new Date(e.target.valueAsNumber).getTime(),
),
});
}}
/>
</Col>
<Col xs="6" md="5" lg="3">
<select
className="form-control"
value={finish.getHours()}
onChange={e =>
this.setState({
finish: new Date(
dateMinusTime(finish).getTime() +
e.target.value * 3600000,
),
})
}>
{displayClockOptions()}
</select>
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Grades:')}</Label>
</Col>
<Col xs="10" sm="11" md="4" lg="3">
<select
className="form-control"
tabIndex={candidates.length + 3}
onChange={this.handleChangeNumGrades}
defaultValue="7">
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
</select>
</Col>
<Col xs="auto" className="align-self-center pl-0 ">
<HelpButton>
{t(
'You can select here the number of grades for your election',
)}
<br />
<u>{t('For example:')}</u>{' '}
<em>
{' '}
{t('5 = Excellent, Very good, Good, Fair, Passable')}
</em>
</HelpButton>
</Col>
<Col
xs="12"
md="9"
lg="10"
className="offset-xs-0 offset-md-3 offset-lg-2">
{grades.map((mention, i) => {
return (
<div data-tag key={index}>
{email}
<span
data-tag-handle
onClick={() => removeEmail(index)}>
key={i}
className="badge badge-light mr-2 mt-2 "
style={{
backgroundColor: mention.color,
color: '#fff',
opacity: i < numGrades ? 1 : 0.3,
}}>
{mention.label}
</span>
);
})}
</Col>
</Row>
<hr className="mt-2 mb-2" />
<Row>
<Col xs="12" md="3" lg="2">
<Label for="title">{t('Participants:')}</Label>
</Col>
<Col xs="12" md="9" lg="10">
<ReactMultiEmail
placeholder={t("Add here participants' emails")}
emails={electorEmails}
onChange={_emails => {
this.setState({electorEmails: _emails});
}}
validateEmail={email => {
return isEmail(email); // return boolean
}}
getLabel={(email, index, removeEmail) => {
return (
<div data-tag key={index}>
{email}
<span
data-tag-handle
onClick={() => removeEmail(index)}>
×
</span>
</div>
@ -674,49 +678,49 @@ class CreateElection extends Component {
}}>
{mention.label}
</span>
) : (
<span key={i} />
);
})}
</div>
<div className="text-white bg-primary p-1 mt-1">
{t("Voters' list")}
) : (
<span key={i} />
);
})}
</div>
<div className="text-white bg-primary p-1 mt-1">
{t("Voters' list")}
</div>
<div className="p-1 pl-3">
{electorEmails.length > 0 ? (
electorEmails.join(', ')
) : (
<p>
{t('The form contains no address.')}
<br />
<em>
{t(
'The election will be opened to anyone with the link',
)}
</em>
</p>
)}
</div>
</div>
</div>
<div className="p-1 pl-3">
{electorEmails.length > 0 ? (
electorEmails.join(', ')
) : (
<p>
{t('The form contains no address.')}
<br />
<em>
{t(
'The election will be opened to anyone with the link',
)}
</em>
</p>
)}
<div key="modal-confirm" onClick={this.handleSubmit}>
{t('Start the election')}
</div>
</div>
</div>
<div key="modal-confirm" onClick={this.handleSubmit}>
{t('Start the election')}
</div>
<div key="modal-cancel">{t('Cancel')}</div>
</ButtonWithConfirm>
) : (
<Button
type="button"
className="btn btn-dark float-right btn-block"
onClick={() => this.handleSendNotReady(check.msg)}>
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t('Confirm')}
</Button>
)}
</Col>
</Row>
</form>
</Container>
<div key="modal-cancel">{t('Cancel')}</div>
</ButtonWithConfirm>
) : (
<Button
type="button"
className="btn btn-dark float-right btn-block"
onClick={this.handleSendWithoutCandidate}>
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t('Confirm')}
</Button>
)}
</Col>
</Row>
</form>
</Container>
);
}
}

@ -2,7 +2,7 @@ import React, {Component} from 'react';
import {Button, Col, Container, Row} from 'reactstrap';
import {Link} from 'react-router-dom';
import {withTranslation, Trans} from 'react-i18next';
import {faCopy, faUsers} from '@fortawesome/free-solid-svg-icons';
import {faCopy, faUsers, faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import logoLine from '../../logos/logo-line-white.svg';
import {AppContext} from '../../AppContext';
@ -18,8 +18,10 @@ class CreateSuccess extends Component {
console.log(props);
const electionSlug = this.props.match.params.slug;
this.state = {
urlOfVote: `https://${window.location.hostname}/vote/${electionSlug}`,
urlOfResult: `https://${window.location.hostname}/result/${electionSlug}`,
urlOfVote:
window.location.origin + '/vote/' + electionSlug,
urlOfResult:
window.location.origin + '/result/' + electionSlug,
};
this.urlVoteField = React.createRef();
this.urlResultField = React.createRef();
@ -78,10 +80,12 @@ class CreateSuccess extends Component {
</Col>
</Row>
<Row className="mt-4 mb-4">
<Col>
<Col className="text-center offset-lg-3" lg="6">
<div className=" bg-danger text-white p-2 ">
<h4 className="m-0 p-0 text-center">
{t('Keep these links carefully')}
<FontAwesomeIcon icon={faExclamationTriangle} className="mr-2" />
{t('Keep these links carefully')}
</h4>
<p className="small m-2 p-0">
<Trans i18nKey="t">

@ -66,7 +66,6 @@ class Result extends Component {
profile: c.profile,
grade: c.grade,
}));
console.log(response);
this.setState(state => ({candidates: candidates}));
return response;
};

@ -5,6 +5,7 @@ import logoLine from "../../logos/logo-line-white.svg";
import { Link } from "react-router-dom";
import { AppContext } from "../../AppContext";
import Helloasso from "../banner/Helloasso";
import Paypal from "../banner/Paypal";
class VoteSuccess extends Component {
static contextType = AppContext;
@ -22,7 +23,7 @@ class VoteSuccess extends Component {
<h2>{t("Your participation was recorded with success!")}</h2>
<p>{t("Thanks for your participation.")}</p>
<div className="mt-3">
<Helloasso width="60%" className="m-auto d-block" />
<Paypal btnColor="btn-success"/>
</div>
</Col>
</Row>

@ -1,35 +1,35 @@
import React from 'react';
import React from "react";
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 redirectError = (errorMsg, history) => {};
export const errorMessage = (error, t) => {
if (error.startsWith(UNKNOWN_ELECTION_ERROR)) {
return t('Oops... The election is unknown.');
return t("Oops... The election is unknown.");
} else if (error.startsWith(ONGOING_ELECTION_ERROR)) {
return t(
"The election is still going on. You can't access now to the results.",
"The election is still going on. You can't access now to the results."
);
} else if (error.startsWith(NO_VOTE_ERROR)) {
return t('No votes have been recorded yet. Come back later.');
return t("No votes have been recorded yet. Come back later.");
} else if (error.startsWith(ELECTION_NOT_STARTED_ERROR)) {
return t('The election has not started yet.');
return t("The election has not started yet.");
} else if (error.startsWith(ELECTION_FINISHED_ERROR)) {
return t('The election is over. You can\'t vote anymore');
return t("The election is over. You can't vote anymore");
} else if (error.startsWith(INVITATION_ONLY_ERROR)) {
return t('You need a token to vote in this election');
return t("You need a token to vote in this election");
} else if (error.startsWith(USED_TOKEN_ERROR)) {
return t('You seem to have already voted.');
return t("You seem to have already voted.");
} else if (error.startsWith(WRONG_ELECTION_ERROR)) {
return t('The parameters of the election are incorrect.');
return t("The parameters of the election are incorrect.");
}
};

@ -47,10 +47,10 @@ main {
background-attachment: fixed;
background-repeat: no-repeat;
background-color: $mv-blue-color;
min-height: calc(100% - 106px);
min-height: calc(100% - 170px);
overflow: auto;
padding-top: 72px;
padding-bottom: 36px;
padding-bottom: 100px;
}
header {
@ -310,3 +310,23 @@ li.sortable {
.react-multi-email > span[data-placeholder] {
padding: 0.25em !important;
}
/** flag selector **/
.flag-select > button {
height:35px;
}
.flag-select__options{
width:65px;
text-align:center;
background-color: $mv-light-color !important;
}
.flag-select__options .flag-select__option{
padding:0;
margin:0;
}
.flag-select__options .flag-select__option__icon{
top:0;
}

@ -993,6 +993,13 @@
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.28"
"@fortawesome/free-brands-svg-icons@^5.13.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.13.0.tgz#e79de73ba6555055204828dca9c0691e7ce5242b"
integrity sha512-/6xXiJFCMEQxqxXbL0FPJpwq5Cv6MRrjsbJEmH/t5vOvB4dILDpnY0f7zZSlA8+TG7jwlt12miF/yZpZkykucA==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.28"
"@fortawesome/free-solid-svg-icons@^5.9.0":
version "5.13.0"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz#44d9118668ad96b4fd5c9434a43efc5903525739"
@ -8921,6 +8928,13 @@ react-flag-icon-css@^1.0.25:
flag-icon-css "^3.2.1"
prop-types "^15.6.2"
react-flags-select@^1.1.12:
version "1.1.12"
resolved "https://registry.yarnpkg.com/react-flags-select/-/react-flags-select-1.1.12.tgz#428b34f928eab875e8e0296046410d30d75cb818"
integrity sha512-icWC8pHdfTQTIqpLTgPcd4tFlfgUdiwzUJaSkPMzP6BEMRrh8yAJC55IkgmJV8PIX8qTUrEETZ6+6115hGjR7g==
dependencies:
prop-types "^15.5.8"
react-i18next@^11.3.4:
version "11.3.4"
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.3.4.tgz#355df5fe5133e5e30302d166f529678100ffc968"

Loading…
Cancel
Save