Add register form, missing proper feedback on error or success

This commit is contained in:
pikiou 2022-03-17 00:22:34 +01:00
parent cef7c5f7b0
commit 7fc3ec08ba
26 changed files with 350 additions and 260 deletions

View File

@ -4,15 +4,16 @@ import { toast } from "react-toastify"
import _ from "lodash"
import styles from "./styles.module.scss"
import { fetchPreVolunteerAdd } from "../../store/preVolunteerAdd"
import { fetchPostulantAdd } from "../../store/postulantAdd"
import { AppDispatch, AppState } from "../../store"
import { fetchVolunteerPartialAdd } from "../../store/volunteerPartialAdd"
interface Props {
dispatch: AppDispatch
preVolunteerCount: number | undefined
}
const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element => {
const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const [potentialVolunteer, setPotentialVolunteer] = useState(true)
const [firstname, setFirstname] = useState("")
const [lastname, setLastname] = useState("")
const [email, setEmail] = useState("")
@ -21,31 +22,60 @@ const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element =>
const [comment, setComment] = useState("")
const [sending, setSending] = useState(false)
const onNewVolunteer = (e: React.ChangeEvent<HTMLInputElement>) =>
setPotentialVolunteer(!e.target.value)
const onPotentialVolunteer = (e: React.ChangeEvent<HTMLInputElement>) =>
setPotentialVolunteer(!!e.target.value)
const onFirstnameChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
setFirstname(e.target.value)
const onLastnameChanged = (e: React.ChangeEvent<HTMLInputElement>) =>
setLastname(e.target.value)
const onEmailChanged = (e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)
const onMobileChanged = (e: React.ChangeEvent<HTMLInputElement>) => setMobile(e.target.value)
const onAlreadyVolunteer = (e: React.ChangeEvent<HTMLInputElement>) =>
setAlreadyVolunteer(!!e.target.value)
const onNotYesVolunteer = (e: React.ChangeEvent<HTMLInputElement>) =>
setAlreadyVolunteer(!e.target.value)
const onCommentChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) =>
setComment(e.target.value)
const onSubmit = () => {
if (firstname && lastname && email && mobile && !sending) {
dispatch(
fetchPreVolunteerAdd({
firstname,
lastname,
email,
mobile,
alreadyVolunteer,
comment,
})
)
if (potentialVolunteer) {
dispatch(
fetchPostulantAdd({
firstname,
lastname,
email,
mobile,
potential: true,
comment,
})
)
} else {
dispatch(
fetchPostulantAdd({
firstname,
lastname,
email,
mobile,
potential: false,
comment,
})
)
dispatch(
fetchVolunteerPartialAdd({
firstname,
lastname,
email,
mobile,
})
)
}
setSending(true)
} else {
@ -61,13 +91,13 @@ const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element =>
}
}
const { error, entities: preVolunteer } = useSelector(
(state: AppState) => state.preVolunteerAdd,
const { error, entities: postulant } = useSelector(
(state: AppState) => state.postulantAdd,
shallowEqual
)
let sendSuccess
if (!_.isEmpty(preVolunteer)) {
if (!_.isEmpty(postulant)) {
if (sending) {
setSending(false)
}
@ -75,7 +105,7 @@ const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element =>
}
let sendError
if (error && _.isEmpty(preVolunteer)) {
if (error && _.isEmpty(postulant)) {
if (sending) {
setSending(false)
}
@ -140,31 +170,53 @@ const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element =>
confort avant tout !
</p>
<p>
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 wishes et disponibilités !
La majorité d&apos;entre nous sommes bénévoles les <b>samedi et dimanche</b>
, mais certains bénévoles ne sont pas disponibles les deux jours. On leur
demande alors d&apos;aider à la mise en place jeudi ou vendredi, ou au
rangement le lundi. Bref, chacun participe comme il peut mais deux jours
minimum !
</p>
<p>
Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en
dînant avec les auteurs, illustrateurs et éditeurs présents sur le festival.
dînant avec les exposants présents sur le festival.
</p>
</dd>
<dt>
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.
Si l&apos;expérience vous tente, remplissez le formulaire suivant pour devenir
bénévoles !<br />
Vous pouvez aussi juste nous rencontrer avant de vous décider à devenir
bénévole, on comprend qu&apos;un saut dans l&apos;inconnu soit difficile.
<br />
Les prochains sont les 21 décembre et 27 janvier, mais nous vous appelerons
d&apos;ici pour les détails :)
Dans les deux cas, venez nous rencontrer mardi 22 mars près de Châtelet, détails
après l&apos;inscription :)
<br />
{/* */}
<span className={styles.lightTitle} hidden={(preVolunteerCount || 0) < 3}>
(Déjà {preVolunteerCount} inscrits !)
</span>
</dt>
<dd>
<div className={styles.formLine} key="line-potential-volunteer">
<div>
Je veux devenir bénévole
<input
type="radio"
name="potentialVolunteer"
id="potentialVolunteer-yes"
className={styles.inputRadio}
checked={!potentialVolunteer}
onChange={onNewVolunteer}
/>
<label htmlFor="potentialVolunteer-yes">Tout de suite !</label>
<input
type="radio"
name="potentialVolunteer"
id="potentialVolunteer-no"
className={styles.inputRadio}
checked={potentialVolunteer}
onChange={onPotentialVolunteer}
/>
<label htmlFor="potentialVolunteer-no">
Peut-être après une rencontre avec des bénévoles
</label>
</div>
</div>
<div className={styles.formLine} key="line-firstname">
<label htmlFor="firstname">Prénom</label>
<input
@ -232,7 +284,7 @@ const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element =>
<textarea
name="message"
id="message"
placeholder="Des petits mots sympas, questions, wishes, des infos sur toi, des compétences dont tu aimerais te servir... ou rien de tout ça et nous en discuterons au téléphone :)"
placeholder="Dis-nous ici comment tu as connu le festival, ce qui te motive à nous rejoindre, quelles compétences tu aimerais développer ou utiliser... ou tu n'y as pas trop réfléchi et tu trouveras en discutant avec nous :)"
value={comment}
onChange={onCommentChanged}
/>
@ -253,4 +305,4 @@ const PreRegisterForm = ({ dispatch, preVolunteerCount }: Props): JSX.Element =>
)
}
export default memo(PreRegisterForm)
export default memo(RegisterForm)

