mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-08 08:34:20 +02:00
Add register form, missing proper feedback on error or success
This commit is contained in:
parent
cef7c5f7b0
commit
7fc3ec08ba
@ -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'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'entre nous sommes bénévoles les <b>samedi et dimanche</b>
|
||||
, mais certains bénévoles ne sont pas disponibles les deux jours. On leur
|
||||
demande alors d'aider à la mise en place jeudi ou vendredi, ou au
|
||||
rangement le lundi. 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'expérience pourrait vous tenter, remplissez le formulaire suivant pour
|
||||
en discuter lors d'un des gros apéros mensuels !<br />
|
||||
Cette inscription ne vous oblige en rien il s'agit juste d'une prise
|
||||
de contact.
|
||||
Si l'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'un saut dans l'inconnu soit difficile.
|
||||
<br />
|
||||
Les prochains sont les 21 décembre et 27 janvier, mais nous vous appelerons
|
||||
d'ici là pour les détails :)
|
||||
Dans les deux cas, venez nous rencontrer mardi 22 mars près de Châtelet, détails
|
||||
après l'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)
|
@ -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,
|
||||
|
@ -1,48 +0,0 @@
|
||||
import { FC, useEffect, memo } from "react"
|
||||
import { RouteComponentProps } from "react-router-dom"
|
||||
import { useDispatch, useSelector, shallowEqual } from "react-redux"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import { AppState, AppThunk, ValueRequest } from "../../store"
|
||||
import { fetchPreVolunteerCountIfNeed } from "../../store/preVolunteerCount"
|
||||
import { PreRegisterForm } from "../../components"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
export type Props = RouteComponentProps
|
||||
|
||||
function useList(
|
||||
stateToProp: (state: AppState) => ValueRequest<number | undefined>,
|
||||
fetchDataIfNeed: () => AppThunk
|
||||
) {
|
||||
const dispatch = useDispatch()
|
||||
const { readyStatus, value } = useSelector(stateToProp, shallowEqual)
|
||||
|
||||
// Fetch client-side data here
|
||||
useEffect(() => {
|
||||
dispatch(fetchDataIfNeed())
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dispatch])
|
||||
|
||||
return () => {
|
||||
if (!readyStatus || readyStatus === "idle" || readyStatus === "request")
|
||||
return <p>Loading...</p>
|
||||
|
||||
if (readyStatus === "failure") return <p>Oops, Failed to load!</p>
|
||||
|
||||
return <PreRegisterForm dispatch={dispatch} preVolunteerCount={value} />
|
||||
}
|
||||
}
|
||||
|
||||
const PreRegisterPage: FC<Props> = (): JSX.Element => (
|
||||
<div className={styles.preRegisterPage}>
|
||||
<div className={styles.preRegisterContent}>
|
||||
<Helmet title="PreRegisterPage" />
|
||||
{useList((state: AppState) => state.preVolunteerCount, fetchPreVolunteerCountIfNeed)()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Fetch server-side data here
|
||||
export const loadData = (): AppThunk[] => [fetchPreVolunteerCountIfNeed()]
|
||||
|
||||
export default memo(PreRegisterPage)
|
27
src/pages/Register/Register.tsx
Normal file
27
src/pages/Register/Register.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { FC, memo } from "react"
|
||||
import { RouteComponentProps } from "react-router-dom"
|
||||
import { useDispatch } from "react-redux"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import { AppThunk } from "../../store"
|
||||
import { RegisterForm } from "../../components"
|
||||
import styles from "./styles.module.scss"
|
||||
|
||||
export type Props = RouteComponentProps
|
||||
|
||||
const RegisterPage: FC<Props> = (): JSX.Element => {
|
||||
const dispatch = useDispatch()
|
||||
return (
|
||||
<div className={styles.registerPage}>
|
||||
<div className={styles.registerContent}>
|
||||
<Helmet title="RegisterPage" />
|
||||
<RegisterForm dispatch={dispatch} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch server-side data here
|
||||
export const loadData = (): AppThunk[] => []
|
||||
|
||||
export default memo(RegisterPage)
|
@ -1,15 +1,15 @@
|
||||
import loadable from "@loadable/component"
|
||||
|
||||
import { 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>
|
||||
)
|
||||
|
@ -1,9 +1,9 @@
|
||||
@import "../../theme/mixins";
|
||||
|
||||
.preRegisterPage {
|
||||
.registerPage {
|
||||
@include page-wrapper-center;
|
||||
}
|
||||
|
||||
.preRegisterContent {
|
||||
.registerContent {
|
||||
@include page-content-wrapper(600px);
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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 })
|
||||
|
@ -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
|
||||
}
|
||||
|
44
src/server/gsheets/postulants.ts
Normal file
44
src/server/gsheets/postulants.ts
Normal 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
|
||||
}
|
@ -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)
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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">
|
9
src/services/postulantsAccessors.ts
Normal file
9
src/services/postulantsAccessors.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import ServiceAccessors from "./accessors"
|
||||
import { elementName, Postulant, PostulantWithoutId } from "./postulants"
|
||||
|
||||
const serviceAccessors = new ServiceAccessors<PostulantWithoutId, Postulant>(elementName)
|
||||
|
||||
export const postulantListGet = serviceAccessors.listGet()
|
||||
export const postulantGet = serviceAccessors.get()
|
||||
export const postulantAdd = serviceAccessors.add()
|
||||
export const postulantSet = serviceAccessors.set()
|
@ -1,10 +0,0 @@
|
||||
import ServiceAccessors from "./accessors"
|
||||
import { elementName, PreVolunteer, PreVolunteerWithoutId } from "./preVolunteers"
|
||||
|
||||
const serviceAccessors = new ServiceAccessors<PreVolunteerWithoutId, PreVolunteer>(elementName)
|
||||
|
||||
export const preVolunteerListGet = serviceAccessors.listGet()
|
||||
export const preVolunteerGet = serviceAccessors.get()
|
||||
export const preVolunteerAdd = serviceAccessors.add()
|
||||
export const preVolunteerSet = serviceAccessors.set()
|
||||
export const preVolunteerCountGet = serviceAccessors.countGet()
|
@ -1,4 +1,5 @@
|
||||
export class Volunteer {
|
||||
/* eslint-disable max-classes-per-file */
|
||||
export class Volunteer implements VolunteerPartial {
|
||||
id = 0
|
||||
|
||||
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 = {
|
||||
|
@ -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
39
src/store/postulantAdd.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
|
||||
|
||||
import { StateRequest, elementAddFetch } from "./utils"
|
||||
import { Postulant } from "../services/postulants"
|
||||
import { postulantAdd } from "../services/postulantsAccessors"
|
||||
|
||||
const postulantAdapter = createEntityAdapter<Postulant>()
|
||||
|
||||
const postulantAddSlice = createSlice({
|
||||
name: "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
|
||||
)
|
@ -1,39 +0,0 @@
|
||||
import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit"
|
||||
|
||||
import { StateRequest, elementAddFetch } from "./utils"
|
||||
import { PreVolunteer } from "../services/preVolunteers"
|
||||
import { preVolunteerAdd } from "../services/preVolunteersAccessors"
|
||||
|
||||
const preVolunteerAdapter = createEntityAdapter<PreVolunteer>()
|
||||
|
||||
const preVolunteerAddSlice = createSlice({
|
||||
name: "addPreVolunteer",
|
||||
initialState: preVolunteerAdapter.getInitialState({
|
||||
readyStatus: "idle",
|
||||
} as StateRequest),
|
||||
reducers: {
|
||||
getRequesting: (state) => {
|
||||
state.readyStatus = "request"
|
||||
},
|
||||
getSuccess: (state, { payload }: PayloadAction<PreVolunteer>) => {
|
||||
state.readyStatus = "success"
|
||||
preVolunteerAdapter.addOne(state, payload)
|
||||
},
|
||||
getFailure: (state, { payload }: PayloadAction<string>) => {
|
||||
state.readyStatus = "failure"
|
||||
state.error = payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default preVolunteerAddSlice.reducer
|
||||
export const { getRequesting, getSuccess, getFailure } = preVolunteerAddSlice.actions
|
||||
|
||||
export const fetchPreVolunteerAdd = elementAddFetch(
|
||||
preVolunteerAdd,
|
||||
getRequesting,
|
||||
getSuccess,
|
||||
getFailure,
|
||||
() => null,
|
||||
() => null
|
||||
)
|
@ -1,46 +0,0 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||
|
||||
import { StateRequest, toastError, elementValueFetch } from "./utils"
|
||||
import { preVolunteerCountGet } from "../services/preVolunteersAccessors"
|
||||
import { AppThunk, AppState } from "."
|
||||
|
||||
export const initialState: StateRequest & { value?: number } = { readyStatus: "idle" }
|
||||
|
||||
const preVolunteerCount = createSlice({
|
||||
name: "preVolunteerCount",
|
||||
initialState,
|
||||
reducers: {
|
||||
getRequesting: (state) => {
|
||||
state.readyStatus = "request"
|
||||
},
|
||||
getSuccess: (state, { payload }: PayloadAction<number>) => {
|
||||
state.readyStatus = "success"
|
||||
state.value = payload
|
||||
},
|
||||
getFailure: (state, { payload }: PayloadAction<string>) => {
|
||||
state.readyStatus = "failure"
|
||||
state.error = payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default preVolunteerCount.reducer
|
||||
export const { getRequesting, getSuccess, getFailure } = preVolunteerCount.actions
|
||||
|
||||
export const fetchPreVolunteerCount = elementValueFetch(
|
||||
preVolunteerCountGet,
|
||||
getRequesting,
|
||||
getSuccess,
|
||||
getFailure,
|
||||
(error: Error) =>
|
||||
toastError(`Erreur lors du chargement des bénévoles potentiels: ${error.message}`)
|
||||
)
|
||||
|
||||
const shouldFetchPreVolunteerCount = (state: AppState) =>
|
||||
state.preVolunteerCount.readyStatus !== "success"
|
||||
|
||||
export const fetchPreVolunteerCountIfNeed = (): AppThunk => (dispatch, getState) => {
|
||||
if (shouldFetchPreVolunteerCount(getState())) return dispatch(fetchPreVolunteerCount())
|
||||
|
||||
return null
|
||||
}
|
@ -4,12 +4,11 @@ import { connectRouter } from "connected-react-router"
|
||||
import auth from "./auth"
|
||||
import 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,
|
||||
|
@ -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
|
||||
}>,
|
||||
|
@ -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
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user