parent
e05ddea3b6
commit
35a66d55d6
@ -1,123 +0,0 @@
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Label,
|
||||
Input,
|
||||
InputGroup,
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
Form
|
||||
} from "reactstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faPlus,
|
||||
faArrowLeft,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useTranslation} from "next-i18next";
|
||||
import Image from 'next/image';
|
||||
import {useElection, useElectionDispatch} from './ElectionContext';
|
||||
import defaultAvatar from '../../public/default-avatar.svg'
|
||||
|
||||
|
||||
const CandidateModal = ({isOpen, position, toggle}) => {
|
||||
|
||||
const {t} = useTranslation();
|
||||
|
||||
const election = useElection();
|
||||
const dispatch = useElectionDispatch();
|
||||
const candidate = election.candidates[position];
|
||||
const image = candidate && candidate.image ? candidate.image : defaultAvatar;
|
||||
|
||||
const addCandidate = () => {
|
||||
dispatch({'type': 'candidate-push', 'value': "default"})
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
toggle={toggle}
|
||||
keyboard={true}
|
||||
>
|
||||
<div className="modal-header p-4">
|
||||
<h4 className="modal-title">
|
||||
{t('admin.add-candidate')}
|
||||
</h4>
|
||||
<button type="button" onClick={toggle} className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<ModalBody className='p-4'>
|
||||
<p>{t('admin.add-candidate-desc')}
|
||||
</p>
|
||||
<Col>
|
||||
<InputGroup>
|
||||
<Form className='container container-fluid'>
|
||||
<Row className='gx-4 mb-3'>
|
||||
<Col className='col-auto'>
|
||||
<Image src={image} height={120} width={120} alt={t('admin.photo')} />
|
||||
</Col>
|
||||
<Col className='col-auto'>
|
||||
<Label className='fw-bold'>{t('admin.photo')} <span className='text-muted'> ({t('admin.optional')})</span></Label>
|
||||
<p>{t('admin.photo-type')} jpg, png, pdf</p>
|
||||
<div>
|
||||
<input type="file" name="image-upload" id="image-upload" />
|
||||
<label className="inputfile" htmlfor="image-upload">{t('admin.photo-import')}</label>
|
||||
</div>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
<div className='mb-3'>
|
||||
<Label className='fw-bold'>{t('common.name')} </Label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t("admin.candidate-name-placeholder")}
|
||||
tabIndex={position + 1}
|
||||
maxLength="250"
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className=''>
|
||||
<Label className='fw-bold'>{t('common.description')} <span className='text-muted'> ({t('admin.optional')})</span></Label>
|
||||
<Input
|
||||
type="text"
|
||||
defaultValue={candidate.description}
|
||||
placeholder={t("admin.candidate-desc-placeholder")}
|
||||
maxLength="250"
|
||||
/>
|
||||
</div>
|
||||
<Row className='mt-5 mb-3'>
|
||||
<Col className='col-auto me-auto'>
|
||||
<Button onClick={toggle} color='dark' outline={true}>
|
||||
<Row className='gx-2 align-items-end'>
|
||||
<Col>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</Col>
|
||||
<Col>
|
||||
{t('common.cancel')}
|
||||
</Col>
|
||||
</Row>
|
||||
</Button>
|
||||
</Col>
|
||||
<Col className='col-auto '>
|
||||
<Button outline={true} color="primary" onClick={addCandidate}>
|
||||
<Row className='gx-2 align-items-end'>
|
||||
<Col>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</Col>
|
||||
<Col>
|
||||
<span>{t('common.save')}</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Button>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
</Form>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</ModalBody >
|
||||
</Modal >);
|
||||
|
||||
}
|
||||
export default CandidateModal;
|
@ -0,0 +1,86 @@
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Label,
|
||||
Input,
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
Form
|
||||
} from "reactstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faTrashCan,
|
||||
faTrashAlt,
|
||||
faArrowLeft,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useTranslation} from "next-i18next";
|
||||
import Image from 'next/image';
|
||||
import {useElection, useElectionDispatch} from './ElectionContext';
|
||||
import {upload} from '@services/imgpush';
|
||||
import {IMGPUSH_URL} from '@services/constants';
|
||||
import defaultAvatar from '../../public/default-avatar.svg'
|
||||
import {useEffect} from "react";
|
||||
|
||||
|
||||
const CandidateModal = ({isOpen, position, toggle}) => {
|
||||
|
||||
const {t} = useTranslation();
|
||||
|
||||
const election = useElection();
|
||||
const dispatch = useElectionDispatch();
|
||||
const candidate = election.candidates[position];
|
||||
|
||||
const removeCandidate = () => {
|
||||
dispatch({'type': 'candidate-rm', 'position': position})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
toggle={toggle}
|
||||
keyboard={true}
|
||||
className='modal_candidate'
|
||||
>
|
||||
<ModalBody className='flex-column justify-contenter-center d-flex p-4'>
|
||||
<Row className='justify-content-center'>
|
||||
<Col className='col-auto px-4 py-4 rounded-circle bg-light'>
|
||||
<FontAwesomeIcon size="2x" icon={faTrashCan} />
|
||||
</Col>
|
||||
</Row>
|
||||
<p className='text-danger fw-bold text-center mt-4'>{t('admin.candidate-confirm-del')}
|
||||
</p>
|
||||
{candidate.name ? <h4 className='text-center'>{candidate.name}</h4> : null}
|
||||
<Row className='mt-5 mb-3'>
|
||||
<Col className='col-auto me-auto'>
|
||||
<Button onClick={toggle} color='dark' outline={true}>
|
||||
<Row className='gx-2 align-items-end'>
|
||||
<Col className='col-auto'>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</Col>
|
||||
<Col className='col-auto'>
|
||||
{t('admin.candidate-confirm-back')}
|
||||
</Col>
|
||||
</Row>
|
||||
</Button>
|
||||
</Col>
|
||||
<Col className='col-auto '>
|
||||
<Button outline={true} color="primary" onClick={removeCandidate}>
|
||||
<Row className='gx-2 align-items-end'>
|
||||
<Col className='col-auto'>
|
||||
<FontAwesomeIcon icon={faTrashAlt} />
|
||||
</Col>
|
||||
<Col className='col-auto'>
|
||||
{t('admin.candidate-confirm-ok')}
|
||||
</Col>
|
||||
</Row>
|
||||
</Button>
|
||||
</Col>
|
||||
|
||||
</Row >
|
||||
</ModalBody >
|
||||
</Modal >);
|
||||
|
||||
}
|
||||
export default CandidateModal;
|
@ -0,0 +1,180 @@
|
||||
import {useState, useEffect, useRef} from 'react'
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Label,
|
||||
Input,
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
Form
|
||||
} from "reactstrap";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faPlus,
|
||||
faArrowLeft,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {useTranslation} from "next-i18next";
|
||||
import Image from 'next/image';
|
||||
import {useElection, useElectionDispatch} from './ElectionContext';
|
||||
import {upload} from '@services/imgpush';
|
||||
import {IMGPUSH_URL} from '@services/constants';
|
||||
import defaultAvatar from '../../public/default-avatar.svg'
|
||||
|
||||
|
||||
const CandidateModal = ({isOpen, position, toggle}) => {
|
||||
|
||||
const {t} = useTranslation();
|
||||
const election = useElection();
|
||||
const dispatch = useElectionDispatch();
|
||||
const candidate = election.candidates[position];
|
||||
const [state, setState] = useState(candidate);
|
||||
console.log('state', state);
|
||||
const image = state.image && state.image != "" ? state.image : defaultAvatar;
|
||||
|
||||
const handleFile = async (event) => {
|
||||
const payload = await upload(event.target.files[0])
|
||||
setState(s => ({...s, "image": `${IMGPUSH_URL}/${payload['filename']}`}))
|
||||
}
|
||||
|
||||
// to manage the hidden ugly file input
|
||||
const hiddenFileInput = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setState(election.candidates[position]);
|
||||
console.log('effect election', election)
|
||||
}, [election])
|
||||
|
||||
const save = () => {
|
||||
dispatch({
|
||||
'type': 'candidate-set',
|
||||
'position': position,
|
||||
'field': "image",
|
||||
'value': state.image,
|
||||
})
|
||||
dispatch({
|
||||
'type': 'candidate-set',
|
||||
'position': position,
|
||||
'field': "name",
|
||||
'value': state.name,
|
||||
})
|
||||
dispatch({
|
||||
'type': 'candidate-set',
|
||||
'position': position,
|
||||
'field': "description",
|
||||
'value': state.description,
|
||||
})
|
||||
toggle();
|
||||
}
|
||||
|
||||
const handleName = (e) => {
|
||||
setState(s => ({...s, 'name': e.target.value}))
|
||||
}
|
||||
|
||||
const handleDescription = (e) => {
|
||||
setState(s => ({...s, 'description': e.target.value}))
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
toggle={toggle}
|
||||
keyboard={true}
|
||||
className='modal_candidate'
|
||||
>
|
||||
<div className="modal-header p-4">
|
||||
<h4 className="modal-title">
|
||||
{t('admin.add-candidate')}
|
||||
</h4>
|
||||
<button type="button" onClick={toggle} className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<ModalBody className='p-4'>
|
||||
<p>{t('admin.add-candidate-desc')}
|
||||
</p>
|
||||
<Col>
|
||||
<Form className='container container-fluid'>
|
||||
<Row className='gx-4 mb-3'>
|
||||
<Col className='col-auto'>
|
||||
<Image src={image} alt={t('admin.photo')} height={120} width={120} />
|
||||
</Col>
|
||||
<Col className='col-auto'>
|
||||
<Label className='fw-bold'>{t('admin.photo')} <span className='text-muted'> ({t('admin.optional')})</span></Label>
|
||||
<p>{t('admin.photo-type')} jpg, png, pdf</p>
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
className='hide'
|
||||
onChange={handleFile}
|
||||
ref={hiddenFileInput}
|
||||
/>
|
||||
<Button
|
||||
color='dark'
|
||||
outline={true}
|
||||
onClick={() => hiddenFileInput.current.click()}
|
||||
>
|
||||
{t('admin.photo-import')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
<div className='mb-3'>
|
||||
<Label className='fw-bold'>{t('common.name')} </Label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={t("admin.candidate-name-placeholder")}
|
||||
tabIndex={position + 1}
|
||||
value={state.name}
|
||||
onChange={handleName}
|
||||
maxLength="250"
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className=''>
|
||||
<Label className='fw-bold'>{t('common.description')} <span className='text-muted'> ({t('admin.optional')})</span></Label>
|
||||
<Input
|
||||
type="text"
|
||||
defaultValue={candidate.description}
|
||||
placeholder={t("admin.candidate-desc-placeholder")}
|
||||
onChange={handleDescription}
|
||||
value={state.description}
|
||||
maxLength="250"
|
||||
/>
|
||||
</div>
|
||||
<Row className='mt-5 mb-3'>
|
||||
<Col className='col-auto me-auto'>
|
||||
<Button onClick={toggle} color='dark' outline={true}>
|
||||
<Row className='gx-2 align-items-end'>
|
||||
<Col>
|
||||
<FontAwesomeIcon icon={faArrowLeft} />
|
||||
</Col>
|
||||
<Col>
|
||||
{t('common.cancel')}
|
||||
</Col>
|
||||
</Row>
|
||||
</Button>
|
||||
</Col>
|
||||
<Col className='col-auto '>
|
||||
<Button outline={true} color="primary" onClick={save}>
|
||||
<Row className='gx-2 align-items-end'>
|
||||
<Col>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</Col>
|
||||
<Col>
|
||||
{t('common.save')}
|
||||
</Col>
|
||||
</Row>
|
||||
</Button>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
</Form>
|
||||
</Col>
|
||||
</ModalBody >
|
||||
</Modal >);
|
||||
|
||||
}
|
||||
export default CandidateModal;
|
@ -1,5 +1,16 @@
|
||||
const {i18n} = require('./next-i18next.config.js')
|
||||
|
||||
const remoteImage = process.env.IMGPUSH_URL ? process.env.IMGPUSH_URL.split('/')[-1] : "imgpush.mieuxvoter.fr";
|
||||
|
||||
module.exports = {
|
||||
i18n,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: remoteImage,
|
||||
pathname: '**',
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* This is a mini-SDK to submit files upload to imgpush
|
||||
*/
|
||||
import {IMGPUSH_URL} from '@services/constants';
|
||||
|
||||
export const upload = async (photo) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', photo);
|
||||
formData.append('fileName', photo.name);
|
||||
return fetch(
|
||||
IMGPUSH_URL,
|
||||
{method: "POST", body: formData}
|
||||
)
|
||||
.then(ans => {return ans.json();})
|
||||
}
|
||||
|
Loading…
Reference in new issue