diff --git a/src/components/banner/Gform.jsx b/src/components/banner/Gform.jsx index 51adf15..34454e9 100644 --- a/src/components/banner/Gform.jsx +++ b/src/components/banner/Gform.jsx @@ -1,9 +1,10 @@ import React, { useContext } from "react"; import PropTypes from 'prop-types'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faGoogleDrive } from "@fortawesome/free-brands-svg-icons"; +import {faCommentAlt} from "@fortawesome/free-solid-svg-icons"; import { AppContext } from "../../AppContext" + const Gform = (props) => { const context = useContext(AppContext); console.log(context); @@ -15,7 +16,7 @@ const Gform = (props) => { target="_blank" rel="noopener noreferrer" > - + Votre avis nous intéresse ! ); diff --git a/src/components/views/CreateElection.jsx b/src/components/views/CreateElection.jsx deleted file mode 100644 index 6e8e032..0000000 --- a/src/components/views/CreateElection.jsx +++ /dev/null @@ -1,896 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React, { Component } from "react"; -import { Redirect, withRouter } from "react-router-dom"; -import { - Collapse, - Container, - Row, - Col, - Input, - Label, - InputGroup, - InputGroupAddon, - Button, - Card, - CardBody -} from "reactstrap"; -import { withTranslation } from "react-i18next"; -import { ReactMultiEmail, isEmail } from "react-multi-email"; -import "react-multi-email/style.css"; -import { toast, ToastContainer } from "react-toastify"; -import "react-toastify/dist/ReactToastify.css"; -import { resolve } from "url"; -import queryString from "query-string"; -import { - arrayMove, - sortableContainer, - sortableElement, - sortableHandle -} from "react-sortable-hoc"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faPlus, - faTrashAlt, - faCheck, - faCogs, - faExclamationTriangle -} from "@fortawesome/free-solid-svg-icons"; -import { i18nGrades } from "../../Util"; -import { AppContext } from "../../AppContext"; -import HelpButton from "../form/HelpButton"; -import ButtonWithConfirm from "../form/ButtonWithConfirm"; -import Loader from "../wait"; -import i18n from "../../i18n"; - -// Error messages -const AT_LEAST_2_CANDIDATES_ERROR = "Please add at least 2 candidates."; -const NO_TITLE_ERROR = "Please add a title."; - -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); - -// Retrieve the current hour, minute, sec, ms, time into a timestamp -const hours = date => getOnlyValidDate(date).getHours() * 3600 * 1000; -const minutes = date => getOnlyValidDate(date).getMinutes() * 60 * 1000; -const seconds = date => getOnlyValidDate(date).getSeconds() * 1000; -const ms = date => getOnlyValidDate(date).getMilliseconds(); -const time = date => - hours(getOnlyValidDate(date)) + - minutes(getOnlyValidDate(date)) + - seconds(getOnlyValidDate(date)) + - ms(getOnlyValidDate(date)); - -// Retrieve the time part from a timestamp and remove the day. Return a int. -const timeMinusDate = date => time(getOnlyValidDate(date)); - -// Retrieve the day and remove the time. Return a Date -const dateMinusTime = date => - new Date(getOnlyValidDate(date).getTime() - time(getOnlyValidDate(date))); - -const DragHandle = sortableHandle(({ children }) => ( - {children} -)); - -const displayClockOptions = () => - Array(24) - .fill(1) - .map((x, i) => ( - - )); - -const SortableCandidate = sortableElement( - ({ candidate, sortIndex, form, t }) => ( -
  • - - - - - - {sortIndex + 1} - - - 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" - /> - -
    - -
    -
    {t("Delete?")}
    -
    - {t("Are you sure to delete")}{" "} - {candidate.label !== "" ? ( - "{candidate.label}" - ) : ( - - {t("the row")} {sortIndex + 1} - - )}{" "} - ? -
    -
    form.removeCandidate(sortIndex)} - > - Oui -
    -
    Non
    -
    -
    - - - - {t( - "Enter the name of your candidate or proposal here (250 characters max.)" - )} - - -
    -
  • - ) -); - -const SortableCandidatesContainer = sortableContainer(({ items, form, t }) => { - return ( - - ); -}); - -class CreateElection extends Component { - static contextType = AppContext; - constructor(props) { - super(props); - // default value : start at the last hour - const now = new Date(); - const start = new Date( - now.getTime() - minutes(now) - seconds(now) - ms(now) - ); - const { title } = queryString.parse(this.props.location.search); - - this.state = { - candidates: [{ label: "" }, { label: "" }], - title: title || "", - isVisibleTipsDragAndDropCandidate: true, - numGrades: 7, - waiting: false, - successCreate: false, - redirectTo: null, - isAdvancedOptionsOpen: false, - restrictResult: false, - isTimeLimited: false, - start, - // by default, the election ends in a week - finish: new Date(start.getTime() + 7 * 24 * 3600 * 1000), - electorEmails: [] - }; - this.candidateInputs = []; - this.focusInput = React.createRef(); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleRestrictResultCheck = this.handleRestrictResultCheck.bind(this); - this.handleIsTimeLimited = this.handleIsTimeLimited.bind(this); - } - - handleChangeTitle = event => { - this.setState({ title: event.target.value }); - }; - - handleIsTimeLimited = event => { - this.setState({ isTimeLimited: event.target.value === "1" }); - }; - - handleRestrictResultCheck = event => { - this.setState({ restrictResult: event.target.value === "1" }); - }; - - addCandidate = event => { - let candidates = this.state.candidates; - if (candidates.length < 100) { - candidates.push({ label: "" }); - this.setState({ candidates: candidates }); - } - if (event.type === "keypress") { - setTimeout(() => { - this.candidateInputs[this.state.candidates.length - 1].focus(); - }, 250); - } - }; - - removeCandidate = index => { - let candidates = this.state.candidates; - candidates.splice(index, 1); - if (candidates.length === 0) { - candidates = [{ label: "" }]; - } - this.setState({ candidates: candidates }); - }; - - editCandidateLabel = (event, index) => { - let candidates = this.state.candidates; - candidates[index].label = event.currentTarget.value; - candidates.map(candidate => { - return candidate.label; - }); - this.setState({ - candidates: candidates - }); - }; - - handleKeypressOnCandidateLabel = (event, index) => { - if (event.key === "Enter") { - event.preventDefault(); - if (index + 1 === this.state.candidates.length) { - this.addCandidate(event); - } else { - this.candidateInputs[index + 1].focus(); - } - } - }; - - onCandidatesSortEnd = ({ oldIndex, newIndex }) => { - let candidates = this.state.candidates; - candidates = arrayMove(candidates, oldIndex, newIndex); - this.setState({ candidates: candidates }); - }; - - handleChangeNumGrades = event => { - this.setState({ numGrades: event.target.value }); - }; - - toggleAdvancedOptions = () => { - this.setState({ isAdvancedOptionsOpen: !this.state.isAdvancedOptionsOpen }); - }; - - checkFields() { - const { candidates, title } = this.state; - if (!candidates) { - return { ok: false, msg: AT_LEAST_2_CANDIDATES_ERROR }; - } - - let numCandidates = 0; - candidates.forEach(c => { - if (c.label !== "") numCandidates += 1; - }); - if (numCandidates < 2) { - return { ok: false, msg: AT_LEAST_2_CANDIDATES_ERROR }; - } - - if (!title || title === "") { - return { ok: false, msg: NO_TITLE_ERROR }; - } - - return { ok: true, msg: "OK" }; - } - - handleSubmit() { - const { - candidates, - title, - numGrades, - electorEmails - } = this.state; - - let { - start, - finish, - } = this.state; - - const endpoint = resolve( - this.context.urlServer, - this.context.routesServer.setElection - ); - - if(!this.state.isTimeLimited){ - let now = new Date(); - start = new Date( - now.getTime() - minutes(now) - seconds(now) - ms(now) - ); - finish=new Date(start.getTime() + 10 * 365 * 24 * 3600 * 1000); - } - - const { t } = this.props; - const locale = - i18n.language.substring(0, 2).toLowerCase() === "fr" ? "fr" : "en"; - - const check = this.checkFields(); - if (!check.ok) { - toast.error(t(check.msg), { - position: toast.POSITION.TOP_CENTER - }); - return; - } - - this.setState({ waiting: true }); - - fetch(endpoint, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - title: title, - candidates: candidates.map(c => c.label).filter(c => c !== ""), - on_invitation_only: electorEmails.length > 0, - num_grades: numGrades, - elector_emails: electorEmails, - start_at: start.getTime() / 1000, - finish_at: finish.getTime() / 1000, - select_language: locale, - front_url: window.location.origin, - restrict_result: this.state.restrictResult - }) - }) - .then(response => response.json()) - .then(result => { - if (result.id) { - const nextPage = - electorEmails && electorEmails.length - ? `/link/${result.id}` - : `/links/${result.id}`; - this.setState(() => ({ - redirectTo: nextPage, - successCreate: true, - waiting: false - })); - } else { - toast.error(t("Unknown error. Try again please."), { - position: toast.POSITION.TOP_CENTER - }); - this.setState({ waiting: false }); - } - }) - .catch(error => error); - } - - handleSendNotReady = msg => { - const { t } = this.props; - toast.error(t(msg), { - position: toast.POSITION.TOP_CENTER - }); - }; - - render() { - const { - successCreate, - redirectTo, - waiting, - title, - start, - finish, - candidates, - numGrades, - isAdvancedOptionsOpen, - electorEmails - } = this.state; - const { t } = this.props; - - const grades = i18nGrades(); - const check = this.checkFields(); - - if (successCreate) return ; - - return ( - - - {waiting ? : ""} -
    - - -

    {t("Start an election")}

    - -
    -
    - - - - - - - - - - {t( - "Write here your question or introduce simple your election (250 characters max.)" - )} -
    - {t("For example:")}{" "} - - {t( - "For the role of my representative, I judge this candidate..." - )} - -
    - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - -
    - - - - {t("Starting date")} - - - { - this.setState({ - start: new Date( - timeMinusDate(start) + - new Date(e.target.valueAsNumber).getTime() - ) - }); - }} - /> - - - - - - - - - - {t("Ending date")} - - - { - this.setState({ - finish: new Date( - timeMinusDate(finish) + - new Date(e.target.valueAsNumber).getTime() - ) - }); - }} - /> - - - - - -
    -
    - - - {t("Grades")} - - - - - - - {t( - "You can select here the number of grades for your election" - )} -
    - {t("For example:")}{" "} - - {" "} - {t("5 = Excellent, Very good, Good, Fair, Passable")} - -
    - - - {grades.map((mention, i) => { - return ( - - {mention.label} - - ); - })} - -
    -
    - - - {t("Participants")} - - - { - this.setState({ electorEmails: _emails }); - }} - validateEmail={email => { - return isEmail(email); // return boolean - }} - getLabel={(email, index, removeEmail) => { - return ( -
    - {email} - removeEmail(index)} - > - × - -
    - ); - }} - /> -
    - - {t( - "If you list voters' emails, only them will be able to access the election" - )} - -
    - -
    -
    -
    -
    -
    - - - {check.ok ? ( - -
    - - {t("Validate")} -
    -
    - {t("Confirm your vote")} -
    -
    -
    -
    - {t("Question of the election")} -
    -
    {title}
    -
    - {t("Candidates/Proposals")} -
    -
    -
      - {candidates.map((candidate, i) => { - if (candidate.label !== "") { - return ( -
    • - {candidate.label} -
    • - ); - } else { - return
    • ; - } - })} -
    -
    -
    -
    - {t("Dates")} -
    -
    - {t("The election will take place from")}{" "} - - {start.toLocaleDateString()}, {t("at")}{" "} - {start.toLocaleTimeString()} - {" "} - {t("to")}{" "} - - {finish.toLocaleDateString()}, {t("at")}{" "} - {finish.toLocaleTimeString()} - -
    -
    -
    - {t("Grades")} -
    -
    - {grades.map((mention, i) => { - return i < numGrades ? ( - - {mention.label} - - ) : ( - - ); - })} -
    -
    - {t("Voters' list")} -
    -
    - {electorEmails.length > 0 ? ( - electorEmails.join(", ") - ) : ( -

    - {t("The form contains no address.")} -
    - - {t( - "The election will be opened to anyone with the link" - )} - -

    - )} -
    - {this.state.restrictResult ? ( -
    -
    -
    - - {t("Results available at the close of the vote")} -
    -

    - {electorEmails.length > 0 ? ( - - {t( - "The results page will not be accessible until all participants have voted." - )} - - ) : ( - - {t( - "The results page will not be accessible until the end date is reached." - )}{" "} - ({finish.toLocaleDateString()} {t("at")}{" "} - {finish.toLocaleTimeString()}) - - )} -

    -
    -
    - ) : null} -
    -
    -
    - {t("Start the election")} -
    -
    {t("Cancel")}
    -
    - ) : ( - - )} - -
    -
    -
    - ); - } -} - -export default withTranslation()(withRouter(CreateElection)); diff --git a/src/components/views/CreateSuccess.jsx b/src/components/views/CreateSuccess.jsx index 24bf256..542c46c 100644 --- a/src/components/views/CreateSuccess.jsx +++ b/src/components/views/CreateSuccess.jsx @@ -5,7 +5,7 @@ import { Link } from "react-router-dom"; import { withTranslation } from "react-i18next"; import { faCopy, - faUsers, + faVoteYea, faExclamationTriangle, faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; @@ -62,7 +62,7 @@ class CreateSuccess extends Component {

    {t("Successful election creation!")}

    {this.props.invitationOnly ? null : ( */} - - - - - {t("Participate now!")} - - - + {this.props.invitationOnly ? null : ( + + + + + {t("Participate now!")} + + + + )} ); }