Fix tests & improve pre-register

This commit is contained in:
pikiou 2021-12-03 10:39:37 +01:00
parent fde48e6cbb
commit d63f906206
44 changed files with 739 additions and 899 deletions

View File

@ -41,9 +41,8 @@
"scripts": { "scripts": {
"dev": "yarn dev:build && nodemon ./public/server", "dev": "yarn dev:build && nodemon ./public/server",
"dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts", "dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts",
"local-start": "cross-env LOCAL=true node ./public/server", "local-start": "cross-env LOCAL=true yarn build && node ./public/server",
"start": "node ./public/server", "start": "node ./public/server",
"local-build": "cross-env LOCAL=true run-s build:*",
"build": "run-s build:*", "build": "run-s build:*",
"build:server": "cross-env NODE_ENV=production webpack --config ./webpack/server.config.ts", "build:server": "cross-env NODE_ENV=production webpack --config ./webpack/server.config.ts",
"build:client": "cross-env NODE_ENV=production webpack --config ./webpack/client.config.ts", "build:client": "cross-env NODE_ENV=production webpack --config ./webpack/client.config.ts",

View File

@ -9,52 +9,52 @@ exports[`<AddEnvie /> renders 1`] = `
</h2> </h2>
<form> <form>
<label <label
for="postDomaine" for="postDomain"
> >
Domaine: Domaine:
<input <input
id="postDomaine" id="postDomain"
name="postDomaine" name="postDomain"
type="text" type="text"
value="" value=""
/> />
</label> </label>
<label <label
for="postEnvies" for="postWish"
> >
Envies: Envies:
<textarea <textarea
id="postEnvies" id="postWish"
name="postEnvies" name="postWish"
/> />
</label> </label>
<label <label
for="postPrecisions" for="postDetails"
> >
Precisions: Precisions:
<textarea <textarea
id="postPrecisions" id="postDetails"
name="postPrecisions" name="postDetails"
/> />
</label> </label>
<label <label
for="postEquipes" for="postTeams"
> >
Equipes: Equipes:
<input <input
id="postEquipes" id="postTeams"
name="postEquipes" name="postTeams"
type="text" type="text"
value="" value=""
/> />
</label> </label>
<label <label
for="postDateAjout" for="postAddedDate"
> >
DateAjout: Date dajout:
<input <input
id="postDateAjout" id="postAddedDate"
name="postDateAjout" name="postAddedDate"
type="date" type="date"
value="" value=""
/> />

View File

@ -1,50 +1,49 @@
import React, { useState, memo } from "react" import React, { useState, memo } from "react"
import { toast } from "react-toastify" import { toast } from "react-toastify"
import styles from "./styles.module.scss"
import { AppDispatch } from "../../store" import { AppDispatch } from "../../store"
import { fetchEnvieAdd } from "../../store/envieAdd" import { fetchEnvieAdd } from "../../store/envieAdd"
import styles from "./styles.module.scss"
interface Props { interface Props {
dispatch: AppDispatch dispatch: AppDispatch
} }
const AddEnvie = ({ dispatch }: Props) => { const AddEnvie = ({ dispatch }: Props) => {
const [domaine, setDomaine] = useState("") const [domain, setDomain] = useState("")
const [envies, setEnvies] = useState("") const [wish, setWish] = useState("")
const [precisions, setPrecisions] = useState("") const [details, setDetails] = useState("")
const [equipes, setEquipes] = useState([""]) const [teams, setTeams] = useState([""])
const [dateAjout, setDateAjout] = useState("") const [addedDate, setAddedDate] = useState("")
const onDomaineChanged = (e: React.ChangeEvent<HTMLInputElement>) => setDomaine(e.target.value) const onDomainChanged = (e: React.ChangeEvent<HTMLInputElement>) => setDomain(e.target.value)
const onEnviesChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => setEnvies(e.target.value) const onWishChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => setWish(e.target.value)
const onPrecisionsChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => const onDetailsChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
setPrecisions(e.target.value) setDetails(e.target.value)
const onEquipesChanged = (e: React.ChangeEvent<HTMLInputElement>) => const onTeamsChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
setEquipes(e.target.value.split(/, ?/)) setTeams(e.target.value.split(/, ?/))
const onDateAjoutChanged = (e: React.ChangeEvent<HTMLInputElement>) => const onAddedDateChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
setDateAjout(e.target.value) setAddedDate(e.target.value)
const onSavePostClicked = () => { const onSavePostClicked = () => {
if (domaine && envies) { if (domain && wish) {
dispatch( dispatch(
fetchEnvieAdd({ fetchEnvieAdd({
domaine, domain,
envies, wish,
precisions, details,
equipes, teams,
dateAjout, addedDate,
}) })
) )
setDomaine("") setDomain("")
setEnvies("") setWish("")
setPrecisions("") setDetails("")
setEquipes([""]) setTeams([""])
setDateAjout("") setAddedDate("")
} else { } else {
toast.warning("Il faut au moins préciser un domaine et l'envie", { toast.warning("Il faut au moins préciser un domain et l'envie", {
position: "top-center", position: "top-center",
autoClose: 6000, autoClose: 6000,
hideProgressBar: true, hideProgressBar: true,
@ -59,52 +58,47 @@ const AddEnvie = ({ dispatch }: Props) => {
<section className={styles.EnvieList}> <section className={styles.EnvieList}>
<h2>Ajouter une nouvelle envie</h2> <h2>Ajouter une nouvelle envie</h2>
<form> <form>
<label htmlFor="postDomaine"> <label htmlFor="postDomain">
Domaine: Domaine:
<input <input
type="text" type="text"
id="postDomaine" id="postDomain"
name="postDomaine" name="postDomain"
value={domaine} value={domain}
onChange={onDomaineChanged} onChange={onDomainChanged}
/> />
</label> </label>
<label htmlFor="postEnvies"> <label htmlFor="postWish">
Envies: Envies:
<textarea <textarea id="postWish" name="postWish" value={wish} onChange={onWishChanged} />
id="postEnvies"
name="postEnvies"
value={envies}
onChange={onEnviesChanged}
/>
</label> </label>
<label htmlFor="postPrecisions"> <label htmlFor="postDetails">
Precisions: Precisions:
<textarea <textarea
id="postPrecisions" id="postDetails"
name="postPrecisions" name="postDetails"
value={precisions} value={details}
onChange={onPrecisionsChanged} onChange={onDetailsChanged}
/> />
</label> </label>
<label htmlFor="postEquipes"> <label htmlFor="postTeams">
Equipes: Equipes:
<input <input
type="text" type="text"
id="postEquipes" id="postTeams"
name="postEquipes" name="postTeams"
value={equipes.join(", ")} value={teams.join(", ")}
onChange={onEquipesChanged} onChange={onTeamsChanged}
/> />
</label> </label>
<label htmlFor="postDateAjout"> <label htmlFor="postAddedDate">
DateAjout: Date dajout:
<input <input
type="date" type="date"
id="postDateAjout" id="postAddedDate"
name="postDateAjout" name="postAddedDate"
value={dateAjout} value={addedDate}
onChange={onDateAjoutChanged} onChange={onAddedDateChanged}
/> />
</label> </label>
<button type="button" onClick={onSavePostClicked}> <button type="button" onClick={onSavePostClicked}>

View File

@ -28,21 +28,20 @@ describe("<List />", () => {
entities: { entities: {
"5": { "5": {
id: 5, id: 5,
titre: "6 qui prend!", title: "6 qui prend!",
auteur: "Wolfgang Kramer", author: "Wolfgang Kramer",
editeur: "(uncredited) , Design Edge , B", editor: "(uncredited) , Design Edge , B",
minJoueurs: 2, playersMin: 2,
maxJoueurs: 10, playersMax: 10,
duree: 45, duration: 45,
type: "Ambiance", type: "Ambiance",
poufpaf: "0-9-2/6-qui-prend-6-nimmt", poufpaf: "0-9-2/6-qui-prend-6-nimmt",
photo: "https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg", bggPhoto:
bggPhoto: "", "https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
bggId: 432, bggId: 432,
exemplaires: 1, copies: 1,
dispoPret: 1, lendAvailability: 1,
nonRangee: 0, notStored: 0,
horodatage: "0000-00-00",
ean: "3421272101313", ean: "3421272101313",
}, },
}, },

View File

@ -21,10 +21,10 @@ const JeuJavList = ({ ids }: Props) => {
if (!jeu) { if (!jeu) {
return <li key={id}>Le jeu #{id} n&apos;existe pas</li> return <li key={id}>Le jeu #{id} n&apos;existe pas</li>
} }
const { titre, bggId } = jeu const { title, bggId } = jeu
return ( return (
<li key={id}> <li key={id}>
{titre} - [{bggId}] {title} - [{bggId}]
</li> </li>
) )
})} })}

View File

@ -13,18 +13,18 @@ describe("<MembreInfo />", () => {
<MembreInfo <MembreInfo
item={{ item={{
id: 1, id: 1,
nom: "Aupeix", firstname: "Aupeix",
prenom: "Amélie", lastname: "Amélie",
mail: "pakouille.lakouille@yahoo.fr", email: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392", mobile: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg", photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien", food: "Végétarien",
majeur: 1, adult: 1,
privilege: 0, privileges: 0,
actif: 0, active: 0,
commentaire: "", comment: "",
horodatage: "0000-00-00", timestamp: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O", password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
}} }}
/> />
</MemoryRouter> </MemoryRouter>

View File

@ -10,11 +10,11 @@ exports[`<MembreInfo /> renders 1`] = `
<ul> <ul>
<li> <li>
Prénom: Prénom:
Amélie Aupeix
</li> </li>
<li> <li>
Nom: Nom:
Aupeix Amélie
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -11,8 +11,8 @@ const MembreInfo = ({ item }: Props) => (
<div className={styles.MembreCard}> <div className={styles.MembreCard}>
<h4>Membre Info</h4> <h4>Membre Info</h4>
<ul> <ul>
<li>Prénom: {item.prenom}</li> <li>Prénom: {item.firstname}</li>
<li>Nom: {item.nom}</li> <li>Nom: {item.lastname}</li>
</ul> </ul>
</div> </div>
) )

View File

@ -14,18 +14,19 @@ describe("<MembreList />", () => {
items={[ items={[
{ {
id: 1, id: 1,
nom: "Aupeix", firstname: "Aupeix",
prenom: "Amélie", lastname: "Amélie",
mail: "pakouille.lakouille@yahoo.fr", email: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392", mobile: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg", photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien", food: "Végétarien",
majeur: 1, adult: 1,
privilege: 0, privileges: 0,
actif: 0, active: 0,
commentaire: "", comment: "",
horodatage: "0000-00-00", timestamp: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O", password:
"$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
}, },
]} ]}
/> />

View File

@ -13,10 +13,10 @@ exports[`<MembreList /> renders 1`] = `
href="/Membre/1" href="/Membre/1"
> >
<b> <b>
Amélie Aupeix
</b> </b>
Aupeix Amélie
</a> </a>
</li> </li>
</ul> </ul>

View File

@ -12,10 +12,10 @@ const MembreList = ({ items }: Props) => (
<div className={styles["user-list"]}> <div className={styles["user-list"]}>
<h4>Membre List</h4> <h4>Membre List</h4>
<ul> <ul>
{items.map(({ id, nom, prenom }) => ( {items.map(({ id, lastname, firstname }) => (
<li key={id}> <li key={id}>
<Link to={`/Membre/${id}`}> <Link to={`/Membre/${id}`}>
<b>{prenom}</b> {nom} <b>{firstname}</b> {lastname}
</Link> </Link>
</li> </li>
))} ))}

View File

@ -15,18 +15,18 @@ describe("<SetMembre />", () => {
dispatch={dispatch} dispatch={dispatch}
membre={{ membre={{
id: 1, id: 1,
nom: "Aupeix", firstname: "Aupeix",
prenom: "Amélie", lastname: "Amélie",
mail: "pakouille.lakouille@yahoo.fr", email: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392", mobile: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg", photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien", food: "Végétarien",
majeur: 1, adult: 1,
privilege: 0, privileges: 0,
actif: 0, active: 0,
commentaire: "", comment: "",
horodatage: "0000-00-00", timestamp: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O", password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
}} }}
/> />
</MemoryRouter> </MemoryRouter>

View File

@ -9,34 +9,34 @@ exports[`<SetMembre /> renders 1`] = `
</h2> </h2>
<form> <form>
<label <label
for="postPrenom" for="postFirstname"
> >
Prenom: Prénom:
<input <input
id="postPrenom" id="postFirstname"
name="postPrenom" name="postFirstname"
type="text"
value="Amélie"
/>
</label>
<label
for="postNom"
>
Nom:
<input
id="postNom"
name="postNom"
type="text" type="text"
value="Aupeix" value="Aupeix"
/> />
</label> </label>
<label <label
for="postMajeur" for="postName"
>
Nom:
<input
id="postName"
name="postName"
type="text"
value="Amélie"
/>
</label>
<label
for="postAdult"
> >
Majeur: Majeur:
<input <input
id="postMajeur" id="postAdult"
name="postMajeur" name="postAdult"
type="text" type="text"
value="1" value="1"
/> />

View File

@ -13,22 +13,23 @@ interface Props {
} }
const MembreSet = ({ dispatch, membre }: Props) => { const MembreSet = ({ dispatch, membre }: Props) => {
const [prenom, setPrenom] = useState(membre.prenom) const [firstname, setFirstname] = useState(membre.firstname)
const [nom, setNom] = useState(membre.nom) const [lastname, setName] = useState(membre.lastname)
const [majeur, setMajeur] = useState(membre.majeur) const [adult, setAdult] = useState(membre.adult)
const onPrenomChanged = (e: React.ChangeEvent<HTMLInputElement>) => setPrenom(e.target.value) const onFirstnameChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
const onNomChanged = (e: React.ChangeEvent<HTMLInputElement>) => setNom(e.target.value) setFirstname(e.target.value)
const onMajeurChanged = (e: React.ChangeEvent<HTMLInputElement>) => setMajeur(+e.target.value) const onNameChanged = (e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value)
const onAdultChanged = (e: React.ChangeEvent<HTMLInputElement>) => setAdult(+e.target.value)
const onSavePostClicked = () => { const onSavePostClicked = () => {
if (prenom && nom) { if (firstname && lastname) {
dispatch( dispatch(
fetchMembreSet({ fetchMembreSet({
...membre, ...membre,
prenom, firstname,
nom, lastname,
majeur, adult,
}) })
) )
} else { } else {
@ -47,34 +48,34 @@ const MembreSet = ({ dispatch, membre }: Props) => {
<section className={styles.MembreList}> <section className={styles.MembreList}>
<h2>Modifier un membre</h2> <h2>Modifier un membre</h2>
<form> <form>
<label htmlFor="postPrenom"> <label htmlFor="postFirstname">
Prenom: Prénom:
<input <input
type="text" type="text"
id="postPrenom" id="postFirstname"
name="postPrenom" name="postFirstname"
value={prenom} value={firstname}
onChange={onPrenomChanged} onChange={onFirstnameChanged}
/> />
</label> </label>
<label htmlFor="postNom"> <label htmlFor="postName">
Nom: Nom:
<input <input
type="text" type="text"
id="postNom" id="postName"
name="postNom" name="postName"
value={nom} value={lastname}
onChange={onNomChanged} onChange={onNameChanged}
/> />
</label> </label>
<label htmlFor="postMajeur"> <label htmlFor="postAdult">
Majeur: Majeur:
<input <input
type="text" type="text"
id="postMajeur" id="postAdult"
name="postMajeur" name="postAdult"
value={majeur} value={adult}
onChange={onMajeurChanged} onChange={onAdultChanged}
/> />
</label> </label>
<button type="button" onClick={onSavePostClicked}> <button type="button" onClick={onSavePostClicked}>

View File

@ -1,28 +1,93 @@
import React, { memo, useCallback } from "react" 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 styles from "./styles.module.scss"
const RegisterForm = (): JSX.Element => { import { fetchPreMemberAdd } from "../../store/preMemberAdd"
const onSubmit = useCallback((event: React.SyntheticEvent): void => { import { AppDispatch, AppState } from "../../store"
event.preventDefault()
const target = event.target as typeof event.target & { interface Props {
firstname: { value: string } dispatch: AppDispatch
lastname: { value: string } }
email: { value: string }
phone: { value: string } const RegisterForm = ({ dispatch }: 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(
fetchPreMemberAdd({
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 firstname = target.firstname.value }
const lastname = target.lastname.value
const email = target.email.value
const phone = target.phone.value
console.log("register fields checked", firstname, lastname, email, phone) const { error, entities: preMember } = useSelector(
(state: AppState) => state.preMemberAdd,
shallowEqual
)
// call service with fields let sendSuccess
}, []) if (!_.isEmpty(preMember)) {
if (sending) {
setSending(false)
}
sendSuccess = <span className={styles.success}>Formulaire envoyé !</span>
}
let sendError
if (error && _.isEmpty(preMember)) {
if (sending) {
setSending(false)
}
sendError = <span className={styles.error}>{error}</span>
}
let sendingElement
if (sending) {
sendingElement = <span className={styles.sending}>Envoi en cours...</span>
}
/* /*
prenom firstname
nom lastname
mail mail
tel tel
j'ai déjà é bénévole pour PEL j'ai déjà é bénévole pour PEL
@ -35,8 +100,8 @@ const RegisterForm = (): JSX.Element => {
<dt>Qu&apos;est-ce que Paris est Ludique ?</dt> <dt>Qu&apos;est-ce que Paris est Ludique ?</dt>
<dd> <dd>
<p> <p>
Cette grande fête est dédiée aux <b>jeux de société modernes</b> sous toutes Un festival en plein air dédiée aux <b>jeux de société modernes</b> sous
leurs formes. toutes leurs formes.
</p> </p>
<p> <p>
En 2019 lors de la dernière édition, ce sont <b>16 000</b> joueurs qui se En 2019 lors de la dernière édition, ce sont <b>16 000</b> joueurs qui se
@ -53,60 +118,88 @@ const RegisterForm = (): JSX.Element => {
<dd> <dd>
<p> <p>
L&apos;organisation du festival est <b>entièrement gérée par nous</b>, les L&apos;organisation du festival est <b>entièrement gérée par nous</b>, les
bénévoles. À aucun moment ça ne doit devenir une corvée, donc nous faisons bénévoles. À aucun moment ça ne ressemble à du travail : nous faisons tout
tout pour passer <b>un aussi bon moment que les visiteurs</b> :) pour passer <b>un aussi bon moment que les visiteurs</b> :)
</p> </p>
<p> <p>
C&apos;est pour ça que chaque mois, ceux qui sont dispo prennent D&apos;ailleurs, un soir par mois nous nous réunissons pour un apéro ludique
l&apos;apéro tous ensemble en jouant et discutant de l&apos;organisation. discuter de l&apos;organisation ! On joue autant que les visiteurs, mais
sur toute l&apos;année ^^
</p> </p>
<p> <p>
Pendant le festival de 2019, nous étions <b>187 bénévoles</b> organisés en Pendant le festival de 2019, nous étions <b>187 bénévoles</b> organisés en
équipes spécialisées qui chouchoutent les visiteurs en les accueillant, en équipes qui chouchoutent les visiteurs en les accueillant, en
s&apos;assurant que tout se passe bien, ou en expliquant des règles de jeux. s&apos;assurant que tout se passe bien, ou encore en expliquant des règles
de jeux.
</p> </p>
<p> <p>
Une équipe s&apos;occupe même du bien être des bénévoles en leur servant à 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 faire des pauses régulières. boire et à manger dans un espace à part faire des pauses régulières. Et
puis nous hébergeons ceux d&apos;entre nous qui habitent loin de Paris. Le
confort avant tout !
</p> </p>
<p> <p>
Les deux jours avant et le jour après le festival, ceux qui le peuvent Certains bénévoles sont visiteurs le samedi ou le dimanche pour vivre le
viennent tout préparer et ranger. Certains ne sont disponibles que ces jours festival de l&apos;intérieur. Les deux jours avant et le jour après le
et c&apos;est déjà d&apos;une grande aide ! festival, ceux qui le peuvent viennent préparer et ranger. Bref, chacun
participe à la hauteur de ses envies et disponibilités !
</p> </p>
<p> <p>
Nous nous arrangeons pour héberger les bénévoles qui habitent loin de Paris, Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en
et certains ne viennent qu&apos;un seul jour du weekend pour être visiteur dînant avec les auteurs, illustrateurs et éditeurs présents sur le festival.
l&apos;autre.
</p>
<p>
Le samedi soir, cerise sur le gâteau, nous prenons un{" "}
<b>dîner avec les auteurs, illustrateurs et éditeurs</b> qui sont présents
sur le festival !
</p> </p>
</dd> </dd>
<dt> <dt>
Si l&apos;expérience vous tente, n&apos;hésitez pas à remplir le formulaire Si l&apos;expérience pourrait vous tenter, remplissez le formulaire suivant pour
suivant pour nous rencontrer lors d&apos;un des gros apéros mensuels !<br /> en discuter lors d&apos;un des gros apéros mensuels !<br />
Cette inscription ne vous oblige en rien il s&apos;agit juste d&apos;une prise
de contact.
<br />
Les prochains sont les 21 décembre et 27 janvier, mais nous vous appelerons Les prochains sont les 21 décembre et 27 janvier, mais nous vous appelerons
d&apos;ici pour discuter :) d&apos;ici pour les détails :)
<br />
<span className={styles.lightTitle}>(Déjà au moins 8 inscrits !)</span>
</dt> </dt>
<dd> <dd>
<div className={styles.formLine} key="line-firstname"> <div className={styles.formLine} key="line-firstname">
<label htmlFor="firstname">Prénom</label> <label htmlFor="firstname">Prénom</label>
<input type="text" id="firstname" /> <input
type="text"
id="firstname"
required
value={firstname}
onChange={onFirstnameChanged}
/>
</div> </div>
<div className={styles.formLine} key="line-lastname"> <div className={styles.formLine} key="line-lastname">
<label htmlFor="lastname">Nom</label> <label htmlFor="lastname">Nom</label>
<input type="text" id="lastname" /> <input
type="text"
id="lastname"
required
value={lastname}
onChange={onLastnameChanged}
/>
</div> </div>
<div className={styles.formLine} key="line-email"> <div className={styles.formLine} key="line-email">
<label htmlFor="email">Email</label> <label htmlFor="email">Email</label>
<input type="email" id="email" /> <input
type="email"
id="email"
required
value={email}
onChange={onEmailChanged}
/>
</div> </div>
<div className={styles.formLine} key="line-phone"> <div className={styles.formLine} key="line-mobile">
<label htmlFor="phone">Téléphone</label> <label htmlFor="mobile">Téléphone</label>
<input type="text" id="phone" /> <input
type="text"
id="mobile"
required
value={mobile}
onChange={onMobileChanged}
/>
</div> </div>
<div className={styles.formLine} key="line-already-volunteer"> <div className={styles.formLine} key="line-already-volunteer">
<div> <div>
@ -116,6 +209,8 @@ const RegisterForm = (): JSX.Element => {
name="alreadyVolunteer" name="alreadyVolunteer"
id="alreadyVolunteer-yes" id="alreadyVolunteer-yes"
className={styles.inputRadio} className={styles.inputRadio}
checked={alreadyVolunteer}
onChange={onAlreadyVolunteer}
/> />
<label htmlFor="alreadyVolunteer-yes">Oui</label> <label htmlFor="alreadyVolunteer-yes">Oui</label>
<input <input
@ -123,6 +218,8 @@ const RegisterForm = (): JSX.Element => {
name="alreadyVolunteer" name="alreadyVolunteer"
id="alreadyVolunteer-no" id="alreadyVolunteer-no"
className={styles.inputRadio} className={styles.inputRadio}
checked={!alreadyVolunteer}
onChange={onNotYesVolunteer}
/> />
<label htmlFor="alreadyVolunteer-no">Non</label> <label htmlFor="alreadyVolunteer-no">Non</label>
</div> </div>
@ -132,10 +229,19 @@ const RegisterForm = (): JSX.Element => {
name="message" name="message"
id="message" id="message"
placeholder="Des petits mots sympas, questions, envies, 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 :)" placeholder="Des petits mots sympas, questions, envies, 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>
<div className={styles.formButtons}> <div className={styles.formButtons}>
<button type="submit">Envoyer</button> <button type="button" onClick={onSubmit} disabled={sending}>
Envoyer
</button>
</div>
<div className={styles.formReactions}>
{sendingElement}
{sendSuccess}
{sendError}
</div> </div>
</dd> </dd>
</dl> </dl>

View File

@ -16,6 +16,10 @@
} }
} }
.lightTitle {
font-weight: normal;
}
.formLine { .formLine {
padding: 5px 0; padding: 5px 0;
@ -48,4 +52,25 @@
margin-top: 10px; margin-top: 10px;
padding: 5px 0; padding: 5px 0;
text-align: center; 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);
}
} }

View File

@ -1,5 +1,7 @@
const PORT = 4000 const PROTOCOL = (typeof window !== "undefined" && window?.location?.protocol) || "http:"
const API_URL = __DEV__ || __LOCAL__ ? `http://localhost:${PORT}` : "https://fo.parisestludique.fr" const PORT = 4000 + (PROTOCOL === "https:" ? 2 : 0)
const API_URL =
__DEV__ || __LOCAL__ ? `${PROTOCOL}//localhost:${PORT}` : `${PROTOCOL}//fo.parisestludique.fr`
export default { export default {
PORT, PORT,

View File

@ -1,79 +0,0 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import { fetchJeuJavListIfNeed } from "../../../store/jeuJavList"
import mockStore from "../../../utils/mockStore"
import Home from "../Home"
describe("<Home />", () => {
const renderHelper = (reducer = { readyStatus: "idle" }) => {
const { dispatch, ProviderWithStore } = mockStore({ jeuJavList: reducer })
const { container } = render(
<ProviderWithStore>
<MemoryRouter>
{/*
@ts-expect-error */}
<Home />
</MemoryRouter>
</ProviderWithStore>
)
return { dispatch, firstChild: container.firstChild }
}
it("should fetch data when page loaded", () => {
const { dispatch } = renderHelper()
expect(dispatch).toHaveBeenCalledTimes(1)
expect(dispatch.mock.calls[0][0].toString()).toBe(fetchJeuJavListIfNeed().toString())
})
it("renders the loading status if data invalid", () => {
expect(renderHelper().firstChild).toMatchSnapshot()
})
it("renders the loading status if requesting data", () => {
const reducer = { readyStatus: "request" }
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
})
it("renders an error if loading failed", () => {
const reducer = { readyStatus: "failure" }
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
})
it("renders the <List /> if loading was successful", () => {
const reducer = {
readyStatus: "success",
ids: [5],
entities: {
"5": {
id: 5,
titre: "6 qui prend!",
auteur: "Wolfgang Kramer",
editeur: "(uncredited) , Design Edge , B",
minJoueurs: 2,
maxJoueurs: 10,
duree: 45,
type: "Ambiance",
poufpaf: "0-9-2/6-qui-prend-6-nimmt",
photo: "https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
bggPhoto: "",
bggId: 432,
exemplaires: 1,
dispoPret: 1,
nonRangee: 0,
horodatage: "0000-00-00",
ean: "3421272101313",
},
},
}
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
})
})

View File

@ -1,313 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<Home /> renders an error if loading failed 1`] = `
<div
class="home"
>
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<p>
Oops, Failed to load list!
</p>
</div>
`;
exports[`<Home /> renders the <List /> if loading was successful 1`] = `
<div
class="home"
>
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<div
class="JeuJavList"
>
<h4>
Jeux JAV
</h4>
<ul>
<li>
6 qui prend!
- [
432
]
</li>
</ul>
</div>
</div>
`;
exports[`<Home /> renders the loading status if data invalid 1`] = `
<div
class="home"
>
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<p>
Loading...
</p>
</div>
`;
exports[`<Home /> renders the loading status if requesting data 1`] = `
<div
class="home"
>
<section
class="EnvieList"
>
<h2>
Ajouter une nouvelle envie
</h2>
<form>
<label
for="postDomaine"
>
Domaine:
<input
id="postDomaine"
name="postDomaine"
type="text"
value=""
/>
</label>
<label
for="postEnvies"
>
Envies:
<textarea
id="postEnvies"
name="postEnvies"
/>
</label>
<label
for="postPrecisions"
>
Precisions:
<textarea
id="postPrecisions"
name="postPrecisions"
/>
</label>
<label
for="postEquipes"
>
Equipes:
<input
id="postEquipes"
name="postEquipes"
type="text"
value=""
/>
</label>
<label
for="postDateAjout"
>
DateAjout:
<input
id="postDateAjout"
name="postDateAjout"
type="date"
value=""
/>
</label>
<button
type="button"
>
Save Post
</button>
</form>
</section>
<p>
Loading...
</p>
</div>
`;

View File

@ -1,72 +0,0 @@
/**
* @jest-environment jsdom
*/
import { render } from "@testing-library/react"
import { MemoryRouter } from "react-router-dom"
import { fetchMembreIfNeed } from "../../../store/membre"
import mockStore from "../../../utils/mockStore"
import MembrePage from "../MembrePage"
describe("<MembrePage />", () => {
const mockData = {
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
alimentation: "Végétarien",
majeur: 1,
privilege: 0,
actif: 0,
commentaire: "",
horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}
const { id } = mockData
const renderHelper = (reducer = {}) => {
const { dispatch, ProviderWithStore } = mockStore({ membre: reducer })
const { container } = render(
<ProviderWithStore>
<MemoryRouter>
{/*
@ts-expect-error */}
<MembrePage match={{ params: { id } }} />
</MemoryRouter>
</ProviderWithStore>
)
return { dispatch, firstChild: container.firstChild }
}
it("should fetch data when page loaded", () => {
const { dispatch } = renderHelper()
expect(dispatch).toHaveBeenCalledTimes(1)
expect(dispatch.mock.calls[0][0].toString()).toBe(fetchMembreIfNeed(id).toString())
})
it("renders the loading status if data invalid", () => {
expect(renderHelper().firstChild).toMatchSnapshot()
})
it("renders the loading status if requesting data", () => {
const reducer = { readyStatus: "request" }
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
})
it("renders an error if loading failed", () => {
const reducer = { readyStatus: "failure" }
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
})
it("renders the <Info /> if loading was successful", () => {
const reducer = { readyStatus: "success", entity: mockData }
expect(renderHelper(reducer).firstChild).toMatchSnapshot()
})
})

View File

@ -1,104 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<MembrePage /> renders an error if loading failed 1`] = `
<div
class="membre"
>
<p>
Oops! Failed to load data.
</p>
</div>
`;
exports[`<MembrePage /> renders the <Info /> if loading was successful 1`] = `
<div
class="membre"
>
<div>
<div
class="MembreCard"
>
<h4>
Membre Info
</h4>
<ul>
<li>
Prénom:
Amélie
</li>
<li>
Nom:
Aupeix
</li>
</ul>
</div>
<section
class="MembreList"
>
<h2>
Modifier un membre
</h2>
<form>
<label
for="postPrenom"
>
Prenom:
<input
id="postPrenom"
name="postPrenom"
type="text"
value="Amélie"
/>
</label>
<label
for="postNom"
>
Nom:
<input
id="postNom"
name="postNom"
type="text"
value="Aupeix"
/>
</label>
<label
for="postMajeur"
>
Majeur:
<input
id="postMajeur"
name="postMajeur"
type="text"
value="1"
/>
</label>
<button
type="button"
>
Save changes
</button>
</form>
</section>
</div>
</div>
`;
exports[`<MembrePage /> renders the loading status if data invalid 1`] = `
<div
class="membre"
>
<p>
Oops! Failed to load data.
</p>
</div>
`;
exports[`<MembrePage /> renders the loading status if requesting data 1`] = `
<div
class="membre"
>
<p>
Loading...
</p>
</div>
`;

View File

@ -1,18 +1,22 @@
import { RouteComponentProps } from "react-router-dom" import { RouteComponentProps } from "react-router-dom"
import React, { memo } from "react" import { useDispatch } from "react-redux"
import { FC, memo } from "react"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import RegisterForm from "../../components/RegisterForm/RegisterForm" import RegisterForm from "../../components/RegisterForm/RegisterForm"
export type Props = RouteComponentProps export type Props = RouteComponentProps
const RegisterPage: React.FC<Props> = (): JSX.Element => ( const RegisterPage: FC<Props> = (): JSX.Element => {
<div className={styles.registerPage}> const dispatch = useDispatch()
<div className={styles.registerContent}> return (
<Helmet title="RegisterPage" /> <div className={styles.registerPage}>
<RegisterForm /> <div className={styles.registerContent}>
<Helmet title="RegisterPage" />
<RegisterForm dispatch={dispatch} />
</div>
</div> </div>
</div> )
) }
export default memo(RegisterPage) export default memo(RegisterPage)

View File

@ -13,6 +13,7 @@ export default [
routes: [ routes: [
{ {
path: "/", path: "/",
exact: true,
component: Register, component: Register,
}, },
{ {
@ -26,7 +27,6 @@ export default [
}, },
{ {
path: "/register", path: "/register",
exact: true,
component: AsyncHome, component: AsyncHome,
loadData: loadHomeData, loadData: loadHomeData,
}, },

View File

@ -13,7 +13,12 @@ export default function getAccessors<
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
ElementNoId extends object, ElementNoId extends object,
Element extends ElementNoId & ElementWithId Element extends ElementNoId & ElementWithId
>(sheetName: string, specimen: Element): any { >(sheetName: string, specimen: Element, translation: { [k in keyof Element]: string }): any {
const frenchSpecimen = _.mapValues(
_.invert(translation),
(englishProp: string) => (specimen as any)[englishProp]
) as Element
const addDBOperation = DBManager(sheetName) const addDBOperation = DBManager(sheetName)
async function listGet(): Promise<Element[]> { async function listGet(): Promise<Element[]> {
@ -27,10 +32,13 @@ export default function getAccessors<
if (!rows[0]) { if (!rows[0]) {
throw new Error(`No column types defined in sheet ${sheetName}`) throw new Error(`No column types defined in sheet ${sheetName}`)
} }
const types = _.pick(rows[0], Object.keys(specimen)) as Record<keyof Element, string> const types = _.pick(rows[0], Object.values(translation)) as Record<
keyof Element,
string
>
rows.shift() rows.shift()
rows.forEach((row) => { rows.forEach((row) => {
const stringifiedElement = _.pick(row, Object.keys(specimen)) as Record< const stringifiedElement = _.pick(row, Object.values(translation)) as Record<
keyof Element, keyof Element,
string string
> >
@ -274,7 +282,7 @@ export default function getAccessors<
} }
return element return element
}, },
JSON.parse(JSON.stringify(specimen)) JSON.parse(JSON.stringify(frenchSpecimen))
) )
return fullElement return fullElement
} }
@ -365,7 +373,7 @@ export default function getAccessors<
return stringifiedElement return stringifiedElement
}, },
JSON.parse(JSON.stringify(element)) JSON.parse(JSON.stringify(frenchSpecimen))
) )
return rawElement return rawElement

View File

@ -1,10 +1,10 @@
import getExpressAccessors from "./expressAccessors" import getExpressAccessors from "./expressAccessors"
import { Envie, EnvieWithoutId } from "../../services/envies" import { Envie, EnvieWithoutId, translationEnvie } from "../../services/envies"
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors< const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
EnvieWithoutId, EnvieWithoutId,
Envie Envie
>("Envies d'aider", new Envie()) >("Envies d'aider", new Envie(), translationEnvie)
export const envieListGet = listGetRequest() export const envieListGet = listGetRequest()

View File

@ -5,8 +5,8 @@ export default function getExpressAccessors<
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
ElementNoId extends object, ElementNoId extends object,
Element extends ElementNoId & ElementWithId Element extends ElementNoId & ElementWithId
>(sheetName: string, specimen: Element): any { >(sheetName: string, specimen: Element, translation: { [k in keyof Element]: string }): any {
const { get, listGet, add, set } = getAccessors(sheetName, specimen) const { get, listGet, add, set } = getAccessors(sheetName, specimen, translation)
function listGetRequest() { function listGetRequest() {
return async ( return async (

View File

@ -1,10 +1,10 @@
import getExpressAccessors from "./expressAccessors" import getExpressAccessors from "./expressAccessors"
import { JeuJav, JeuJavWithoutId } from "../../services/jeuxJav" import { JeuJav, JeuJavWithoutId, translationJeuJav } from "../../services/jeuxJav"
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors< const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
JeuJavWithoutId, JeuJavWithoutId,
JeuJav JeuJav
>("Jeux JAV", new JeuJav()) >("Jeux JAV", new JeuJav(), translationJeuJav)
export const jeuJavListGet = listGetRequest() export const jeuJavListGet = listGetRequest()

View File

@ -1,10 +1,10 @@
import getExpressAccessors from "./expressAccessors" import getExpressAccessors from "./expressAccessors"
import { Membre, MembreWithoutId } from "../../services/membres" import { Membre, MembreWithoutId, translationMember } from "../../services/membres"
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors< const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
MembreWithoutId, MembreWithoutId,
Membre Membre
>("Membres", new Membre()) >("Membres", new Membre(), translationMember)
export const membreListGet = listGetRequest() export const membreListGet = listGetRequest()

View File

@ -0,0 +1,15 @@
import getExpressAccessors from "./expressAccessors"
import { PreMember, PreMemberWithoutId, translationPreMember } from "../../services/preMembers"
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
PreMemberWithoutId,
PreMember
>("PreMembres", new PreMember(), translationPreMember)
export const preMemberListGet = listGetRequest()
export const preMemberGet = getRequest()
export const preMemberAdd = addRequest()
export const preMemberSet = setRequest()

View File

@ -18,6 +18,7 @@ import certbotRouter from "../routes/certbot"
import { secure } from "./secure" import { secure } from "./secure"
import { jeuJavListGet } from "./gsheets/jeuJav" import { jeuJavListGet } from "./gsheets/jeuJav"
import { envieListGet, envieAdd } from "./gsheets/envies" import { envieListGet, envieAdd } from "./gsheets/envies"
import { preMemberAdd } from "./gsheets/preMembers"
import { membreGet, membreSet } from "./gsheets/membres" import { membreGet, membreSet } from "./gsheets/membres"
import loginHandler from "./userManagement/login" import loginHandler from "./userManagement/login"
import config from "../config" import config from "../config"
@ -56,6 +57,7 @@ app.post("/api/user/login", loginHandler)
app.get("/JeuJavListGet", jeuJavListGet) app.get("/JeuJavListGet", jeuJavListGet)
app.get("/EnvieListGet", envieListGet) app.get("/EnvieListGet", envieListGet)
app.post("/EnvieAdd", envieAdd) app.post("/EnvieAdd", envieAdd)
app.post("/PreMemberAdd", preMemberAdd)
// Secured APIs // Secured APIs
app.get("/MembreGet", secure as RequestHandler, membreGet) app.get("/MembreGet", secure as RequestHandler, membreGet)

View File

@ -10,9 +10,9 @@ import { login } from "../login"
// Full test with Bearer: wget --header='Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicGlraW91c3ViQGdlYWlsLmNvbSIsInBlcm1pc3Npb25zIjpbXSwiaWF0IjoxNjM4MjUzODgzLCJleHAiOjE2Mzg4NTg2ODN9.MknJ4NfcVlgW2ODeimfwZI1a4z8asdEXtHwHgViy6c4' http://localhost:3000/MembreGet?id=1 // Full test with Bearer: wget --header='Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicGlraW91c3ViQGdlYWlsLmNvbSIsInBlcm1pc3Npb25zIjpbXSwiaWF0IjoxNjM4MjUzODgzLCJleHAiOjE2Mzg4NTg2ODN9.MknJ4NfcVlgW2ODeimfwZI1a4z8asdEXtHwHgViy6c4' http://localhost:3000/MembreGet?id=1
const mockUser = { const mockUser = {
mail: "my.email@gmail.com", email: "my.email@gmail.com",
passe: "$2y$10$cuKFHEow2IVSZSPtoVsw6uZFNFOOP/v1V7fubbyvrxhZdsnxLHr.2", password: "$2y$10$cuKFHEow2IVSZSPtoVsw6uZFNFOOP/v1V7fubbyvrxhZdsnxLHr.2",
prenom: "monPrénom", firstname: "monPrénom",
} }
jest.mock("../../gsheets/accessors", () => () => ({ jest.mock("../../gsheets/accessors", () => () => ({
@ -24,7 +24,7 @@ describe("login with", () => {
const res = await login("my.email@gmail.com", "12345678") const res = await login("my.email@gmail.com", "12345678")
expect(_.omit(res, "jwt")).toEqual({ expect(_.omit(res, "jwt")).toEqual({
membre: { membre: {
prenom: mockUser.prenom, firstname: mockUser.firstname,
}, },
}) })
expect(res.jwt).toBeDefined() expect(res.jwt).toBeDefined()

View File

@ -1,10 +1,16 @@
import { Request, Response, NextFunction } from "express" import { Request, Response, NextFunction } from "express"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import { Membre, MemberLogin, emailRegexp, passwordMinLength } from "../../services/membres" import {
Membre,
MemberLogin,
emailRegexp,
passwordMinLength,
translationMember,
} from "../../services/membres"
import getAccessors from "../gsheets/accessors" import getAccessors from "../gsheets/accessors"
import { getJwt } from "../secure" import { getJwt } from "../secure"
const { listGet } = getAccessors("Membres", new Membre()) const { listGet } = getAccessors("Membres", new Membre(), translationMember)
export default async function loginHandler( export default async function loginHandler(
request: Request, request: Request,
@ -41,12 +47,12 @@ export async function login(rawEmail: string, rawPassword: string): Promise<Memb
} }
const membres: Membre[] = await listGet() const membres: Membre[] = await listGet()
const membre = membres.find((m) => m.mail === email) const membre = membres.find((m) => m.email === email)
if (!membre) { if (!membre) {
throw Error("Cet email ne correspond à aucun utilisateur") throw Error("Cet email ne correspond à aucun utilisateur")
} }
const passwordMatch = await bcrypt.compare(password, membre.passe.replace(/^\$2y/, "$2a")) const passwordMatch = await bcrypt.compare(password, membre.password.replace(/^\$2y/, "$2a"))
if (!passwordMatch) { if (!passwordMatch) {
throw Error("Mauvais mot de passe pour cet email") throw Error("Mauvais mot de passe pour cet email")
} }
@ -55,7 +61,7 @@ export async function login(rawEmail: string, rawPassword: string): Promise<Memb
return { return {
membre: { membre: {
prenom: membre.prenom, firstname: membre.firstname,
}, },
jwt, jwt,
} }

View File

@ -1,11 +1,17 @@
import axios from "axios" import axios from "axios"
import _ from "lodash"
import config from "../config" import config from "../config"
import { axiosConfig } from "./auth" import { axiosConfig } from "./auth"
export type ElementWithId = unknown & { id: number } export type ElementWithId = unknown & { id: number }
export function get<Element>(elementName: string): (id: number) => Promise<{ export type ElementTranslation = { [englishProp: string]: string }
export function get<Element>(
elementName: string,
translation: ElementTranslation
): (id: number) => Promise<{
data?: Element data?: Element
error?: Error error?: Error
}> { }> {
@ -19,14 +25,24 @@ export function get<Element>(elementName: string): (id: number) => Promise<{
...axiosConfig, ...axiosConfig,
params: { id }, params: { id },
}) })
return { data } if (!data) {
return { data }
}
const englishData = _.mapValues(
translation,
(frenchProp: string) => data[frenchProp]
) as Element
return { data: englishData }
} catch (error) { } catch (error) {
return { error: error as Error } return { error: error as Error }
} }
} }
} }
export function listGet<Element>(elementName: string): () => Promise<{ export function listGet<Element>(
elementName: string,
translation: ElementTranslation
): () => Promise<{
data?: Element[] data?: Element[]
error?: Error error?: Error
}> { }> {
@ -37,7 +53,18 @@ export function listGet<Element>(elementName: string): () => Promise<{
return async (): Promise<ElementListGetResponse> => { return async (): Promise<ElementListGetResponse> => {
try { try {
const { data } = await axios.get(`${config.API_URL}/${elementName}ListGet`, axiosConfig) const { data } = await axios.get(`${config.API_URL}/${elementName}ListGet`, axiosConfig)
return { data } if (!data) {
return { data }
}
const englishDataList = data.map(
(frenchData: any) =>
_.mapValues(
translation,
(frenchProp: string) => frenchData[frenchProp]
) as Element
)
return { data: englishDataList }
} catch (error) { } catch (error) {
return { error: error as Error } return { error: error as Error }
} }
@ -46,7 +73,8 @@ export function listGet<Element>(elementName: string): () => Promise<{
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
export function add<ElementNoId extends object, Element extends ElementNoId & ElementWithId>( export function add<ElementNoId extends object, Element extends ElementNoId & ElementWithId>(
elementName: string elementName: string,
translation: ElementTranslation
): (membreWithoutId: ElementNoId) => Promise<{ ): (membreWithoutId: ElementNoId) => Promise<{
data?: Element data?: Element
error?: Error error?: Error
@ -57,19 +85,36 @@ export function add<ElementNoId extends object, Element extends ElementNoId & El
} }
return async (membreWithoutId: ElementNoId): Promise<ElementGetResponse> => { return async (membreWithoutId: ElementNoId): Promise<ElementGetResponse> => {
try { try {
const invertedTranslationWithoutId = _.invert(_.omit(translation, "id"))
const frenchDataWithoutId = _.mapValues(
invertedTranslationWithoutId,
(englishProp: string, _frenchProp: string) => (membreWithoutId as any)[englishProp]
)
const { data } = await axios.post( const { data } = await axios.post(
`${config.API_URL}/${elementName}Add`, `${config.API_URL}/${elementName}Add`,
membreWithoutId, frenchDataWithoutId,
axiosConfig axiosConfig
) )
return { data } if (!data) {
return { data }
}
const englishData = _.mapValues(
translation,
(frenchProp: string) => data[frenchProp]
) as Element
return { data: englishData }
} catch (error) { } catch (error) {
return { error: error as Error } return { error: error as Error }
} }
} }
} }
export function set<Element>(elementName: string): (membre: Element) => Promise<{ export function set<Element>(
elementName: string,
translation: ElementTranslation
): (membre: Element) => Promise<{
data?: Element data?: Element
error?: Error error?: Error
}> { }> {
@ -79,12 +124,26 @@ export function set<Element>(elementName: string): (membre: Element) => Promise<
} }
return async (membre: Element): Promise<ElementGetResponse> => { return async (membre: Element): Promise<ElementGetResponse> => {
try { try {
const invertedTranslation = _.invert(translation)
const frenchData = _.mapValues(
invertedTranslation,
(englishProp: string) => (membre as any)[englishProp]
)
const { data } = await axios.post( const { data } = await axios.post(
`${config.API_URL}/${elementName}Set`, `${config.API_URL}/${elementName}Set`,
membre, frenchData,
axiosConfig axiosConfig
) )
return { data } if (!data) {
return { data }
}
const englishData = _.mapValues(
translation,
(frenchProp: string) => data[frenchProp]
) as Element
return { data: englishData }
} catch (error) { } catch (error) {
return { error: error as Error } return { error: error as Error }
} }

View File

@ -3,23 +3,34 @@ import { get, listGet, add, set } from "./accessors"
export class Envie { export class Envie {
id = 0 id = 0
domaine = "" domain = ""
envies = "" wish = ""
precisions = "" details = ""
equipes: string[] = [] teams: string[] = []
dateAjout = "" addedDate = ""
} }
export const translationEnvie: { [k in keyof Envie]: string } = {
id: "id",
domain: "domaine",
wish: "envies",
details: "precisions",
teams: "equipes",
addedDate: "dateAjout",
}
const elementName = "Envie"
export type EnvieWithoutId = Omit<Envie, "id"> export type EnvieWithoutId = Omit<Envie, "id">
export const envieGet = get<Envie>("Envie") export const envieGet = get<Envie>(elementName, translationEnvie)
export const envieListGet = listGet<Envie>("Envie") export const envieListGet = listGet<Envie>(elementName, translationEnvie)
export const envieAdd = add<EnvieWithoutId, Envie>("Envie") export const envieAdd = add<EnvieWithoutId, Envie>(elementName, translationEnvie)
export const envieSet = set<Envie>("Envie") export const envieSet = set<Envie>(elementName, translationEnvie)

View File

@ -3,17 +3,17 @@ import { get, listGet, add, set } from "./accessors"
export class JeuJav { export class JeuJav {
id = 0 id = 0
titre = "" title = ""
auteur = "" author = ""
editeur = "" editor = ""
minJoueurs = 0 playersMin = 0
maxJoueurs = 0 playersMax = 0
duree = 0 duration = 0
type: "Ambiance" | "Famille" | "Expert" | "" = "" type: "Ambiance" | "Famille" | "Expert" | "" = ""
@ -21,23 +21,43 @@ export class JeuJav {
bggId = 0 bggId = 0
exemplaires = 1 copies = 1
dispoPret = 0 lendAvailability = 0
nonRangee = 0 notStored = 0
ean = "" ean = ""
bggPhoto = "" bggPhoto = ""
} }
export const translationJeuJav: { [k in keyof JeuJav]: string } = {
id: "id",
title: "titre",
author: "auteur",
editor: "editeur",
playersMin: "minJoueurs",
playersMax: "maxJoueurs",
duration: "duree",
type: "type",
poufpaf: "poufpaf",
bggId: "bggId",
copies: "exemplaires",
lendAvailability: "dispoPret",
notStored: "nonRangee",
ean: "ean",
bggPhoto: "bggPhoto",
}
const elementName = "JeuJav"
export type JeuJavWithoutId = Omit<JeuJav, "id"> export type JeuJavWithoutId = Omit<JeuJav, "id">
export const jeuJavGet = get<JeuJav>("JeuJav") export const jeuJavGet = get<JeuJav>(elementName, translationJeuJav)
export const jeuJavListGet = listGet<JeuJav>("JeuJav") export const jeuJavListGet = listGet<JeuJav>(elementName, translationJeuJav)
export const jeuJavAdd = add<JeuJavWithoutId, JeuJav>("JeuJav") export const jeuJavAdd = add<JeuJavWithoutId, JeuJav>(elementName, translationJeuJav)
export const jeuJavSet = set<JeuJav>("JeuJav") export const jeuJavSet = set<JeuJav>(elementName, translationJeuJav)

View File

@ -3,38 +3,56 @@ import { get, listGet, add, set } from "./accessors"
export class Membre { export class Membre {
id = 0 id = 0
nom = "" lastname = ""
prenom = "" firstname = ""
mail = "" email = ""
telephone = "" mobile = ""
photo = "" photo = ""
alimentation = "" food = ""
majeur = 1 adult = 1
privilege = 0 privileges = 0
actif = 0 active = 0
commentaire = "" comment = ""
horodatage = "" timestamp = ""
passe = "" password = ""
} }
export const translationMember: { [k in keyof Membre]: string } = {
id: "id",
lastname: "nom",
firstname: "prenom",
email: "mail",
mobile: "telephone",
photo: "photo",
food: "alimentation",
adult: "majeur",
privileges: "privilege",
active: "actif",
comment: "commentaire",
timestamp: "horodatage",
password: "passe",
}
const elementName = "Membre"
export const emailRegexp = export const emailRegexp =
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
export const passwordMinLength = 4 export const passwordMinLength = 4
export interface MemberLogin { export interface MemberLogin {
membre?: { membre?: {
prenom: string firstname: string
} }
jwt?: string jwt?: string
error?: string error?: string
@ -42,10 +60,10 @@ export interface MemberLogin {
export type MembreWithoutId = Omit<Membre, "id"> export type MembreWithoutId = Omit<Membre, "id">
export const membreGet = get<Membre>("Membre") export const membreGet = get<Membre>(elementName, translationMember)
export const membreListGet = listGet<Membre>("Membre") export const membreListGet = listGet<Membre>(elementName, translationMember)
export const membreAdd = add<MembreWithoutId, Membre>("Membre") export const membreAdd = add<MembreWithoutId, Membre>(elementName, translationMember)
export const membreSet = set<Membre>("Membre") export const membreSet = set<Membre>(elementName, translationMember)

View File

@ -0,0 +1,39 @@
import { get, listGet, add, set } from "./accessors"
export class PreMember {
id = 0
firstname = ""
lastname = ""
email = ""
mobile = ""
alreadyVolunteer = false
comment = ""
}
export const translationPreMember: { [k in keyof PreMember]: string } = {
id: "id",
firstname: "prenom",
lastname: "nom",
email: "email",
mobile: "telephone",
alreadyVolunteer: "dejaBenevole",
comment: "commentaire",
}
const elementName = "PreMember"
export type PreMemberWithoutId = Omit<PreMember, "id">
export const preMemberGet = get<PreMember>(elementName, translationPreMember)
export const preMemberListGet = listGet<PreMember>(elementName, translationPreMember)
export const preMemberAdd = add<PreMemberWithoutId, PreMember>(elementName, translationPreMember)
export const preMemberSet = set<PreMember>(elementName, translationPreMember)

View File

@ -1,4 +1,5 @@
import axios from "axios" import axios from "axios"
import _ from "lodash"
import mockStore from "../../utils/mockStore" import mockStore from "../../utils/mockStore"
import JeuJavList, { import JeuJavList, {
@ -8,11 +9,12 @@ import JeuJavList, {
getFailure, getFailure,
fetchJeuJavList, fetchJeuJavList,
} from "../jeuJavList" } from "../jeuJavList"
import { JeuJav } from "../../services/jeuxJav"
jest.mock("axios") jest.mock("axios")
const mockData = { const mockFrenchData: any[] = [
"5": { {
id: 5, id: 5,
titre: "6 qui prend!", titre: "6 qui prend!",
auteur: "Wolfgang Kramer", auteur: "Wolfgang Kramer",
@ -22,16 +24,35 @@ const mockData = {
duree: 45, duree: 45,
type: "Ambiance", type: "Ambiance",
poufpaf: "0-9-2/6-qui-prend-6-nimmt", poufpaf: "0-9-2/6-qui-prend-6-nimmt",
photo: "https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg", bggPhoto:
bggPhoto: "", "https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
bggId: 432, bggId: 432,
exemplaires: 1, exemplaires: 1,
dispoPret: 1, dispoPret: 1,
nonRangee: 0, nonRangee: 0,
horodatage: "0000-00-00",
ean: "3421272101313", ean: "3421272101313",
}, },
} ]
const mockEnglishData: JeuJav[] = [
{
id: 5,
title: "6 qui prend!",
author: "Wolfgang Kramer",
editor: "(uncredited) , Design Edge , B",
playersMin: 2,
playersMax: 10,
duration: 45,
type: "Ambiance",
poufpaf: "0-9-2/6-qui-prend-6-nimmt",
bggPhoto:
"https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
bggId: 432,
copies: 1,
lendAvailability: 1,
notStored: 0,
ean: "3421272101313",
},
]
const mockError = "Oops! Something went wrong." const mockError = "Oops! Something went wrong."
describe("JeuJavList reducer", () => { describe("JeuJavList reducer", () => {
@ -49,11 +70,11 @@ describe("JeuJavList reducer", () => {
}) })
it("should handle success correctly", () => { it("should handle success correctly", () => {
expect(JeuJavList(undefined, { type: getSuccess.type, payload: mockData })).toEqual({ expect(JeuJavList(undefined, { type: getSuccess.type, payload: mockEnglishData })).toEqual({
...initialState, ...initialState,
readyStatus: "success", readyStatus: "success",
ids: [5], ids: _.map(mockEnglishData, "id"),
entities: mockData, entities: _.keyBy(mockEnglishData, "id"),
}) })
}) })
@ -70,12 +91,12 @@ describe("JeuJavList action", () => {
it("fetches JeuJav list successful", async () => { it("fetches JeuJav list successful", async () => {
const { dispatch, getActions } = mockStore() const { dispatch, getActions } = mockStore()
const expectedActions = [ const expectedActions = [
{ type: getRequesting.type }, { type: getRequesting.type, payload: undefined },
{ type: getSuccess.type, payload: mockData }, { type: getSuccess.type, payload: mockEnglishData },
] ]
// @ts-expect-error // @ts-expect-error
axios.get.mockResolvedValue({ data: mockData }) axios.get.mockResolvedValue({ data: mockFrenchData })
await dispatch(fetchJeuJavList()) await dispatch(fetchJeuJavList())
expect(getActions()).toEqual(expectedActions) expect(getActions()).toEqual(expectedActions)

View File

@ -2,10 +2,11 @@ import axios from "axios"
import mockStore from "../../utils/mockStore" import mockStore from "../../utils/mockStore"
import membre, { getRequesting, getSuccess, getFailure, fetchMembre, initialState } from "../membre" import membre, { getRequesting, getSuccess, getFailure, fetchMembre, initialState } from "../membre"
import { Membre } from "../../services/membres"
jest.mock("axios") jest.mock("axios")
const mockData = { const mockFrenchData: any = {
id: 1, id: 1,
nom: "Aupeix", nom: "Aupeix",
prenom: "Amélie", prenom: "Amélie",
@ -20,7 +21,23 @@ const mockData = {
horodatage: "0000-00-00", horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O", passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
} }
const { id } = mockData
const mockEnglishData: Membre = {
id: 1,
lastname: "Aupeix",
firstname: "Amélie",
email: "pakouille.lakouille@yahoo.fr",
mobile: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
food: "Végétarien",
adult: 1,
privileges: 0,
active: 0,
comment: "",
timestamp: "0000-00-00",
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}
const { id } = mockEnglishData
const mockError = "Oops! Something went wrong." const mockError = "Oops! Something went wrong."
describe("membre reducer", () => { describe("membre reducer", () => {
@ -39,9 +56,9 @@ describe("membre reducer", () => {
expect( expect(
membre(undefined, { membre(undefined, {
type: getSuccess.type, type: getSuccess.type,
payload: mockData, payload: mockEnglishData,
}) })
).toEqual({ readyStatus: "success", entity: mockData }) ).toEqual({ readyStatus: "success", entity: mockEnglishData })
}) })
it("should handle failure correctly", () => { it("should handle failure correctly", () => {
@ -58,12 +75,12 @@ describe("membre action", () => {
it("fetches membre data successful", async () => { it("fetches membre data successful", async () => {
const { dispatch, getActions } = mockStore() const { dispatch, getActions } = mockStore()
const expectedActions = [ const expectedActions = [
{ type: getRequesting.type }, { type: getRequesting.type, payload: undefined },
{ type: getSuccess.type, payload: mockData }, { type: getSuccess.type, payload: mockEnglishData },
] ]
// @ts-expect-error // @ts-expect-error
axios.get.mockResolvedValue({ data: mockData }) axios.get.mockResolvedValue({ data: mockFrenchData })
await dispatch(fetchMembre(id)) await dispatch(fetchMembre(id))
expect(getActions()).toEqual(expectedActions) expect(getActions()).toEqual(expectedActions)

View File

@ -1,4 +1,5 @@
import axios from "axios" import axios from "axios"
import _ from "lodash"
import mockStore from "../../utils/mockStore" import mockStore from "../../utils/mockStore"
import membreList, { import membreList, {
@ -8,11 +9,12 @@ import membreList, {
getFailure, getFailure,
fetchMembreList, fetchMembreList,
} from "../membreList" } from "../membreList"
import { Membre } from "../../services/membres"
jest.mock("axios") jest.mock("axios")
const mockData = { const mockFrenchData: any[] = [
"1": { {
id: 1, id: 1,
nom: "Aupeix", nom: "Aupeix",
prenom: "Amélie", prenom: "Amélie",
@ -27,7 +29,25 @@ const mockData = {
horodatage: "0000-00-00", horodatage: "0000-00-00",
passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O", passe: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
}, },
} ]
const mockEnglishData: Membre[] = [
{
id: 1,
lastname: "Aupeix",
firstname: "Amélie",
email: "pakouille.lakouille@yahoo.fr",
mobile: "0675650392",
photo: "images/membres/$taille/amélie_aupeix.jpg",
food: "Végétarien",
adult: 1,
privileges: 0,
active: 0,
comment: "",
timestamp: "0000-00-00",
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPlobkyRrNIal8ASimSjNj4SR.9O",
},
]
const mockError = "Oops! Something went wrong." const mockError = "Oops! Something went wrong."
describe("membreList reducer", () => { describe("membreList reducer", () => {
@ -45,11 +65,11 @@ describe("membreList reducer", () => {
}) })
it("should handle success correctly", () => { it("should handle success correctly", () => {
expect(membreList(undefined, { type: getSuccess.type, payload: mockData })).toEqual({ expect(membreList(undefined, { type: getSuccess.type, payload: mockEnglishData })).toEqual({
...initialState, ...initialState,
readyStatus: "success", readyStatus: "success",
ids: [1], ids: _.map(mockEnglishData, "id"),
entities: mockData, entities: _.keyBy(mockEnglishData, "id"),
}) })
}) })
@ -66,12 +86,12 @@ describe("membreList action", () => {
it("fetches membre list successful", async () => { it("fetches membre list successful", async () => {
const { dispatch, getActions } = mockStore() const { dispatch, getActions } = mockStore()
const expectedActions = [ const expectedActions = [
{ type: getRequesting.type }, { type: getRequesting.type, payload: undefined },
{ type: getSuccess.type, payload: mockData }, { type: getSuccess.type, payload: mockEnglishData },
] ]
// @ts-expect-error // @ts-expect-error
axios.get.mockResolvedValue({ data: mockData }) axios.get.mockResolvedValue({ data: mockFrenchData })
await dispatch(fetchMembreList()) await dispatch(fetchMembreList())
expect(getActions()).toEqual(expectedActions) expect(getActions()).toEqual(expectedActions)

38
src/store/preMemberAdd.ts Normal file
View File

@ -0,0 +1,38 @@
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
import { StateRequest, elementAddFetch } from "./utils"
import { PreMember, preMemberAdd } from "../services/preMembers"
const preMemberAdapter = createEntityAdapter<PreMember>()
const preMemberAddSlice = createSlice({
name: "addPreMember",
initialState: preMemberAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<PreMember>) => {
state.readyStatus = "success"
preMemberAdapter.addOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default preMemberAddSlice.reducer
export const { getRequesting, getSuccess, getFailure } = preMemberAddSlice.actions
export const fetchPreMemberAdd = elementAddFetch(
preMemberAdd,
getRequesting,
getSuccess,
getFailure,
() => null,
() => null
)

View File

@ -8,6 +8,7 @@ import membre from "./membre"
import membreAdd from "./membreAdd" import membreAdd from "./membreAdd"
import membreList from "./membreList" import membreList from "./membreList"
import membreSet from "./membreSet" import membreSet from "./membreSet"
import preMemberAdd from "./preMemberAdd"
// Use inferred return type for making correctly Redux types // Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@ -19,6 +20,7 @@ export default (history: History) => ({
membreAdd, membreAdd,
membreList, membreList,
membreSet, membreSet,
preMemberAdd,
router: connectRouter(history) as any, router: connectRouter(history) as any,
// Register more reducers... // Register more reducers...
}) })

View File

@ -23,7 +23,7 @@ export function toastError(message: string): void {
export function toastSuccess(message: string): void { export function toastSuccess(message: string): void {
toast.success(message, { toast.success(message, {
position: "top-center", position: "top-center",
autoClose: 3000, autoClose: 5000,
hideProgressBar: true, hideProgressBar: true,
closeOnClick: true, closeOnClick: true,
pauseOnHover: true, pauseOnHover: true,

View File

@ -31,6 +31,7 @@ const config: Configuration = {
}), }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
localStorage: { getItem: () => null, setItem: () => null, removeItem: () => null }, localStorage: { getItem: () => null, setItem: () => null, removeItem: () => null },
"location.protocol": "http:",
}), }),
], ],
} }