diff --git a/src/app/img/barnums.jpg b/src/app/img/barnums.jpg
new file mode 100644
index 0000000..acc5a38
Binary files /dev/null and b/src/app/img/barnums.jpg differ
diff --git a/src/app/img/bene2019.jpg b/src/app/img/bene2019.jpg
new file mode 100644
index 0000000..a7df818
Binary files /dev/null and b/src/app/img/bene2019.jpg differ
diff --git a/src/app/img/pel2016.jpg b/src/app/img/pel2016.jpg
new file mode 100644
index 0000000..d6d0ef5
Binary files /dev/null and b/src/app/img/pel2016.jpg differ
diff --git a/src/app/img/pel2017.jpg b/src/app/img/pel2017.jpg
new file mode 100644
index 0000000..452d34b
Binary files /dev/null and b/src/app/img/pel2017.jpg differ
diff --git a/src/app/img/plan2019.jpg b/src/app/img/plan2019.jpg
new file mode 100644
index 0000000..eeef5d6
Binary files /dev/null and b/src/app/img/plan2019.jpg differ
diff --git a/src/components/Asks/AskDiscord.tsx b/src/components/Asks/AskDiscord.tsx
new file mode 100644
index 0000000..0b23cec
--- /dev/null
+++ b/src/components/Asks/AskDiscord.tsx
@@ -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,
+
+
+ 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 !
+ 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.
+
+ Pour rejoindre le serveur PeL, voici le lien d'invitation à cliquer :{" "}
+
+ https://discord.gg/eXhjKxSBB4
+ {" "}
+ !
+
+
+ Prends le temps de le rejoindre maintenant, c'est via cet outil que la plupart des
+ équipes s'organisent !
+
+
+ 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”.
+
+
+
+ Ok, noté
+
+
+ )
+}
+
+// Fetch server-side data here
+export const fetchFor = [fetchVolunteerDiscordIdIfNeed]
diff --git a/src/components/Asks/AskPushNotif.tsx b/src/components/Asks/AskPushNotif.tsx
index 9676c19..7c963b9 100644
--- a/src/components/Asks/AskPushNotif.tsx
+++ b/src/components/Asks/AskPushNotif.tsx
@@ -191,7 +191,7 @@ export function AskPushNotif(asks: JSX.Element[], id: number): void {
volunteerAsks,
true,
needToShow,
-
+
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en aura
d'autres à t'afficher ici ?
diff --git a/src/components/Asks/index.tsx b/src/components/Asks/index.tsx
index e4877d5..14428e1 100644
--- a/src/components/Asks/index.tsx
+++ b/src/components/Asks/index.tsx
@@ -3,19 +3,21 @@ import React, { memo } from "react"
import styles from "./styles.module.scss"
import { useAskTools } from "./utils"
import { AskWelcome } from "./AskWelcome"
-import { AskPushNotif } from "./AskPushNotif"
+import { AskDiscord, fetchFor as fetchForDiscord } from "./AskDiscord"
import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
import {
AskParticipationDetails,
fetchFor as fetchForParticipationDetails,
} from "./AskParticipationDetails"
+import { AskPushNotif } from "./AskPushNotif"
const Asks = (): JSX.Element | null => {
const { volunteerAsks } = useAskTools()
const asks: JSX.Element[] = []
AskWelcome(asks, 1)
+ AskDiscord(asks, 3)
AskDayWishes(asks, 10)
AskTeamWishes(asks, 11)
@@ -28,7 +30,7 @@ const Asks = (): JSX.Element | null => {
-
+
Tu as fait le tour des dernières infos ou questions importantes,
merci ! :)
@@ -54,6 +56,7 @@ export default memo(Asks)
// Fetch server-side data here
export const fetchFor = [
+ ...fetchForDiscord,
...fetchForDayWishes,
...fetchForTeamWishes,
...fetchForParticipationDetails,
diff --git a/src/components/Asks/utils.tsx b/src/components/Asks/utils.tsx
index aca7104..a2644f2 100644
--- a/src/components/Asks/utils.tsx
+++ b/src/components/Asks/utils.tsx
@@ -34,7 +34,7 @@ export function addAsk(
volunteerAsks: VolunteerAsks | undefined,
isNarrow: boolean,
needToShow: boolean,
- children: JSX.Element
+ children: JSX.Element | undefined
): void {
const hidden = volunteerAsks?.hiddenAsks || []
if (_.includes(hidden, id) || !_.isEmpty(asks) || !needToShow) {
diff --git a/src/components/Form/FormButton/FormButton.tsx b/src/components/Form/FormButton/FormButton.tsx
index 07e6eeb..b0c76ec 100644
--- a/src/components/Form/FormButton/FormButton.tsx
+++ b/src/components/Form/FormButton/FormButton.tsx
@@ -1,24 +1,31 @@
import { FC, ReactNode } from "react"
+import { toastError } from "../../../store/utils"
import styles from "./styles.module.scss"
type Props = {
type?: "grey"
+ disabled?: boolean
children: ReactNode
onClick?: () => void
}
-const FormButton: FC = ({ type, children, onClick }): JSX.Element => (
-
- {children}
-
-)
+const FormButton: FC = ({ type, disabled, children, onClick }): JSX.Element => {
+ const onDisabledClick = () => toastError("Bouton désactivé")
+
+ return (
+
+ {children}
+
+ )
+}
FormButton.defaultProps = {
type: undefined,
+ disabled: false,
onClick: undefined,
}
diff --git a/src/components/PreRegisterForm/index.tsx b/src/components/PreRegisterForm/index.tsx
deleted file mode 100644
index 39b79cd..0000000
--- a/src/components/PreRegisterForm/index.tsx
+++ /dev/null
@@ -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) =>
- setFirstname(e.target.value)
- const onLastnameChanged = (e: React.ChangeEvent) =>
- setLastname(e.target.value)
- const onEmailChanged = (e: React.ChangeEvent) => setEmail(e.target.value)
- const onMobileChanged = (e: React.ChangeEvent) => setMobile(e.target.value)
- const onAlreadyVolunteer = (e: React.ChangeEvent) =>
- setAlreadyVolunteer(!!e.target.value)
- const onNotYesVolunteer = (e: React.ChangeEvent) =>
- setAlreadyVolunteer(!e.target.value)
- const onCommentChanged = (e: React.ChangeEvent) =>
- 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 = Formulaire envoyé !
- }
-
- let sendError
- if (error && _.isEmpty(preVolunteer)) {
- if (sending) {
- setSending(false)
- }
- sendError = {error}
- }
-
- let sendingElement
- if (sending) {
- sendingElement = Envoi en cours...
- }
- /*
- firstname
- lastname
- mail
- tel
- j'ai déjà été bénévole pour PEL
- un petit mot...
- */
-
- return (
-
- )
-}
-
-export default memo(PreRegisterForm)
diff --git a/src/components/PreRegisterForm/styles.module.scss b/src/components/PreRegisterForm/styles.module.scss
deleted file mode 100755
index e6e854c..0000000
--- a/src/components/PreRegisterForm/styles.module.scss
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/components/RegisterForm/index.tsx b/src/components/RegisterForm/index.tsx
new file mode 100644
index 0000000..6fc1ace
--- /dev/null
+++ b/src/components/RegisterForm/index.tsx
@@ -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>) =>
+ (e: React.ChangeEvent) =>
+ dispatchSetter(e.target.value)
+
+ const sendTextareaDispatch =
+ (dispatchSetter: React.Dispatch>) =>
+ (e: React.ChangeEvent) =>
+ dispatchSetter(e.target.value)
+
+ const sendBooleanRadioboxDispatch =
+ (dispatchSetter: React.Dispatch>, isYes: boolean) =>
+ (e: React.ChangeEvent) =>
+ dispatchSetter(isYes ? !!e.target.value : !e.target.value)
+
+ const sendRadioboxDispatch =
+ (dispatchSetter: React.Dispatch>) =>
+ (e: React.ChangeEvent) =>
+ 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 = Formulaire envoyé !
+ } else if (postulantError && _.isEmpty(postulant)) {
+ if (sending) {
+ setSending(false)
+ }
+ sendError = {postulantError}
+ } else if (volunteerError && _.isEmpty(volunteer)) {
+ if (sending) {
+ setSending(false)
+ }
+ sendError = {volunteerError}
+ } else if (sending) {
+ sendingElement = (
+
+ Envoi en cours...
+
+ En cas de problème, écrire à contact@parisestludique.fr
+
+ )
+ }
+
+ const intro = (
+
+ Qu'est-ce que Paris est Ludique ?
+
+
+ Un festival en plein air dédié aux jeux de société modernes sous toutes
+ leurs formes. Les samedi 2 et dimanche 3 juillet 2022 !
+
+
+ En 2019 lors de la dernière édition, ce sont 16 000 joueurs qui se sont
+ réunis sous 300 chapiteaux et 2 000 tables.
+
+
+ Les 2 jours que durent le festival sont entièrement dédiés à ce que le public{" "}
+ JOUE , 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.
+
+
+
+ Et les bénévoles de PeL ?
+
+
+ L'organisation du festival est entièrement gérée par nous , les
+ bénévoles. À aucun moment ça ne ressemble à du travail : nous faisons tout pour
+ passer un aussi bon moment que les visiteurs :)
+
+
+ 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 ^^
+
+
+ Pendant le festival de 2019, nous étions 187 bénévoles 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.
+
+
+ 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 !
+
+
+ La majorité d'entre nous sommes bénévoles les samedi et dimanche , 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 !
+
+
+ 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.
+
+
+
+
+ Si l'expérience vous tente, remplissez le formulaire suivant pour devenir
+ bénévole !
+ 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.
+
+ Dans les deux cas, venez rencontrer une poignée d'entre nous dans un bar/resto près
+ de Châtelet ! :) Sur inscription uniquement...
+
+
+
+ )
+
+ const potentialVolunteerQuestion = (
+
+
+
Je veux devenir bénévole :
+
+
+ {["Tout de suite !", "Peut-être après une rencontre avec des bénévoles"].map(
+ (option) => (
+
+ {" "}
+ {option}
+
+ )
+ )}
+
+
+ )
+
+ const alreadyVolunteerQuestion = !potentialVolunteer && (
+ <>
+
+
+
J'ai déjà été bénévole à PeL
+
+
+
+ {["Oui", "Non"].map((option) => (
+
+ {" "}
+ {option}
+
+ ))}
+
+
+
+
+ {alreadyVolunteer && (
+
+
+ Dans ce cas pourquoi t'inscris-tu ici ? ^^
+
+ 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{" "}
+
+ t'identifier ici
+ {" "}
+ avec ton ancien mot de passe, ou en{" "}
+
+ demander un nouveau ici
+
+ .
+
+
+ Autrement, si tu as changé d'email, mieux vaut nous le communiquer à
+ benevoles@parisestludique.fr en précisant bien tes nom et prénom :)
+
+
+
+ )}
+ >
+ )
+
+ const commentQuestion = (
+
+
+
+
+
+ )
+
+ const cameAsVisitor = (
+ <>
+
+
+
+ Es-tu déjà venu à PeL en tant que visiteur ?
+
+
+
+
+ {["Oui", "Non"].map((option) => (
+
+ {" "}
+ {option}
+
+ ))}
+
+
+
+
+ {!alreadyCame && (
+
+ Dans ce cas laisse-moi t'en dire un peu plus sur le festival !
+
+
+ Il a lieu peu après la Foire du Trône et au même emplacement, sur la{" "}
+
+ "pelouse" de Reuilly
+
+ . 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.
+
+
+
+
+ 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.
+
+
+ Après l'édition de 2019, on a fait cette petite vidéo qui donne une
+ bonne impression de l'ambiance :{" "}
+
+ https://www.youtube.com/watch?v=eVuQaERb7EU
+
+
+
+ Et des visiteurs passionnés (
+
+ Road N Troll
+
+ ) 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é :{" "}
+
+ https://www.youtube.com/watch?v=jSCHWqjHJIQ
+
+
+
+
+ )}
+ >
+ )
+
+ const meeting = (
+ <>
+
+ {!potentialVolunteer && Faisons connaissance ! }
+ {potentialVolunteer && (
+ Se rencontrer avant de se décider à devenir bénévole ?
+ )}
+
+
+ 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.
+
+
+ Ces rencontres ont lieu dans un bar/resto calme à Châtelet, le{" "}
+
+ Street Food Market
+
+ .
+
+
+
+
+
+
+
+ À quelle date pourrais-tu venir ?
+
+
+
+
+ {[
+ { value: "20avril", desc: "Mercredi 20 avril à 19h" },
+ { value: "13mai", desc: "Vendredi 13 mai à 19h30" },
+ { value: "", desc: "Aucune date possible" },
+ ].map((option) => (
+
+ {" "}
+ {option.desc}
+
+ ))}
+
+
+
+
+ {firstMeeting !== "" && (
+
+
+
+ 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 :)
+
+ Si tu as un contre-temps, écris-nous à benevoles@parisestludique.fr
+ À très bientôt !
+
+
+ )}
+
+ {firstMeeting === "" && (
+
+ )}
+ >
+ )
+
+ const helpBefore = !potentialVolunteer && (
+ <>
+
+ Bénévolat en amont du festival
+
+
+ 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 ^^
+
+
+ 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.
+
+
+
+
+
+
+ Bref, as-tu le temps et l'envie de voir si tu peux aider en amont du
+ festival ?
+
+
+
+
+ {[
+ { value: "oui", desc: "Oui" },
+ { value: "non", desc: "Non" },
+ { value: "", desc: "Ne sais pas" },
+ ].map((option) => (
+
+ {" "}
+ {option.desc}
+
+ ))}
+
+
+
+
+
+ {canHelpBefore === "oui" && (
+
+ 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.
+
+ )}
+ {canHelpBefore === "non" && (
+
+ Aucun souci tu nous seras d'une aide précieuse le jour J c'est déjà
+ énorme !
+
+ )}
+
+ Si tu changes d'avis, il sera possible de revenir sur cette décision dans
+ ton profil sur le site.
+
+
+
+ >
+ )
+
+ const pelMemberQuestion = !potentialVolunteer && (
+ <>
+
+ Association Paris est Ludique
+
+
+ Légalement il faut que le festival soit organisé par une structure, et c'est
+ l'association Paris est Ludique ! 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.
+
+
+
+
+
+
+ Acceptes-tu de devenir membre de l'association Paris est Ludique ! ?
+
+
+
+
+ {["Oui", "Non"].map((option) => (
+
+ {" "}
+ {option}
+
+ ))}
+
+
+
+ {!pelMember && (
+
+
+
+ Tant que tu n'as pas accepté cette condition je suis désolé on ne peut
+ pas continuer.
+
+
+
+ )}
+ >
+ )
+
+ const nameMobileEmail = (
+ <>
+
+ Quelques infos sur toi pour finir
+
+
+
+
+
+
+
+
+ Par quel moyen fiable et rapide préfères-tu être contacté si on en a besoin
+ ?
+
+
+
+
+ {["Email", "SMS", "WhatsApp", "Signal", "Appel", "Aucun"].map((option) => (
+
+ {" "}
+ {option}
+
+ ))}
+
+
+
+ {howToContact === "Aucun" && (
+
+
+
+ Aïe ça va poser problème, je suis désolé. Il faut faire un effort en
+ choisissant un moyen de communication proposé.
+
+
+ Tu en connais un suffisamment répandu et meilleur que ceux proposés ?
+ Parle-nous en à benevoles@parisestludique.fr :)
+
+
+
+ )}
+ >
+ )
+
+ const submitButton = (
+ <>
+
+
+ Envoyer
+
+
+
+ {sendingElement}
+ {sendSuccess}
+ {sendError}
+
+ >
+ )
+
+ return (
+
+ {intro}
+ {potentialVolunteerQuestion}
+ {alreadyVolunteerQuestion}
+
+ {(potentialVolunteer || !alreadyVolunteer) && (
+ <>
+ {commentQuestion}
+ {cameAsVisitor}
+ {meeting}
+ {helpBefore}
+ {pelMemberQuestion}
+ {(potentialVolunteer || pelMember) && (
+ <>
+ {nameMobileEmail}
+ {howToContact !== "Aucun" && submitButton}
+ >
+ )}
+ >
+ )}
+
+ )
+}
+
+export default memo(RegisterForm)
diff --git a/src/components/RegisterForm/styles.module.scss b/src/components/RegisterForm/styles.module.scss
new file mode 100755
index 0000000..114b4d2
--- /dev/null
+++ b/src/components/RegisterForm/styles.module.scss
@@ -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;
+ }
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index 8392574..3f08d7d 100755
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -11,8 +11,8 @@ import Asks, { fetchFor as fetchForAsks } from "./Asks"
import ParticipationDetailsForm, {
fetchFor as fetchForParticipationDetailsForm,
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
-import PreRegisterForm from "./PreRegisterForm"
import TeamAssignment, { fetchFor as fetchForTeamAssignment } from "./TeamAssignment/TeamAssignment"
+import RegisterForm from "./RegisterForm"
import TeamWishesForm, {
fetchFor as fetchForTeamWishesForm,
} from "./VolunteerBoard/TeamWishesForm/TeamWishesForm"
@@ -35,9 +35,9 @@ export {
fetchForAsks,
ParticipationDetailsForm,
fetchForParticipationDetailsForm,
- PreRegisterForm,
TeamAssignment,
fetchForTeamAssignment,
+ RegisterForm,
TeamWishesForm,
fetchForTeamWishesForm,
VolunteerInfo,
diff --git a/src/pages/PreRegister/PreRegister.tsx b/src/pages/PreRegister/PreRegister.tsx
deleted file mode 100644
index a262c5d..0000000
--- a/src/pages/PreRegister/PreRegister.tsx
+++ /dev/null
@@ -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,
- 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 Loading...
-
- if (readyStatus === "failure") return Oops, Failed to load!
-
- return
- }
-}
-
-const PreRegisterPage: FC = (): JSX.Element => (
-
-
-
- {useList((state: AppState) => state.preVolunteerCount, fetchPreVolunteerCountIfNeed)()}
-
-
-)
-
-// Fetch server-side data here
-export const loadData = (): AppThunk[] => [fetchPreVolunteerCountIfNeed()]
-
-export default memo(PreRegisterPage)
diff --git a/src/pages/Register/Register.tsx b/src/pages/Register/Register.tsx
new file mode 100644
index 0000000..588bb88
--- /dev/null
+++ b/src/pages/Register/Register.tsx
@@ -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 = (): JSX.Element => {
+ const dispatch = useDispatch()
+ return (
+
+ )
+}
+
+// Fetch server-side data here
+export const loadData = (): AppThunk[] => []
+
+export default memo(RegisterPage)
diff --git a/src/pages/PreRegister/index.tsx b/src/pages/Register/index.tsx
similarity index 62%
rename from src/pages/PreRegister/index.tsx
rename to src/pages/Register/index.tsx
index 0c8f838..fdb32ba 100755
--- a/src/pages/PreRegister/index.tsx
+++ b/src/pages/Register/index.tsx
@@ -1,15 +1,15 @@
import loadable from "@loadable/component"
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: ,
})
export default (props: Props): JSX.Element => (
-
+
)
diff --git a/src/pages/PreRegister/styles.module.scss b/src/pages/Register/styles.module.scss
similarity index 73%
rename from src/pages/PreRegister/styles.module.scss
rename to src/pages/Register/styles.module.scss
index 7d678c1..c7f3efb 100755
--- a/src/pages/PreRegister/styles.module.scss
+++ b/src/pages/Register/styles.module.scss
@@ -1,9 +1,9 @@
@import "../../theme/mixins";
-.preRegisterPage {
+.registerPage {
@include page-wrapper-center;
}
-.preRegisterContent {
+.registerContent {
@include page-content-wrapper(600px);
}
diff --git a/src/pages/VolunteerPage/VolunteerPage.tsx b/src/pages/VolunteerPage/VolunteerPage.tsx
deleted file mode 100755
index d40a438..0000000
--- a/src/pages/VolunteerPage/VolunteerPage.tsx
+++ /dev/null
@@ -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 Loading...
-
- if (volunteerInfo.readyStatus === "failure" || !volunteerInfo.entity)
- return Oops! Failed to load data.
-
- return (
-
-
-
-
- )
- }
-
- return (
-
-
- {renderInfo()}
-
- )
-}
-
-interface LoadDataArgs {
- params: { id: number }
-}
-
-export const loadData = ({ params }: LoadDataArgs): AppThunk[] => [fetchVolunteerIfNeed(params.id)]
-
-export default memo(VolunteerPage)
diff --git a/src/pages/VolunteerPage/index.tsx b/src/pages/VolunteerPage/index.tsx
deleted file mode 100755
index a871900..0000000
--- a/src/pages/VolunteerPage/index.tsx
+++ /dev/null
@@ -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: ,
-})
-
-export default (props: Props): JSX.Element => (
-
-
-
-)
-export { loadData }
diff --git a/src/pages/VolunteerPage/styles.module.scss b/src/pages/VolunteerPage/styles.module.scss
deleted file mode 100755
index b95a81a..0000000
--- a/src/pages/VolunteerPage/styles.module.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-.VolunteerPage {
- padding: 0 15px;
-}
diff --git a/src/routes/index.ts b/src/routes/index.ts
index 6ba47d3..15a1fd8 100755
--- a/src/routes/index.ts
+++ b/src/routes/index.ts
@@ -4,12 +4,11 @@ import App from "../app"
import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
import AsyncAnnouncements, { loadData as loadAnnouncementsData } from "../pages/Announcements"
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 AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
import AsyncVolunteers, { loadData as loadVolunteersData } from "../pages/Volunteers"
import AsyncWish, { loadData as loadWishData } from "../pages/Wish"
-import AsyncVolunteerPage, { loadData as loadVolunteerPageData } from "../pages/VolunteerPage"
import Login from "../pages/Login"
import Forgot from "../pages/Forgot"
import NotFound from "../pages/NotFound"
@@ -26,18 +25,13 @@ export default [
},
{
path: "/preRegister",
- component: AsyncPreRegisterPage,
- loadData: loadPreRegisterPage,
+ component: AsyncRegisterPage,
+ loadData: loadRegisterPage,
},
{
path: "/sinscrire",
- component: AsyncPreRegisterPage,
- loadData: loadPreRegisterPage,
- },
- {
- path: "/VolunteerPage/:id",
- component: AsyncVolunteerPage,
- loadData: loadVolunteerPageData,
+ component: AsyncRegisterPage,
+ loadData: loadRegisterPage,
},
{
path: "/login",
diff --git a/src/server/gsheets/accessors.ts b/src/server/gsheets/accessors.ts
index db502e7..8b51ab2 100644
--- a/src/server/gsheets/accessors.ts
+++ b/src/server/gsheets/accessors.ts
@@ -7,7 +7,7 @@ import { SheetNames, saveLocalDb, loadLocalDb } 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")
@@ -122,7 +122,7 @@ export class Sheet<
return (_.max(ids) || 0) + 1
}
- async add(elementWithoutId: ElementNoId): Promise {
+ async add(elementWithoutId: Omit): Promise {
const elements: Element[] = (await this.getList()) || []
// eslint-disable-next-line @typescript-eslint/ban-types
const element: Element = { id: await this.nextId(), ...elementWithoutId } as Element
@@ -604,7 +604,7 @@ async function tryNTimes(
return await func()
} catch (e: any) {
console.error(e?.error || e?.message || e)
- console.error(`${repeatCount} attemps left every ${delayBetweenAttempts}`)
+ console.error(`${repeatCount} attempts left every ${delayBetweenAttempts}`)
await new Promise((resolve) => {
setTimeout(() => resolve(), delayBetweenAttempts)
})
diff --git a/src/server/gsheets/expressAccessors.ts b/src/server/gsheets/expressAccessors.ts
index 56c7fc2..c3dfe18 100644
--- a/src/server/gsheets/expressAccessors.ts
+++ b/src/server/gsheets/expressAccessors.ts
@@ -3,6 +3,7 @@ import { SheetNames, ElementWithId, getSheet, Sheet } from "./accessors"
export type RequestBody = Request["body"]
export type CustomSetReturn = { toDatabase: Element; toCaller: any }
+export type CustomAddReturn = { toDatabase: Omit; toCaller: any }
export default class ExpressAccessors<
// 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
+ ) {
return async (request: Request, response: Response, _next: NextFunction): Promise => {
try {
const sheet = await this.getSheet()
- const element: Element = await sheet.add(request.body)
- if (element) {
- response.status(200).json(element)
+ if (!custom) {
+ await sheet.add(request.body)
+ 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) {
response.status(200).json({ error: e.message })
diff --git a/src/server/gsheets/localDb.ts b/src/server/gsheets/localDb.ts
index 6e59cf9..a685084 100644
--- a/src/server/gsheets/localDb.ts
+++ b/src/server/gsheets/localDb.ts
@@ -3,7 +3,7 @@ import path from "path"
import _ from "lodash"
import { promises as fs } from "fs"
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_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json")
@@ -14,7 +14,7 @@ export class SheetNames {
Games = "Jeux"
- PreVolunteers = "PreMembres"
+ Postulants = "Postulants"
Teams = "Equipes"
@@ -263,8 +263,8 @@ function anonimizedDb(_s: States): States {
anonimizedNotifs(v)
})
}
- if (s.PreVolunteers) {
- ;(s.PreVolunteers as PreVolunteer[]).forEach((v) => {
+ if (s.Postulants) {
+ ;(s.Postulants as Postulant[]).forEach((v) => {
anonimizedNameEmailMobile(v)
v.comment = v.id % 3 === 0 ? "Bonjour, j'adore l'initiative!" : ""
})
@@ -272,11 +272,11 @@ function anonimizedDb(_s: States): States {
return s
}
-function idADev(v: Volunteer | PreVolunteer): boolean {
+function idADev(v: Volunteer | Postulant): boolean {
return ((v as Volunteer)?.roles || []).includes("dev")
}
-function anonimizedNameEmailMobile(v: Volunteer | PreVolunteer): void {
+function anonimizedNameEmailMobile(v: Volunteer | Postulant): void {
if (idADev(v)) {
return
}
diff --git a/src/server/gsheets/postulants.ts b/src/server/gsheets/postulants.ts
new file mode 100644
index 0000000..95e73c7
--- /dev/null
+++ b/src/server/gsheets/postulants.ts
@@ -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(
+ "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(list: T[], rawEmail: string): T | undefined {
+ const email = canonicalEmail(rawEmail || "")
+ const volunteer = list.find((v) => canonicalEmail(v.email) === email)
+ return volunteer
+}
diff --git a/src/server/gsheets/preVolunteers.ts b/src/server/gsheets/preVolunteers.ts
deleted file mode 100644
index 553f6a5..0000000
--- a/src/server/gsheets/preVolunteers.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import ExpressAccessors from "./expressAccessors"
-import {
- PreVolunteer,
- PreVolunteerWithoutId,
- translationPreVolunteer,
-} from "../../services/preVolunteers"
-
-const expressAccessor = new ExpressAccessors(
- "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)
diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts
index e384afb..a778d99 100644
--- a/src/server/gsheets/volunteers.ts
+++ b/src/server/gsheets/volunteers.ts
@@ -14,7 +14,7 @@ import {
VolunteerParticipationDetails,
VolunteerTeamAssign,
} from "../../services/volunteers"
-import { canonicalEmail } from "../../utils/standardization"
+import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
import { getJwt } from "../secure"
const expressAccessor = new ExpressAccessors(
@@ -24,9 +24,74 @@ const expressAccessor = new ExpressAccessors(
)
export const volunteerListGet = expressAccessor.listGet()
-export const volunteerAdd = expressAccessor.add()
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 {
+ 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 ! :) Voici ton mot de passe pour accéder au site des bénévoles : ${password} Tu y trouveras notamment comment on communique entre bénévoles. Bonne journée ! Pierre`,
+ }
+ await sgMail.send(msg)
+ }
+}
+
export const volunteerLogin = expressAccessor.get(async (list, bodyArray) => {
const [body] = bodyArray
const volunteer = getByEmail(list, body.email)
diff --git a/src/server/index.ts b/src/server/index.ts
index a5d7734..a11786e 100755
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -19,16 +19,18 @@ import certbotRouter from "../routes/certbot"
import { hasSecret, secure } from "./secure"
import { announcementListGet } from "./gsheets/announcements"
import { gameListGet } from "./gsheets/games"
-import { preVolunteerAdd, preVolunteerCountGet } from "./gsheets/preVolunteers"
+import { postulantAdd } from "./gsheets/postulants"
import { teamListGet } from "./gsheets/teams"
import {
- volunteerSet,
- volunteerLogin,
- volunteerForgot,
volunteerAsksSet,
- volunteerParticipationDetailsSet,
- volunteerTeamWishesSet,
volunteerDayWishesSet,
+ volunteerForgot,
+ volunteerDiscordId,
+ volunteerLogin,
+ volunteerPartialAdd,
+ volunteerParticipationDetailsSet,
+ volunteerSet,
+ volunteerTeamWishesSet,
volunteerTeamAssignSet,
volunteerListGet,
} from "./gsheets/volunteers"
@@ -84,8 +86,8 @@ app.get(
app.get("/GameListGet", gameListGet)
app.get("/WishListGet", wishListGet)
app.post("/WishAdd", wishAdd)
-app.post("/PreVolunteerAdd", preVolunteerAdd)
-app.get("/PreVolunteerCountGet", preVolunteerCountGet)
+app.post("/PostulantAdd", postulantAdd)
+app.post("/VolunteerPartialAdd", volunteerPartialAdd)
app.post("/VolunteerLogin", volunteerLogin)
app.post("/VolunteerForgot", volunteerForgot)
app.get("/VolunteerListGet", volunteerListGet)
@@ -94,7 +96,7 @@ app.get("/VolunteerListGet", volunteerListGet)
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
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(
"/VolunteerParticipationDetailsSet",
diff --git a/src/services/accessors.ts b/src/services/accessors.ts
index d256ae7..509795f 100644
--- a/src/services/accessors.ts
+++ b/src/services/accessors.ts
@@ -30,6 +30,9 @@ export default class ServiceAccessors<
...axiosConfig,
params: { id },
})
+ if (data.error) {
+ throw Error(data.error)
+ }
return { data }
} catch (error) {
return { error: error as Error }
@@ -51,6 +54,9 @@ export default class ServiceAccessors<
`${config.API_URL}/${this.elementName}ListGet`,
axiosConfig
)
+ if (data.error) {
+ throw Error(data.error)
+ }
return { data }
} catch (error) {
return { error: error as Error }
@@ -58,7 +64,7 @@ export default class ServiceAccessors<
}
}
- secureListGet(): (jwt: string) => Promise<{
+ securedListGet(): (jwt: string) => Promise<{
data?: Element[]
error?: Error
}> {
@@ -100,6 +106,9 @@ export default class ServiceAccessors<
volunteerWithoutId,
axiosConfig
)
+ if (data.error) {
+ throw Error(data.error)
+ }
return { data }
} catch (error) {
return { error: error as Error }
@@ -122,6 +131,9 @@ export default class ServiceAccessors<
volunteer,
axiosConfig
)
+ if (data.error) {
+ throw Error(data.error)
+ }
return { data }
} catch (error) {
return { error: error as Error }
@@ -143,6 +155,9 @@ export default class ServiceAccessors<
`${config.API_URL}/${this.elementName}CountGet`,
axiosConfig
)
+ if (data.error) {
+ throw Error(data.error)
+ }
return { data }
} catch (error) {
return { error: error as Error }
@@ -177,6 +192,37 @@ export default class ServiceAccessors<
}
}
+ securedCustomGet>(
+ apiName: string
+ ): (
+ jwt: string,
+ ...params: InputElements
+ ) => Promise<{
+ data?: any
+ error?: Error
+ }> {
+ interface ElementGetResponse {
+ data?: any
+ error?: Error
+ }
+ return async (jwt: string, ...params: InputElements): Promise => {
+ 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>(
apiName: string
): (
diff --git a/src/services/announcementAccessors.ts b/src/services/announcementAccessors.ts
index 460cf42..4571e7f 100644
--- a/src/services/announcementAccessors.ts
+++ b/src/services/announcementAccessors.ts
@@ -7,4 +7,4 @@ const serviceAccessors = new ServiceAccessors()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
+export const passwordMinLength = 4
+
+export type PostulantWithoutId = Omit
diff --git a/src/services/postulantsAccessors.ts b/src/services/postulantsAccessors.ts
new file mode 100644
index 0000000..7a25e93
--- /dev/null
+++ b/src/services/postulantsAccessors.ts
@@ -0,0 +1,9 @@
+import ServiceAccessors from "./accessors"
+import { elementName, Postulant, PostulantWithoutId } from "./postulants"
+
+const serviceAccessors = new ServiceAccessors(elementName)
+
+export const postulantListGet = serviceAccessors.listGet()
+export const postulantGet = serviceAccessors.get()
+export const postulantAdd = serviceAccessors.add()
+export const postulantSet = serviceAccessors.set()
diff --git a/src/services/preVolunteers.ts b/src/services/preVolunteers.ts
deleted file mode 100644
index 55e4e3b..0000000
--- a/src/services/preVolunteers.ts
+++ /dev/null
@@ -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
diff --git a/src/services/preVolunteersAccessors.ts b/src/services/preVolunteersAccessors.ts
deleted file mode 100644
index 3c04c11..0000000
--- a/src/services/preVolunteersAccessors.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import ServiceAccessors from "./accessors"
-import { elementName, PreVolunteer, PreVolunteerWithoutId } from "./preVolunteers"
-
-const serviceAccessors = new ServiceAccessors(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()
diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts
index d9895ff..ccc7075 100644
--- a/src/services/volunteers.ts
+++ b/src/services/volunteers.ts
@@ -1,4 +1,5 @@
-export class Volunteer {
+/* eslint-disable max-classes-per-file */
+export class Volunteer implements VolunteerPartial {
id = 0
lastname = ""
@@ -9,7 +10,7 @@ export class Volunteer {
mobile = ""
- photo = ""
+ photo = "anonyme.png"
adult = 1
@@ -23,11 +24,11 @@ export class Volunteer {
dayWishesComment = ""
- tshirtCount = ""
+ tshirtCount = 0
tshirtSize = ""
- food = ""
+ food = "Aucune"
team = 0
@@ -35,6 +36,12 @@ export class Volunteer {
teamWishesComment = ""
+ howToContact = ""
+
+ canHelpBefore = ""
+
+ pelMember = false
+
hiddenAsks: number[] = []
created = new Date()
@@ -67,6 +74,9 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
team: "équipe",
teamWishes: "enviesEquipe",
teamWishesComment: "commentaireEnviesEquipe",
+ howToContact: "commentContacter",
+ canHelpBefore: "aideEnAmont",
+ pelMember: "membrePel",
hiddenAsks: "questionsCachees",
created: "creation",
password1: "passe1",
@@ -75,6 +85,16 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
acceptsNotifs: "accepteLesNotifs",
}
+export class VolunteerPartial {
+ lastname = ""
+
+ firstname = ""
+
+ email = ""
+
+ mobile = ""
+}
+
export const elementName = "Volunteer"
export const volunteerExample: Volunteer = {
@@ -90,12 +110,15 @@ export const volunteerExample: Volunteer = {
discordId: "",
dayWishes: [],
dayWishesComment: "",
- tshirtCount: "1",
+ tshirtCount: 1,
tshirtSize: "Femme M",
food: "Végétarien",
team: 2,
teamWishes: [],
teamWishesComment: "",
+ howToContact: "",
+ canHelpBefore: "",
+ pelMember: false,
hiddenAsks: [],
created: new Date(0),
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
@@ -120,6 +143,11 @@ export interface VolunteerForgot {
message: string
}
+export interface VolunteerDiscordId {
+ id: Volunteer["id"]
+ discordId: Volunteer["discordId"]
+}
+
export interface VolunteerAsks {
id: Volunteer["id"]
firstname: Volunteer["firstname"]
diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts
index 32d2679..3c1d2f4 100644
--- a/src/services/volunteersAccessors.ts
+++ b/src/services/volunteersAccessors.ts
@@ -13,8 +13,8 @@ import {
const serviceAccessors = new ServiceAccessors(elementName)
export const volunteerListGet = serviceAccessors.listGet()
-export const volunteerGet = serviceAccessors.get()
-export const volunteerAdd = serviceAccessors.add()
+export const volunteerDiscordIdGet = serviceAccessors.securedCustomGet<[number]>("DiscordId")
+export const volunteerPartialAdd = serviceAccessors.customPost<[Partial]>("PartialAdd")
export const volunteerSet = serviceAccessors.set()
export const volunteerLogin =
diff --git a/src/store/__tests__/volunteer.ts b/src/store/__tests__/volunteer.ts
deleted file mode 100644
index 4a5f993..0000000
--- a/src/store/__tests__/volunteer.ts
+++ /dev/null
@@ -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)
- })
-})
diff --git a/src/store/auth.ts b/src/store/auth.ts
index 2220b33..86e8265 100644
--- a/src/store/auth.ts
+++ b/src/store/auth.ts
@@ -35,9 +35,9 @@ export const auth = createSlice({
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)
diff --git a/src/store/postulantAdd.ts b/src/store/postulantAdd.ts
new file mode 100644
index 0000000..f576a5c
--- /dev/null
+++ b/src/store/postulantAdd.ts
@@ -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()
+
+const postulantAddSlice = createSlice({
+ name: "postulantAdd",
+ initialState: postulantAdapter.getInitialState({
+ readyStatus: "idle",
+ } as StateRequest),
+ reducers: {
+ getRequesting: (state) => {
+ state.readyStatus = "request"
+ },
+ getSuccess: (state, { payload }: PayloadAction) => {
+ state.readyStatus = "success"
+ postulantAdapter.setOne(state, payload)
+ },
+ getFailure: (state, { payload }: PayloadAction) => {
+ 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
+)
diff --git a/src/store/preVolunteerAdd.ts b/src/store/preVolunteerAdd.ts
deleted file mode 100644
index 09149ba..0000000
--- a/src/store/preVolunteerAdd.ts
+++ /dev/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()
-
-const preVolunteerAddSlice = createSlice({
- name: "addPreVolunteer",
- initialState: preVolunteerAdapter.getInitialState({
- readyStatus: "idle",
- } as StateRequest),
- reducers: {
- getRequesting: (state) => {
- state.readyStatus = "request"
- },
- getSuccess: (state, { payload }: PayloadAction) => {
- state.readyStatus = "success"
- preVolunteerAdapter.addOne(state, payload)
- },
- getFailure: (state, { payload }: PayloadAction) => {
- 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
-)
diff --git a/src/store/preVolunteerCount.ts b/src/store/preVolunteerCount.ts
deleted file mode 100644
index 50cdcd8..0000000
--- a/src/store/preVolunteerCount.ts
+++ /dev/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) => {
- state.readyStatus = "success"
- state.value = payload
- },
- getFailure: (state, { payload }: PayloadAction) => {
- 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
-}
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index f5880df..8a9abfc 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -4,12 +4,11 @@ import { connectRouter } from "connected-react-router"
import auth from "./auth"
import gameList from "./gameList"
import announcementList from "./announcementList"
-import preVolunteerAdd from "./preVolunteerAdd"
-import preVolunteerCount from "./preVolunteerCount"
+import postulantAdd from "./postulantAdd"
import teamList from "./teamList"
import ui from "./ui"
-import volunteer from "./volunteer"
-import volunteerAdd from "./volunteerAdd"
+import volunteerAdd from "./volunteerPartialAdd"
+import volunteerDiscordId from "./volunteerDiscordId"
import volunteerList from "./volunteerList"
import volunteerSet from "./volunteerSet"
import volunteerLogin from "./volunteerLogin"
@@ -28,12 +27,11 @@ export default (history: History) => ({
auth,
gameList,
announcementList,
- preVolunteerAdd,
- preVolunteerCount,
+ postulantAdd,
teamList,
ui,
- volunteer,
volunteerAdd,
+ volunteerDiscordId,
volunteerList,
volunteerSet,
volunteerLogin,
diff --git a/src/store/utils.ts b/src/store/utils.ts
index 8649df8..cf1ad83 100644
--- a/src/store/utils.ts
+++ b/src/store/utils.ts
@@ -65,7 +65,7 @@ export function elementFetch>(
}
export function elementAddFetch(
- elementAddService: (volunteerWithoutId: Omit) => Promise<{
+ elementAddService: (elementWithoutId: Omit) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
@@ -74,12 +74,12 @@ export function elementAddFetch(
getFailure: ActionCreatorWithPayload,
errorMessage?: (error: Error) => void,
successMessage?: () => void
-): (volunteerWithoutId: Omit) => AppThunk {
- return (volunteerWithoutId: Omit): AppThunk =>
+): (elementWithoutId: Omit) => AppThunk {
+ return (elementWithoutId: Omit): AppThunk =>
async (dispatch) => {
dispatch(getRequesting())
- const { error, data } = await elementAddService(volunteerWithoutId)
+ const { error, data } = await elementAddService(elementWithoutId)
if (error) {
dispatch(getFailure(error.message))
@@ -119,7 +119,7 @@ export function elementListFetch>(
}
export function elementSet(
- elementSetService: (volunteer: Element) => Promise<{
+ elementSetService: (element: Element) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
diff --git a/src/store/volunteer.ts b/src/store/volunteer.ts
deleted file mode 100644
index b883500..0000000
--- a/src/store/volunteer.ts
+++ /dev/null
@@ -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) => ({
- readyStatus: "success",
- entity: payload,
- }),
- getFailure: (_, { payload }: PayloadAction) => ({
- 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
- }
diff --git a/src/store/volunteerDiscordId.ts b/src/store/volunteerDiscordId.ts
new file mode 100644
index 0000000..15fddc2
--- /dev/null
+++ b/src/store/volunteerDiscordId.ts
@@ -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) => ({
+ readyStatus: "success",
+ entity: payload,
+ }),
+ getFailure: (_, { payload }: PayloadAction) => ({
+ 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
+)
diff --git a/src/store/volunteerAdd.ts b/src/store/volunteerPartialAdd.ts
similarity index 60%
rename from src/store/volunteerAdd.ts
rename to src/store/volunteerPartialAdd.ts
index 24803e3..a26ef63 100644
--- a/src/store/volunteerAdd.ts
+++ b/src/store/volunteerPartialAdd.ts
@@ -1,13 +1,13 @@
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 { volunteerAdd } from "../services/volunteersAccessors"
+import { volunteerPartialAdd } from "../services/volunteersAccessors"
const volunteerAdapter = createEntityAdapter()
-const volunteerAddSlice = createSlice({
- name: "addVolunteer",
+const volunteerPartialAddSlice = createSlice({
+ name: "volunteerAdd",
initialState: volunteerAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
@@ -17,7 +17,7 @@ const volunteerAddSlice = createSlice({
},
getSuccess: (state, { payload }: PayloadAction) => {
state.readyStatus = "success"
- volunteerAdapter.addOne(state, payload)
+ volunteerAdapter.setOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction) => {
state.readyStatus = "failure"
@@ -26,14 +26,14 @@ const volunteerAddSlice = createSlice({
},
})
-export default volunteerAddSlice.reducer
-export const { getRequesting, getSuccess, getFailure } = volunteerAddSlice.actions
+export default volunteerPartialAddSlice.reducer
+export const { getRequesting, getSuccess, getFailure } = volunteerPartialAddSlice.actions
-export const fetchVolunteerAdd = elementAddFetch(
- volunteerAdd,
+export const fetchVolunteerPartialAdd = elementAddFetch(
+ volunteerPartialAdd,
getRequesting,
getSuccess,
getFailure,
- (error: Error) => toastError(`Erreur lors de l'ajout d'un bénévole: ${error.message}`),
- () => toastSuccess("Volunteer ajoutée !")
+ () => null,
+ () => null
)
diff --git a/src/store/wishAdd.ts b/src/store/wishAdd.ts
index d8440e8..6845d7f 100644
--- a/src/store/wishAdd.ts
+++ b/src/store/wishAdd.ts
@@ -7,7 +7,7 @@ import { wishAdd } from "../services/wishesAccessors"
const wishAdapter = createEntityAdapter()
const wishAddSlice = createSlice({
- name: "addWish",
+ name: "wishAdd",
initialState: wishAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
diff --git a/src/store/wishList.ts b/src/store/wishList.ts
index 23ce147..fbb42ae 100644
--- a/src/store/wishList.ts
+++ b/src/store/wishList.ts
@@ -8,7 +8,7 @@ import { wishListGet } from "../services/wishesAccessors"
const wishAdapter = createEntityAdapter()
const wishList = createSlice({
- name: "getWishList",
+ name: "wishList",
initialState: wishAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),