Browse Source

reformat code

pull/73/head
Pierre-Louis Guhur 3 years ago
committed by guhur
parent
commit
85b03f3fc0
  1. 1
      src/App.css
  2. 18
      src/App.jsx
  3. 148
      src/App.test.jsx
  4. 29
      src/Routes.jsx
  5. 18
      src/Util.jsx
  6. 104
      src/components/form/ButtonWithConfirm.jsx
  7. 105
      src/components/form/HelpButton.jsx
  8. 78
      src/components/form/ModalConfirm.jsx
  9. 42
      src/components/layouts/Footer.jsx
  10. 110
      src/components/layouts/Header.jsx
  11. 1102
      src/components/views/CreateElection.jsx
  12. 219
      src/components/views/CreateSuccess.js
  13. 131
      src/components/views/Home.jsx
  14. 545
      src/components/views/Result.jsx
  15. 59
      src/components/views/UnknownElection.js
  16. 57
      src/components/views/UnknownView.jsx
  17. 489
      src/components/views/Vote.jsx
  18. 55
      src/components/views/VoteSuccess.js
  19. 13
      src/index.jsx
  20. 216
      src/scss/_app.scss
  21. 5
      src/scss/_bootstrap.scss
  22. 32
      src/scss/config.scss
  23. 30
      src/serviceWorker.jsx

1
src/App.css

@ -1 +0,0 @@

18
src/App.jsx

@ -1,5 +1,5 @@
import React from 'react';
import { BrowserRouter as Router} from "react-router-dom";
import React from "react";
import { BrowserRouter as Router } from "react-router-dom";
import Routes from "./Routes";
import Header from "./components/layouts/Header";
@ -7,13 +7,13 @@ import Footer from "./components/layouts/Footer";
function App() {
return (
<Router>
<div>
<Header />
<Routes />
<Footer />
</div>
</Router>
<Router>
<div>
<Header />
<Routes />
<Footer />
</div>
</Router>
);
}

148
src/App.test.jsx