View File

@ -11,7 +11,7 @@ import Asks, { fetchFor as fetchForAsks } from "./Asks"
import ParticipationDetailsForm, {
fetchFor as fetchForParticipationDetailsForm,
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
import PreRegisterForm from "./PreRegisterForm"
import RegisterForm from "./RegisterForm"
import TeamWishesForm, {
fetchFor as fetchForTeamWishesForm,
} from "./VolunteerBoard/TeamWishesForm/TeamWishesForm"
@ -34,7 +34,7 @@ export {
fetchForAsks,
ParticipationDetailsForm,
fetchForParticipationDetailsForm,
PreRegisterForm,
RegisterForm,
TeamWishesForm,
fetchForTeamWishesForm,
VolunteerInfo,

View File

@ -1,48 +0,0 @@
import { FC, useEffect, memo } from "react"
import { RouteComponentProps } from "react-router-dom"
import { useDispatch, useSelector, shallowEqual } from "react-redux"
import { Helmet } from "react-helmet"
import { AppState, AppThunk, ValueRequest } from "../../store"
import { fetchPreVolunteerCountIfNeed } from "../../store/preVolunteerCount"
import { PreRegisterForm } from "../../components"
import styles from "./styles.module.scss"
export type Props = RouteComponentProps
function useList(
stateToProp: (state: AppState) => ValueRequest<number | undefined>,
fetchDataIfNeed: () => AppThunk
) {
const dispatch = useDispatch()
const { readyStatus, value } = useSelector(stateToProp, shallowEqual)
// Fetch client-side data here
useEffect(() => {
dispatch(fetchDataIfNeed())
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch])
return () => {
if (!readyStatus || readyStatus === "idle" || readyStatus === "request")
return <p>Loading...</p>
if (readyStatus === "failure") return <p>Oops, Failed to load!</p>
return <PreRegisterForm dispatch={dispatch} preVolunteerCount={value} />
}
}
const PreRegisterPage: FC<Props> = (): JSX.Element => (
<div className={styles.preRegisterPage}>
<div className={styles.preRegisterContent}>
<Helmet title="PreRegisterPage" />
{useList((state: AppState) => state.preVolunteerCount, fetchPreVolunteerCountIfNeed)()}
</div>
</div>
)
// Fetch server-side data here
export const loadData = (): AppThunk[] => [fetchPreVolunteerCountIfNeed()]
export default memo(PreRegisterPage)

View File

@ -0,0 +1,27 @@
import { FC, memo } from "react"
import { RouteComponentProps } from "react-router-dom"
import { useDispatch } from "react-redux"
import { Helmet } from "react-helmet"
import { AppThunk } from "../../store"
import { RegisterForm } from "../../components"
import styles from "./styles.module.scss"
export type Props = RouteComponentProps
const RegisterPage: FC<Props> = (): JSX.Element => {
const dispatch = useDispatch()
return (
<div className={styles.registerPage}>
<div className={styles.registerContent}>
<Helmet title="RegisterPage" />
<RegisterForm dispatch={dispatch} />
</div>
</div>
)
}
// Fetch server-side data here
export const loadData = (): AppThunk[] => []
export default memo(RegisterPage)

View File

@ -1,15 +1,15 @@
import loadable from "@loadable/component"
import { Loading, ErrorBoundary } from "../../components"
import { Props, loadData } from "./PreRegister"
import { Props, loadData } from "./Register"
const PreRegister = loadable(() => import("./PreRegister"), {
const Register = loadable(() => import("./Register"), {
fallback: <Loading />,
})
export default (props: Props): JSX.Element => (
<ErrorBoundary>
<PreRegister {...props} />
<Register {...props} />
</ErrorBoundary>
)

View File

@ -1,9 +1,9 @@
@import "../../theme/mixins";
.preRegisterPage {
.registerPage {
@include page-wrapper-center;
}
.preRegisterContent {
.registerContent {
@include page-content-wrapper(600px);
}

View File

@ -3,7 +3,7 @@ import { RouteConfig } from "react-router-config"
import App from "../app"
import AsyncHome, { loadData as loadHomeData } from "../pages/Home"
import AsyncAnnouncements, { loadData as loadAnnouncementsData } from "../pages/Announcements"
import AsyncPreRegisterPage, { loadData as loadPreRegisterPage } from "../pages/PreRegister"
import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register"
import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams"
import AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
import AsyncVolunteers, { loadData as loadVolunteersData } from "../pages/Volunteers"
@ -25,13 +25,13 @@ export default [
},
{
path: "/preRegister",
component: AsyncPreRegisterPage,
loadData: loadPreRegisterPage,
component: AsyncRegisterPage,
loadData: loadRegisterPage,
},
{
path: "/sinscrire",
component: AsyncPreRegisterPage,
loadData: loadPreRegisterPage,
component: AsyncRegisterPage,
loadData: loadRegisterPage,
},
{
path: "/VolunteerPage/:id",

View File

@ -7,7 +7,7 @@ import { SheetNames, saveLocalDb, loadLocalDb } from "./localDb"
export { SheetNames } from "./localDb"
// Test write attack with: wget --header='Content-Type:application/json' --post-data='{"prenom":"Pierre","nom":"SCELLES","email":"test@gmail.com","telephone":"0601010101","dejaBenevole":false,"commentaire":""}' http://localhost:3000/PreVolunteerAdd
// Test write attack with: wget --header='Content-Type:application/json' --post-data='{"prenom":"Pierre","nom":"SCELLES","email":"test@gmail.com","telephone":"0601010101","dejaBenevole":false,"commentaire":""}' http://localhost:3000/PostulantAdd
const CRED_PATH = path.resolve(process.cwd(), "access/gsheets.json")
@ -122,7 +122,7 @@ export class Sheet<
return (_.max(ids) || 0) + 1
}
async add(elementWithoutId: ElementNoId): Promise<Element> {
async add(elementWithoutId: Omit<Element, "id">): Promise<Element> {
const elements: Element[] = (await this.getList()) || []
// eslint-disable-next-line @typescript-eslint/ban-types
const element: Element = { id: await this.nextId(), ...elementWithoutId } as Element

View File

@ -3,6 +3,7 @@ import { SheetNames, ElementWithId, getSheet, Sheet } from "./accessors"
export type RequestBody = Request["body"]
export type CustomSetReturn<Element> = { toDatabase: Element; toCaller: any }
export type CustomAddReturn<Element> = { toDatabase: Omit<Element, "id">; toCaller: any }
export default class ExpressAccessors<
// eslint-disable-next-line @typescript-eslint/ban-types
@ -88,13 +89,44 @@ export default class ExpressAccessors<
}
}
add() {
add(
custom?: (
list: Element[],
body: RequestBody,
id: number,
roles: string[]
) => Promise<CustomAddReturn<Element>> | CustomAddReturn<Element>
) {
return async (request: Request, response: Response, _next: NextFunction): Promise<void> => {
try {
const sheet = await this.getSheet()
const element: Element = await sheet.add(request.body)
if (element) {
response.status(200).json(element)
if (!custom) {
await sheet.add(request.body)
response.status(200)
} else {
const memberId = response?.locals?.jwt?.id || -1
const roles: string[] = response?.locals?.jwt?.roles || []
const list = (await sheet.getList()) || []
const { toDatabase, toCaller } = await custom(
list,
request.body,
memberId,
roles
)
let toReturn = toCaller
if (toDatabase !== undefined) {
const element: Element = await sheet.add(toDatabase)
toCaller.id = element.id
if (!toCaller) {
toReturn = element
}
}
if (toReturn !== undefined) {
response.status(200).json(toReturn)
} else {
response.status(200)
}
}
} catch (e: any) {
response.status(200).json({ error: e.message })

View File

@ -3,7 +3,7 @@ import path from "path"
import _ from "lodash"
import { promises as fs } from "fs"
import { Volunteer } from "../../services/volunteers"
import { PreVolunteer } from "../../services/preVolunteers"
import { Postulant } from "../../services/postulants"
const DB_PATH = path.resolve(process.cwd(), "access/db.json")
const DB_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json")
@ -14,7 +14,7 @@ export class SheetNames {
Games = "Jeux"
PreVolunteers = "PreMembres"
Postulants = "Postulants"
Teams = "Equipes"
@ -263,8 +263,8 @@ function anonimizedDb(_s: States): States {
anonimizedNotifs(v)
})
}
if (s.PreVolunteers) {
;(s.PreVolunteers as PreVolunteer[]).forEach((v) => {
if (s.Postulants) {
;(s.Postulants as Postulant[]).forEach((v) => {
anonimizedNameEmailMobile(v)
v.comment = v.id % 3 === 0 ? "Bonjour, j'adore l'initiative!" : ""
})
@ -272,11 +272,11 @@ function anonimizedDb(_s: States): States {
return s
}
function idADev(v: Volunteer | PreVolunteer): boolean {
function idADev(v: Volunteer | Postulant): boolean {
return ((v as Volunteer)?.roles || []).includes("dev")
}
function anonimizedNameEmailMobile(v: Volunteer | PreVolunteer): void {
function anonimizedNameEmailMobile(v: Volunteer | Postulant): void {
if (idADev(v)) {
return
}

View File

@ -0,0 +1,44 @@
import _ from "lodash"
import ExpressAccessors from "./expressAccessors"
import { Postulant, PostulantWithoutId, translationPostulant } from "../../services/postulants"
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
const expressAccessor = new ExpressAccessors<PostulantWithoutId, Postulant>(
"Postulants",
new Postulant(),
translationPostulant
)
export const postulantListGet = expressAccessor.listGet()
export const postulantGet = expressAccessor.get()
export const postulantAdd = expressAccessor.add(async (list, body) => {
const params = body
const postulant = getByEmail(list, params.email)
if (postulant) {
throw Error("Il y a déjà quelqu'un avec cet email")
}
if (!validMobile(params.mobile)) {
throw Error("Numéro de téléphone invalide, contacter pierre.scelles@gmail.com")
}
const newPostulant = _.omit(new Postulant(), "id")
_.assign(newPostulant, {
lastname: trim(params.lastname),
firstname: trim(params.firstname),
email: trim(params.email),
mobile: canonicalMobile(params.mobile),
})
return {
toDatabase: newPostulant,
toCaller: {},
}
})
export const postulantSet = expressAccessor.set()
function getByEmail<T extends { email: string }>(list: T[], rawEmail: string): T | undefined {
const email = canonicalEmail(rawEmail || "")
const volunteer = list.find((v) => canonicalEmail(v.email) === email)
return volunteer
}

View File

@ -1,19 +0,0 @@
import ExpressAccessors from "./expressAccessors"
import {
PreVolunteer,
PreVolunteerWithoutId,
translationPreVolunteer,
} from "../../services/preVolunteers"
const expressAccessor = new ExpressAccessors<PreVolunteerWithoutId, PreVolunteer>(
"PreVolunteers",
new PreVolunteer(),
translationPreVolunteer
)
export const preVolunteerListGet = expressAccessor.listGet()
export const preVolunteerGet = expressAccessor.get()
export const preVolunteerAdd = expressAccessor.add()
export const preVolunteerSet = expressAccessor.set()
export const preVolunteerCountGet = expressAccessor.get((list) => list?.length || 0)

View File

@ -12,8 +12,9 @@ import {
translationVolunteer,
VolunteerDayWishes,
VolunteerParticipationDetails,
VolunteerPartialAddReturn,
} from "../../services/volunteers"
import { canonicalEmail } from "../../utils/standardization"
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
import { getJwt } from "../secure"
const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
@ -23,9 +24,41 @@ const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
)
export const volunteerListGet = expressAccessor.listGet()
export const volunteerAdd = expressAccessor.add()
// export const volunteerAdd = expressAccessor.add()
export const volunteerSet = expressAccessor.set()
export const volunteerPartialAdd = expressAccessor.add(async (list, body) => {
const params = body[0]
const volunteer = getByEmail(list, params.email)
if (volunteer) {
throw Error("Il y a déjà un bénévole avec cet email")
}
if (!validMobile(params.mobile)) {
throw Error("Numéro de téléphone invalide, contacter pierre.scelles@gmail.com")
}
const password = generatePassword()
const passwordHash = await bcrypt.hash(password, 10)
const newVolunteer = _.omit(new Volunteer(), "id")
_.assign(newVolunteer, {
lastname: trim(params.lastname),
firstname: trim(params.firstname),
email: trim(params.email),
mobile: canonicalMobile(params.mobile),
password1: passwordHash,
password2: passwordHash,
})
return {
toDatabase: newVolunteer,
toCaller: {
password,
} as VolunteerPartialAddReturn,
}
})
export const volunteerLogin = expressAccessor.get<VolunteerLogin>(async (list, bodyArray) => {
const [body] = bodyArray
const volunteer = getByEmail(list, body.email)

View File

@ -19,16 +19,17 @@ import certbotRouter from "../routes/certbot"
import { hasSecret, secure } from "./secure"
import { announcementListGet } from "./gsheets/announcements"
import { gameListGet } from "./gsheets/games"
import { preVolunteerAdd, preVolunteerCountGet } from "./gsheets/preVolunteers"
import { postulantAdd } from "./gsheets/postulants"
import { teamListGet } from "./gsheets/teams"
import {
volunteerSet,
volunteerLogin,
volunteerForgot,
volunteerAsksSet,
volunteerParticipationDetailsSet,
volunteerTeamWishesSet,
volunteerDayWishesSet,
volunteerForgot,
volunteerLogin,
volunteerAsksSet,
volunteerPartialAdd,
volunteerParticipationDetailsSet,
volunteerSet,
volunteerTeamWishesSet,
} from "./gsheets/volunteers"
import { wishListGet, wishAdd } from "./gsheets/wishes"
import config from "../config"
@ -82,8 +83,8 @@ app.get(
app.get("/GameListGet", gameListGet)
app.get("/WishListGet", wishListGet)
app.post("/WishAdd", wishAdd)
app.post("/PreVolunteerAdd", preVolunteerAdd)
app.get("/PreVolunteerCountGet", preVolunteerCountGet)
app.post("/PostulantAdd", postulantAdd)
app.post("/VolunteerPartialAdd", volunteerPartialAdd)
app.post("/VolunteerLogin", volunteerLogin)
app.post("/VolunteerForgot", volunteerForgot)

View File

@ -1,4 +1,4 @@
export class PreVolunteer {
export class Postulant {
id = 0
firstname = ""
@ -9,25 +9,25 @@ export class PreVolunteer {
mobile = ""
alreadyVolunteer = false
potential = false
comment = ""
}
export const translationPreVolunteer: { [k in keyof PreVolunteer]: string } = {
export const translationPostulant: { [k in keyof Postulant]: string } = {
id: "id",
firstname: "prenom",
lastname: "nom",
email: "email",
mobile: "telephone",
alreadyVolunteer: "dejaBenevole",
potential: "potentiel",
comment: "commentaire",
}
export const elementName = "PreVolunteer"
export const elementName = "Postulant"
export const emailRegexp =
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i
export const passwordMinLength = 4
export type PreVolunteerWithoutId = Omit<PreVolunteer, "id">
export type PostulantWithoutId = Omit<Postulant, "id">

View File

@ -0,0 +1,9 @@
import ServiceAccessors from "./accessors"
import { elementName, Postulant, PostulantWithoutId } from "./postulants"
const serviceAccessors = new ServiceAccessors<PostulantWithoutId, Postulant>(elementName)
export const postulantListGet = serviceAccessors.listGet()
export const postulantGet = serviceAccessors.get()
export const postulantAdd = serviceAccessors.add()
export const postulantSet = serviceAccessors.set()

View File

@ -1,10 +0,0 @@
import ServiceAccessors from "./accessors"
import { elementName, PreVolunteer, PreVolunteerWithoutId } from "./preVolunteers"
const serviceAccessors = new ServiceAccessors<PreVolunteerWithoutId, PreVolunteer>(elementName)
export const preVolunteerListGet = serviceAccessors.listGet()
export const preVolunteerGet = serviceAccessors.get()
export const preVolunteerAdd = serviceAccessors.add()
export const preVolunteerSet = serviceAccessors.set()
export const preVolunteerCountGet = serviceAccessors.countGet()

View File

@ -1,4 +1,5 @@
export class Volunteer {
/* eslint-disable max-classes-per-file */
export class Volunteer implements VolunteerPartial {
id = 0
lastname = ""
@ -9,7 +10,7 @@ export class Volunteer {
mobile = ""
photo = ""
photo = "anonyme.png"
adult = 1
@ -27,7 +28,7 @@ export class Volunteer {
tshirtSize = ""
food = ""
food = "Aucune"
teamWishes: number[] = []
@ -72,6 +73,22 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
acceptsNotifs: "accepteLesNotifs",
}
export class VolunteerPartial {
lastname = ""
firstname = ""
email = ""
mobile = ""
}
export class VolunteerPartialAddReturn {
id = 0
password = ""
}
export const elementName = "Volunteer"
export const volunteerExample: Volunteer = {

View File

@ -13,7 +13,7 @@ const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(ele
export const volunteerListGet = serviceAccessors.listGet()
export const volunteerGet = serviceAccessors.get()
export const volunteerAdd = serviceAccessors.add()
export const volunteerPartialAdd = serviceAccessors.customPost<[Partial<Volunteer>]>("PartialAdd")
export const volunteerSet = serviceAccessors.set()
export const volunteerLogin =

39
src/store/postulantAdd.ts Normal file
View File

@ -0,0 +1,39 @@
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
import { StateRequest, elementAddFetch } from "./utils"
import { Postulant } from "../services/postulants"
import { postulantAdd } from "../services/postulantsAccessors"
const postulantAdapter = createEntityAdapter<Postulant>()
const postulantAddSlice = createSlice({
name: "addPostulant",
initialState: postulantAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<Postulant>) => {
state.readyStatus = "success"
postulantAdapter.addOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default postulantAddSlice.reducer
export const { getRequesting, getSuccess, getFailure } = postulantAddSlice.actions
export const fetchPostulantAdd = elementAddFetch(
postulantAdd,
getRequesting,
getSuccess,
getFailure,
() => null,
() => null
)

View File

@ -1,39 +0,0 @@
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
import { StateRequest, elementAddFetch } from "./utils"
import { PreVolunteer } from "../services/preVolunteers"
import { preVolunteerAdd } from "../services/preVolunteersAccessors"
const preVolunteerAdapter = createEntityAdapter<PreVolunteer>()
const preVolunteerAddSlice = createSlice({
name: "addPreVolunteer",
initialState: preVolunteerAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest),
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<PreVolunteer>) => {
state.readyStatus = "success"
preVolunteerAdapter.addOne(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default preVolunteerAddSlice.reducer
export const { getRequesting, getSuccess, getFailure } = preVolunteerAddSlice.actions
export const fetchPreVolunteerAdd = elementAddFetch(
preVolunteerAdd,
getRequesting,
getSuccess,
getFailure,
() => null,
() => null
)

View File

@ -1,46 +0,0 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
import { StateRequest, toastError, elementValueFetch } from "./utils"
import { preVolunteerCountGet } from "../services/preVolunteersAccessors"
import { AppThunk, AppState } from "."
export const initialState: StateRequest & { value?: number } = { readyStatus: "idle" }
const preVolunteerCount = createSlice({
name: "preVolunteerCount",
initialState,
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<number>) => {
state.readyStatus = "success"
state.value = payload
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default preVolunteerCount.reducer
export const { getRequesting, getSuccess, getFailure } = preVolunteerCount.actions
export const fetchPreVolunteerCount = elementValueFetch(
preVolunteerCountGet,
getRequesting,
getSuccess,
getFailure,
(error: Error) =>
toastError(`Erreur lors du chargement des bénévoles potentiels: ${error.message}`)
)
const shouldFetchPreVolunteerCount = (state: AppState) =>
state.preVolunteerCount.readyStatus !== "success"
export const fetchPreVolunteerCountIfNeed = (): AppThunk => (dispatch, getState) => {
if (shouldFetchPreVolunteerCount(getState())) return dispatch(fetchPreVolunteerCount())
return null
}

View File

@ -4,12 +4,11 @@ import { connectRouter } from "connected-react-router"
import auth from "./auth"
import gameList from "./gameList"
import announcementList from "./announcementList"
import preVolunteerAdd from "./preVolunteerAdd"
import preVolunteerCount from "./preVolunteerCount"
import postulantAdd from "./postulantAdd"
import teamList from "./teamList"
import ui from "./ui"
import volunteer from "./volunteer"
import volunteerAdd from "./volunteerAdd"
import volunteerAdd from "./volunteerPartialAdd"
import volunteerList from "./volunteerList"
import volunteerSet from "./volunteerSet"
import volunteerLogin from "./volunteerLogin"
@ -27,8 +26,7 @@ export default (history: History) => ({
auth,
gameList,
announcementList,
preVolunteerAdd,
preVolunteerCount,
postulantAdd,
teamList,
ui,
volunteer,

View File

@ -65,7 +65,7 @@ export function elementFetch<Element, ServiceInput extends Array<any>>(
}
export function elementAddFetch<Element>(
elementAddService: (volunteerWithoutId: Omit<Element, "id">) => Promise<{
elementAddService: (elementWithoutId: Omit<Element, "id">) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,
@ -74,12 +74,12 @@ export function elementAddFetch<Element>(
getFailure: ActionCreatorWithPayload<string, string>,
errorMessage?: (error: Error) => void,
successMessage?: () => void
): (volunteerWithoutId: Omit<Element, "id">) => AppThunk {
return (volunteerWithoutId: Omit<Element, "id">): AppThunk =>
): (elementWithoutId: Omit<Element, "id">) => AppThunk {
return (elementWithoutId: Omit<Element, "id">): AppThunk =>
async (dispatch) => {
dispatch(getRequesting())
const { error, data } = await elementAddService(volunteerWithoutId)
const { error, data } = await elementAddService(elementWithoutId)
if (error) {
dispatch(getFailure(error.message))
@ -119,7 +119,7 @@ export function elementListFetch<Element, ServiceInput extends Array<any>>(
}
export function elementSet<Element>(
elementSetService: (volunteer: Element) => Promise<{
elementSetService: (element: Element) => Promise<{
data?: Element | undefined
error?: Error | undefined
}>,

View File

@ -1,12 +1,12 @@
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
import { StateRequest, toastError, toastSuccess, elementAddFetch } from "./utils"
import { StateRequest, elementAddFetch } from "./utils"
import { Volunteer } from "../services/volunteers"
import { volunteerAdd } from "../services/volunteersAccessors"
import { volunteerPartialAdd } from "../services/volunteersAccessors"
const volunteerAdapter = createEntityAdapter<Volunteer>()
const volunteerAddSlice = createSlice({
const volunteerPartialAddSlice = createSlice({
name: "addVolunteer",
initialState: volunteerAdapter.getInitialState({
readyStatus: "idle",
@ -26,14 +26,14 @@ const volunteerAddSlice = createSlice({
},
})
export default volunteerAddSlice.reducer
export const { getRequesting, getSuccess, getFailure } = volunteerAddSlice.actions
export default volunteerPartialAddSlice.reducer
export const { getRequesting, getSuccess, getFailure } = volunteerPartialAddSlice.actions
export const fetchVolunteerAdd = elementAddFetch(
volunteerAdd,
export const fetchVolunteerPartialAdd = elementAddFetch(
volunteerPartialAdd,
getRequesting,
getSuccess,
getFailure,
(error: Error) => toastError(`Erreur lors de l'ajout d'un bénévole: ${error.message}`),
() => toastSuccess("Volunteer ajoutée !")
() => null,
() => null
)