@ -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'
/ / E r r o r m e s s a g e s
@ -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 {
/ / d e f a u l t v a l u e : s t a r t a t t h e l a s t h o u r
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 ) ; / / r e t u r n b o o l e a n
} }
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 ) ; / / r e t u r n b o o l e a n
} }
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 >
) ;
}
}