mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-09-11 05:46:28 +02:00
Merge from registration branch which was in prod
This commit is contained in:
BIN
src/app/img/barnums.jpg
Normal file
BIN
src/app/img/barnums.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
BIN
src/app/img/bene2019.jpg
Normal file
BIN
src/app/img/bene2019.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
BIN
src/app/img/pel2016.jpg
Normal file
BIN
src/app/img/pel2016.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
BIN
src/app/img/pel2017.jpg
Normal file
BIN
src/app/img/pel2017.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
BIN
src/app/img/plan2019.jpg
Normal file
BIN
src/app/img/plan2019.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 302 KiB |
67
src/components/Asks/AskDiscord.tsx
Normal file
67
src/components/Asks/AskDiscord.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { useCallback } from "react"
|
||||||
|
import { useSelector } from "react-redux"
|
||||||
|
import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
|
||||||
|
import styles from "./styles.module.scss"
|
||||||
|
import { useAskTools, addAsk } from "./utils"
|
||||||
|
import FormButton from "../Form/FormButton/FormButton"
|
||||||
|
import {
|
||||||
|
fetchVolunteerDiscordIdIfNeed,
|
||||||
|
selectVolunteerDiscordId,
|
||||||
|
} from "../../store/volunteerDiscordId"
|
||||||
|
|
||||||
|
export function AskDiscord(asks: JSX.Element[], id: number): void {
|
||||||
|
const { dispatch, jwtToken, volunteerAsks } = useAskTools()
|
||||||
|
const discordId: number | undefined = useSelector(selectVolunteerDiscordId)
|
||||||
|
|
||||||
|
const onSubmit = useCallback((): void => {
|
||||||
|
dispatch(
|
||||||
|
fetchVolunteerAsksSet(jwtToken, 0, {
|
||||||
|
hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}, [dispatch, id, jwtToken, volunteerAsks?.hiddenAsks])
|
||||||
|
|
||||||
|
const needToShow = !discordId
|
||||||
|
|
||||||
|
addAsk(
|
||||||
|
asks,
|
||||||
|
id,
|
||||||
|
volunteerAsks,
|
||||||
|
true,
|
||||||
|
needToShow,
|
||||||
|
<div className={styles.formLine}>
|
||||||
|
<p>
|
||||||
|
Discord nous permet gratuitement et sans pub de s'écrire entre bénévoles via nos
|
||||||
|
navigateurs ou smartphones. Et donc de s'organiser super efficacement !<br />
|
||||||
|
C'est un peu déroutant au début, mais extrêmement pratique car à chaque sujet de
|
||||||
|
discussion correspond un salon différent que tu peux demander à suivre ou ignorer
|
||||||
|
totalement via la gestion des notifications.
|
||||||
|
<br />
|
||||||
|
Pour rejoindre le serveur PeL, voici le lien d'invitation à cliquer :{" "}
|
||||||
|
<a href="https://discord.gg/eXhjKxSBB4" onClick={onSubmit}>
|
||||||
|
https://discord.gg/eXhjKxSBB4
|
||||||
|
</a>{" "}
|
||||||
|
!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Prends le temps de le rejoindre maintenant, c'est via cet outil que la plupart des
|
||||||
|
équipes s'organisent !
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Pour s'y retrouver tellement on est nombreux (plus de 120), il est nécessaire
|
||||||
|
d'avoir son prénom comme alias. Voir même d'avoir ensuite la première lettre de ton
|
||||||
|
nom de famille si un autre bénévole présent sur le serveur a le même prénom. Pour
|
||||||
|
changer ton alias uniquement sur le serveur PeL, il faut faire un clique droit sur
|
||||||
|
l'icône ronde du serveur en haut à gauche, et aller dans “Modifier le profil du
|
||||||
|
serveur”.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className={styles.formButtons}>
|
||||||
|
<FormButton onClick={onSubmit}>Ok, noté</FormButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch server-side data here
|
||||||
|
export const fetchFor = [fetchVolunteerDiscordIdIfNeed]
|
@@ -191,7 +191,7 @@ export function AskPushNotif(asks: JSX.Element[], id: number): void {
|
|||||||
volunteerAsks,
|
volunteerAsks,
|
||||||
true,
|
true,
|
||||||
needToShow,
|
needToShow,
|
||||||
<div className={styles.formLine} key="line-participation">
|
<div className={styles.formLine}>
|
||||||
<label>
|
<label>
|
||||||
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en aura
|
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en aura
|
||||||
d'autres à t'afficher ici ?<br />
|
d'autres à t'afficher ici ?<br />
|
||||||
|
@@ -3,19 +3,21 @@ import React, { memo } from "react"
|
|||||||
import styles from "./styles.module.scss"
|
import styles from "./styles.module.scss"
|
||||||
import { useAskTools } from "./utils"
|
import { useAskTools } from "./utils"
|
||||||
import { AskWelcome } from "./AskWelcome"
|
import { AskWelcome } from "./AskWelcome"
|
||||||
import { AskPushNotif } from "./AskPushNotif"
|
import { AskDiscord, fetchFor as fetchForDiscord } from "./AskDiscord"
|
||||||
import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
|
import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
|
||||||
import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
|
import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
|
||||||
import {
|
import {
|
||||||
AskParticipationDetails,
|
AskParticipationDetails,
|
||||||
fetchFor as fetchForParticipationDetails,
|
fetchFor as fetchForParticipationDetails,
|
||||||
} from "./AskParticipationDetails"
|
} from "./AskParticipationDetails"
|
||||||
|
import { AskPushNotif } from "./AskPushNotif"
|
||||||
|
|
||||||
const Asks = (): JSX.Element | null => {
|
const Asks = (): JSX.Element | null => {
|
||||||
const { volunteerAsks } = useAskTools()
|
const { volunteerAsks } = useAskTools()
|
||||||
const asks: JSX.Element[] = []
|
const asks: JSX.Element[] = []
|
||||||
|
|
||||||
AskWelcome(asks, 1)
|
AskWelcome(asks, 1)
|
||||||
|
AskDiscord(asks, 3)
|
||||||
|
|
||||||
AskDayWishes(asks, 10)
|
AskDayWishes(asks, 10)
|
||||||
AskTeamWishes(asks, 11)
|
AskTeamWishes(asks, 11)
|
||||||
@@ -28,7 +30,7 @@ const Asks = (): JSX.Element | null => {
|
|||||||
<div key="pushNotifs">
|
<div key="pushNotifs">
|
||||||
<div className={styles.notificationsPage}>
|
<div className={styles.notificationsPage}>
|
||||||
<div className={styles.notificationsContent}>
|
<div className={styles.notificationsContent}>
|
||||||
<div className={styles.formLine} key="line-participation">
|
<div className={styles.formLine}>
|
||||||
<label>
|
<label>
|
||||||
Tu as fait le tour des dernières infos ou questions importantes,
|
Tu as fait le tour des dernières infos ou questions importantes,
|
||||||
merci ! :)
|
merci ! :)
|
||||||
@@ -54,6 +56,7 @@ export default memo(Asks)
|
|||||||
|
|
||||||
// Fetch server-side data here
|
// Fetch server-side data here
|
||||||
export const fetchFor = [
|
export const fetchFor = [
|
||||||
|
...fetchForDiscord,
|
||||||
...fetchForDayWishes,
|
...fetchForDayWishes,
|
||||||
...fetchForTeamWishes,
|
...fetchForTeamWishes,
|
||||||
...fetchForParticipationDetails,
|
...fetchForParticipationDetails,
|
||||||
|
@@ -34,7 +34,7 @@ export function addAsk(
|
|||||||
volunteerAsks: VolunteerAsks | undefined,
|
volunteerAsks: VolunteerAsks | undefined,
|
||||||
isNarrow: boolean,
|
isNarrow: boolean,
|
||||||
needToShow: boolean,
|
needToShow: boolean,
|
||||||
children: JSX.Element
|
children: JSX.Element | undefined
|
||||||
): void {
|
): void {
|
||||||
const hidden = volunteerAsks?.hiddenAsks || []
|
const hidden = volunteerAsks?.hiddenAsks || []
|
||||||
if (_.includes(hidden, id) || !_.isEmpty(asks) || !needToShow) {
|
if (_.includes(hidden, id) || !_.isEmpty(asks) || !needToShow) {
|
||||||
|
@@ -1,24 +1,31 @@
|
|||||||
import { FC, ReactNode } from "react"
|
import { FC, ReactNode } from "react"
|
||||||
|
import { toastError } from "../../../store/utils"
|
||||||
import styles from "./styles.module.scss"
|
import styles from "./styles.module.scss"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
type?: "grey"
|
type?: "grey"
|
||||||
|
disabled?: boolean
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
onClick?: () => void
|
onClick?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormButton: FC<Props> = ({ type, children, onClick }): JSX.Element => (
|
const FormButton: FC<Props> = ({ type, disabled, children, onClick }): JSX.Element => {
|
||||||
<button
|
const onDisabledClick = () => toastError("Bouton désactivé")
|
||||||
type="button"
|
|
||||||
className={type === "grey" ? styles.greyButton : styles.button}
|
return (
|
||||||
onClick={onClick}
|
<button
|
||||||
>
|
type="button"
|
||||||
{children}
|
className={type === "grey" || disabled ? styles.greyButton : styles.button}
|
||||||
</button>
|
onClick={disabled ? onDisabledClick : onClick}
|
||||||
)
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
FormButton.defaultProps = {
|
FormButton.defaultProps = {
|
||||||
type: undefined,
|
type: undefined,
|
||||||
|
disabled: false,
|
||||||
onClick: undefined,
|
onClick: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,256 +0,0 @@
|
|||||||
import React, { memo, useState } from "react"
|
|
||||||
import { useSelector, shallowEqual } from "react-redux"
|
|
||||||
import { toast } from "react-toastify"
|
|
||||||
import _ from "lodash"
|
|
||||||
import styles from "./styles.module.scss"
|
|
||||||
|
|
||||||
import { fetchPreVolunteerAdd } from "../../store/preVolunteerAdd"
|
|
||||||
import { AppDispatch, AppState } from "../../store"
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
dispatch: AppDispatch
|
|
||||||
preVolunteerCount: number | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element => {
|
|
||||||
const [firstname, setFirstname] = useState("")
|
|
||||||
const [lastname, setLastname] = useState("")
|
|
||||||
const [email, setEmail] = useState("")
|
|
||||||
const [mobile, setMobile] = useState("")
|
|
||||||
const [alreadyVolunteer, setAlreadyVolunteer] = useState(false)
|
|
||||||
const [comment, setComment] = useState("")
|
|
||||||
const [sending, setSending] = useState(false)
|
|
||||||
|
|
||||||
const onFirstnameChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setFirstname(e.target.value)
|
|
||||||
const onLastnameChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setLastname(e.target.value)
|
|
||||||
const onEmailChanged = (e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)
|
|
||||||
const onMobileChanged = (e: React.ChangeEvent<HTMLInputElement>) => setMobile(e.target.value)
|
|
||||||
const onAlreadyVolunteer = (e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setAlreadyVolunteer(!!e.target.value)
|
|
||||||
const onNotYesVolunteer = (e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setAlreadyVolunteer(!e.target.value)
|
|
||||||
const onCommentChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
|
||||||
setComment(e.target.value)
|
|
||||||
|
|
||||||
const onSubmit = () => {
|
|
||||||
if (firstname && lastname && email && mobile && !sending) {
|
|
||||||
dispatch(
|
|
||||||
fetchPreVolunteerAdd({
|
|
||||||
firstname,
|
|
||||||
lastname,
|
|
||||||
email,
|
|
||||||
mobile,
|
|
||||||
alreadyVolunteer,
|
|
||||||
comment,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
setSending(true)
|
|
||||||
} else {
|
|
||||||
toast.warning("Il faut remplir tous les champs (sauf le dernier)", {
|
|
||||||
position: "top-center",
|
|
||||||
autoClose: 6000,
|
|
||||||
hideProgressBar: true,
|
|
||||||
closeOnClick: true,
|
|
||||||
pauseOnHover: true,
|
|
||||||
draggable: true,
|
|
||||||
progress: undefined,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { error, entities: preVolunteer } = useSelector(
|
|
||||||
(state: AppState) => state.preVolunteerAdd,
|
|
||||||
shallowEqual
|
|
||||||
)
|
|
||||||
|
|
||||||
let sendSuccess
|
|
||||||
if (!_.isEmpty(preVolunteer)) {
|
|
||||||
if (sending) {
|
|
||||||
setSending(false)
|
|
||||||
}
|
|
||||||
sendSuccess = <span className={styles.success}>Formulaire envoyé !</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendError
|
|
||||||
if (error && _.isEmpty(preVolunteer)) {
|
|
||||||
if (sending) {
|
|
||||||
setSending(false)
|
|
||||||
}
|
|
||||||
sendError = <span className={styles.error}>{error}</span>
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendingElement
|
|
||||||
if (sending) {
|
|
||||||
sendingElement = <span className={styles.sending}>Envoi en cours...</span>
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
firstname
|
|
||||||
lastname
|
|
||||||
mail
|
|
||||||
tel
|
|
||||||
j'ai déjà été bénévole pour PEL
|
|
||||||
un petit mot...
|
|
||||||
*/
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={onSubmit}>
|
|
||||||
<dl className={styles.preRegisterIntro} key="preRegister-intro">
|
|
||||||
<dt>Qu'est-ce que Paris est Ludique ?</dt>
|
|
||||||
<dd>
|
|
||||||
<p>
|
|
||||||
Un festival en plein air dédiée aux <b>jeux de société modernes</b> sous
|
|
||||||
toutes leurs formes.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
En 2019 lors de la dernière édition, ce sont <b>16 000</b> joueurs qui se
|
|
||||||
sont réunis sous 300 chapiteaux et 2 000 tables.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Les 2 jours que durent le festival sont entièrement dédiés à ce que le
|
|
||||||
public <b>JOUE</b>, que ce soit sur les stands d'éditeurs,
|
|
||||||
d'associations, d'animateurs bénévoles, du coin des petits
|
|
||||||
joueurs, de l'espace tournois, ou de l'espace prototypes.
|
|
||||||
</p>
|
|
||||||
</dd>
|
|
||||||
<dt>Et les bénévoles de PeL ?</dt>
|
|
||||||
<dd>
|
|
||||||
<p>
|
|
||||||
L'organisation du festival est <b>entièrement gérée par nous</b>, les
|
|
||||||
bénévoles. À aucun moment ça ne ressemble à du travail : nous faisons tout
|
|
||||||
pour passer <b>un aussi bon moment que les visiteurs</b> :)
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
D'ailleurs, un soir par mois nous nous réunissons pour un apéro ludique
|
|
||||||
où discuter de l'organisation ! On joue autant que les visiteurs, mais
|
|
||||||
sur toute l'année ^^
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Pendant le festival de 2019, nous étions <b>187 bénévoles</b> organisés en
|
|
||||||
équipes qui chouchoutent les visiteurs en les accueillant, en
|
|
||||||
s'assurant que tout se passe bien, ou encore en expliquant des règles
|
|
||||||
de jeux.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Une équipe est même dédiée au bien être des bénévoles en leur servant à
|
|
||||||
boire et à manger dans un espace à part où faire des pauses régulières. Et
|
|
||||||
puis nous hébergeons ceux d'entre nous qui habitent loin de Paris. Le
|
|
||||||
confort avant tout !
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Certains bénévoles sont visiteurs le samedi ou le dimanche pour vivre le
|
|
||||||
festival de l'intérieur. Les deux jours avant et le jour après le
|
|
||||||
festival, ceux qui le peuvent viennent préparer et ranger. Bref, chacun
|
|
||||||
participe à la hauteur de ses wishes et disponibilités !
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en
|
|
||||||
dînant avec les auteurs, illustrateurs et éditeurs présents sur le festival.
|
|
||||||
</p>
|
|
||||||
</dd>
|
|
||||||
<dt>
|
|
||||||
Si l'expérience pourrait vous tenter, remplissez le formulaire suivant pour
|
|
||||||
en discuter lors d'un des gros apéros mensuels !<br />
|
|
||||||
Cette inscription ne vous oblige en rien il s'agit juste d'une prise
|
|
||||||
de contact.
|
|
||||||
<br />
|
|
||||||
Les prochains sont les 21 décembre et 27 janvier, mais nous vous appelerons
|
|
||||||
d'ici là pour les détails :)
|
|
||||||
<br />
|
|
||||||
{/* */}
|
|
||||||
<span className={styles.lightTitle} hidden={(preVolunteerCount || 0) < 3}>
|
|
||||||
(Déjà {preVolunteerCount} inscrits !)
|
|
||||||
</span>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
<div className={styles.formLine} key="line-firstname">
|
|
||||||
<label htmlFor="firstname">Prénom</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="firstname"
|
|
||||||
required
|
|
||||||
value={firstname}
|
|
||||||
onChange={onFirstnameChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formLine} key="line-lastname">
|
|
||||||
<label htmlFor="lastname">Nom</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="lastname"
|
|
||||||
required
|
|
||||||
value={lastname}
|
|
||||||
onChange={onLastnameChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formLine} key="line-email">
|
|
||||||
<label htmlFor="email">Email</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
required
|
|
||||||
value={email}
|
|
||||||
onChange={onEmailChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formLine} key="line-mobile">
|
|
||||||
<label htmlFor="mobile">Téléphone</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="mobile"
|
|
||||||
required
|
|
||||||
value={mobile}
|
|
||||||
onChange={onMobileChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formLine} key="line-already-volunteer">
|
|
||||||
<div>
|
|
||||||
J'ai déjà été bénévole à PeL
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="alreadyVolunteer"
|
|
||||||
id="alreadyVolunteer-yes"
|
|
||||||
className={styles.inputRadio}
|
|
||||||
checked={alreadyVolunteer}
|
|
||||||
onChange={onAlreadyVolunteer}
|
|
||||||
/>
|
|
||||||
<label htmlFor="alreadyVolunteer-yes">Oui</label>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="alreadyVolunteer"
|
|
||||||
id="alreadyVolunteer-no"
|
|
||||||
className={styles.inputRadio}
|
|
||||||
checked={!alreadyVolunteer}
|
|
||||||
onChange={onNotYesVolunteer}
|
|
||||||
/>
|
|
||||||
<label htmlFor="alreadyVolunteer-no">Non</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formLine} key="line-message">
|
|
||||||
<textarea
|
|
||||||
name="message"
|
|
||||||
id="message"
|
|
||||||
placeholder="Des petits mots sympas, questions, wishes, des infos sur toi, des compétences dont tu aimerais te servir... ou rien de tout ça et nous en discuterons au téléphone :)"
|
|
||||||
value={comment}
|
|
||||||
onChange={onCommentChanged}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formButtons}>
|
|
||||||
<button type="button" onClick={onSubmit} disabled={sending}>
|
|
||||||
Envoyer
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className={styles.formReactions}>
|
|
||||||
{sendingElement}
|
|
||||||
{sendSuccess}
|
|
||||||
{sendError}
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(PreRegisterForm)
|
|
@@ -1,76 +0,0 @@
|
|||||||
@import "../../theme/variables";
|
|
||||||
@import "../../theme/mixins";
|
|
||||||
|
|
||||||
.preRegisterIntro {
|
|
||||||
dt {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
dd {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin-block-start: 0.3em;
|
|
||||||
margin-block-end: 0.3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightTitle {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formLine {
|
|
||||||
padding: 5px 0;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
input,
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 4px;
|
|
||||||
border: 1px solid #333;
|
|
||||||
border-radius: 4px;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
.inputRadio {
|
|
||||||
margin-left: 12px;
|
|
||||||
width: inherit;
|
|
||||||
}
|
|
||||||
.inputRadio + label {
|
|
||||||
display: inline;
|
|
||||||
margin: 0 0 0 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.formButtons {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding: 5px 0;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
[disabled="true"] {
|
|
||||||
background-color: #333333c0;
|
|
||||||
color: #cccccce7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.formReactions {
|
|
||||||
margin-top: 3px;
|
|
||||||
padding: 5px 0;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.sending {
|
|
||||||
color: rgb(0, 0, 255);
|
|
||||||
}
|
|
||||||
.success {
|
|
||||||
color: rgb(0, 133, 0);
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: rgb(255, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
793
src/components/RegisterForm/index.tsx
Normal file
793
src/components/RegisterForm/index.tsx
Normal file
@@ -0,0 +1,793 @@
|
|||||||
|
import React, { memo, useEffect, useState } from "react"
|
||||||
|
import { useSelector, shallowEqual } from "react-redux"
|
||||||
|
import { toast } from "react-toastify"
|
||||||
|
import _ from "lodash"
|
||||||
|
import classnames from "classnames"
|
||||||
|
import styles from "./styles.module.scss"
|
||||||
|
|
||||||
|
import { fetchPostulantAdd } from "../../store/postulantAdd"
|
||||||
|
import { AppDispatch, AppState } from "../../store"
|
||||||
|
import { fetchVolunteerPartialAdd } from "../../store/volunteerPartialAdd"
|
||||||
|
import FormButton from "../Form/FormButton/FormButton"
|
||||||
|
import { validEmail } from "../../utils/standardization"
|
||||||
|
import { toastError } from "../../store/utils"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
dispatch: AppDispatch
|
||||||
|
}
|
||||||
|
|
||||||
|
const animations = [
|
||||||
|
[styles.imgTransitionDoHide, styles.imgTransitionShow],
|
||||||
|
[styles.imgTransitionHidden, styles.imgTransitionShow],
|
||||||
|
[styles.imgTransitionReset, styles.imgTransitionShow],
|
||||||
|
[styles.imgTransitionAbouToShow, styles.imgTransitionShow],
|
||||||
|
[styles.imgTransitionShow, styles.imgTransitionDoHide],
|
||||||
|
[styles.imgTransitionShow, styles.imgTransitionHidden],
|
||||||
|
[styles.imgTransitionShow, styles.imgTransitionReset],
|
||||||
|
[styles.imgTransitionShow, styles.imgTransitionAbouToShow],
|
||||||
|
]
|
||||||
|
const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
||||||
|
const [potentialVolunteer, setPotentialVolunteer] = useState(true)
|
||||||
|
const [firstname, setFirstname] = useState("")
|
||||||
|
const [lastname, setLastname] = useState("")
|
||||||
|
const [email, setEmail] = useState("")
|
||||||
|
const [mobile, setMobile] = useState("")
|
||||||
|
const [alreadyVolunteer, setAlreadyVolunteer] = useState(false)
|
||||||
|
const [comment, setComment] = useState("")
|
||||||
|
const [alreadyCame, setAlreadyCame] = useState(true)
|
||||||
|
const [firstMeeting, setFirstMeeting] = useState("")
|
||||||
|
const [commentFirstMeeting, setCommentFirstMeeting] = useState("")
|
||||||
|
const [canHelpBefore, setCanHelpBefore] = useState("")
|
||||||
|
const [pelMember, setPelMember] = useState(false)
|
||||||
|
const [howToContact, setHowToContact] = useState("Email")
|
||||||
|
const [sending, setSending] = useState(false)
|
||||||
|
const [changingBackground, setChangingBackground] = useState(0)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setChangingBackground((changingBackground + 1) % animations.length)
|
||||||
|
}, 60000 / animations.length)
|
||||||
|
return () => clearInterval(timer)
|
||||||
|
}, [changingBackground, setChangingBackground])
|
||||||
|
const transitionClass = (i: number) => animations[changingBackground][i - 1]
|
||||||
|
|
||||||
|
const sendTextDispatch =
|
||||||
|
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatchSetter(e.target.value)
|
||||||
|
|
||||||
|
const sendTextareaDispatch =
|
||||||
|
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||||
|
(e: React.ChangeEvent<HTMLTextAreaElement>) =>
|
||||||
|
dispatchSetter(e.target.value)
|
||||||
|
|
||||||
|
const sendBooleanRadioboxDispatch =
|
||||||
|
(dispatchSetter: React.Dispatch<React.SetStateAction<boolean>>, isYes: boolean) =>
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatchSetter(isYes ? !!e.target.value : !e.target.value)
|
||||||
|
|
||||||
|
const sendRadioboxDispatch =
|
||||||
|
(dispatchSetter: React.Dispatch<React.SetStateAction<string>>) =>
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatchSetter(e.target.value)
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (!validEmail(email)) {
|
||||||
|
toastError("Cet email est invalid ><")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!firstname || !lastname || !email || !mobile || sending) {
|
||||||
|
toast.warning("Il faut remplir les quelques infos sur toi ><", {
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 6000,
|
||||||
|
hideProgressBar: true,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true,
|
||||||
|
progress: undefined,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (potentialVolunteer) {
|
||||||
|
dispatch(
|
||||||
|
fetchPostulantAdd({
|
||||||
|
potential: true,
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
|
email,
|
||||||
|
mobile,
|
||||||
|
howToContact,
|
||||||
|
alreadyCame,
|
||||||
|
firstMeeting,
|
||||||
|
commentFirstMeeting: firstMeeting ? "" : commentFirstMeeting,
|
||||||
|
comment,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
fetchVolunteerPartialAdd({
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
|
email,
|
||||||
|
mobile,
|
||||||
|
howToContact,
|
||||||
|
canHelpBefore,
|
||||||
|
pelMember,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
dispatch(
|
||||||
|
fetchPostulantAdd({
|
||||||
|
potential: false,
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
|
email,
|
||||||
|
mobile,
|
||||||
|
howToContact,
|
||||||
|
alreadyCame,
|
||||||
|
firstMeeting,
|
||||||
|
commentFirstMeeting: firstMeeting ? "" : commentFirstMeeting,
|
||||||
|
comment,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSending(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error: postulantError, entities: postulant } = useSelector(
|
||||||
|
(state: AppState) => state.postulantAdd,
|
||||||
|
shallowEqual
|
||||||
|
)
|
||||||
|
|
||||||
|
const { error: volunteerError, entities: volunteer } = useSelector(
|
||||||
|
(state: AppState) => state.volunteerAdd,
|
||||||
|
shallowEqual
|
||||||
|
)
|
||||||
|
|
||||||
|
let sendSuccess
|
||||||
|
let sendError
|
||||||
|
let sendingElement
|
||||||
|
if (
|
||||||
|
!postulantError &&
|
||||||
|
!_.isEmpty(postulant) &&
|
||||||
|
(potentialVolunteer || (!volunteerError && !_.isEmpty(volunteer)))
|
||||||
|
) {
|
||||||
|
if (sending) {
|
||||||
|
setSending(false)
|
||||||
|
}
|
||||||
|
sendSuccess = <span className={styles.success}>Formulaire envoyé !</span>
|
||||||
|
} else if (postulantError && _.isEmpty(postulant)) {
|
||||||
|
if (sending) {
|
||||||
|
setSending(false)
|
||||||
|
}
|
||||||
|
sendError = <span className={styles.error}>{postulantError}</span>
|
||||||
|
} else if (volunteerError && _.isEmpty(volunteer)) {
|
||||||
|
if (sending) {
|
||||||
|
setSending(false)
|
||||||
|
}
|
||||||
|
sendError = <span className={styles.error}>{volunteerError}</span>
|
||||||
|
} else if (sending) {
|
||||||
|
sendingElement = (
|
||||||
|
<span className={styles.sending}>
|
||||||
|
Envoi en cours...
|
||||||
|
<br />
|
||||||
|
En cas de problème, écrire à contact@parisestludique.fr
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const intro = (
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dt>Qu'est-ce que Paris est Ludique ?</dt>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
Un festival en plein air dédié aux <b>jeux de société modernes</b> sous toutes
|
||||||
|
leurs formes. Les samedi 2 et dimanche 3 juillet 2022 !
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
En 2019 lors de la dernière édition, ce sont <b>16 000</b> joueurs qui se sont
|
||||||
|
réunis sous 300 chapiteaux et 2 000 tables.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Les 2 jours que durent le festival sont entièrement dédiés à ce que le public{" "}
|
||||||
|
<b>JOUE</b>, que ce soit sur les stands d'éditeurs, d'associations,
|
||||||
|
d'animateurs bénévoles, du coin des petits joueurs, de l'espace
|
||||||
|
tournois, ou de l'espace prototypes.
|
||||||
|
</p>
|
||||||
|
<div id="pelImg" className={styles.pelImg}>
|
||||||
|
<div className={classnames(styles.pelImg1, transitionClass(1))}> </div>
|
||||||
|
<div className={classnames(styles.pelImg2, transitionClass(2))}> </div>
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
<dt>Et les bénévoles de PeL ?</dt>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
L'organisation du festival est <b>entièrement gérée par nous</b>, les
|
||||||
|
bénévoles. À aucun moment ça ne ressemble à du travail : nous faisons tout pour
|
||||||
|
passer <b>un aussi bon moment que les visiteurs</b> :)
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
D'ailleurs, un soir par mois nous nous réunissons pour un apéro ludique où
|
||||||
|
discuter de l'organisation ! On joue autant que les visiteurs, mais sur
|
||||||
|
toute l'année ^^
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Pendant le festival de 2019, nous étions <b>187 bénévoles</b> organisés en
|
||||||
|
équipes qui chouchoutent les visiteurs en les accueillant, en s'assurant
|
||||||
|
que tout se passe bien, ou encore en expliquant des règles de jeux.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Une équipe est même dédiée au bien être des bénévoles en leur servant à boire et
|
||||||
|
à manger dans un espace à part où faire des pauses régulières. Et puis nous
|
||||||
|
hébergeons ceux d'entre nous qui habitent loin de Paris. Le confort avant
|
||||||
|
tout !
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
La majorité d'entre nous sommes bénévoles les <b>samedi et dimanche</b>, mais
|
||||||
|
certains bénévoles ne sont pas disponibles les deux jours. On leur demande alors
|
||||||
|
d'aider à la mise en place jeudi ou vendredi, ou au rangement le lundi, à la
|
||||||
|
place d'un des jours du weekend. Bref, chacun participe comme il peut mais deux
|
||||||
|
jours minimum !
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en
|
||||||
|
dînant avec les exposants présents sur le festival. Le dimanche rebelote juste
|
||||||
|
entre bénévoles.
|
||||||
|
</p>
|
||||||
|
<div className={styles.beneImg}> </div>
|
||||||
|
</dd>
|
||||||
|
<dt>
|
||||||
|
Si l'expérience vous tente, remplissez le formulaire suivant pour devenir
|
||||||
|
bénévole !<br />
|
||||||
|
Vous pouvez aussi juste nous rencontrer avant de vous décider à devenir bénévole, on
|
||||||
|
comprend qu'un saut pareil dans l'inconnu soit difficile.
|
||||||
|
<br />
|
||||||
|
Dans les deux cas, venez rencontrer une poignée d'entre nous dans un bar/resto près
|
||||||
|
de Châtelet ! :) Sur inscription uniquement...
|
||||||
|
<br />
|
||||||
|
</dt>
|
||||||
|
</dl>
|
||||||
|
)
|
||||||
|
|
||||||
|
const potentialVolunteerQuestion = (
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.multipleChoiceTitle}>Je veux devenir bénévole :</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
{["Tout de suite !", "Peut-être après une rencontre avec des bénévoles"].map(
|
||||||
|
(option) => (
|
||||||
|
<label className={styles.longAnswerLabel} key={option}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="potentialVolunteer"
|
||||||
|
onChange={sendBooleanRadioboxDispatch(
|
||||||
|
setPotentialVolunteer,
|
||||||
|
option !== "Tout de suite !"
|
||||||
|
)}
|
||||||
|
checked={potentialVolunteer === (option !== "Tout de suite !")}
|
||||||
|
/>{" "}
|
||||||
|
{option}
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
const alreadyVolunteerQuestion = !potentialVolunteer && (
|
||||||
|
<>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.multipleChoiceTitle}>J'ai déjà été bénévole à PeL</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<div className={styles.rightColContainer}>
|
||||||
|
{["Oui", "Non"].map((option) => (
|
||||||
|
<label className={styles.shortAnswerLabel} key={option}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="alreadyVolunteer"
|
||||||
|
onChange={sendBooleanRadioboxDispatch(
|
||||||
|
setAlreadyVolunteer,
|
||||||
|
option === "Oui"
|
||||||
|
)}
|
||||||
|
checked={alreadyVolunteer === (option === "Oui")}
|
||||||
|
/>{" "}
|
||||||
|
{option}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{alreadyVolunteer && (
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dd>
|
||||||
|
<p>Dans ce cas pourquoi t'inscris-tu ici ? ^^</p>
|
||||||
|
<p>
|
||||||
|
Si tu te rappelles de l'email que tu avais utilisé à ta dernière
|
||||||
|
inscription sur le site Force Orange des bénévoles (même sur l'ancienne
|
||||||
|
version) tu peux{" "}
|
||||||
|
<a href="/sidentifier" target="_blank" rel="noreferrer">
|
||||||
|
t'identifier ici
|
||||||
|
</a>{" "}
|
||||||
|
avec ton ancien mot de passe, ou en{" "}
|
||||||
|
<a href="/sinscrire" target="_blank" rel="noreferrer">
|
||||||
|
demander un nouveau ici
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Autrement, si tu as changé d'email, mieux vaut nous le communiquer à
|
||||||
|
benevoles@parisestludique.fr en précisant bien tes nom et prénom :)
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const commentQuestion = (
|
||||||
|
<dl className={styles.inputWrapper}>
|
||||||
|
<dd className={styles.commentWrapper}>
|
||||||
|
<textarea
|
||||||
|
name="message"
|
||||||
|
id="message"
|
||||||
|
className={styles.inputWrapper}
|
||||||
|
placeholder="Peux-tu nous dire ici comment tu as connu le festival, ce qui te motive à nous rejoindre, quelles compétences tu aimerais éventuellement développer ou utiliser... ou tu n'y as pas trop réfléchi et tu trouveras en discutant avec nous :)"
|
||||||
|
value={comment}
|
||||||
|
onChange={sendTextareaDispatch(setComment)}
|
||||||
|
/>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
)
|
||||||
|
|
||||||
|
const cameAsVisitor = (
|
||||||
|
<>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.multipleChoiceTitle}>
|
||||||
|
Es-tu déjà venu à PeL en tant que visiteur ?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<div className={styles.rightColContainer}>
|
||||||
|
{["Oui", "Non"].map((option) => (
|
||||||
|
<label className={styles.shortAnswerLabel} key={option}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="alreadyCame"
|
||||||
|
onChange={sendBooleanRadioboxDispatch(
|
||||||
|
setAlreadyCame,
|
||||||
|
option === "Oui"
|
||||||
|
)}
|
||||||
|
checked={alreadyCame === (option === "Oui")}
|
||||||
|
/>{" "}
|
||||||
|
{option}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!alreadyCame && (
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dt>Dans ce cas laisse-moi t'en dire un peu plus sur le festival !</dt>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
Il a lieu peu après la Foire du Trône et au même emplacement, sur la{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.google.com/maps/place/Pelouse+de+Reuilly,+75012+Paris/@48.8301639,2.4043365,17z/data=!3m1!4b1!4m5!3m4!1s0x47e67258d44aa311:0x2c6a08bb6aa88f4d!8m2!3d48.8301604!4d2.4065252?hl=en"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
"pelouse" de Reuilly
|
||||||
|
</a>
|
||||||
|
. En voici le plan de 2019, quand il s'étendait sur 2 hectares pour
|
||||||
|
accueillir 16 000 visiteurs. La plupart des rectangles colorés que tu
|
||||||
|
vois dessus sont d'énormes barnums, ou agglomérats de tonnelles.
|
||||||
|
</p>
|
||||||
|
<div className={styles.barnumsImg}> </div>
|
||||||
|
<div className={styles.planImg}> </div>
|
||||||
|
<p>
|
||||||
|
Les espaces jeux bleu, violet, gris, ou marron sont installés et animés
|
||||||
|
par des éditeurs professionnels. Des pros gèrent les zones de
|
||||||
|
restauration rouges. Tout le reste est tenu par des associations ou des
|
||||||
|
bénévoles du festival.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Après l'édition de 2019, on a fait cette petite vidéo qui donne une
|
||||||
|
bonne impression de l'ambiance :{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/watch?v=eVuQaERb7EU"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
https://www.youtube.com/watch?v=eVuQaERb7EU
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Et des visiteurs passionnés (
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/c/RoadNTroll"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Road N Troll
|
||||||
|
</a>
|
||||||
|
) ont fait cette présentation plus en détail des différentes zones en
|
||||||
|
2018. J'ai coupé les présentations de jeux mais tout le reste est encore
|
||||||
|
d'actualité :{" "}
|
||||||
|
<a
|
||||||
|
href="https://www.youtube.com/watch?v=jSCHWqjHJIQ"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
https://www.youtube.com/watch?v=jSCHWqjHJIQ
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const meeting = (
|
||||||
|
<>
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
{!potentialVolunteer && <dt>Faisons connaissance !</dt>}
|
||||||
|
{potentialVolunteer && (
|
||||||
|
<dt>Se rencontrer avant de se décider à devenir bénévole ?</dt>
|
||||||
|
)}
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
On organise des rencontres entre de nouvelles personnes comme toi, et des
|
||||||
|
bénévoles suffisamment expérimentés pour te parler en détail du festival et
|
||||||
|
répondre à toutes tes questions liées au bénévolat ou au festival.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Ces rencontres ont lieu dans un bar/resto calme à Châtelet, le{" "}
|
||||||
|
<a
|
||||||
|
href="https://goo.gl/maps/N5NYWDF66vNQDFMh8"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Street Food Market
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.multipleChoiceTitle}>
|
||||||
|
À quelle date pourrais-tu venir ?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<div className={styles.rightColContainer}>
|
||||||
|
{[
|
||||||
|
{ value: "20avril", desc: "Mercredi 20 avril à 19h" },
|
||||||
|
{ value: "13mai", desc: "Vendredi 13 mai à 19h30" },
|
||||||
|
{ value: "", desc: "Aucune date possible" },
|
||||||
|
].map((option) => (
|
||||||
|
<label className={styles.longAnswerLabel} key={option.value}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="firstMeeting"
|
||||||
|
value={option.value}
|
||||||
|
onChange={sendRadioboxDispatch(setFirstMeeting)}
|
||||||
|
checked={firstMeeting === option.value}
|
||||||
|
/>{" "}
|
||||||
|
{option.desc}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{firstMeeting !== "" && (
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
Top ! On fait en sorte qu'il y ait assez de bénévoles expérimentés pour
|
||||||
|
les nombreux curieux comme toi, donc pour ne pas gâcher leur temps on
|
||||||
|
compte sur ta présence :)
|
||||||
|
</p>
|
||||||
|
<p>Si tu as un contre-temps, écris-nous à benevoles@parisestludique.fr</p>
|
||||||
|
<p>À très bientôt !</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{firstMeeting === "" && (
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.commentWrapper}>
|
||||||
|
<textarea
|
||||||
|
name="commentFirstMeeting"
|
||||||
|
id="commentFirstMeeting"
|
||||||
|
className={styles.inputWrapper}
|
||||||
|
placeholder={
|
||||||
|
potentialVolunteer
|
||||||
|
? "Mince. Quelles dates t'arrangeraient ? Ou si c'est plus simple, quels jours sont à éviter ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?"
|
||||||
|
: "Ce n'est pas obligé mais ça aurait été top ! Manques-tu de temps ? Préfères-tu une autre date ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?"
|
||||||
|
}
|
||||||
|
value={commentFirstMeeting}
|
||||||
|
onChange={sendTextareaDispatch(setCommentFirstMeeting)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const helpBefore = !potentialVolunteer && (
|
||||||
|
<>
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dt>Bénévolat en amont du festival</dt>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
En tant que bénévole, tu fais selon tes envies, tes disponibilités, ton
|
||||||
|
énergie. Si personne ne veut faire quelque chose de primordial pour le
|
||||||
|
festival, on paye quelqu'un de l'extérieur. Par exemple le transport+montage
|
||||||
|
des tentes+tables, ou la sécurité de nuit sont délégués à des prestataires.
|
||||||
|
Et si ce quelque chose n'est pas primordiale et que personne ne veut s'en
|
||||||
|
occuper, bah tant pis on le fait pas ^^
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Après on essaye de faire plein de choses sans aide extérieure. Pour le
|
||||||
|
plaisir de collaborer à un projet entre bénévoles parfois devenus amis, pour
|
||||||
|
acquérir de nouvelles compétences, parce que chaque économie d'argent fait
|
||||||
|
baisser le prix d'entrée au festival et contribue à le rendre plus
|
||||||
|
accessible.
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.multipleChoiceTitle}>
|
||||||
|
Bref, as-tu le temps et l'envie de voir si tu peux aider en amont du
|
||||||
|
festival ?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<div className={styles.rightColContainer}>
|
||||||
|
{[
|
||||||
|
{ value: "oui", desc: "Oui" },
|
||||||
|
{ value: "non", desc: "Non" },
|
||||||
|
{ value: "", desc: "Ne sais pas" },
|
||||||
|
].map((option) => (
|
||||||
|
<label className={styles.shortAnswerLabel} key={option.value}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="canHelpBefore"
|
||||||
|
value={option.value}
|
||||||
|
onChange={sendRadioboxDispatch(setCanHelpBefore)}
|
||||||
|
checked={canHelpBefore === option.value}
|
||||||
|
/>{" "}
|
||||||
|
{option.desc}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dd>
|
||||||
|
{canHelpBefore === "oui" && (
|
||||||
|
<p>
|
||||||
|
Génial ! Quand tu auras fini de t'inscrire et que tu seras identifié sur
|
||||||
|
le site, nous t'en parlerons plus en détail.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{canHelpBefore === "non" && (
|
||||||
|
<p>
|
||||||
|
Aucun souci tu nous seras d'une aide précieuse le jour J c'est déjà
|
||||||
|
énorme !
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
Si tu changes d'avis, il sera possible de revenir sur cette décision dans
|
||||||
|
ton profil sur le site.
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const pelMemberQuestion = !potentialVolunteer && (
|
||||||
|
<>
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dt>Association Paris est Ludique</dt>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
Légalement il faut que le festival soit organisé par une structure, et c'est
|
||||||
|
l'association <i>Paris est Ludique !</i> qui s'en charge. Pour aider à
|
||||||
|
organiser bénévolement le festival il faut donc en faire partie. Ça n'engage
|
||||||
|
à rien et c'est gratuit, mais absolument nécessaire.
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.multipleChoiceTitle}>
|
||||||
|
Acceptes-tu de devenir membre de l'association <i>Paris est Ludique !</i> ?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<div className={styles.rightColContainer}>
|
||||||
|
{["Oui", "Non"].map((option) => (
|
||||||
|
<label className={styles.shortAnswerLabel} key={option}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="pelMember"
|
||||||
|
onChange={sendBooleanRadioboxDispatch(
|
||||||
|
setPelMember,
|
||||||
|
option === "Oui"
|
||||||
|
)}
|
||||||
|
checked={pelMember === (option === "Oui")}
|
||||||
|
/>{" "}
|
||||||
|
{option}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!pelMember && (
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
Tant que tu n'as pas accepté cette condition je suis désolé on ne peut
|
||||||
|
pas continuer.
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const nameMobileEmail = (
|
||||||
|
<>
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dt>Quelques infos sur toi pour finir</dt>
|
||||||
|
</dl>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftColTiny}>
|
||||||
|
<label htmlFor="firstname">Prénom</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColLefter}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="firstname"
|
||||||
|
required
|
||||||
|
value={firstname}
|
||||||
|
onChange={sendTextDispatch(setFirstname)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftColTiny}>
|
||||||
|
<label htmlFor="lastname">Nom</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColLefter}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastname"
|
||||||
|
required
|
||||||
|
value={lastname}
|
||||||
|
onChange={sendTextDispatch(setLastname)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftColTiny}>
|
||||||
|
<label htmlFor="email">Email</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColLefter}>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
required
|
||||||
|
value={email}
|
||||||
|
onChange={sendTextDispatch(setEmail)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftColTiny}>
|
||||||
|
<label htmlFor="mobile">Téléphone</label>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightColLefter}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="mobile"
|
||||||
|
required
|
||||||
|
value={mobile}
|
||||||
|
onChange={sendTextDispatch(setMobile)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.multipleChoiceTitle}>
|
||||||
|
Par quel moyen fiable et rapide préfères-tu être contacté si on en a besoin
|
||||||
|
?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<div className={styles.rightColContainer}>
|
||||||
|
{["Email", "SMS", "WhatsApp", "Signal", "Appel", "Aucun"].map((option) => (
|
||||||
|
<label className={styles.shortAnswerLabel} key={option}>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="howToContact"
|
||||||
|
value={option}
|
||||||
|
onChange={sendRadioboxDispatch(setHowToContact)}
|
||||||
|
checked={howToContact === option}
|
||||||
|
/>{" "}
|
||||||
|
{option}
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{howToContact === "Aucun" && (
|
||||||
|
<dl className={styles.registerIntro}>
|
||||||
|
<dd>
|
||||||
|
<p>
|
||||||
|
Aïe ça va poser problème, je suis désolé. Il faut faire un effort en
|
||||||
|
choisissant un moyen de communication proposé.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Tu en connais un suffisamment répandu et meilleur que ceux proposés ?
|
||||||
|
Parle-nous en à benevoles@parisestludique.fr :)
|
||||||
|
</p>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
const submitButton = (
|
||||||
|
<>
|
||||||
|
<div className={styles.buttonWrapper}>
|
||||||
|
<FormButton onClick={onSubmit} disabled={!potentialVolunteer && alreadyVolunteer}>
|
||||||
|
Envoyer
|
||||||
|
</FormButton>
|
||||||
|
</div>
|
||||||
|
<div className={styles.formReactions}>
|
||||||
|
{sendingElement}
|
||||||
|
{sendSuccess}
|
||||||
|
{sendError}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form>
|
||||||
|
{intro}
|
||||||
|
{potentialVolunteerQuestion}
|
||||||
|
{alreadyVolunteerQuestion}
|
||||||
|
|
||||||
|
{(potentialVolunteer || !alreadyVolunteer) && (
|
||||||
|
<>
|
||||||
|
{commentQuestion}
|
||||||
|
{cameAsVisitor}
|
||||||
|
{meeting}
|
||||||
|
{helpBefore}
|
||||||
|
{pelMemberQuestion}
|
||||||
|
{(potentialVolunteer || pelMember) && (
|
||||||
|
<>
|
||||||
|
{nameMobileEmail}
|
||||||
|
{howToContact !== "Aucun" && submitButton}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(RegisterForm)
|
223
src/components/RegisterForm/styles.module.scss
Executable file
223
src/components/RegisterForm/styles.module.scss
Executable file
@@ -0,0 +1,223 @@
|
|||||||
|
@import "../../theme/variables";
|
||||||
|
@import "../../theme/mixins";
|
||||||
|
|
||||||
|
.registerIntro {
|
||||||
|
dt {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
dd {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-block-start: 0.3em;
|
||||||
|
margin-block-end: 0.3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lightTitle {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputWrapper {
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: 10px 0;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
min-width: 175px;
|
||||||
|
border: 1px solid $color-grey-medium;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftCol {
|
||||||
|
flex: 0 0 240px;
|
||||||
|
}
|
||||||
|
.leftColTiny {
|
||||||
|
flex: 0 0 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightCol,
|
||||||
|
.rightColLefter {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.rightCol {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.rightColLefter {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightColContainer {
|
||||||
|
display: inline-block;
|
||||||
|
width: 300px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multipleChoiceTitle {
|
||||||
|
display: inline-block;
|
||||||
|
width: 240px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.longAnswerLabel {
|
||||||
|
text-align: left;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortAnswerLabel {
|
||||||
|
text-align: left;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonWrapper {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
[disabled="true"] {
|
||||||
|
background-color: #333333c0;
|
||||||
|
color: #cccccce7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.commentWrapper {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 14px;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 120px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid $color-grey-light;
|
||||||
|
background-color: $color-grey-lighter;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.formReactions {
|
||||||
|
margin-top: 3px;
|
||||||
|
padding: 5px 0;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.sending {
|
||||||
|
color: rgb(0, 0, 255);
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
color: rgb(0, 133, 0);
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: rgb(255, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.imgTransitionReset {
|
||||||
|
left: calc(90vw);
|
||||||
|
transition: none;
|
||||||
|
@include desktop {
|
||||||
|
left: 552px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.imgTransitionAbouToShow {
|
||||||
|
left: calc(90vw);
|
||||||
|
transition: left ease-in-out 1000ms;
|
||||||
|
@include desktop {
|
||||||
|
left: 552px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.imgTransitionShow {
|
||||||
|
left: 0;
|
||||||
|
transition: left ease-in-out 1000ms;
|
||||||
|
}
|
||||||
|
.imgTransitionDoHide {
|
||||||
|
left: calc(-90vw);
|
||||||
|
transition: left ease-in-out 1000ms;
|
||||||
|
@include desktop {
|
||||||
|
left: -552px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.imgTransitionHidden {
|
||||||
|
left: calc(-90vw);
|
||||||
|
transition: none;
|
||||||
|
@include desktop {
|
||||||
|
left: -552px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pelImg1 {
|
||||||
|
background: url("../../app/img/pel2016.jpg") no-repeat center center;
|
||||||
|
}
|
||||||
|
.pelImg2 {
|
||||||
|
background: url("../../app/img/pel2017.jpg") no-repeat center center;
|
||||||
|
}
|
||||||
|
.pelImg1,
|
||||||
|
.pelImg2 {
|
||||||
|
position: absolute;
|
||||||
|
width: calc(90vw);
|
||||||
|
height: calc(60vw);
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
width: 552px;
|
||||||
|
height: 368px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pelImg {
|
||||||
|
position: relative;
|
||||||
|
width: calc(90vw);
|
||||||
|
height: calc(60vw);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
width: 552px;
|
||||||
|
height: 368px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.beneImg {
|
||||||
|
position: relative;
|
||||||
|
width: calc(90vw);
|
||||||
|
height: calc(40vw);
|
||||||
|
background: url("../../app/img/bene2019.jpg") no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
width: 552px;
|
||||||
|
height: 249px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.planImg {
|
||||||
|
position: relative;
|
||||||
|
width: calc(90vw);
|
||||||
|
height: calc(168vw);
|
||||||
|
background: url("../../app/img/plan2019.jpg") no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
width: 552px;
|
||||||
|
height: 1028px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.barnumsImg {
|
||||||
|
position: relative;
|
||||||
|
width: calc(90vw);
|
||||||
|
height: calc(18vw);
|
||||||
|
background: url("../../app/img/barnums.jpg") no-repeat center center;
|
||||||
|
background-size: cover;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
width: 552px;
|
||||||
|
height: 112px;
|
||||||
|
}
|
||||||
|
}
|
@@ -11,8 +11,8 @@ import Asks, { fetchFor as fetchForAsks } from "./Asks"
|
|||||||
import ParticipationDetailsForm, {
|
import ParticipationDetailsForm, {
|
||||||
fetchFor as fetchForParticipationDetailsForm,
|
fetchFor as fetchForParticipationDetailsForm,
|
||||||
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
|
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
|
||||||
import PreRegisterForm from "./PreRegisterForm"
|
|
||||||
import TeamAssignment, { fetchFor as fetchForTeamAssignment } from "./TeamAssignment/TeamAssignment"
|
import TeamAssignment, { fetchFor as fetchForTeamAssignment } from "./TeamAssignment/TeamAssignment"
|
||||||
|
import RegisterForm from "./RegisterForm"
|
||||||
import TeamWishesForm, {
|
import TeamWishesForm, {
|
||||||
fetchFor as fetchForTeamWishesForm,
|
fetchFor as fetchForTeamWishesForm,
|
||||||
} from "./VolunteerBoard/TeamWishesForm/TeamWishesForm"
|
} from "./VolunteerBoard/TeamWishesForm/TeamWishesForm"
|
||||||
@@ -35,9 +35,9 @@ export {
|
|||||||
fetchForAsks,
|
fetchForAsks,
|
||||||
ParticipationDetailsForm,
|
ParticipationDetailsForm,
|
||||||
fetchForParticipationDetailsForm,
|
fetchForParticipationDetailsForm,
|
||||||
PreRegisterForm,
|
|
||||||
TeamAssignment,
|
TeamAssignment,
|
||||||
fetchForTeamAssignment,
|
fetchForTeamAssignment,
|
||||||
|
RegisterForm,
|
||||||
TeamWishesForm,
|
TeamWishesForm,
|
||||||
fetchForTeamWishesForm,
|
fetchForTeamWishesForm,
|
||||||
VolunteerInfo,
|
VolunteerInfo,
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
import { FC, useEffect, memo } from "react"
|
|
||||||
import { RouteComponentProps } from "react-router-dom"
|
|
||||||
import { useDispatch, useSelector, shallowEqual } from "react-redux"
|
|
||||||
import { Helmet } from "react-helmet"
|
|
||||||
|
|
||||||
import { AppState, AppThunk, ValueRequest } from "../../store"
|
|
||||||
import { fetchPreVolunteerCountIfNeed } from "../../store/preVolunteerCount"
|
|
||||||
import { PreRegisterForm } from "../../components"
|
|
||||||
import styles from "./styles.module.scss"
|
|
||||||
|
|
||||||
export type Props = RouteComponentProps
|
|
||||||
|
|
||||||
function useList(
|
|
||||||
stateToProp: (state: AppState) => ValueRequest<number | undefined>,
|
|
||||||
fetchDataIfNeed: () => AppThunk
|
|
||||||
) {
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const { readyStatus, value } = useSelector(stateToProp, shallowEqual)
|
|
||||||
|
|
||||||
// Fetch client-side data here
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchDataIfNeed())
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [dispatch])
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (!readyStatus || readyStatus === "idle" || readyStatus === "request")
|
|
||||||
return <p>Loading...</p>
|
|
||||||
|
|
||||||
if (readyStatus === "failure") return <p>Oops, Failed to load!</p>
|
|
||||||
|
|
||||||
return <PreRegisterForm dispatch={dispatch} preVolunteerCount={value} />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PreRegisterPage: FC<Props> = (): JSX.Element => (
|
|
||||||
<div className={styles.preRegisterPage}>
|
|
||||||
<div className={styles.preRegisterContent}>
|
|
||||||
<Helmet title="PreRegisterPage" />
|
|
||||||
{useList((state: AppState) => state.preVolunteerCount, fetchPreVolunteerCountIfNeed)()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fetch server-side data here
|
|
||||||
export const loadData = (): AppThunk[] => [fetchPreVolunteerCountIfNeed()]
|
|
||||||
|
|
||||||
export default memo(PreRegisterPage)
|
|
27
src/pages/Register/Register.tsx
Normal file
27
src/pages/Register/Register.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { FC, memo } from "react"
|
||||||
|
import { RouteComponentProps } from "react-router-dom"
|
||||||
|
import { useDispatch } from "react-redux"
|
||||||
|
import { Helmet } from "react-helmet"
|
||||||
|
|
||||||
|
import { AppThunk } from "../../store"
|
||||||
|
import { RegisterForm } from "../../components"
|
||||||
|
import styles from "./styles.module.scss"
|
||||||
|
|
||||||
|
export type Props = RouteComponentProps
|
||||||
|
|
||||||
|
const RegisterPage: FC<Props> = (): JSX.Element => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
return (
|
||||||
|
<div className={styles.registerPage}>
|
||||||
|
<div className={styles.registerContent}>
|
||||||
|
<Helmet title="RegisterPage" />
|
||||||
|
<RegisterForm dispatch={dispatch} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch server-side data here
|
||||||
|
export const loadData = (): AppThunk[] => []
|
||||||
|
|
||||||
|
export default memo(RegisterPage)
|
@@ -1,15 +1,15 @@
|
|||||||
import loadable from "@loadable/component"
|
import loadable from "@loadable/component"
|
||||||
|
|
||||||
import { Loading, ErrorBoundary } from "../../components"
|
import { Loading, ErrorBoundary } from "../../components"
|
||||||
import { Props, loadData } from "./PreRegister"
|
import { Props, loadData } from "./Register"
|
||||||
|
|
||||||
const PreRegister = loadable(() => import("./PreRegister"), {
|
const Register = loadable(() => import("./Register"), {
|
||||||
fallback: <Loading />,
|
fallback: <Loading />,
|
||||||
})
|
})
|
||||||
|
|
||||||
export default (props: Props): JSX.Element => (
|
export default (props: Props): JSX.Element => (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<PreRegister {...props} />
|
<Register {...props} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
|
|
@@ -1,9 +1,9 @@
|
|||||||
@import "../../theme/mixins";
|
@import "../../theme/mixins";
|
||||||
|
|
||||||
.preRegisterPage {
|
.registerPage {
|
||||||
@include page-wrapper-center;
|
@include page-wrapper-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preRegisterContent {
|
.registerContent {
|
||||||
@include page-content-wrapper(600px);
|
@include page-content-wrapper(600px);
|
||||||
}
|
}
|
@@ -1,53 +0,0 @@
|
|||||||
import { useEffect, memo } from "react"
|
|
||||||
import { RouteComponentProps } from "react-router-dom"
|
|
||||||
import { useDispatch, useSelector, shallowEqual } from "react-redux"
|
|
||||||
import { Helmet } from "react-helmet"
|
|
||||||
|
|
||||||
import { AppState, AppThunk } from "../../store"
|
|
||||||
import { fetchVolunteerIfNeed } from "../../store/volunteer"
|
|
||||||
import { VolunteerInfo, VolunteerSet } from "../../components"
|
|
||||||
import styles from "./styles.module.scss"
|
|
||||||
|
|
||||||
export type Props = RouteComponentProps<{ id: string }>
|
|
||||||
|
|
||||||
const VolunteerPage = ({ match }: Props): JSX.Element => {
|
|
||||||
const { id: rawId } = match.params
|
|
||||||
const id = +rawId
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const volunteer = useSelector((state: AppState) => state.volunteer, shallowEqual)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(fetchVolunteerIfNeed(id))
|
|
||||||
}, [dispatch, id])
|
|
||||||
|
|
||||||
const renderInfo = () => {
|
|
||||||
const volunteerInfo = volunteer
|
|
||||||
|
|
||||||
if (!volunteerInfo || volunteerInfo.readyStatus === "request") return <p>Loading...</p>
|
|
||||||
|
|
||||||
if (volunteerInfo.readyStatus === "failure" || !volunteerInfo.entity)
|
|
||||||
return <p>Oops! Failed to load data.</p>
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<VolunteerInfo item={volunteerInfo.entity} />
|
|
||||||
<VolunteerSet dispatch={dispatch} volunteer={volunteerInfo.entity} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.VolunteerPage}>
|
|
||||||
<Helmet title="User Info" />
|
|
||||||
{renderInfo()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface LoadDataArgs {
|
|
||||||
params: { id: number }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadData = ({ params }: LoadDataArgs): AppThunk[] => [fetchVolunteerIfNeed(params.id)]
|
|
||||||
|
|
||||||
export default memo(VolunteerPage)
|
|
@@ -1,15 +0,0 @@
|
|||||||
import loadable from "@loadable/component"
|
|
||||||
|
|
||||||
import { Loading, ErrorBoundary } from "../../components"
|
|
||||||
import { Props, loadData } from "./VolunteerPage"
|
|
||||||
|
|
||||||
const VolunteerPage = loadable(() => import("./VolunteerPage"), {
|
|
||||||
fallback: <Loading />,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default (props: Props): JSX.Element => (
|
|
||||||
<ErrorBoundary>
|
|
||||||
<VolunteerPage {...props} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
)
|
|
||||||
export { loadData }
|
|
@@ -1,3 +0,0 @@
|
|||||||
.VolunteerPage {
|
|
||||||
padding: 0 15px;
|
|
||||||
}
|
|
@@ -4,12 +4,11 @@ import App from "../app"
|
|||||||
import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
|
import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
|
||||||
import AsyncAnnouncements, { loadData as loadAnnouncementsData } from "../pages/Announcements"
|
import AsyncAnnouncements, { loadData as loadAnnouncementsData } from "../pages/Announcements"
|
||||||
import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../pages/TeamAssignment"
|
import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../pages/TeamAssignment"
|
||||||
import AsyncPreRegisterPage, { loadData as loadPreRegisterPage } from "../pages/PreRegister"
|
import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register"
|
||||||
import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams"
|
import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams"
|
||||||
import AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
|
import AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
|
||||||
import AsyncVolunteers, { loadData as loadVolunteersData } from "../pages/Volunteers"
|
import AsyncVolunteers, { loadData as loadVolunteersData } from "../pages/Volunteers"
|
||||||
import AsyncWish, { loadData as loadWishData } from "../pages/Wish"
|
import AsyncWish, { loadData as loadWishData } from "../pages/Wish"
|
||||||
import AsyncVolunteerPage, { loadData as loadVolunteerPageData } from "../pages/VolunteerPage"
|
|
||||||
import Login from "../pages/Login"
|
import Login from "../pages/Login"
|
||||||
import Forgot from "../pages/Forgot"
|
import Forgot from "../pages/Forgot"
|
||||||
import NotFound from "../pages/NotFound"
|
import NotFound from "../pages/NotFound"
|
||||||
@@ -26,18 +25,13 @@ export default [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/preRegister",
|
path: "/preRegister",
|
||||||
component: AsyncPreRegisterPage,
|
component: AsyncRegisterPage,
|
||||||
loadData: loadPreRegisterPage,
|
loadData: loadRegisterPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/sinscrire",
|
path: "/sinscrire",
|
||||||
component: AsyncPreRegisterPage,
|
component: AsyncRegisterPage,
|
||||||
loadData: loadPreRegisterPage,
|
loadData: loadRegisterPage,
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "/VolunteerPage/:id",
|
|
||||||
component: AsyncVolunteerPage,
|
|
||||||
loadData: loadVolunteerPageData,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/login",
|
path: "/login",
|
||||||
|
@@ -7,7 +7,7 @@ import { SheetNames, saveLocalDb, loadLocalDb } from "./localDb"
|
|||||||
|
|
||||||
export { SheetNames } from "./localDb"
|
export { SheetNames } from "./localDb"
|
||||||
|
|
||||||
// Test write attack with: wget --header='Content-Type:application/json' --post-data='{"prenom":"Pierre","nom":"SCELLES","email":"test@gmail.com","telephone":"0601010101","dejaBenevole":false,"commentaire":""}' http://localhost:3000/PreVolunteerAdd
|
// Test write attack with: wget --header='Content-Type:application/json' --post-data='{"prenom":"Pierre","nom":"SCELLES","email":"test@gmail.com","telephone":"0601010101","dejaBenevole":false,"commentaire":""}' http://localhost:3000/PostulantAdd
|
||||||
|
|
||||||
const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json")
|
const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json")
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ export class Sheet<
|
|||||||
return (_.max(ids) || 0) + 1
|
return (_.max(ids) || 0) + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
async add(elementWithoutId: ElementNoId): Promise<Element> {
|
async add(elementWithoutId: Omit<Element, "id">): Promise<Element> {
|
||||||
const elements: Element[] = (await this.getList()) || []
|
const elements: Element[] = (await this.getList()) || []
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
const element: Element = { id: await this.nextId(), ...elementWithoutId } as Element
|
const element: Element = { id: await this.nextId(), ...elementWithoutId } as Element
|
||||||
@@ -604,7 +604,7 @@ async function tryNTimes<T>(
|
|||||||
return await func()
|
return await func()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e?.error || e?.message || e)
|
console.error(e?.error || e?.message || e)
|
||||||
console.error(`${repeatCount} attemps left every ${delayBetweenAttempts}`)
|
console.error(`${repeatCount} attempts left every ${delayBetweenAttempts}`)
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
setTimeout(() => resolve(), delayBetweenAttempts)
|
setTimeout(() => resolve(), delayBetweenAttempts)
|
||||||
})
|
})
|
||||||
|
@@ -3,6 +3,7 @@ import { SheetNames, ElementWithId, getSheet, Sheet } from "./accessors"
|
|||||||
|
|
||||||
export type RequestBody = Request["body"]
|
export type RequestBody = Request["body"]
|
||||||
export type CustomSetReturn<Element> = { toDatabase: Element; toCaller: any }
|
export type CustomSetReturn<Element> = { toDatabase: Element; toCaller: any }
|
||||||
|
export type CustomAddReturn<Element> = { toDatabase: Omit<Element, "id">; toCaller: any }
|
||||||
|
|
||||||
export default class ExpressAccessors<
|
export default class ExpressAccessors<
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
@@ -88,13 +89,44 @@ export default class ExpressAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add() {
|
add(
|
||||||
|
custom?: (
|
||||||
|
list: Element[],
|
||||||
|
body: RequestBody,
|
||||||
|
id: number,
|
||||||
|
roles: string[]
|
||||||
|
) => Promise<CustomAddReturn<Element>> | CustomAddReturn<Element>
|
||||||
|
) {
|
||||||
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const sheet = await this.getSheet()
|
const sheet = await this.getSheet()
|
||||||
const element: Element = await sheet.add(request.body)
|
if (!custom) {
|
||||||
if (element) {
|
await sheet.add(request.body)
|
||||||
response.status(200).json(element)
|
response.status(200)
|
||||||
|
} else {
|
||||||
|
const memberId = response?.locals?.jwt?.id || -1
|
||||||
|
const roles: string[] = response?.locals?.jwt?.roles || []
|
||||||
|
const list = (await sheet.getList()) || []
|
||||||
|
const { toDatabase, toCaller } = await custom(
|
||||||
|
list,
|
||||||
|
request.body,
|
||||||
|
memberId,
|
||||||
|
roles
|
||||||
|
)
|
||||||
|
let toReturn = toCaller
|
||||||
|
|
||||||
|
if (toDatabase !== undefined) {
|
||||||
|
const element: Element = await sheet.add(toDatabase)
|
||||||
|
toCaller.id = element.id
|
||||||
|
if (!toCaller) {
|
||||||
|
toReturn = element
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toReturn !== undefined) {
|
||||||
|
response.status(200).json(toReturn)
|
||||||
|
} else {
|
||||||
|
response.status(200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
response.status(200).json({ error: e.message })
|
response.status(200).json({ error: e.message })
|
||||||
|
@@ -3,7 +3,7 @@ import path from "path"
|
|||||||
import _ from "lodash"
|
import _ from "lodash"
|
||||||
import { promises as fs } from "fs"
|
import { promises as fs } from "fs"
|
||||||
import { Volunteer } from "../../services/volunteers"
|
import { Volunteer } from "../../services/volunteers"
|
||||||
import { PreVolunteer } from "../../services/preVolunteers"
|
import { Postulant } from "../../services/postulants"
|
||||||
|
|
||||||
const DB_PATH = path.resolve(process.cwd(), "access/db.json")
|
const DB_PATH = path.resolve(process.cwd(), "access/db.json")
|
||||||
const DB_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json")
|
const DB_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json")
|
||||||
@@ -14,7 +14,7 @@ export class SheetNames {
|
|||||||
|
|
||||||
Games = "Jeux"
|
Games = "Jeux"
|
||||||
|
|
||||||
PreVolunteers = "PreMembres"
|
Postulants = "Postulants"
|
||||||
|
|
||||||
Teams = "Equipes"
|
Teams = "Equipes"
|
||||||
|
|
||||||
@@ -263,8 +263,8 @@ function anonimizedDb(_s: States): States {
|
|||||||
anonimizedNotifs(v)
|
anonimizedNotifs(v)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (s.PreVolunteers) {
|
if (s.Postulants) {
|
||||||
;(s.PreVolunteers as PreVolunteer[]).forEach((v) => {
|
;(s.Postulants as Postulant[]).forEach((v) => {
|
||||||
anonimizedNameEmailMobile(v)
|
anonimizedNameEmailMobile(v)
|
||||||
v.comment = v.id % 3 === 0 ? "Bonjour, j'adore l'initiative!" : ""
|
v.comment = v.id % 3 === 0 ? "Bonjour, j'adore l'initiative!" : ""
|
||||||
})
|
})
|
||||||
@@ -272,11 +272,11 @@ function anonimizedDb(_s: States): States {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
function idADev(v: Volunteer | PreVolunteer): boolean {
|
function idADev(v: Volunteer | Postulant): boolean {
|
||||||
return ((v as Volunteer)?.roles || []).includes("dev")
|
return ((v as Volunteer)?.roles || []).includes("dev")
|
||||||
}
|
}
|
||||||
|
|
||||||
function anonimizedNameEmailMobile(v: Volunteer | PreVolunteer): void {
|
function anonimizedNameEmailMobile(v: Volunteer | Postulant): void {
|
||||||
if (idADev(v)) {
|
if (idADev(v)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
50
src/server/gsheets/postulants.ts
Normal file
50
src/server/gsheets/postulants.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import _ from "lodash"
|
||||||
|
import ExpressAccessors from "./expressAccessors"
|
||||||
|
import { Postulant, PostulantWithoutId, translationPostulant } from "../../services/postulants"
|
||||||
|
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
|
||||||
|
|
||||||
|
const expressAccessor = new ExpressAccessors<PostulantWithoutId, Postulant>(
|
||||||
|
"Postulants",
|
||||||
|
new Postulant(),
|
||||||
|
translationPostulant
|
||||||
|
)
|
||||||
|
|
||||||
|
export const postulantListGet = expressAccessor.listGet()
|
||||||
|
export const postulantGet = expressAccessor.get()
|
||||||
|
export const postulantAdd = expressAccessor.add(async (list, body) => {
|
||||||
|
const params = body
|
||||||
|
const postulant = getByEmail(list, params.email)
|
||||||
|
if (postulant) {
|
||||||
|
throw Error("Il y a déjà quelqu'un avec cet email")
|
||||||
|
}
|
||||||
|
if (!validMobile(params.mobile)) {
|
||||||
|
throw Error("Numéro de téléphone invalide, contacter pierre.scelles@gmail.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPostulant = _.omit(new Postulant(), "id")
|
||||||
|
|
||||||
|
_.assign(newPostulant, {
|
||||||
|
lastname: trim(params.lastname),
|
||||||
|
firstname: trim(params.firstname),
|
||||||
|
email: trim(params.email),
|
||||||
|
mobile: canonicalMobile(params.mobile),
|
||||||
|
howToContact: trim(params.howToContact),
|
||||||
|
potential: params.potential === true,
|
||||||
|
alreadyCame: params.alreadyCame === true,
|
||||||
|
firstMeeting: trim(params.firstMeeting),
|
||||||
|
commentFirstMeeting: trim(params.commentFirstMeeting),
|
||||||
|
comment: trim(params.comment),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
toDatabase: newPostulant,
|
||||||
|
toCaller: {},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export const postulantSet = expressAccessor.set()
|
||||||
|
|
||||||
|
function getByEmail<T extends { email: string }>(list: T[], rawEmail: string): T | undefined {
|
||||||
|
const email = canonicalEmail(rawEmail || "")
|
||||||
|
const volunteer = list.find((v) => canonicalEmail(v.email) === email)
|
||||||
|
return volunteer
|
||||||
|
}
|
@@ -1,19 +0,0 @@
|
|||||||
import ExpressAccessors from "./expressAccessors"
|
|
||||||
import {
|
|
||||||
PreVolunteer,
|
|
||||||
PreVolunteerWithoutId,
|
|
||||||
translationPreVolunteer,
|
|
||||||
} from "../../services/preVolunteers"
|
|
||||||
|
|
||||||
const expressAccessor = new ExpressAccessors<PreVolunteerWithoutId, PreVolunteer>(
|
|
||||||
"PreVolunteers",
|
|
||||||
new PreVolunteer(),
|
|
||||||
translationPreVolunteer
|
|
||||||
)
|
|
||||||
|
|
||||||
export const preVolunteerListGet = expressAccessor.listGet()
|
|
||||||
export const preVolunteerGet = expressAccessor.get()
|
|
||||||
export const preVolunteerAdd = expressAccessor.add()
|
|
||||||
export const preVolunteerSet = expressAccessor.set()
|
|
||||||
|
|
||||||
export const preVolunteerCountGet = expressAccessor.get((list) => list?.length || 0)
|
|
@@ -14,7 +14,7 @@ import {
|
|||||||
VolunteerParticipationDetails,
|
VolunteerParticipationDetails,
|
||||||
VolunteerTeamAssign,
|
VolunteerTeamAssign,
|
||||||
} from "../../services/volunteers"
|
} from "../../services/volunteers"
|
||||||
import { canonicalEmail } from "../../utils/standardization"
|
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
|
||||||
import { getJwt } from "../secure"
|
import { getJwt } from "../secure"
|
||||||
|
|
||||||
const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
|
const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
|
||||||
@@ -24,9 +24,74 @@ const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
|
|||||||
)
|
)
|
||||||
|
|
||||||
export const volunteerListGet = expressAccessor.listGet()
|
export const volunteerListGet = expressAccessor.listGet()
|
||||||
export const volunteerAdd = expressAccessor.add()
|
|
||||||
export const volunteerSet = expressAccessor.set()
|
export const volunteerSet = expressAccessor.set()
|
||||||
|
|
||||||
|
export const volunteerDiscordId = expressAccessor.get(async (list, body, id) => {
|
||||||
|
const requestedId = +body[0] || id
|
||||||
|
if (requestedId !== id && requestedId !== 0) {
|
||||||
|
throw Error(`On ne peut acceder qu'à ses propres envies de jours`)
|
||||||
|
}
|
||||||
|
const volunteer = list.find((v) => v.id === requestedId)
|
||||||
|
if (!volunteer) {
|
||||||
|
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
||||||
|
}
|
||||||
|
return _.pick(volunteer, "id", "discordId")
|
||||||
|
})
|
||||||
|
|
||||||
|
export const volunteerPartialAdd = expressAccessor.add(async (list, body) => {
|
||||||
|
const params = body[0]
|
||||||
|
const volunteer = getByEmail(list, params.email)
|
||||||
|
if (volunteer) {
|
||||||
|
throw Error(
|
||||||
|
"Il y a déjà un bénévole avec cet email. Mieux vaut redemander un mot de passe si tu l'as oublié."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!validMobile(params.mobile)) {
|
||||||
|
throw Error("Numéro de téléphone invalide, contacter pierre.scelles@gmail.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
const password = generatePassword()
|
||||||
|
const passwordHash = await bcrypt.hash(password, 10)
|
||||||
|
|
||||||
|
const newVolunteer = _.omit(new Volunteer(), "id")
|
||||||
|
|
||||||
|
_.assign(newVolunteer, {
|
||||||
|
lastname: trim(params.lastname),
|
||||||
|
firstname: trim(params.firstname),
|
||||||
|
email: trim(params.email),
|
||||||
|
mobile: canonicalMobile(params.mobile),
|
||||||
|
howToContact: trim(params.howToContact),
|
||||||
|
canHelpBefore: trim(params.canHelpBefore),
|
||||||
|
pelMember: params.pelMember === true,
|
||||||
|
password1: passwordHash,
|
||||||
|
password2: passwordHash,
|
||||||
|
})
|
||||||
|
|
||||||
|
await sendSignUpEmail(newVolunteer.email, password)
|
||||||
|
|
||||||
|
return {
|
||||||
|
toDatabase: newVolunteer,
|
||||||
|
toCaller: {},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function sendSignUpEmail(email: string, password: string): Promise<void> {
|
||||||
|
const apiKey = process.env.SENDGRID_API_KEY || ""
|
||||||
|
if (__DEV__ || apiKey === "") {
|
||||||
|
console.error(`Fake sending signup email to ${email} with password ${password}`)
|
||||||
|
} else {
|
||||||
|
sgMail.setApiKey(apiKey)
|
||||||
|
const msg = {
|
||||||
|
to: email,
|
||||||
|
from: "contact@parisestludique.fr",
|
||||||
|
subject: "Accès au site des bénévoles de Paris est Ludique",
|
||||||
|
text: `Ton inscription est bien enregistrée, l'aventure PeL peut commencer ! :)\nVoici ton mot de passe pour accéder au site des bénévoles où tu t'es inscrit.e : ${password}\nTu y trouveras notamment comment on communique entre bénévoles.\nBonne journée !\nPierre`,
|
||||||
|
html: `Ton inscription est bien enregistrée, l'aventure PeL peut commencer ! :)<br />Voici ton mot de passe pour accéder au <a href="https://fo.parisestludique.fr/">site des bénévoles</a> : <strong>${password}</strong><br />Tu y trouveras notamment comment on communique entre bénévoles.<br />Bonne journée !<br />Pierre`,
|
||||||
|
}
|
||||||
|
await sgMail.send(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const volunteerLogin = expressAccessor.get<VolunteerLogin>(async (list, bodyArray) => {
|
export const volunteerLogin = expressAccessor.get<VolunteerLogin>(async (list, bodyArray) => {
|
||||||
const [body] = bodyArray
|
const [body] = bodyArray
|
||||||
const volunteer = getByEmail(list, body.email)
|
const volunteer = getByEmail(list, body.email)
|
||||||
|
@@ -19,16 +19,18 @@ import certbotRouter from "../routes/certbot"
|
|||||||
import { hasSecret, secure } from "./secure"
|
import { hasSecret, secure } from "./secure"
|
||||||
import { announcementListGet } from "./gsheets/announcements"
|
import { announcementListGet } from "./gsheets/announcements"
|
||||||
import { gameListGet } from "./gsheets/games"
|
import { gameListGet } from "./gsheets/games"
|
||||||
import { preVolunteerAdd, preVolunteerCountGet } from "./gsheets/preVolunteers"
|
import { postulantAdd } from "./gsheets/postulants"
|
||||||
import { teamListGet } from "./gsheets/teams"
|
import { teamListGet } from "./gsheets/teams"
|
||||||
import {
|
import {
|
||||||
volunteerSet,
|
|
||||||
volunteerLogin,
|
|
||||||
volunteerForgot,
|
|
||||||
volunteerAsksSet,
|
volunteerAsksSet,
|
||||||
volunteerParticipationDetailsSet,
|
|
||||||
volunteerTeamWishesSet,
|
|
||||||
volunteerDayWishesSet,
|
volunteerDayWishesSet,
|
||||||
|
volunteerForgot,
|
||||||
|
volunteerDiscordId,
|
||||||
|
volunteerLogin,
|
||||||
|
volunteerPartialAdd,
|
||||||
|
volunteerParticipationDetailsSet,
|
||||||
|
volunteerSet,
|
||||||
|
volunteerTeamWishesSet,
|
||||||
volunteerTeamAssignSet,
|
volunteerTeamAssignSet,
|
||||||
volunteerListGet,
|
volunteerListGet,
|
||||||
} from "./gsheets/volunteers"
|
} from "./gsheets/volunteers"
|
||||||
@@ -84,8 +86,8 @@ app.get(
|
|||||||
app.get("/GameListGet", gameListGet)
|
app.get("/GameListGet", gameListGet)
|
||||||
app.get("/WishListGet", wishListGet)
|
app.get("/WishListGet", wishListGet)
|
||||||
app.post("/WishAdd", wishAdd)
|
app.post("/WishAdd", wishAdd)
|
||||||
app.post("/PreVolunteerAdd", preVolunteerAdd)
|
app.post("/PostulantAdd", postulantAdd)
|
||||||
app.get("/PreVolunteerCountGet", preVolunteerCountGet)
|
app.post("/VolunteerPartialAdd", volunteerPartialAdd)
|
||||||
app.post("/VolunteerLogin", volunteerLogin)
|
app.post("/VolunteerLogin", volunteerLogin)
|
||||||
app.post("/VolunteerForgot", volunteerForgot)
|
app.post("/VolunteerForgot", volunteerForgot)
|
||||||
app.get("/VolunteerListGet", volunteerListGet)
|
app.get("/VolunteerListGet", volunteerListGet)
|
||||||
@@ -94,7 +96,7 @@ app.get("/VolunteerListGet", volunteerListGet)
|
|||||||
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
|
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
|
||||||
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
|
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
|
||||||
app.get("/TeamListGet", teamListGet)
|
app.get("/TeamListGet", teamListGet)
|
||||||
// UNSAFE app.post("/VolunteerGet", secure as RequestHandler, volunteerGet)
|
app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId)
|
||||||
app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet)
|
app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet)
|
||||||
app.post(
|
app.post(
|
||||||
"/VolunteerParticipationDetailsSet",
|
"/VolunteerParticipationDetailsSet",
|
||||||
|
@@ -30,6 +30,9 @@ export default class ServiceAccessors<
|
|||||||
...axiosConfig,
|
...axiosConfig,
|
||||||
params: { id },
|
params: { id },
|
||||||
})
|
})
|
||||||
|
if (data.error) {
|
||||||
|
throw Error(data.error)
|
||||||
|
}
|
||||||
return { data }
|
return { data }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error as Error }
|
return { error: error as Error }
|
||||||
@@ -51,6 +54,9 @@ export default class ServiceAccessors<
|
|||||||
`${config.API_URL}/${this.elementName}ListGet`,
|
`${config.API_URL}/${this.elementName}ListGet`,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
|
if (data.error) {
|
||||||
|
throw Error(data.error)
|
||||||
|
}
|
||||||
return { data }
|
return { data }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error as Error }
|
return { error: error as Error }
|
||||||
@@ -58,7 +64,7 @@ export default class ServiceAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
secureListGet(): (jwt: string) => Promise<{
|
securedListGet(): (jwt: string) => Promise<{
|
||||||
data?: Element[]
|
data?: Element[]
|
||||||
error?: Error
|
error?: Error
|
||||||
}> {
|
}> {
|
||||||
@@ -100,6 +106,9 @@ export default class ServiceAccessors<
|
|||||||
volunteerWithoutId,
|
volunteerWithoutId,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
|
if (data.error) {
|
||||||
|
throw Error(data.error)
|
||||||
|
}
|
||||||
return { data }
|
return { data }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error as Error }
|
return { error: error as Error }
|
||||||
@@ -122,6 +131,9 @@ export default class ServiceAccessors<
|
|||||||
volunteer,
|
volunteer,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
|
if (data.error) {
|
||||||
|
throw Error(data.error)
|
||||||
|
}
|
||||||
return { data }
|
return { data }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error as Error }
|
return { error: error as Error }
|
||||||
@@ -143,6 +155,9 @@ export default class ServiceAccessors<
|
|||||||
`${config.API_URL}/${this.elementName}CountGet`,
|
`${config.API_URL}/${this.elementName}CountGet`,
|
||||||
axiosConfig
|
axiosConfig
|
||||||
)
|
)
|
||||||
|
if (data.error) {
|
||||||
|
throw Error(data.error)
|
||||||
|
}
|
||||||
return { data }
|
return { data }
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error as Error }
|
return { error: error as Error }
|
||||||
@@ -177,6 +192,37 @@ export default class ServiceAccessors<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
securedCustomGet<InputElements extends Array<any>>(
|
||||||
|
apiName: string
|
||||||
|
): (
|
||||||
|
jwt: string,
|
||||||
|
...params: InputElements
|
||||||
|
) => Promise<{
|
||||||
|
data?: any
|
||||||
|
error?: Error
|
||||||
|
}> {
|
||||||
|
interface ElementGetResponse {
|
||||||
|
data?: any
|
||||||
|
error?: Error
|
||||||
|
}
|
||||||
|
return async (jwt: string, ...params: InputElements): Promise<ElementGetResponse> => {
|
||||||
|
try {
|
||||||
|
const auth = { headers: { Authorization: `Bearer ${jwt}` } }
|
||||||
|
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
|
||||||
|
const { data } = await axios.get(
|
||||||
|
`${config.API_URL}/${this.elementName}${apiName}`,
|
||||||
|
{ ...fullAxiosConfig, params }
|
||||||
|
)
|
||||||
|
if (data.error) {
|
||||||
|
throw Error(data.error)
|
||||||
|
}
|
||||||
|
return { data }
|
||||||
|
} catch (error) {
|
||||||
|
return { error: error as Error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
securedCustomPost<InputElements extends Array<any>>(
|
securedCustomPost<InputElements extends Array<any>>(
|
||||||
apiName: string
|
apiName: string
|
||||||
): (
|
): (
|
||||||
|
@@ -7,4 +7,4 @@ const serviceAccessors = new ServiceAccessors<AnnouncementWithoutId, Announcemen
|
|||||||
// export const announcementAdd = serviceAccessors.add()
|
// export const announcementAdd = serviceAccessors.add()
|
||||||
// export const announcementSet = serviceAccessors.set()
|
// export const announcementSet = serviceAccessors.set()
|
||||||
|
|
||||||
export const announcementListGet = serviceAccessors.secureListGet()
|
export const announcementListGet = serviceAccessors.securedListGet()
|
||||||
|
45
src/services/postulants.ts
Normal file
45
src/services/postulants.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
export class Postulant {
|
||||||
|
id = 0
|
||||||
|
|
||||||
|
firstname = ""
|
||||||
|
|
||||||
|
lastname = ""
|
||||||
|
|
||||||
|
email = ""
|
||||||
|
|
||||||
|
mobile = ""
|
||||||
|
|
||||||
|
howToContact = ""
|
||||||
|
|
||||||
|
potential = false
|
||||||
|
|
||||||
|
alreadyCame = false
|
||||||
|
|
||||||
|
firstMeeting = ""
|
||||||
|
|
||||||
|
commentFirstMeeting = ""
|
||||||
|
|
||||||
|
comment = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
export const translationPostulant: { [k in keyof Postulant]: string } = {
|
||||||
|
id: "id",
|
||||||
|
firstname: "prenom",
|
||||||
|
lastname: "nom",
|
||||||
|
email: "email",
|
||||||
|
mobile: "telephone",
|
||||||
|
howToContact: "commentContacter",
|
||||||
|
potential: "potentiel",
|
||||||
|
alreadyCame: "déjàVenu",
|
||||||
|
firstMeeting: "dateRencontre",
|
||||||
|
commentFirstMeeting: "commentaireDateRencontre",
|
||||||
|
comment: "commentaire",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const elementName = "Postulant"
|
||||||
|
|
||||||
|
export const emailRegexp =
|
||||||
|
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
|
||||||
|
export const passwordMinLength = 4
|
||||||
|
|
||||||
|
export type PostulantWithoutId = Omit<Postulant, "id">
|
9
src/services/postulantsAccessors.ts
Normal file
9
src/services/postulantsAccessors.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import ServiceAccessors from "./accessors"
|
||||||
|
import { elementName, Postulant, PostulantWithoutId } from "./postulants"
|
||||||
|
|
||||||
|
const serviceAccessors = new ServiceAccessors<PostulantWithoutId, Postulant>(elementName)
|
||||||
|
|
||||||
|
export const postulantListGet = serviceAccessors.listGet()
|
||||||
|
export const postulantGet = serviceAccessors.get()
|
||||||
|
export const postulantAdd = serviceAccessors.add()
|
||||||
|
export const postulantSet = serviceAccessors.set()
|
@@ -1,33 +0,0 @@
|
|||||||
export class PreVolunteer {
|
|
||||||
id = 0
|
|
||||||
|
|
||||||
firstname = ""
|
|
||||||
|
|
||||||
lastname = ""
|
|
||||||
|
|
||||||
email = ""
|
|
||||||
|
|
||||||
mobile = ""
|
|
||||||
|
|
||||||
alreadyVolunteer = false
|
|
||||||
|
|
||||||
comment = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
export const translationPreVolunteer: { [k in keyof PreVolunteer]: string } = {
|
|
||||||
id: "id",
|
|
||||||
firstname: "prenom",
|
|
||||||
lastname: "nom",
|
|
||||||
email: "email",
|
|
||||||
mobile: "telephone",
|
|
||||||
alreadyVolunteer: "dejaBenevole",
|
|
||||||
comment: "commentaire",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const elementName = "PreVolunteer"
|
|
||||||
|
|
||||||
export const emailRegexp =
|
|
||||||
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
|
|
||||||
export const passwordMinLength = 4
|
|
||||||
|
|
||||||
export type PreVolunteerWithoutId = Omit<PreVolunteer, "id">
|
|
@@ -1,10 +0,0 @@
|
|||||||
import ServiceAccessors from "./accessors"
|
|
||||||
import { elementName, PreVolunteer, PreVolunteerWithoutId } from "./preVolunteers"
|
|
||||||
|
|
||||||
const serviceAccessors = new ServiceAccessors<PreVolunteerWithoutId, PreVolunteer>(elementName)
|
|
||||||
|
|
||||||
export const preVolunteerListGet = serviceAccessors.listGet()
|
|
||||||
export const preVolunteerGet = serviceAccessors.get()
|
|
||||||
export const preVolunteerAdd = serviceAccessors.add()
|
|
||||||
export const preVolunteerSet = serviceAccessors.set()
|
|
||||||
export const preVolunteerCountGet = serviceAccessors.countGet()
|
|
@@ -1,4 +1,5 @@
|
|||||||
export class Volunteer {
|
/* eslint-disable max-classes-per-file */
|
||||||
|
export class Volunteer implements VolunteerPartial {
|
||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
lastname = ""
|
lastname = ""
|
||||||
@@ -9,7 +10,7 @@ export class Volunteer {
|
|||||||
|
|
||||||
mobile = ""
|
mobile = ""
|
||||||
|
|
||||||
photo = ""
|
photo = "anonyme.png"
|
||||||
|
|
||||||
adult = 1
|
adult = 1
|
||||||
|
|
||||||
@@ -23,11 +24,11 @@ export class Volunteer {
|
|||||||
|
|
||||||
dayWishesComment = ""
|
dayWishesComment = ""
|
||||||
|
|
||||||
tshirtCount = ""
|
tshirtCount = 0
|
||||||
|
|
||||||
tshirtSize = ""
|
tshirtSize = ""
|
||||||
|
|
||||||
food = ""
|
food = "Aucune"
|
||||||
|
|
||||||
team = 0
|
team = 0
|
||||||
|
|
||||||
@@ -35,6 +36,12 @@ export class Volunteer {
|
|||||||
|
|
||||||
teamWishesComment = ""
|
teamWishesComment = ""
|
||||||
|
|
||||||
|
howToContact = ""
|
||||||
|
|
||||||
|
canHelpBefore = ""
|
||||||
|
|
||||||
|
pelMember = false
|
||||||
|
|
||||||
hiddenAsks: number[] = []
|
hiddenAsks: number[] = []
|
||||||
|
|
||||||
created = new Date()
|
created = new Date()
|
||||||
@@ -67,6 +74,9 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
|||||||
team: "équipe",
|
team: "équipe",
|
||||||
teamWishes: "enviesEquipe",
|
teamWishes: "enviesEquipe",
|
||||||
teamWishesComment: "commentaireEnviesEquipe",
|
teamWishesComment: "commentaireEnviesEquipe",
|
||||||
|
howToContact: "commentContacter",
|
||||||
|
canHelpBefore: "aideEnAmont",
|
||||||
|
pelMember: "membrePel",
|
||||||
hiddenAsks: "questionsCachees",
|
hiddenAsks: "questionsCachees",
|
||||||
created: "creation",
|
created: "creation",
|
||||||
password1: "passe1",
|
password1: "passe1",
|
||||||
@@ -75,6 +85,16 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
|||||||
acceptsNotifs: "accepteLesNotifs",
|
acceptsNotifs: "accepteLesNotifs",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class VolunteerPartial {
|
||||||
|
lastname = ""
|
||||||
|
|
||||||
|
firstname = ""
|
||||||
|
|
||||||
|
email = ""
|
||||||
|
|
||||||
|
mobile = ""
|
||||||
|
}
|
||||||
|
|
||||||
export const elementName = "Volunteer"
|
export const elementName = "Volunteer"
|
||||||
|
|
||||||
export const volunteerExample: Volunteer = {
|
export const volunteerExample: Volunteer = {
|
||||||
@@ -90,12 +110,15 @@ export const volunteerExample: Volunteer = {
|
|||||||
discordId: "",
|
discordId: "",
|
||||||
dayWishes: [],
|
dayWishes: [],
|
||||||
dayWishesComment: "",
|
dayWishesComment: "",
|
||||||
tshirtCount: "1",
|
tshirtCount: 1,
|
||||||
tshirtSize: "Femme M",
|
tshirtSize: "Femme M",
|
||||||
food: "Végétarien",
|
food: "Végétarien",
|
||||||
team: 2,
|
team: 2,
|
||||||
teamWishes: [],
|
teamWishes: [],
|
||||||
teamWishesComment: "",
|
teamWishesComment: "",
|
||||||
|
howToContact: "",
|
||||||
|
canHelpBefore: "",
|
||||||
|
pelMember: false,
|
||||||
hiddenAsks: [],
|
hiddenAsks: [],
|
||||||
created: new Date(0),
|
created: new Date(0),
|
||||||
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
@@ -120,6 +143,11 @@ export interface VolunteerForgot {
|
|||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VolunteerDiscordId {
|
||||||
|
id: Volunteer["id"]
|
||||||
|
discordId: Volunteer["discordId"]
|
||||||
|
}
|
||||||
|
|
||||||
export interface VolunteerAsks {
|
export interface VolunteerAsks {
|
||||||
id: Volunteer["id"]
|
id: Volunteer["id"]
|
||||||
firstname: Volunteer["firstname"]
|
firstname: Volunteer["firstname"]
|
||||||
|
@@ -13,8 +13,8 @@ import {
|
|||||||
const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
|
const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
|
||||||
|
|
||||||
export const volunteerListGet = serviceAccessors.listGet()
|
export const volunteerListGet = serviceAccessors.listGet()
|
||||||
export const volunteerGet = serviceAccessors.get()
|
export const volunteerDiscordIdGet = serviceAccessors.securedCustomGet<[number]>("DiscordId")
|
||||||
export const volunteerAdd = serviceAccessors.add()
|
export const volunteerPartialAdd = serviceAccessors.customPost<[Partial<Volunteer>]>("PartialAdd")
|
||||||
export const volunteerSet = serviceAccessors.set()
|
export const volunteerSet = serviceAccessors.set()
|
||||||
|
|
||||||
export const volunteerLogin =
|
export const volunteerLogin =
|
||||||
|
@@ -1,78 +0,0 @@
|
|||||||
import axios from "axios"
|
|
||||||
|
|
||||||
import mockStore from "../../utils/mockStore"
|
|
||||||
import volunteer, {
|
|
||||||
getRequesting,
|
|
||||||
getSuccess,
|
|
||||||
getFailure,
|
|
||||||
fetchVolunteer,
|
|
||||||
initialState,
|
|
||||||
} from "../volunteer"
|
|
||||||
import { Volunteer, volunteerExample } from "../../services/volunteers"
|
|
||||||
|
|
||||||
jest.mock("axios")
|
|
||||||
|
|
||||||
const mockData: Volunteer = volunteerExample
|
|
||||||
const { id } = mockData
|
|
||||||
const mockError = "Oops! Something went wrong."
|
|
||||||
|
|
||||||
describe("volunteer reducer", () => {
|
|
||||||
it("should handle initial state correctly", () => {
|
|
||||||
// @ts-expect-error
|
|
||||||
expect(volunteer(undefined, {})).toEqual(initialState)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle requesting correctly", () => {
|
|
||||||
expect(volunteer(undefined, { type: getRequesting.type, payload: id })).toEqual({
|
|
||||||
readyStatus: "request",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle success correctly", () => {
|
|
||||||
expect(
|
|
||||||
volunteer(undefined, {
|
|
||||||
type: getSuccess.type,
|
|
||||||
payload: mockData,
|
|
||||||
})
|
|
||||||
).toEqual({ readyStatus: "success", entity: mockData })
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should handle failure correctly", () => {
|
|
||||||
expect(
|
|
||||||
volunteer(undefined, {
|
|
||||||
type: getFailure.type,
|
|
||||||
payload: mockError,
|
|
||||||
})
|
|
||||||
).toEqual({ readyStatus: "failure", error: mockError })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("volunteer action", () => {
|
|
||||||
it("fetches volunteer data successful", async () => {
|
|
||||||
const { dispatch, getActions } = mockStore()
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: getRequesting.type, payload: undefined },
|
|
||||||
{ type: getSuccess.type, payload: mockData },
|
|
||||||
]
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
axios.get.mockResolvedValue({ data: mockData })
|
|
||||||
|
|
||||||
await dispatch(fetchVolunteer(id))
|
|
||||||
expect(getActions()).toEqual(expectedActions)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("fetches volunteer data failed", async () => {
|
|
||||||
const { dispatch, getActions } = mockStore()
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: getRequesting.type },
|
|
||||||
{ type: getFailure.type, payload: mockError },
|
|
||||||
]
|
|
||||||
|
|
||||||
// @ts-expect-error
|
|
||||||
axios.get.mockRejectedValue({ message: mockError })
|
|
||||||
|
|
||||||
await dispatch(fetchVolunteer(id))
|
|
||||||
expect(getActions()).toEqual(expectedActions)
|
|
||||||
})
|
|
||||||
})
|
|
@@ -35,9 +35,9 @@ export const auth = createSlice({
|
|||||||
|
|
||||||
export const { setCurrentUser, logoutUser } = auth.actions
|
export const { setCurrentUser, logoutUser } = auth.actions
|
||||||
|
|
||||||
export const selectAuthData = (state: AppState): AuthState => state.auth
|
const selectAuthData = (state: AppState): AuthState => state.auth
|
||||||
|
|
||||||
export const selectRouter = (state: AppState): AppState["router"] => state.router
|
const selectRouter = (state: AppState): AppState["router"] => state.router
|
||||||
|
|
||||||
export const selectUserJwtToken = createSelector(selectAuthData, (authData) => authData.jwt)
|
export const selectUserJwtToken = createSelector(selectAuthData, (authData) => authData.jwt)
|
||||||
|
|
||||||
|
39
src/store/postulantAdd.ts
Normal file
39
src/store/postulantAdd.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
|
import { StateRequest, elementAddFetch } from "./utils"
|
||||||
|
import { Postulant } from "../services/postulants"
|
||||||
|
import { postulantAdd } from "../services/postulantsAccessors"
|
||||||
|
|
||||||
|
const postulantAdapter = createEntityAdapter<Postulant>()
|
||||||
|
|
||||||
|
const postulantAddSlice = createSlice({
|
||||||
|
name: "postulantAdd",
|
||||||
|
initialState: postulantAdapter.getInitialState({
|
||||||
|
readyStatus: "idle",
|
||||||
|
} as StateRequest),
|
||||||
|
reducers: {
|
||||||
|
getRequesting: (state) => {
|
||||||
|
state.readyStatus = "request"
|
||||||
|
},
|
||||||
|
getSuccess: (state, { payload }: PayloadAction<Postulant>) => {
|
||||||
|
state.readyStatus = "success"
|
||||||
|
postulantAdapter.setOne(state, payload)
|
||||||
|
},
|
||||||
|
getFailure: (state, { payload }: PayloadAction<string>) => {
|
||||||
|
state.readyStatus = "failure"
|
||||||
|
state.error = payload
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default postulantAddSlice.reducer
|
||||||
|
export const { getRequesting, getSuccess, getFailure } = postulantAddSlice.actions
|
||||||
|
|
||||||
|
export const fetchPostulantAdd = elementAddFetch(
|
||||||
|
postulantAdd,
|
||||||
|
getRequesting,
|
||||||
|
getSuccess,
|
||||||
|
getFailure,
|
||||||
|
() => null,
|
||||||
|
() => null
|
||||||
|
)
|
@@ -1,39 +0,0 @@
|
|||||||
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
|
|
||||||
|
|
||||||
import { StateRequest, elementAddFetch } from "./utils"
|
|
||||||
import { PreVolunteer } from "../services/preVolunteers"
|
|
||||||
import { preVolunteerAdd } from "../services/preVolunteersAccessors"
|
|
||||||
|
|
||||||
const preVolunteerAdapter = createEntityAdapter<PreVolunteer>()
|
|
||||||
|
|
||||||
const preVolunteerAddSlice = createSlice({
|
|
||||||
name: "addPreVolunteer",
|
|
||||||
initialState: preVolunteerAdapter.getInitialState({
|
|
||||||
readyStatus: "idle",
|
|
||||||
} as StateRequest),
|
|
||||||
reducers: {
|
|
||||||
getRequesting: (state) => {
|
|
||||||
state.readyStatus = "request"
|
|
||||||
},
|
|
||||||
getSuccess: (state, { payload }: PayloadAction<PreVolunteer>) => {
|
|
||||||
state.readyStatus = "success"
|
|
||||||
preVolunteerAdapter.addOne(state, payload)
|
|
||||||
},
|
|
||||||
getFailure: (state, { payload }: PayloadAction<string>) => {
|
|
||||||
state.readyStatus = "failure"
|
|
||||||
state.error = payload
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default preVolunteerAddSlice.reducer
|
|
||||||
export const { getRequesting, getSuccess, getFailure } = preVolunteerAddSlice.actions
|
|
||||||
|
|
||||||
export const fetchPreVolunteerAdd = elementAddFetch(
|
|
||||||
preVolunteerAdd,
|
|
||||||
getRequesting,
|
|
||||||
getSuccess,
|
|
||||||
getFailure,
|
|
||||||
() => null,
|
|
||||||
() => null
|
|
||||||
)
|
|
@@ -1,46 +0,0 @@
|
|||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
|
||||||
|
|
||||||
import { StateRequest, toastError, elementValueFetch } from "./utils"
|
|
||||||
import { preVolunteerCountGet } from "../services/preVolunteersAccessors"
|
|
||||||
import { AppThunk, AppState } from "."
|
|
||||||
|
|
||||||
export const initialState: StateRequest & { value?: number } = { readyStatus: "idle" }
|
|
||||||
|
|
||||||
const preVolunteerCount = createSlice({
|
|
||||||
name: "preVolunteerCount",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
getRequesting: (state) => {
|
|
||||||
state.readyStatus = "request"
|
|
||||||
},
|
|
||||||
getSuccess: (state, { payload }: PayloadAction<number>) => {
|
|
||||||
state.readyStatus = "success"
|
|
||||||
state.value = payload
|
|
||||||
},
|
|
||||||
getFailure: (state, { payload }: PayloadAction<string>) => {
|
|
||||||
state.readyStatus = "failure"
|
|
||||||
state.error = payload
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default preVolunteerCount.reducer
|
|
||||||
export const { getRequesting, getSuccess, getFailure } = preVolunteerCount.actions
|
|
||||||
|
|
||||||
export const fetchPreVolunteerCount = elementValueFetch(
|
|
||||||
preVolunteerCountGet,
|
|
||||||
getRequesting,
|
|
||||||
getSuccess,
|
|
||||||
getFailure,
|
|
||||||
(error: Error) =>
|
|
||||||
toastError(`Erreur lors du chargement des bénévoles potentiels: ${error.message}`)
|
|
||||||
)
|
|
||||||
|
|
||||||
const shouldFetchPreVolunteerCount = (state: AppState) =>
|
|
||||||
state.preVolunteerCount.readyStatus !== "success"
|
|
||||||
|
|
||||||
export const fetchPreVolunteerCountIfNeed = (): AppThunk => (dispatch, getState) => {
|
|
||||||
if (shouldFetchPreVolunteerCount(getState())) return dispatch(fetchPreVolunteerCount())
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
@@ -4,12 +4,11 @@ import { connectRouter } from "connected-react-router"
|
|||||||
import auth from "./auth"
|
import auth from "./auth"
|
||||||
import gameList from "./gameList"
|
import gameList from "./gameList"
|
||||||
import announcementList from "./announcementList"
|
import announcementList from "./announcementList"
|
||||||
import preVolunteerAdd from "./preVolunteerAdd"
|
import postulantAdd from "./postulantAdd"
|
||||||
import preVolunteerCount from "./preVolunteerCount"
|
|
||||||
import teamList from "./teamList"
|
import teamList from "./teamList"
|
||||||
import ui from "./ui"
|
import ui from "./ui"
|
||||||
import volunteer from "./volunteer"
|
import volunteerAdd from "./volunteerPartialAdd"
|
||||||
import volunteerAdd from "./volunteerAdd"
|
import volunteerDiscordId from "./volunteerDiscordId"
|
||||||
import volunteerList from "./volunteerList"
|
import volunteerList from "./volunteerList"
|
||||||
import volunteerSet from "./volunteerSet"
|
import volunteerSet from "./volunteerSet"
|
||||||
import volunteerLogin from "./volunteerLogin"
|
import volunteerLogin from "./volunteerLogin"
|
||||||
@@ -28,12 +27,11 @@ export default (history: History) => ({
|
|||||||
auth,
|
auth,
|
||||||
gameList,
|
gameList,
|
||||||
announcementList,
|
announcementList,
|
||||||
preVolunteerAdd,
|
postulantAdd,
|
||||||
preVolunteerCount,
|
|
||||||
teamList,
|
teamList,
|
||||||
ui,
|
ui,
|
||||||
volunteer,
|
|
||||||
volunteerAdd,
|
volunteerAdd,
|
||||||
|
volunteerDiscordId,
|
||||||
volunteerList,
|
volunteerList,
|
||||||
volunteerSet,
|
volunteerSet,
|
||||||
volunteerLogin,
|
volunteerLogin,
|
||||||
|
@@ -65,7 +65,7 @@ export function elementFetch<Element, ServiceInput extends Array<any>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function elementAddFetch<Element>(
|
export function elementAddFetch<Element>(
|
||||||
elementAddService: (volunteerWithoutId: Omit<Element, "id">) => Promise<{
|
elementAddService: (elementWithoutId: Omit<Element, "id">) => Promise<{
|
||||||
data?: Element | undefined
|
data?: Element | undefined
|
||||||
error?: Error | undefined
|
error?: Error | undefined
|
||||||
}>,
|
}>,
|
||||||
@@ -74,12 +74,12 @@ export function elementAddFetch<Element>(
|
|||||||
getFailure: ActionCreatorWithPayload<string, string>,
|
getFailure: ActionCreatorWithPayload<string, string>,
|
||||||
errorMessage?: (error: Error) => void,
|
errorMessage?: (error: Error) => void,
|
||||||
successMessage?: () => void
|
successMessage?: () => void
|
||||||
): (volunteerWithoutId: Omit<Element, "id">) => AppThunk {
|
): (elementWithoutId: Omit<Element, "id">) => AppThunk {
|
||||||
return (volunteerWithoutId: Omit<Element, "id">): AppThunk =>
|
return (elementWithoutId: Omit<Element, "id">): AppThunk =>
|
||||||
async (dispatch) => {
|
async (dispatch) => {
|
||||||
dispatch(getRequesting())
|
dispatch(getRequesting())
|
||||||
|
|
||||||
const { error, data } = await elementAddService(volunteerWithoutId)
|
const { error, data } = await elementAddService(elementWithoutId)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
dispatch(getFailure(error.message))
|
dispatch(getFailure(error.message))
|
||||||
@@ -119,7 +119,7 @@ export function elementListFetch<Element, ServiceInput extends Array<any>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function elementSet<Element>(
|
export function elementSet<Element>(
|
||||||
elementSetService: (volunteer: Element) => Promise<{
|
elementSetService: (element: Element) => Promise<{
|
||||||
data?: Element | undefined
|
data?: Element | undefined
|
||||||
error?: Error | undefined
|
error?: Error | undefined
|
||||||
}>,
|
}>,
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
|
||||||
|
|
||||||
import { StateRequest, toastError, elementFetch } from "./utils"
|
|
||||||
import { Volunteer } from "../services/volunteers"
|
|
||||||
import { AppThunk, AppState } from "."
|
|
||||||
import { volunteerGet } from "../services/volunteersAccessors"
|
|
||||||
|
|
||||||
type StateVolunteer = { entity?: Volunteer } & StateRequest
|
|
||||||
|
|
||||||
export const initialState: StateVolunteer = {
|
|
||||||
readyStatus: "idle",
|
|
||||||
}
|
|
||||||
|
|
||||||
const volunteer = createSlice({
|
|
||||||
name: "volunteer",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
getRequesting: (_) => ({
|
|
||||||
readyStatus: "request",
|
|
||||||
}),
|
|
||||||
getSuccess: (_, { payload }: PayloadAction<Volunteer>) => ({
|
|
||||||
readyStatus: "success",
|
|
||||||
entity: payload,
|
|
||||||
}),
|
|
||||||
getFailure: (_, { payload }: PayloadAction<string>) => ({
|
|
||||||
readyStatus: "failure",
|
|
||||||
error: payload,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export default volunteer.reducer
|
|
||||||
export const { getRequesting, getSuccess, getFailure } = volunteer.actions
|
|
||||||
|
|
||||||
export const fetchVolunteer = elementFetch(
|
|
||||||
volunteerGet,
|
|
||||||
getRequesting,
|
|
||||||
getSuccess,
|
|
||||||
getFailure,
|
|
||||||
(error: Error) => toastError(`Erreur lors du chargement d'un bénévole: ${error.message}`)
|
|
||||||
)
|
|
||||||
|
|
||||||
const shouldFetchVolunteer = (state: AppState, id: number) =>
|
|
||||||
state.volunteer.readyStatus !== "success" ||
|
|
||||||
(state.volunteer.entity && state.volunteer.entity.id !== id)
|
|
||||||
|
|
||||||
export const fetchVolunteerIfNeed =
|
|
||||||
(id: number): AppThunk =>
|
|
||||||
(dispatch, getState) => {
|
|
||||||
if (shouldFetchVolunteer(getState(), id)) return dispatch(fetchVolunteer(id))
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
65
src/store/volunteerDiscordId.ts
Normal file
65
src/store/volunteerDiscordId.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { PayloadAction, createSlice, createSelector } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
|
import { StateRequest, toastError, elementFetch } from "./utils"
|
||||||
|
import { VolunteerDiscordId } from "../services/volunteers"
|
||||||
|
import { AppThunk, AppState } from "."
|
||||||
|
import { volunteerDiscordIdGet } from "../services/volunteersAccessors"
|
||||||
|
|
||||||
|
type StateVolunteerDiscordId = { entity?: VolunteerDiscordId } & StateRequest
|
||||||
|
|
||||||
|
export const initialState: StateVolunteerDiscordId = {
|
||||||
|
readyStatus: "idle",
|
||||||
|
}
|
||||||
|
|
||||||
|
const volunteerDiscordId = createSlice({
|
||||||
|
name: "volunteerDiscordId",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
getRequesting: (_) => ({
|
||||||
|
readyStatus: "request",
|
||||||
|
}),
|
||||||
|
getSuccess: (_, { payload }: PayloadAction<VolunteerDiscordId>) => ({
|
||||||
|
readyStatus: "success",
|
||||||
|
entity: payload,
|
||||||
|
}),
|
||||||
|
getFailure: (_, { payload }: PayloadAction<string>) => ({
|
||||||
|
readyStatus: "failure",
|
||||||
|
error: payload,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default volunteerDiscordId.reducer
|
||||||
|
export const { getRequesting, getSuccess, getFailure } = volunteerDiscordId.actions
|
||||||
|
|
||||||
|
export const fetchVolunteerDiscordId = elementFetch(
|
||||||
|
volunteerDiscordIdGet,
|
||||||
|
getRequesting,
|
||||||
|
getSuccess,
|
||||||
|
getFailure,
|
||||||
|
(error: Error) =>
|
||||||
|
toastError(`Erreur lors du chargement du discordId d'un bénévole: ${error.message}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
const shouldFetchVolunteerDiscordId = (state: AppState, id: number) =>
|
||||||
|
state.volunteerDiscordId.readyStatus !== "success" ||
|
||||||
|
(state.volunteerDiscordId.entity && state.volunteerDiscordId.entity.id !== id)
|
||||||
|
|
||||||
|
export const fetchVolunteerDiscordIdIfNeed =
|
||||||
|
(id = 0): AppThunk =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
let jwt = ""
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
;({ jwt, id } = getState().auth)
|
||||||
|
}
|
||||||
|
if (shouldFetchVolunteerDiscordId(getState(), id))
|
||||||
|
return dispatch(fetchVolunteerDiscordId(jwt, id))
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const selectVolunteerDiscordId = createSelector(
|
||||||
|
(state: AppState) => state,
|
||||||
|
(state): number | undefined => state.volunteerDiscordId?.entity?.id
|
||||||
|
)
|
@@ -1,13 +1,13 @@
|
|||||||
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
|
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
import { StateRequest, toastError, toastSuccess, elementAddFetch } from "./utils"
|
import { StateRequest, elementAddFetch } from "./utils"
|
||||||
import { Volunteer } from "../services/volunteers"
|
import { Volunteer } from "../services/volunteers"
|
||||||
import { volunteerAdd } from "../services/volunteersAccessors"
|
import { volunteerPartialAdd } from "../services/volunteersAccessors"
|
||||||
|
|
||||||
const volunteerAdapter = createEntityAdapter<Volunteer>()
|
const volunteerAdapter = createEntityAdapter<Volunteer>()
|
||||||
|
|
||||||
const volunteerAddSlice = createSlice({
|
const volunteerPartialAddSlice = createSlice({
|
||||||
name: "addVolunteer",
|
name: "volunteerAdd",
|
||||||
initialState: volunteerAdapter.getInitialState({
|
initialState: volunteerAdapter.getInitialState({
|
||||||
readyStatus: "idle",
|
readyStatus: "idle",
|
||||||
} as StateRequest),
|
} as StateRequest),
|
||||||
@@ -17,7 +17,7 @@ const volunteerAddSlice = createSlice({
|
|||||||
},
|
},
|
||||||
getSuccess: (state, { payload }: PayloadAction<Volunteer>) => {
|
getSuccess: (state, { payload }: PayloadAction<Volunteer>) => {
|
||||||
state.readyStatus = "success"
|
state.readyStatus = "success"
|
||||||
volunteerAdapter.addOne(state, payload)
|
volunteerAdapter.setOne(state, payload)
|
||||||
},
|
},
|
||||||
getFailure: (state, { payload }: PayloadAction<string>) => {
|
getFailure: (state, { payload }: PayloadAction<string>) => {
|
||||||
state.readyStatus = "failure"
|
state.readyStatus = "failure"
|
||||||
@@ -26,14 +26,14 @@ const volunteerAddSlice = createSlice({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default volunteerAddSlice.reducer
|
export default volunteerPartialAddSlice.reducer
|
||||||
export const { getRequesting, getSuccess, getFailure } = volunteerAddSlice.actions
|
export const { getRequesting, getSuccess, getFailure } = volunteerPartialAddSlice.actions
|
||||||
|
|
||||||
export const fetchVolunteerAdd = elementAddFetch(
|
export const fetchVolunteerPartialAdd = elementAddFetch(
|
||||||
volunteerAdd,
|
volunteerPartialAdd,
|
||||||
getRequesting,
|
getRequesting,
|
||||||
getSuccess,
|
getSuccess,
|
||||||
getFailure,
|
getFailure,
|
||||||
(error: Error) => toastError(`Erreur lors de l'ajout d'un bénévole: ${error.message}`),
|
() => null,
|
||||||
() => toastSuccess("Volunteer ajoutée !")
|
() => null
|
||||||
)
|
)
|
@@ -7,7 +7,7 @@ import { wishAdd } from "../services/wishesAccessors"
|
|||||||
const wishAdapter = createEntityAdapter<Wish>()
|
const wishAdapter = createEntityAdapter<Wish>()
|
||||||
|
|
||||||
const wishAddSlice = createSlice({
|
const wishAddSlice = createSlice({
|
||||||
name: "addWish",
|
name: "wishAdd",
|
||||||
initialState: wishAdapter.getInitialState({
|
initialState: wishAdapter.getInitialState({
|
||||||
readyStatus: "idle",
|
readyStatus: "idle",
|
||||||
} as StateRequest),
|
} as StateRequest),
|
||||||
|
@@ -8,7 +8,7 @@ import { wishListGet } from "../services/wishesAccessors"
|
|||||||
const wishAdapter = createEntityAdapter<Wish>()
|
const wishAdapter = createEntityAdapter<Wish>()
|
||||||
|
|
||||||
const wishList = createSlice({
|
const wishList = createSlice({
|
||||||
name: "getWishList",
|
name: "wishList",
|
||||||
initialState: wishAdapter.getInitialState({
|
initialState: wishAdapter.getInitialState({
|
||||||
readyStatus: "idle",
|
readyStatus: "idle",
|
||||||
} as StateRequest),
|
} as StateRequest),
|
||||||
|
Reference in New Issue
Block a user