diff --git a/src/App.jsx b/src/App.jsx index 6fc57e2..67f94a3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,7 +1,4 @@ import React, {Suspense} from 'react'; -import {BrowserRouter as Router} from 'react-router-dom'; - -import Loader from './components/loader'; import Routes from './Routes'; import Header from './components/layouts/Header'; @@ -10,17 +7,13 @@ import AppContextProvider from './AppContext'; function App() { return ( - } > -
-
-
); } diff --git a/src/AppContext.jsx b/src/AppContext.jsx index 4235e59..37c11ba 100644 --- a/src/AppContext.jsx +++ b/src/AppContext.jsx @@ -1,4 +1,7 @@ -import React, { createContext } from "react"; +import React, { createContext, Suspense } from "react"; +import {BrowserRouter as Router} from 'react-router-dom'; +import Loader from './components/loader'; + export const AppContext = createContext(); @@ -13,7 +16,13 @@ const AppContextProvider = ({ children }) => { } }; return ( - {children} + } > + + + {children} + + + ); }; export default AppContextProvider; diff --git a/src/Routes.jsx b/src/Routes.jsx index 7a012c3..60ca18e 100644 --- a/src/Routes.jsx +++ b/src/Routes.jsx @@ -19,9 +19,8 @@ function Routes() { - - - + } /> + } /> diff --git a/src/components/CopyField.jsx b/src/components/CopyField.jsx new file mode 100644 index 0000000..9eabab2 --- /dev/null +++ b/src/components/CopyField.jsx @@ -0,0 +1,45 @@ +import React, {Component} from 'react'; +import {Button} from 'reactstrap'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; + + +const CopyField = props => { + const ref = React.createRef(); + const handleClickOnField = event => { + event.target.focus(); + event.target.select(); + }; + const handleClickOnButton = event => { + const input = ref.current; + input.focus(); + input.select(); + document.execCommand('copy'); + }; + + const {t, value, icon} = props; + + return ( +
+ + +
+ +
+
+ ); +}; + +export default CopyField; diff --git a/src/components/views/CreateElection.jsx b/src/components/views/CreateElection.jsx index b5047a8..7635a8d 100644 --- a/src/components/views/CreateElection.jsx +++ b/src/components/views/CreateElection.jsx @@ -267,7 +267,7 @@ class CreateElection extends Component { }, body: JSON.stringify({ title: title, - candidates: candidates.map(c => c.label), + candidates: candidates.map(c => c.label).filter( c => c !== ""), on_invitation_only: electorEmails.length > 0, num_grades: numGrades, elector_emails: electorEmails, @@ -279,13 +279,16 @@ class CreateElection extends Component { .then(result => { console.log(result); if (result.id) { + const nextPage = + electorEmails && electorEmails.length + ? `/link/${result.id}` + : `/links/${result.id}`; this.setState(state => ({ - redirectTo: '/create-success/' + result.id, + redirectTo: nextPage, successCreate: true, - waiting: false - })) - } - else { + waiting: false, + })); + } else { toast.error(t('Unknown error. Try again please.'), { position: toast.POSITION.TOP_CENTER, }); @@ -314,7 +317,7 @@ class CreateElection extends Component { numGrades, isAdvancedOptionsOpen, numCandidatesWithLabel, - electorEmails + electorEmails, } = this.state; const {t} = this.props; diff --git a/src/components/views/CreateSuccess.jsx b/src/components/views/CreateSuccess.jsx index d48d458..9163660 100644 --- a/src/components/views/CreateSuccess.jsx +++ b/src/components/views/CreateSuccess.jsx @@ -6,34 +6,25 @@ import {faCopy, faUsers} from '@fortawesome/free-solid-svg-icons'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import logoLine from '../../logos/logo-line-white.svg'; import {AppContext} from '../../AppContext'; +import CopyField from '../CopyField'; + + + class CreateSuccess extends Component { static contextType = AppContext; constructor(props) { super(props); + 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: `https://${window.location.hostname}/vote/${electionSlug}`, + urlOfResult: `https://${window.location.hostname}/result/${electionSlug}`, }; this.urlVoteField = React.createRef(); this.urlResultField = React.createRef(); } - handleClickOnField = event => { - event.target.focus(); - event.target.select(); - }; - - handleClickOnCopyVote = event => { - const input = this.urlVoteField.current; - input.focus(); - input.select(); - document.execCommand('copy'); - }; - handleClickOnCopyResult = event => { const input = this.urlResultField.current; input.focus(); @@ -43,6 +34,26 @@ class CreateSuccess extends Component { render() { const {t} = this.props; + console.log(this.props) + const electionLink = this.props.invitationOnly ? ( + <> +

+ {t('Voters received a link to vote by email. Each link can be used only once!')} +

+ + ) : ( + <> +

+ {t('You can now share the election link to participants:')} +

+ + + ); + return ( @@ -53,54 +64,17 @@ class CreateSuccess extends Component {

{t('Successful election creation!')}

-

- {t('You can now share the election link to participants:')} -

-
- - -
- -
-
+ {electionLink}

{t('Here is the link for the results in real time:')}

-
- - -
- -
-
+
@@ -126,7 +100,7 @@ class CreateSuccess extends Component { to={'/vote/' + this.props.match.params.slug} className="btn btn-success"> - {t("Participate now!")} + {t('Participate now!')} diff --git a/src/components/views/Vote.jsx b/src/components/views/Vote.jsx index a1c5d30..6ceca6a 100644 --- a/src/components/views/Vote.jsx +++ b/src/components/views/Vote.jsx @@ -1,19 +1,17 @@ -import React, { Component } from "react"; -import { Redirect } from "react-router-dom"; -import { withTranslation } from 'react-i18next'; -import { Button, Col, Container, Row } from "reactstrap"; -import { toast, ToastContainer } from "react-toastify"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faCheck } from "@fortawesome/free-solid-svg-icons"; -import { resolve } from "url"; -import { i18nGrades } from "../../Util"; -import { AppContext } from "../../AppContext"; -import { errorMessage } from "../../Errors"; - +import React, {Component} from 'react'; +import {Redirect} from 'react-router-dom'; +import {withTranslation} from 'react-i18next'; +import {Button, Col, Container, Row} from 'reactstrap'; +import {toast, ToastContainer} from 'react-toastify'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faCheck} from '@fortawesome/free-solid-svg-icons'; +import {resolve} from 'url'; +import {i18nGrades} from '../../Util'; +import {AppContext} from '../../AppContext'; +import {errorMessage} from '../../Errors'; const shuffle = array => array.sort(() => Math.random() - 0.5); - class Vote extends Component { static contextType = AppContext; constructor(props) { @@ -30,7 +28,7 @@ class Vote extends Component { colSizeGradeMd: 1, colSizeGradeXs: 1, redirectTo: null, - electionGrades: i18nGrades() + electionGrades: i18nGrades(), }; } @@ -53,18 +51,18 @@ class Vote extends Component { const numGrades = response.num_grades; const candidates = response.candidates.map((c, i) => ({ id: i, - label: c + label: c, })); shuffle(candidates); const colSizeGradeLg = Math.floor( - (12 - this.state.colSizeCandidateLg) / numGrades + (12 - this.state.colSizeCandidateLg) / numGrades, ); const colSizeGradeMd = Math.floor( - (12 - this.state.colSizeCandidateMd) / numGrades + (12 - this.state.colSizeCandidateMd) / numGrades, ); const colSizeGradeXs = Math.floor( - (12 - this.state.colSizeCandidateXs) / numGrades + (12 - this.state.colSizeCandidateXs) / numGrades, ); this.setState(state => ({ @@ -86,7 +84,7 @@ class Vote extends Component { 12 - colSizeGradeXs * numGrades > 0 ? 12 - colSizeGradeXs * numGrades : 12, - electionGrades: i18nGrades().slice(0, numGrades) + electionGrades: i18nGrades().slice(0, numGrades), })); return response; }; @@ -97,9 +95,9 @@ class Vote extends Component { const detailsEndpoint = resolve( this.context.urlServer, this.context.routesServer.getElection.replace( - new RegExp(":slug", "g"), - electionSlug - ) + new RegExp(':slug', 'g'), + electionSlug, + ), ); fetch(detailsEndpoint) .then(this.handleErrors) @@ -110,33 +108,33 @@ class Vote extends Component { handleGradeClick = event => { let data = { - id: parseInt(event.currentTarget.getAttribute("data-id")), - value: parseInt(event.currentTarget.value) + id: parseInt(event.currentTarget.getAttribute('data-id')), + value: parseInt(event.currentTarget.value), }; //remove candidate let ratedCandidates = this.state.ratedCandidates.filter( - ratedCandidate => ratedCandidate.id !== data.id + ratedCandidate => ratedCandidate.id !== data.id, ); ratedCandidates.push(data); - this.setState({ ratedCandidates: ratedCandidates }); + this.setState({ratedCandidates}); }; handleSubmitWithoutAllRate = () => { const {t} = this.props; - toast.error(t("You have to judge every candidate/proposal!"), { - position: toast.POSITION.TOP_CENTER + toast.error(t('You have to judge every candidate/proposal!'), { + position: toast.POSITION.TOP_CENTER, }); }; handleSubmit = event => { event.preventDefault(); - const { ratedCandidates } = this.state; + const {ratedCandidates} = this.state; const electionSlug = this.props.match.params.slug; const token = this.props.location.search.substr(7); const endpoint = resolve( this.context.urlServer, - this.context.routesServer.voteElection + this.context.routesServer.voteElection, ); const gradesById = {}; @@ -144,34 +142,33 @@ class Vote extends Component { gradesById[c.id] = c.value; }); const gradesByCandidate = []; - Object.keys(gradesById) - .forEach(id => { - gradesByCandidate.push(gradesById[id]); - }); + Object.keys(gradesById).forEach(id => { + gradesByCandidate.push(gradesById[id]); + }); const payload = { - election: electionSlug, - grades_by_candidate: gradesByCandidate, - } - if (token !== ""){ - payload["token"] = token; + election: electionSlug, + grades_by_candidate: gradesByCandidate, + }; + if (token !== '') { + payload['token'] = token; } fetch(endpoint, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify(payload), }) .then(this.handleErrors) .then(result => - this.setState({ redirectTo: "/vote-success/" + electionSlug }) + this.setState({redirectTo: '/vote-success/' + electionSlug}), ) .catch(error => error); }; render() { const {t} = this.props; - const { redirectTo, candidates, electionGrades } = this.state; + const {redirectTo, candidates, electionGrades} = this.state; if (redirectTo) { return ; @@ -190,8 +187,7 @@ class Vote extends Component { + lg={this.state.colSizeCandidateLg}>
 
{electionGrades.map((grade, j) => { @@ -202,12 +198,10 @@ class Vote extends Component { lg={this.state.colSizeGradeLg} key={j} className="text-center p-0" - style={{ lineHeight: 2 }} - > + style={{lineHeight: 2}}> + style={{backgroundColor: grade.color, color: '#fff'}}> {grade.label} @@ -221,8 +215,7 @@ class Vote extends Component { + lg={this.state.colSizeCandidateLg}>
{candidate.label}

@@ -233,36 +226,33 @@ class Vote extends Component { md={this.state.colSizeGradeMd} lg={this.state.colSizeGradeLg} key={j} - className="text-lg-center" - > + className="text-lg-center">