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": {
"dev": "yarn dev:build && nodemon ./public/server",
"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",
"local-build": "cross-env LOCAL=true run-s build:*",
"build": "run-s build:*",
"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",

View File

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

View File

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

View File

@ -28,21 +28,20 @@ describe("<List />", () => {
entities: {
"5": {
id: 5,
titre: "6 qui prend!",
auteur: "Wolfgang Kramer",
editeur: "(uncredited) , Design Edge , B",
minJoueurs: 2,
maxJoueurs: 10,
duree: 45,
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",
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,
exemplaires: 1,
dispoPret: 1,
nonRangee: 0,
horodatage: "0000-00-00",
copies: 1,
lendAvailability: 1,
notStored: 0,
ean: "3421272101313",
},
},

View File

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

View File

@ -13,18 +13,18 @@ describe("<MembreInfo />", () => {
<MembreInfo
item={{
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
firstname: "Aupeix",
lastname: "Amélie",
email: "pakouille.lakouille@yahoo.fr",
mobile: "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",
food: "Végétarien",
adult: 1,
privileges: 0,
active: 0,
comment: "",
timestamp: "0000-00-00",
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
}}
/>
</MemoryRouter>

View File

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

View File

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

View File

@ -14,18 +14,19 @@ describe("<MembreList />", () => {
items={[
{
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
firstname: "Aupeix",
lastname: "Amélie",
email: "pakouille.lakouille@yahoo.fr",
mobile: "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",
food: "Végétarien",
adult: 1,
privileges: 0,
active: 0,
comment: "",
timestamp: "0000-00-00",
password:
"$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
},
]}
/>

View File

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

View File

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

View File

@ -15,18 +15,18 @@ describe("<SetMembre />", () => {
dispatch={dispatch}
membre={{
id: 1,
nom: "Aupeix",
prenom: "Amélie",
mail: "pakouille.lakouille@yahoo.fr",
telephone: "0675650392",
firstname: "Aupeix",
lastname: "Amélie",
email: "pakouille.lakouille@yahoo.fr",
mobile: "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",
food: "Végétarien",
adult: 1,
privileges: 0,
active: 0,
comment: "",
timestamp: "0000-00-00",
password: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
}}
/>
</MemoryRouter>

View File

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

View File

@ -13,22 +13,23 @@ interface Props {
}
const MembreSet = ({ dispatch, membre }: Props) => {
const [prenom, setPrenom] = useState(membre.prenom)
const [nom, setNom] = useState(membre.nom)
const [majeur, setMajeur] = useState(membre.majeur)
const [firstname, setFirstname] = useState(membre.firstname)
const [lastname, setName] = useState(membre.lastname)
const [adult, setAdult] = useState(membre.adult)
const onPrenomChanged = (e: React.ChangeEvent<HTMLInputElement>) => setPrenom(e.target.value)
const onNomChanged = (e: React.ChangeEvent<HTMLInputElement>) => setNom(e.target.value)
const onMajeurChanged = (e: React.ChangeEvent<HTMLInputElement>) => setMajeur(+e.target.value)
const onFirstnameChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
setFirstname(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 = () => {
if (prenom && nom) {
if (firstname && lastname) {
dispatch(
fetchMembreSet({
...membre,
prenom,
nom,
majeur,
firstname,
lastname,
adult,
})
)
} else {
@ -47,34 +48,34 @@ const MembreSet = ({ dispatch, membre }: Props) => {
<section className={styles.MembreList}>
<h2>Modifier un membre</h2>
<form>
<label htmlFor="postPrenom">
Prenom:
<label htmlFor="postFirstname">
Prénom:
<input
type="text"
id="postPrenom"
name="postPrenom"
value={prenom}
onChange={onPrenomChanged}
id="postFirstname"
name="postFirstname"
value={firstname}
onChange={onFirstnameChanged}
/>
</label>
<label htmlFor="postNom">
<label htmlFor="postName">
Nom:
<input
type="text"
id="postNom"
name="postNom"
value={nom}
onChange={onNomChanged}
id="postName"
name="postName"
value={lastname}
onChange={onNameChanged}
/>
</label>
<label htmlFor="postMajeur">
<label htmlFor="postAdult">
Majeur:
<input
type="text"
id="postMajeur"
name="postMajeur"
value={majeur}
onChange={onMajeurChanged}
id="postAdult"
name="postAdult"
value={adult}
onChange={onAdultChanged}
/>
</label>
<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"
const RegisterForm = (): JSX.Element => {
const onSubmit = useCallback((event: React.SyntheticEvent): void => {
event.preventDefault()
const target = event.target as typeof event.target & {
firstname: { value: string }
lastname: { value: string }
email: { value: string }
phone: { value: string }
import { fetchPreMemberAdd } from "../../store/preMemberAdd"
import { AppDispatch, AppState } from "../../store"
interface Props {
dispatch: AppDispatch
}
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
nom
firstname
lastname
mail
tel
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>
<dd>
<p>
Cette grande fête est dédiée aux <b>jeux de société modernes</b> sous toutes
leurs formes.
Un festival en plein air dédiée aux <b>jeux de société modernes</b> sous
toutes leurs formes.
</p>
<p>
En 2019 lors de la dernière édition, ce sont <b>16 000</b> joueurs qui se
@ -53,60 +118,88 @@ const RegisterForm = (): JSX.Element => {
<dd>
<p>
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
tout pour passer <b>un aussi bon moment que les visiteurs</b> :)
bénévoles. À aucun moment ça ne ressemble à du travail : nous faisons tout
pour passer <b>un aussi bon moment que les visiteurs</b> :)
</p>
<p>
C&apos;est pour ça que chaque mois, ceux qui sont dispo prennent
l&apos;apéro tous ensemble en jouant et discutant de l&apos;organisation.
D&apos;ailleurs, un soir par mois nous nous réunissons pour un apéro ludique
discuter de l&apos;organisation ! On joue autant que les visiteurs, mais
sur toute l&apos;année ^^
</p>
<p>
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
s&apos;assurant que tout se passe bien, ou en expliquant des règles de jeux.
équipes qui chouchoutent les visiteurs en les accueillant, en
s&apos;assurant que tout se passe bien, ou encore en expliquant des règles
de jeux.
</p>
<p>
Une équipe s&apos;occupe même du bien être des bénévoles en leur servant à
boire et à manger dans un espace à part faire des pauses régulières.
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. Et
puis nous hébergeons ceux d&apos;entre nous qui habitent loin de Paris. Le
confort avant tout !
</p>
<p>
Les deux jours avant et le jour après le festival, ceux qui le peuvent
viennent tout préparer et ranger. Certains ne sont disponibles que ces jours
et c&apos;est déjà d&apos;une grande aide !
Certains bénévoles sont visiteurs le samedi ou le dimanche pour vivre le
festival de l&apos;intérieur. Les deux jours avant et le jour après le
festival, ceux qui le peuvent viennent préparer et ranger. Bref, chacun
participe à la hauteur de ses envies et disponibilités !
</p>
<p>
Nous nous arrangeons pour héberger les bénévoles qui habitent loin de Paris,
et certains ne viennent qu&apos;un seul jour du weekend pour être visiteur
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 !
Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en
dînant avec les auteurs, illustrateurs et éditeurs présents sur le festival.
</p>
</dd>
<dt>
Si l&apos;expérience vous tente, n&apos;hésitez pas à remplir le formulaire
suivant pour nous rencontrer lors d&apos;un des gros apéros mensuels !<br />
Si l&apos;expérience pourrait vous tenter, remplissez le formulaire suivant pour
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
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>
<dd>
<div className={styles.formLine} key="line-firstname">
<label htmlFor="firstname">Prénom</label>
<input type="text" id="firstname" />
<input
type="text"
id="firstname"
required
value={firstname}
onChange={onFirstnameChanged}
/>
</div>
<div className={styles.formLine} key="line-lastname">
<label htmlFor="lastname">Nom</label>
<input type="text" id="lastname" />
<input
type="text"
id="lastname"
required
value={lastname}
onChange={onLastnameChanged}
/>
</div>
<div className={styles.formLine} key="line-email">
<label htmlFor="email">Email</label>
<input type="email" id="email" />
<input
type="email"
id="email"
required
value={email}
onChange={onEmailChanged}
/>
</div>
<div className={styles.formLine} key="line-phone">
<label htmlFor="phone">Téléphone</label>
<input type="text" id="phone" />
<div className={styles.formLine} key="line-mobile">
<label htmlFor="mobile">Téléphone</label>
<input
type="text"
id="mobile"
required
value={mobile}
onChange={onMobileChanged}
/>
</div>
<div className={styles.formLine} key="line-already-volunteer">
<div>
@ -116,6 +209,8 @@ const RegisterForm = (): JSX.Element => {
name="alreadyVolunteer"
id="alreadyVolunteer-yes"
className={styles.inputRadio}
checked={alreadyVolunteer}
onChange={onAlreadyVolunteer}
/>
<label htmlFor="alreadyVolunteer-yes">Oui</label>
<input
@ -123,6 +218,8 @@ const RegisterForm = (): JSX.Element => {
name="alreadyVolunteer"
id="alreadyVolunteer-no"
className={styles.inputRadio}
checked={!alreadyVolunteer}
onChange={onNotYesVolunteer}
/>
<label htmlFor="alreadyVolunteer-no">Non</label>
</div>
@ -132,10 +229,19 @@ const RegisterForm = (): JSX.Element => {
name="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 :)"
value={comment}
onChange={onCommentChanged}
/>
</div>
<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>
</dd>
</dl>

View File

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

View File

@ -1,5 +1,7 @@
const PORT = 4000
const API_URL = __DEV__ || __LOCAL__ ? `http://localhost:${PORT}` : "https://fo.parisestludique.fr"
const PROTOCOL = (typeof window !== "undefined" && window?.location?.protocol) || "http:"
const PORT = 4000 + (PROTOCOL === "https:" ? 2 : 0)
const API_URL =
__DEV__ || __LOCAL__ ? `${PROTOCOL}//localhost:${PORT}` : `${PROTOCOL}//fo.parisestludique.fr`
export default {
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 React, { memo } from "react"
import { useDispatch } from "react-redux"
import { FC, memo } from "react"
import { Helmet } from "react-helmet"
import styles from "./styles.module.scss"
import RegisterForm from "../../components/RegisterForm/RegisterForm"
export type Props = RouteComponentProps
const RegisterPage: React.FC<Props> = (): JSX.Element => (
<div className={styles.registerPage}>
<div className={styles.registerContent}>
<Helmet title="RegisterPage" />
<RegisterForm />
const RegisterPage: FC<Props> = (): JSX.Element => {
const dispatch = useDispatch()
return (
<div className={styles.registerPage}>
<div className={styles.registerContent}>
<Helmet title="RegisterPage" />
<RegisterForm dispatch={dispatch} />
</div>
</div>
</div>
)
)
}
export default memo(RegisterPage)

View File

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

View File

@ -13,7 +13,12 @@ export default function getAccessors<
// eslint-disable-next-line @typescript-eslint/ban-types
ElementNoId extends object,
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)
async function listGet(): Promise<Element[]> {
@ -27,10 +32,13 @@ export default function getAccessors<
if (!rows[0]) {
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.forEach((row) => {
const stringifiedElement = _.pick(row, Object.keys(specimen)) as Record<
const stringifiedElement = _.pick(row, Object.values(translation)) as Record<
keyof Element,
string
>
@ -274,7 +282,7 @@ export default function getAccessors<
}
return element
},
JSON.parse(JSON.stringify(specimen))
JSON.parse(JSON.stringify(frenchSpecimen))
)
return fullElement
}
@ -365,7 +373,7 @@ export default function getAccessors<
return stringifiedElement
},
JSON.parse(JSON.stringify(element))
JSON.parse(JSON.stringify(frenchSpecimen))
)
return rawElement

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import getExpressAccessors from "./expressAccessors"
import { Membre, MembreWithoutId } from "../../services/membres"
import { Membre, MembreWithoutId, translationMember } from "../../services/membres"
const { listGetRequest, getRequest, setRequest, addRequest } = getExpressAccessors<
MembreWithoutId,
Membre
>("Membres", new Membre())
>("Membres", new Membre(), translationMember)
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 { jeuJavListGet } from "./gsheets/jeuJav"
import { envieListGet, envieAdd } from "./gsheets/envies"
import { preMemberAdd } from "./gsheets/preMembers"
import { membreGet, membreSet } from "./gsheets/membres"
import loginHandler from "./userManagement/login"
import config from "../config"
@ -56,6 +57,7 @@ app.post("/api/user/login", loginHandler)
app.get("/JeuJavListGet", jeuJavListGet)
app.get("/EnvieListGet", envieListGet)
app.post("/EnvieAdd", envieAdd)
app.post("/PreMemberAdd", preMemberAdd)
// Secured APIs
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
const mockUser = {
mail: "my.email@gmail.com",
passe: "$2y$10$cuKFHEow2IVSZSPtoVsw6uZFNFOOP/v1V7fubbyvrxhZdsnxLHr.2",
prenom: "monPrénom",
email: "my.email@gmail.com",
password: "$2y$10$cuKFHEow2IVSZSPtoVsw6uZFNFOOP/v1V7fubbyvrxhZdsnxLHr.2",
firstname: "monPrénom",
}
jest.mock("../../gsheets/accessors", () => () => ({
@ -24,7 +24,7 @@ describe("login with", () => {
const res = await login("my.email@gmail.com", "12345678")
expect(_.omit(res, "jwt")).toEqual({
membre: {
prenom: mockUser.prenom,
firstname: mockUser.firstname,
},
})
expect(res.jwt).toBeDefined()

View File

@ -1,10 +1,16 @@
import { Request, Response, NextFunction } from "express"
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 { getJwt } from "../secure"
const { listGet } = getAccessors("Membres", new Membre())
const { listGet } = getAccessors("Membres", new Membre(), translationMember)
export default async function loginHandler(
request: Request,
@ -41,12 +47,12 @@ export async function login(rawEmail: string, rawPassword: string): Promise<Memb
}
const membres: Membre[] = await listGet()
const membre = membres.find((m) => m.mail === email)
const membre = membres.find((m) => m.email === email)
if (!membre) {
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) {
throw Error("Mauvais mot de passe pour cet email")
}
@ -55,7 +61,7 @@ export async function login(rawEmail: string, rawPassword: string): Promise<Memb
return {
membre: {
prenom: membre.prenom,
firstname: membre.firstname,
},
jwt,
}

View File

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

View File

@ -3,23 +3,34 @@ import { get, listGet, add, set } from "./accessors"
export class Envie {
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 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 {
id = 0
titre = ""
title = ""
auteur = ""
author = ""
editeur = ""
editor = ""
minJoueurs = 0
playersMin = 0
maxJoueurs = 0
playersMax = 0
duree = 0
duration = 0
type: "Ambiance" | "Famille" | "Expert" | "" = ""
@ -21,23 +21,43 @@ export class JeuJav {
bggId = 0
exemplaires = 1
copies = 1
dispoPret = 0
lendAvailability = 0
nonRangee = 0
notStored = 0
ean = ""
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 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 {
id = 0
nom = ""
lastname = ""
prenom = ""
firstname = ""
mail = ""
email = ""
telephone = ""
mobile = ""
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 =
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
export const passwordMinLength = 4
export interface MemberLogin {
membre?: {
prenom: string
firstname: string
}
jwt?: string
error?: string
@ -42,10 +60,10 @@ export interface MemberLogin {
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 _ from "lodash"
import mockStore from "../../utils/mockStore"
import JeuJavList, {
@ -8,11 +9,12 @@ import JeuJavList, {
getFailure,
fetchJeuJavList,
} from "../jeuJavList"
import { JeuJav } from "../../services/jeuxJav"
jest.mock("axios")
const mockData = {
"5": {
const mockFrenchData: any[] = [
{
id: 5,
titre: "6 qui prend!",
auteur: "Wolfgang Kramer",
@ -22,16 +24,35 @@ const mockData = {
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: "",
bggPhoto:
"https://cf.geekdo-images.com/thumb/img/lzczxR5cw7an7tRWeHdOrRtLyes=/fit-in/200x150/pic772547.jpg",
bggId: 432,
exemplaires: 1,
dispoPret: 1,
nonRangee: 0,
horodatage: "0000-00-00",
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."
describe("JeuJavList reducer", () => {
@ -49,11 +70,11 @@ describe("JeuJavList reducer", () => {
})
it("should handle success correctly", () => {
expect(JeuJavList(undefined, { type: getSuccess.type, payload: mockData })).toEqual({
expect(JeuJavList(undefined, { type: getSuccess.type, payload: mockEnglishData })).toEqual({
...initialState,
readyStatus: "success",
ids: [5],
entities: mockData,
ids: _.map(mockEnglishData, "id"),
entities: _.keyBy(mockEnglishData, "id"),
})
})
@ -70,12 +91,12 @@ describe("JeuJavList action", () => {
it("fetches JeuJav list successful", async () => {
const { dispatch, getActions } = mockStore()
const expectedActions = [
{ type: getRequesting.type },
{ type: getSuccess.type, payload: mockData },
{ type: getRequesting.type, payload: undefined },
{ type: getSuccess.type, payload: mockEnglishData },
]
// @ts-expect-error
axios.get.mockResolvedValue({ data: mockData })
axios.get.mockResolvedValue({ data: mockFrenchData })
await dispatch(fetchJeuJavList())
expect(getActions()).toEqual(expectedActions)

View File

@ -2,10 +2,11 @@ import axios from "axios"
import mockStore from "../../utils/mockStore"
import membre, { getRequesting, getSuccess, getFailure, fetchMembre, initialState } from "../membre"
import { Membre } from "../../services/membres"
jest.mock("axios")
const mockData = {
const mockFrenchData: any = {
id: 1,
nom: "Aupeix",
prenom: "Amélie",
@ -20,7 +21,23 @@ const mockData = {
horodatage: "0000-00-00",
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."
describe("membre reducer", () => {
@ -39,9 +56,9 @@ describe("membre reducer", () => {
expect(
membre(undefined, {
type: getSuccess.type,
payload: mockData,
payload: mockEnglishData,
})
).toEqual({ readyStatus: "success", entity: mockData })
).toEqual({ readyStatus: "success", entity: mockEnglishData })
})
it("should handle failure correctly", () => {
@ -58,12 +75,12 @@ describe("membre action", () => {
it("fetches membre data successful", async () => {
const { dispatch, getActions } = mockStore()
const expectedActions = [
{ type: getRequesting.type },
{ type: getSuccess.type, payload: mockData },
{ type: getRequesting.type, payload: undefined },
{ type: getSuccess.type, payload: mockEnglishData },
]
// @ts-expect-error
axios.get.mockResolvedValue({ data: mockData })
axios.get.mockResolvedValue({ data: mockFrenchData })
await dispatch(fetchMembre(id))
expect(getActions()).toEqual(expectedActions)

View File

@ -1,4 +1,5 @@
import axios from "axios"
import _ from "lodash"
import mockStore from "../../utils/mockStore"
import membreList, {
@ -8,11 +9,12 @@ import membreList, {
getFailure,
fetchMembreList,
} from "../membreList"
import { Membre } from "../../services/membres"
jest.mock("axios")
const mockData = {
"1": {
const mockFrenchData: any[] = [
{
id: 1,
nom: "Aupeix",
prenom: "Amélie",
@ -27,7 +29,25 @@ const mockData = {
horodatage: "0000-00-00",
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."
describe("membreList reducer", () => {
@ -45,11 +65,11 @@ describe("membreList reducer", () => {
})
it("should handle success correctly", () => {
expect(membreList(undefined, { type: getSuccess.type, payload: mockData })).toEqual({
expect(membreList(undefined, { type: getSuccess.type, payload: mockEnglishData })).toEqual({
...initialState,
readyStatus: "success",
ids: [1],
entities: mockData,
ids: _.map(mockEnglishData, "id"),
entities: _.keyBy(mockEnglishData, "id"),
})
})
@ -66,12 +86,12 @@ describe("membreList action", () => {
it("fetches membre list successful", async () => {
const { dispatch, getActions } = mockStore()
const expectedActions = [
{ type: getRequesting.type },
{ type: getSuccess.type, payload: mockData },
{ type: getRequesting.type, payload: undefined },
{ type: getSuccess.type, payload: mockEnglishData },
]
// @ts-expect-error
axios.get.mockResolvedValue({ data: mockData })
axios.get.mockResolvedValue({ data: mockFrenchData })
await dispatch(fetchMembreList())
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 membreList from "./membreList"
import membreSet from "./membreSet"
import preMemberAdd from "./preMemberAdd"
// Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
@ -19,6 +20,7 @@ export default (history: History) => ({
membreAdd,
membreList,
membreSet,
preMemberAdd,
router: connectRouter(history) as any,
// Register more reducers...
})

View File

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

View File

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