@ -1,11 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {MemoryRouter} from 'react-router-dom';
import React from "react";
import ReactDOM from "react-dom";
import { MemoryRouter } from "react-router-dom";
import Routes from './Routes';
import App from './App';
import Adapter from 'enzyme-adapter-react-16';
import {mount, configure} from 'enzyme';
import Routes from "./Routes";
import App from "./App";
import Adapter from "enzyme-adapter-react-16";
import { mount, configure } from "enzyme";
import Home from "./components/views/Home.js";
import CreateElection from "./components/views/CreateElection.js";
@ -13,72 +13,76 @@ import Result from "./components/views/Result.js";
import Vote from "./components/views/Vote.js";
import UnknownView from "./components/views/UnknownView";
configure({adapter: new Adapter()});
configure({ adapter: new Adapter() });
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App/>, div);
ReactDOM.unmountComponentAtNode(div);
it("renders without crashing", () => {
const div = document.createElement("div");
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
describe('open good View component for each route', () => {
it('should show Home view component for `/`', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/']}>
<Routes/>
</MemoryRouter>
);
expect(wrapper.find(Home)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it('should show CreateElection view component for `/create-election`', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/create-election']}>
<Routes/>
</MemoryRouter>
);
expect(wrapper.find(CreateElection)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it('should show CreateElection view component with title for `/create-election/?title=test%20with%20title`', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/create-election/?title=test%20with%20title']}>
<Routes/>
</MemoryRouter>
);
expect(wrapper.find(CreateElection)).toHaveLength(1);
expect(wrapper.find('input[name="title"]').props().defaultValue).toBe("test with title");
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it('should show Vote view component for `/vote`', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/vote']}>
<Routes/>
</MemoryRouter>
);
expect(wrapper.find(Vote)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it('should show Result view component for `/result`', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/result']}>
<Routes/>
</MemoryRouter>
);
expect(wrapper.find(Result)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it('should show UnknownView view component for `/aaabbbcccddd`', () => {
const wrapper = mount(<MemoryRouter initialEntries={['/aaabbbcccddd']} initialIndex={0}>
<Routes/>
</MemoryRouter>
);
expect(wrapper.find(UnknownView)).toHaveLength(1);
});
describe("open good View component for each route", () => {
it("should show Home view component for `/`", () => {
const wrapper = mount(
<MemoryRouter initialEntries={["/"]}>
<Routes />
</MemoryRouter>
);
expect(wrapper.find(Home)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it("should show CreateElection view component for `/create-election`", () => {
const wrapper = mount(
<MemoryRouter initialEntries={["/create-election"]}>
<Routes />
</MemoryRouter>
);
expect(wrapper.find(CreateElection)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it("should show CreateElection view component with title for `/create-election/?title=test%20with%20title`", () => {
const wrapper = mount(
<MemoryRouter
initialEntries={["/create-election/?title=test%20with%20title"]}
>
<Routes />
</MemoryRouter>
);
expect(wrapper.find(CreateElection)).toHaveLength(1);
expect(wrapper.find('input[name="title"]').props().defaultValue).toBe(
"test with title"
);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it("should show Vote view component for `/vote`", () => {
const wrapper = mount(
<MemoryRouter initialEntries={["/vote"]}>
<Routes />
</MemoryRouter>
);
expect(wrapper.find(Vote)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it("should show Result view component for `/result`", () => {
const wrapper = mount(
<MemoryRouter initialEntries={["/result"]}>
<Routes />
</MemoryRouter>
);
expect(wrapper.find(Result)).toHaveLength(1);
expect(wrapper.find(UnknownView)).toHaveLength(0);
});
it("should show UnknownView view component for `/aaabbbcccddd`", () => {
const wrapper = mount(
<MemoryRouter initialEntries={["/aaabbbcccddd"]} initialIndex={0}>
<Routes />
</MemoryRouter>
);
expect(wrapper.find(UnknownView)).toHaveLength(1);
});
});

29
src/Routes.jsx

@ -1,5 +1,5 @@
import React from 'react';
import {Switch, Route } from "react-router-dom";
import React from "react";
import { Switch, Route } from "react-router-dom";
import Home from "./components/views/Home";
import CreateElection from "./components/views/CreateElection";
@ -11,18 +11,19 @@ import CreateSuccess from "./components/views/CreateSuccess";
import VoteSuccess from "./components/views/VoteSuccess";
function Routes() {
return (<main className="d-flex flex-column justify-content-center" >
<Switch>
<Route exact path="/" component={Home} />
<Route path="/create-election" component={CreateElection} />
<Route path="/vote/:handle" component={Vote} />
<Route path="/result/:handle" component={Result} />
<Route path="/create-success/:handle" component={CreateSuccess} />
<Route path="/vote-success/:handle" component={VoteSuccess} />
<Route path="/unknown-election/:handle" component={UnknownElection} />
<Route component={UnknownView} />
</Switch>
</main>
return (
<main className="d-flex flex-column justify-content-center">
<Switch>
<Route exact path="/" component={Home} />
<Route path="/create-election" component={CreateElection} />
<Route path="/vote/:handle" component={Vote} />
<Route path="/result/:handle" component={Result} />
<Route path="/create-success/:handle" component={CreateSuccess} />
<Route path="/vote-success/:handle" component={VoteSuccess} />
<Route path="/unknown-election/:handle" component={UnknownElection} />
<Route component={UnknownView} />
</Switch>
</main>
);
}

18
src/Util.jsx

@ -1,5 +1,13 @@
const colors = ["#015411", "#019812", "#6bca24", "#ffb200", "#ff5d00", "#b20616", "#6f0214"];
export const grades = process.env.REACT_APP_GRADES.split(', ').map(
(e, i) => ({label: e, color:colors[i]}));
const colors = [
"#015411",
"#019812",
"#6bca24",
"#ffb200",
"#ff5d00",
"#b20616",
"#6f0214"
];
export const grades = process.env.REACT_APP_GRADES.split(", ").map((e, i) => ({
label: e,
color: colors[i]
}));

104
src/components/form/ButtonWithConfirm.jsx

@ -1,55 +1,61 @@
import React, {Component} from "react";
import React, { Component } from "react";
import ModalConfirm from "./ModalConfirm";
class ButtonWithConfirm extends Component {
constructor(props) {
super(props);
this._modalConfirm=React.createRef();
this.state={
focused:false
}
}
getComponent= (key) => {
return this.props.children.filter( (comp) => {
return comp.key === key;
});
constructor(props) {
super(props);
this._modalConfirm = React.createRef();
this.state = {
focused: false
};
render() {
const classNames=this.props.className.split(" ");
let classNameForDiv="";
let classNameForButton="";
classNames.forEach(function(className){
if(className==="input-group-prepend" || className==="input-group-append" ){
classNameForDiv+=" "+className;
}else{
classNameForButton+=" "+className;
}
});
return (
<div className={classNameForDiv}>
<button
type="button"
className={classNameForButton}
onClick={() => { this._modalConfirm.current.toggle() }}
tabIndex={this.props.tabIndex}
>{this.getComponent("button")}
</button>
<ModalConfirm className={this.props.modalClassName} ref={this._modalConfirm}>
<div key="title">{this.getComponent("modal-title")}</div>
<div key="body">{this.getComponent("modal-body")}</div>
<div key="confirm">{this.getComponent("modal-confirm")}</div>
<div key="cancel">{this.getComponent("modal-cancel")}</div>
</ModalConfirm>
</div>
);
}
}
getComponent = key => {
return this.props.children.filter(comp => {
return comp.key === key;
});
};
render() {
const classNames = this.props.className.split(" ");
let classNameForDiv = "";
let classNameForButton = "";
classNames.forEach(function(className) {
if (
className === "input-group-prepend" ||
className === "input-group-append"
) {
classNameForDiv += " " + className;
} else {
classNameForButton += " " + className;
}
});
return (
<div className={classNameForDiv}>
<button
type="button"
className={classNameForButton}
onClick={() => {
this._modalConfirm.current.toggle();
}}
tabIndex={this.props.tabIndex}
>
{this.getComponent("button")}
</button>
<ModalConfirm
className={this.props.modalClassName}
ref={this._modalConfirm}
>
<div key="title">{this.getComponent("modal-title")}</div>
<div key="body">{this.getComponent("modal-body")}</div>
<div key="confirm">{this.getComponent("modal-confirm")}</div>
<div key="cancel">{this.getComponent("modal-cancel")}</div>
</ModalConfirm>
</div>
);
}
}
export default ButtonWithConfirm;
export default ButtonWithConfirm;

105
src/components/form/HelpButton.jsx

@ -1,45 +1,76 @@
import React, {Component} from "react";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
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
};
}
constructor(props) {
super(props);
showTooltip = () => {
console.log("show");
this.setState({
tooltipOpen: true
});
this.state = {
tooltipOpen: false
};
}
hideTooltip = () => {
console.log("hide");
this.setState({
tooltipOpen: false
});
};
showTooltip = () => {
console.log("show");
this.setState({
tooltipOpen: true
});
};
render() {
return (
<span>
<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>
hideTooltip = () => {
console.log("hide");
this.setState({
tooltipOpen: false
});
};
);
}
render() {
return (
<span>
<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;
export default HelpButton;

78
src/components/form/ModalConfirm.jsx

@ -1,40 +1,52 @@
import React, {Component} from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import React, { Component } from "react";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
class ModalConfirm extends Component {
constructor(props) {
super(props);
this.state = {
modal: false
};
}
toggle = () => {
this.setState({
modal: !this.state.modal
});
constructor(props) {
super(props);
this.state = {
modal: false
};
}
getComponent= (key) => {
return this.props.children.filter( (comp) => {
return comp.key === key;
});
};
toggle = () => {
this.setState({
modal: !this.state.modal
});
};
getComponent = key => {
return this.props.children.filter(comp => {
return comp.key === key;
});
};
render() {
return (
<Modal isOpen={this.state.modal} toggle={this.toggle} className={this.props.className+" modal-dialog-centered"} >
<ModalHeader toggle={this.toggle}>{this.getComponent("title")}</ModalHeader>
<ModalBody>
{this.getComponent("body")}
</ModalBody>
<ModalFooter>
<Button color="primary-outline" className="text-primary border-primary" onClick={this.toggle}>{this.getComponent("cancel")}</Button>
<Button color="primary" onClick={this.toggle}>{this.getComponent("confirm")}</Button>
</ModalFooter>
</Modal>
);
}
render() {
return (
<Modal
isOpen={this.state.modal}
toggle={this.toggle}
className={this.props.className + " modal-dialog-centered"}
>
<ModalHeader toggle={this.toggle}>
{this.getComponent("title")}
</ModalHeader>
<ModalBody>{this.getComponent("body")}</ModalBody>
<ModalFooter>
<Button
color="primary-outline"
className="text-primary border-primary"
onClick={this.toggle}
>
{this.getComponent("cancel")}
</Button>
<Button color="primary" onClick={this.toggle}>
{this.getComponent("confirm")}
</Button>
</ModalFooter>
</Modal>
);
}
}
export default ModalConfirm;
export default ModalConfirm;

42
src/components/layouts/Footer.jsx

@ -1,27 +1,23 @@
import React, {Component} from "react";
import {Link} from "react-router-dom";
import React, { Component } from "react";
import { Link } from "react-router-dom";
class Footer extends Component {
constructor(props) {
super(props);
this.state = {};
}
constructor(props) {
super(props);
this.state = {
}
}
render(){
return(
<footer className="text-center">
<Link to="/">Accueil</Link>
<span className="m-2">-</span>
<a href="https://github.com/MieuxVoter">Code source</a>
<span className="m-2">-</span>
<a href="https://mieuxvoter.fr/">Qui sommes nous ?</a>
<div className="mt-2">
MieuxVoter &copy;
</div>
</footer>
)
}
render() {
return (
<footer className="text-center">
<Link to="/">Accueil</Link>
<span className="m-2">-</span>
<a href="https://github.com/MieuxVoter">Code source</a>
<span className="m-2">-</span>
<a href="https://mieuxvoter.fr/">Qui sommes nous ?</a>
<div className="mt-2">MieuxVoter &copy;</div>
</footer>
);
}
}
export default Footer;
export default Footer;

110
src/components/layouts/Header.jsx

@ -1,64 +1,60 @@
import React, {Component} from "react";
import {
Collapse,
Navbar,
NavbarToggler,
Nav,
NavItem
} from 'reactstrap'
import {Link} from "react-router-dom";
import React, { Component } from "react";
import { Collapse, Navbar, NavbarToggler, Nav, NavItem } from "reactstrap";
import { Link } from "react-router-dom";
import logo from '../../logos/logo-color.svg';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faRocket} from "@fortawesome/free-solid-svg-icons";
import logo from "../../logos/logo-color.svg";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faRocket } from "@fortawesome/free-solid-svg-icons";
class Header extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
isOpen: false
};
}
this.toggle = this.toggle.bind(this);
this.state = {
isOpen: false
};
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}
render(){
return (
<header>
<Navbar color="light" light expand="md">
<Link to="/" className="navbar-brand">
<div className="d-flex flex-row">
<div className="align-self-center">
<img src={logo} alt="logo" height="32"/>
</div>
<div className="align-self-center ml-2">
<div className="logo-text">
<h1>Plateforme de vote
<small>Jugement Majoritaire</small>
</h1>
</div>
</div>
</div>
</Link>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto" navbar>
<NavItem>
<Link className="text-primary nav-link" to="/create-election/"><FontAwesomeIcon icon={faRocket} className="mr-2"/> Démarrer un vote</Link>
</NavItem>
</Nav>
</Collapse>
</Navbar>
</header>
);
}
render() {
return (
<header>
<Navbar color="light" light expand="md">
<Link to="/" className="navbar-brand">
<div className="d-flex flex-row">
<div className="align-self-center">
<img src={logo} alt="logo" height="32" />
</div>
<div className="align-self-center ml-2">
<div className="logo-text">
<h1>
Plateforme de vote
<small>Jugement Majoritaire</small>
</h1>
</div>
</div>
</div>
</Link>
<NavbarToggler onClick={this.toggle} />
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto" navbar>
<NavItem>
<Link className="text-primary nav-link" to="/create-election/">
<FontAwesomeIcon icon={faRocket} className="mr-2" /> Démarrer
un vote
</Link>
</NavItem>
</Nav>
</Collapse>
</Navbar>
</header>
);
}
}
export default Header;
export default Header;

1102
src/components/views/CreateElection.jsx
File diff suppressed because it is too large
View File

219
src/components/views/CreateSuccess.js

@ -1,97 +1,134 @@
import React, {Component} from "react";
import {Button, Col, Container, Row} from "reactstrap";
import {Link} from 'react-router-dom';
import { faCopy, faUsers } from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import React, { Component } from "react";
import { Button, Col, Container, Row } from "reactstrap";
import { Link } from "react-router-dom";
import { faCopy, faUsers } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import logoLine from "../../logos/logo-line-white.svg";
class UnknownView extends Component {
constructor(props) {
super(props);
const electionSlug = this.props.match.params.handle;
this.state = {
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");
constructor(props) {
super(props);
const electionSlug = this.props.match.params.handle;
this.state = {
urlOfVote:
"https://" + window.location.hostname + "/vote/" + electionSlug,
urlOfResult:
"https://" + window.location.hostname + "/result/" + electionSlug
};
handleClickOnCopyResult=(event)=>{
const input = this.urlResultField.current;
input.focus();
input.select();
document.execCommand("copy");
};
render(){
return(
<Container>
<Row>
<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 offset-lg-3" lg="6"><h2>Vote créé avec succès !</h2>
<p className="mt-4 mb-1">Vous pouvez maintenant partager le lien du vote aux participants :</p>
<div className="input-group ">
<input type="text" className="form-control" ref={this.urlVoteField}
value={this.state.urlOfVote} readOnly onClick={this.handleClickOnField} />
<div className="input-group-append">
<Button className="btn btn-outline-light" onClick={this.handleClickOnCopyVote}
type="button">
<FontAwesomeIcon icon={faCopy} className="mr-2"/>Copier</Button>
</div>
</div>
<p className="mt-4 mb-1">Voici le lien vers les résultats du vote en temps réel :</p>
<div className="input-group ">
<input type="text" className="form-control" ref={this.urlResultField}
value={this.state.urlOfResult} readOnly onClick={this.handleClickOnField} />
<div className="input-group-append">
<Button className="btn btn-outline-light" onClick={this.handleClickOnCopyResult}
type="button">
<FontAwesomeIcon icon={faCopy} className="mr-2"/>Copier</Button>
</div>
</div>
</Col>
</Row>
<Row className="mt-4 mb-4" >
<Col >
<div className=" bg-warning text-white p-2 "><p className="m-0 p-0 text-center">Conservez ces liens précieusement !</p>
<p className="small m-2 p-0"><b>ATTENTION</b> : Vous ne les retrouverez pas ailleurs et nous ne serons pas capable de vous les communiquer. Vous pouvez par exemple les enregistrer dans les favoris de votre
navigateur.</p></div>
</Col>
</Row>
<Row className="mt-4 mb-4" >
<Col className="text-center">
<Link to={ "/vote/" + this.props.match.params.handle} className="btn btn-success"><FontAwesomeIcon icon={faUsers} className="mr-2"/>Participer maintenant !</Link>
</Col>
</Row>
</Container>
)
}
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();
input.select();
document.execCommand("copy");
};
render() {
return (
<Container>
<Row>
<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 offset-lg-3" lg="6">
<h2>Vote créé avec succès !</h2>
<p className="mt-4 mb-1">
Vous pouvez maintenant partager le lien du vote aux participants :
</p>
<div className="input-group ">
<input
type="text"
className="form-control"
ref={this.urlVoteField}
value={this.state.urlOfVote}
readOnly
onClick={this.handleClickOnField}
/>
<div className="input-group-append">
<Button
className="btn btn-outline-light"
onClick={this.handleClickOnCopyVote}
type="button"
>
<FontAwesomeIcon icon={faCopy} className="mr-2" />
Copier
</Button>
</div>
</div>
<p className="mt-4 mb-1">
Voici le lien vers les résultats du vote en temps réel :
</p>
<div className="input-group ">
<input
type="text"
className="form-control"
ref={this.urlResultField}
value={this.state.urlOfResult}
readOnly
onClick={this.handleClickOnField}
/>
<div className="input-group-append">
<Button
className="btn btn-outline-light"
onClick={this.handleClickOnCopyResult}
type="button"
>
<FontAwesomeIcon icon={faCopy} className="mr-2" />
Copier
</Button>
</div>
</div>
</Col>
</Row>
<Row className="mt-4 mb-4">
<Col>
<div className=" bg-warning text-white p-2 ">
<p className="m-0 p-0 text-center">
Conservez ces liens précieusement !
</p>
<p className="small m-2 p-0">
<b>ATTENTION</b> : Vous ne les retrouverez pas ailleurs et nous
ne serons pas capable de vous les communiquer. Vous pouvez par
exemple les enregistrer dans les favoris de votre navigateur.
</p>
</div>
</Col>
</Row>
<Row className="mt-4 mb-4">
<Col className="text-center">
<Link
to={"/vote/" + this.props.match.params.handle}
className="btn btn-success"
>
<FontAwesomeIcon icon={faUsers} className="mr-2" />
Participer maintenant !
</Link>
</Col>
</Row>
</Container>
);
}
}
export default UnknownView;

131
src/components/views/Home.jsx

@ -1,59 +1,90 @@
import React, {Component} from "react";
import { Container, Row, Col,Button, Input } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faRocket } from '@fortawesome/free-solid-svg-icons';
import { Redirect } from 'react-router-dom';
import React, { Component } from "react";
import { Container, Row, Col, Button, Input } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faRocket } from "@fortawesome/free-solid-svg-icons";
import { Redirect } from "react-router-dom";
import logoLine from "../../logos/logo-line-white.svg";
class Home extends Component {
constructor(props) {
super(props);
this.state = {
title:null,
redirect:false,
};
this.focusInput= React.createRef();
}
handleSubmit = (event) => {
event.preventDefault();
this.setState({redirect: true});
constructor(props) {
super(props);
this.state = {
title: null,
redirect: false
};
this.focusInput = React.createRef();
}
handleChangeTitle= (event) => {
this.setState({title: event.target.value});
};
handleSubmit = event => {
event.preventDefault();
this.setState({ redirect: true });
};
render(){
const redirect = this.state.redirect;
handleChangeTitle = event => {
this.setState({ title: event.target.value });
};
if (redirect) {
return <Redirect to={ '/create-election/?title='+encodeURIComponent(this.state.title)} />;
}
return(
<Container>
<form onSubmit={this.handleSubmit} autoComplete="off">
<Row>
<img src={logoLine} alt="logo" height="128" className="d-block ml-auto mr-auto mb-4"/>
</Row>
<Row>
<Col className="text-center"><h3>Simple et gratuit : organisez un vote à l'aide du Jugement Majoritaire.</h3></Col>
</Row>
<Row className="mt-2">
<Col xs="12" md="9" xl="6" className="offset-xl-2">
<Input placeholder="Saisissez ici la question de votre vote" innerRef={this.focusInput} autoFocus required className="mt-2" name="title" value={this.state.title?this.state.title:""} onChange={this.handleChangeTitle} maxLength="250" />
</Col>
<Col xs="12" md="3" xl="2">
<Button type="submit" className="btn btn-block btn-secondary mt-2" ><FontAwesomeIcon icon={faRocket} className="mr-2"/>Lancer</Button>
</Col>
</Row>
<Row className="mt-4">
<Col className="text-center"><p>Pas de publicité et pas de cookie publicitaire.</p></Col>
</Row>
</form>
</Container>
)
};
render() {
const redirect = this.state.redirect;
if (redirect) {
return (
<Redirect
to={"/create-election/?title=" + encodeURIComponent(this.state.title)}
/>
);
}
return (
<Container>
<form onSubmit={this.handleSubmit} autoComplete="off">
<Row>
<img
src={logoLine}
alt="logo"
height="128"
className="d-block ml-auto mr-auto mb-4"
/>
</Row>
<Row>
<Col className="text-center">
<h3>
Simple et gratuit : organisez un vote à l'aide du Jugement
Majoritaire.
</h3>
</Col>
</Row>
<Row className="mt-2">
<Col xs="12" md="9" xl="6" className="offset-xl-2">
<Input
placeholder="Saisissez ici la question de votre vote"
innerRef={this.focusInput}
autoFocus
required
className="mt-2"
name="title"
value={this.state.title ? this.state.title : ""}
onChange={this.handleChangeTitle}
maxLength="250"
/>
</Col>
<Col xs="12" md="3" xl="2">
<Button
type="submit"
className="btn btn-block btn-secondary mt-2"
>
<FontAwesomeIcon icon={faRocket} className="mr-2" />
Lancer
</Button>
</Col>
</Row>
<Row className="mt-4">
<Col className="text-center">
<p>Pas de publicité et pas de cookie publicitaire.</p>
</Col>
</Row>
</form>
</Container>
);
}
}
export default Home;

545
src/components/views/Result.jsx

@ -1,243 +1,344 @@
import React, {Component} from "react";
import { Redirect } from 'react-router-dom';
import { resolve } from 'url';
import {Container, Row, Col, Collapse, Card, CardHeader, CardBody, Table} from "reactstrap";
import { grades } from '../../Util';
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import { resolve } from "url";
import {
Container,
Row,
Col,
Collapse,
Card,
CardHeader,
CardBody,
Table
} from "reactstrap";
import { grades } from "../../Util";
class Result extends Component {
constructor(props) {
super(props);
this.state = {
candidates: [],
title: null,
numGrades: 0,
colSizeCandidateLg: 4,
colSizeCandidateMd: 6,
colSizeCandidateXs: 12,
colSizeGradeLg: 1,
colSizeGradeMd: 1,
colSizeGradeXs: 1,
collapseGraphics: false,
collapseProfiles: false,
redirectLost: false,
electionGrades: grades
};
}
constructor(props) {
super(props);
this.state = {
candidates: [],
title: null,
numGrades: 0,
colSizeCandidateLg: 4,
colSizeCandidateMd: 6,
colSizeCandidateXs: 12,
colSizeGradeLg: 1,
colSizeGradeMd: 1,
colSizeGradeXs: 1,
collapseGraphics: false,
collapseProfiles: false,
redirectLost: false,
electionGrades: grades
};
handleErrors = response => {
if (!response.ok) {
response.json().then(response => {
this.setState(state => ({
redirectLost: "/unknown-election/" + encodeURIComponent(response)
}));
});
throw Error(response);
}
return response;
};
handleErrors = (response) => {
if (!response.ok) {
response.json().then( response => {
this.setState(state => ({
redirectLost: '/unknown-election/' + encodeURIComponent(response)}));
})
throw Error(response);
}
return response;
}
resultsToState = response => {
const candidates = response.map(c => ({
id: c.id,
label: c.name,
profile: c.profile,
grade: c.grade,
score: c.score
}));
this.setState(state => ({ candidates: candidates }));
return response;
};
resultsToState = (response) => {
const candidates = response.map(c => ({
id: c.id, label: c.name, profile: c.profile, grade:c.grade, score: c.score
}));
this.setState(state => ({candidates: candidates}));
return response;
}
detailsToState = response => {
const numGrades = response.num_grades;
const colSizeGradeLg = Math.floor(
(12 - this.state.colSizeCandidateLg) / numGrades
);
const colSizeGradeMd = Math.floor(
(12 - this.state.colSizeCandidateMd) / numGrades
);
const colSizeGradeXs = Math.floor(
(12 - this.state.colSizeCandidateXs) / numGrades
);
this.setState(state => ({
title: response.title,
numGrades: numGrades,
colSizeGradeLg: colSizeGradeLg,
colSizeGradeMd: colSizeGradeMd,
colSizeGradeXs: colSizeGradeXs,
colSizeCandidateLg:
12 - colSizeGradeLg * numGrades > 0
? 12 - colSizeGradeLg * numGrades
: 12,
colSizeCandidateMd:
12 - colSizeGradeMd * numGrades > 0
? 12 - colSizeGradeMd * numGrades
: 12,
colSizeCandidateXs:
12 - colSizeGradeXs * numGrades > 0
? 12 - colSizeGradeXs * numGrades
: 12,
electionGrades: grades.slice(0, numGrades)
}));
return response;
};
detailsToState = (response) => {
const numGrades = response.num_grades;
const colSizeGradeLg = Math.floor((12 - this.state.colSizeCandidateLg) / numGrades);
const colSizeGradeMd = Math.floor((12 - this.state.colSizeCandidateMd) / numGrades);
const colSizeGradeXs = Math.floor((12 - this.state.colSizeCandidateXs) / numGrades);
this.setState(state => ({
title: response.title,
numGrades: numGrades,
colSizeGradeLg: colSizeGradeLg,
colSizeGradeMd: colSizeGradeMd,
colSizeGradeXs: colSizeGradeXs,
colSizeCandidateLg: ((12 - colSizeGradeLg * numGrades) > 0) ? (12 - colSizeGradeLg * numGrades) : 12,
colSizeCandidateMd: ((12 - colSizeGradeMd * numGrades) > 0) ? (12 - colSizeGradeMd * numGrades) : 12,
colSizeCandidateXs: ((12 - colSizeGradeXs * numGrades) > 0) ? (12 - colSizeGradeXs * numGrades) : 12,
electionGrades: grades.slice(0, numGrades)
}));
return response;
}
componentDidMount() {
// FIXME we should better handling logs
const electionSlug = this.props.match.params.handle;
// get details of the election
const detailsEndpoint = resolve(
process.env.REACT_APP_SERVER_URL,
"election/get/".concat(electionSlug)
);
componentDidMount() {
// FIXME we should better handling logs
fetch(detailsEndpoint)
.then(this.handleErrors)
.then(response => response.json())
.then(this.detailsToState)
.catch(error => console.log(error));
const electionSlug = this.props.match.params.handle;
// get results of the election
const resultsEndpoint = resolve(
process.env.REACT_APP_SERVER_URL,
"election/results/".concat(electionSlug)
);
// get details of the election
const detailsEndpoint = resolve(process.env.REACT_APP_SERVER_URL,
'election/get/'.concat(electionSlug));
fetch(resultsEndpoint)
.then(this.handleErrors)
.then(response => response.json())
.then(this.resultsToState)
.catch(error => console.log(error));
}
fetch(detailsEndpoint)
.then(this.handleErrors)
.then(response => response.json())
.then(this.detailsToState)
.catch(error => console.log(error));
toggleGraphics = () => {
this.setState(state => ({ collapseGraphics: !state.collapseGraphics }));
};
// get results of the election
const resultsEndpoint = resolve(process.env.REACT_APP_SERVER_URL,
'election/results/'.concat(electionSlug));
toggleProfiles = () => {
this.setState(state => ({ collapseProfiles: !state.collapseProfiles }));
};
fetch(resultsEndpoint)
.then(this.handleErrors)
.then(response => response.json())
.then(this.resultsToState)
.catch(error => console.log(error));
render() {
const { redirectLost, candidates, electionGrades } = this.state;
if (redirectLost) {
return <Redirect to={redirectLost} />;
}
toggleGraphics = () => {
this.setState(state => ({collapseGraphics: !state.collapseGraphics}));
};
return (
<Container>
<Row>
<Col xs="12">
<h1>{this.state.title}</h1>
</Col>
</Row>
toggleProfiles = () => {
this.setState(state => ({collapseProfiles: !state.collapseProfiles}));
};
<Row className="mt-5">
<Col>
<h1>Résultat du vote :</h1>
<ol>
{candidates.map((candidate, i) => {
return (
<li key={i} className="mt-2">
{candidate.label}
<span className="badge badge-dark mr-2 mt-2">
{candidate.score}%
</span>
<span
className="badge badge-light mr-2 mt-2"
style={{
backgroundColor: electionGrades[candidate.grade].color,
color: "#fff"
}}
>
{grades[candidate.grade].label}
</span>
</li>
);
})}
</ol>
</Col>
</Row>
render() {
const { redirectLost,
candidates,
electionGrades
} = this.state;
if (redirectLost) {
return (<Redirect to={redirectLost}/>)
}
return (
<Container>
<Row>
<Col xs="12"><h1>{this.state.title}</h1></Col>
</Row>
<Row className="mt-5">
<Col><h1>Résultat du vote :</h1>
<ol>{candidates.map((candidate, i) => {
return (<li key={i} className="mt-2">{candidate.label}<span
className="badge badge-dark mr-2 mt-2">{candidate.score}%</span><span
className="badge badge-light mr-2 mt-2" style={{
backgroundColor: electionGrades[candidate.grade].color,
color: "#fff"
}}>{grades[candidate.grade].label}</span></li>);
})}</ol>
</Col>
</Row>
<Row className="mt-5">
<Col>
<Card className="bg-light text-primary">
<CardHeader className="pointer" onClick={this.toggleGraphics}>
<h4 className={"m-0 panel-title " + (this.state.collapseGraphics ? "collapsed" : "")}>Graphique</h4>
</CardHeader>
<Collapse isOpen={this.state.collapseGraphics}>
<CardBody className="pt-5">
<div>
<div className="median"
style={{height: (candidates.length * 28) + 30}}/>
<table style={{width: "100%"}}><tbody>
{candidates.map((candidate, i) => {
return (<tr key={i}>
<td style={{width: "30px"}}>{i + 1}</td>
{/*candidate.label*/}
<td>
<table style={{width: "100%"}}>
<tbody>
<tr>
{candidate.profile.map((value, i) => {
if (value > 0) {
let percent = value + "%";
if (i === 0) {
percent = "auto";
}
return (<td key={i} style={{
width: percent,
backgroundColor: this.state.electionGrades[i].color
}}>&nbsp;</td>);
}else{
return null
}
})}</tr>
</tbody>
</table>
</td>
</tr>)
})}</tbody></table>
</div>
<div className="mt-4">
<small>
{candidates.map((candidate, i) => {
return (
<span key={i}>{(i > 0) ? ", " : ""}<b>{i + 1}</b>: {candidate.label}</span>);
})}
</small>
</div>
<div className="mt-2">
<small>
{electionGrades.map((grade, i) => {
return (
<span key={i} className="badge badge-light mr-2 mt-2" style={{
backgroundColor: grade.color,
color: "#fff"
}}>{grade.label}</span>
)})}
</small>
</div>
</CardBody>
</Collapse>
</Card>
</Col>
</Row>
<Row className="mt-3">
<Col>
<Card className="bg-light text-primary">
<CardHeader className="pointer" onClick={this.toggleProfiles}>
<h4 className={"m-0 panel-title " + (this.state.collapseProfiles ? "collapsed" : "")}>Profils
de mérites</h4>
</CardHeader>
<Collapse isOpen={this.state.collapseProfiles}>
<CardBody>
<div className="table-responsive">
<Table className="profiles">
<thead>
<tr>
<th>#</th>
{electionGrades.map((grade, i) => {
return (<th key={i}><span className="badge badge-light" style={{
backgroundColor: grade.color,
color: "#fff"
}}>{grade.label} </span></th>);
})}</tr>
</thead>
<tbody>{candidates.map((candidate, i) => {
return (<tr key={i}>
<td>{i + 1