Add charter, Fix absence of meeting dates

This commit is contained in:
pikiou 2023-01-16 17:58:02 +01:00
parent 0f06aaea05
commit a906dfff07
21 changed files with 469 additions and 175 deletions

View File

@ -19,10 +19,12 @@ export function AskDayWishes(asks: JSX.Element[], id: number): void {
}, [dispatch, id, jwtToken, volunteerAsks?.hiddenAsks]) }, [dispatch, id, jwtToken, volunteerAsks?.hiddenAsks])
const [userWishes] = useUserDayWishes() const [userWishes] = useUserDayWishes()
const charter = get(userWishes, "charter", false) as boolean
const participation = get(userWishes, "active", "inconnu") as string const participation = get(userWishes, "active", "inconnu") as string
const newSelection = get(userWishes, "dayWishes", []) as string[] const newSelection = get(userWishes, "dayWishes", []) as string[]
const comment = get(userWishes, "dayWishesComment", "") as string const comment = get(userWishes, "dayWishesComment", "") as string
const needToShow = participation === "inconnu" || (newSelection.length === 0 && !comment) const needToShow =
charter === false || participation === "inconnu" || (newSelection.length === 0 && !comment)
addAsk( addAsk(
asks, asks,

View File

@ -3,8 +3,8 @@ import React, { memo } from "react"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import { useAskTools } from "./utils" import { useAskTools } from "./utils"
import { AskWelcome } from "./AskWelcome" import { AskWelcome } from "./AskWelcome"
import { AskBrunch, fetchFor as fetchForBrunch } from "./AskBrunch" // import { AskBrunch, fetchFor as fetchForBrunch } from "./AskBrunch"
import { AskRetex, fetchFor as fetchForRetex } from "./AskRetex" // import { AskRetex, fetchFor as fetchForRetex } from "./AskRetex"
import { AskDiscord, fetchFor as fetchForDiscord } from "./AskDiscord" import { AskDiscord, fetchFor as fetchForDiscord } from "./AskDiscord"
import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes" import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
// import { AskHosting, fetchFor as fetchForHosting } from "./AskHosting" // import { AskHosting, fetchFor as fetchForHosting } from "./AskHosting"
@ -22,8 +22,8 @@ const Asks = (): JSX.Element | null => {
const asks: JSX.Element[] = [] const asks: JSX.Element[] = []
AskWelcome(asks, 1) AskWelcome(asks, 1)
AskBrunch(asks, 2) // AskBrunch(asks, 2)
AskRetex(asks, 3) // AskRetex(asks, 3)
AskDiscord(asks, 5) AskDiscord(asks, 5)
AskDayWishes(asks, 10) AskDayWishes(asks, 10)
@ -66,8 +66,8 @@ export default memo(Asks)
// Fetch server-side data here // Fetch server-side data here
export const fetchFor = [ export const fetchFor = [
...fetchForBrunch, // ...fetchForBrunch,
...fetchForRetex, // ...fetchForRetex,
...fetchForDiscord, ...fetchForDiscord,
...fetchForDayWishes, ...fetchForDayWishes,
// ...fetchForHosting, // ...fetchForHosting,

View File

@ -52,8 +52,8 @@ const MainMenu: FC = (): JSX.Element => {
<MenuItem name="Questions" pathname="/" /> <MenuItem name="Questions" pathname="/" />
<MenuItem name="Annonces" pathname="/annonces" /> <MenuItem name="Annonces" pathname="/annonces" />
<MenuItem name="Mon profil" pathname="/profil" /> <MenuItem name="Mon profil" pathname="/profil" />
<MenuItem name="Emprunter" pathname="/emprunter" /> {/* <MenuItem name="Emprunter" pathname="/emprunter" />
<MenuItem name="Emprunts" pathname="/emprunts" /> <MenuItem name="Emprunts" pathname="/emprunts" /> */}
{/* <MenuItem name="Mes connaissances" pathname="/connaissances" /> */} {/* <MenuItem name="Mes connaissances" pathname="/connaissances" /> */}
<RestrictMenuItem <RestrictMenuItem
role={ROLES.ASSIGNER} role={ROLES.ASSIGNER}

View File

@ -17,6 +17,10 @@ import {
sendRadioboxDispatch, sendRadioboxDispatch,
sendTextDispatch, sendTextDispatch,
} from "../input.utils" } from "../input.utils"
import {
fetchMiscFestivalDateListIfNeed,
selectMiscFestivalDateList,
} from "../../store/miscFestivalDateList"
import { import {
fetchMiscMeetingDateListIfNeed, fetchMiscMeetingDateListIfNeed,
selectMiscMeetingDateList, selectMiscMeetingDateList,
@ -49,9 +53,11 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const [sending, setSending] = useState(false) const [sending, setSending] = useState(false)
const [changingBackground, setChangingBackground] = useState(0) const [changingBackground, setChangingBackground] = useState(0)
const festivalDateList = useSelector(selectMiscFestivalDateList)
const meetingDateList = useSelector(selectMiscMeetingDateList) const meetingDateList = useSelector(selectMiscMeetingDateList)
const enableRegistering = true const enableRegistering = true
const hasMeetingDates = meetingDateList.length > 0
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
@ -149,13 +155,15 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
) )
} }
const festivalFullDate = _.find(festivalDateList, { id: 1 })?.date
const intro = ( const intro = (
<dl className={styles.registerIntro}> <dl className={styles.registerIntro}>
<dt>Qu&apos;est-ce que Paris est Ludique ?</dt> <dt>Qu&apos;est-ce que Paris est Ludique ?</dt>
<dd> <dd>
<p> <p>
Un festival en plein air dédié aux <b>jeux de société modernes</b> sous toutes Un festival en plein air dédié aux <b>jeux de société modernes</b> sous toutes
leurs formes. Les samedi 24 et dimanche 25 juin 2023 ! leurs formes.{festivalFullDate && ` Les ${festivalFullDate} !`}
</p> </p>
<p> <p>
En 2022, ce sont <b>18 000</b> visiteurs qui sont venus sous 300 chapiteaux et 2 En 2022, ce sont <b>18 000</b> visiteurs qui sont venus sous 300 chapiteaux et 2
@ -362,43 +370,91 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
</dd> </dd>
</dl> </dl>
<div className={styles.inputWrapper}> {hasMeetingDates ? (
<div className={styles.leftCol}> <div className={styles.inputWrapper}>
<div className={styles.multipleChoiceTitle}> <div className={styles.leftCol}>
À quelle date pourrais-tu venir ? <div className={styles.multipleChoiceTitle}>
À quelle date pourrais-tu venir ?
</div>
</div>
<div className={styles.rightCol}>
<div className={styles.rightColContainer}>
{_.concat(
meetingDateList.map((meetingDetails) => ({
value: meetingDetails.meetingId,
desc: meetingDetails.meetingTitle,
})),
{ value: "", desc: "Aucune date possible" }
).map((option) => (
<label className={styles.longAnswerLabel} key={option.value}>
<input
type="radio"
name="firstMeeting"
value={option.value}
onChange={sendRadioboxDispatch(setFirstMeeting)}
checked={firstMeeting === option.value}
/>{" "}
{option.desc}
</label>
))}
</div>
</div> </div>
</div> </div>
<div className={styles.rightCol}> ) : null}
<div className={styles.rightColContainer}>
{_.concat(
meetingDateList.map((meetingDetails) => ({
value: meetingDetails.meetingId,
desc: meetingDetails.meetingTitle,
})),
{ value: "", desc: "Aucune date possible" }
).map((option) => (
<label className={styles.longAnswerLabel} key={option.value}>
<input
type="radio"
name="firstMeeting"
value={option.value}
onChange={sendRadioboxDispatch(setFirstMeeting)}
checked={firstMeeting === option.value}
/>{" "}
{option.desc}
</label>
))}
</div>
</div>
</div>
{firstMeeting !== "" && ( {!hasMeetingDates && (
<div className={styles.inputWrapper}>
<div className={styles.leftCol}>
<div className={styles.multipleChoiceTitle}>
Es-tu dispo un lundi ou mardi soir sur Paris pour nous rencontrer ?
</div>
</div>
<div className={styles.rightCol}>
<div className={styles.rightColContainer}>
{[
{ value: "", desc: "Rencontre sur Paris" },
{ value: "visio", desc: "Plutôt en visio" },
].map((option) => (
<label className={styles.longAnswerLabel} key={option.value}>
<input
type="radio"
name="firstMeeting"
value={option.value}
onChange={sendRadioboxDispatch(setFirstMeeting)}
checked={firstMeeting === option.value}
/>{" "}
{option.desc}
</label>
))}
</div>
</div>
</div>
)}
{(!hasMeetingDates || firstMeeting !== "") && (
<dl className={styles.registerIntro}> <dl className={styles.registerIntro}>
<dd> <dd>
<p> <p>
Top ! On fait en sorte qu'il y ait assez de bénévoles expérimentés pour {!hasMeetingDates && firstMeeting === "" && (
les nombreux curieux comme toi, donc pour ne pas gâcher leur temps on <>
compte sur ta présence :) Top ! On te propose très vite des dates, ou à défaut, une visio
:)
</>
)}
{firstMeeting === "visio" && (
<>
Top ! On te recontacte très vite avec des dates pour une visio
avec 2 bénévoles et 2-3 autres personnes intéréssées comme toi
:)
</>
)}
{hasMeetingDates && firstMeeting !== "" && (
<>
Top ! On fait en sorte qu'il y ait assez de bénévoles
expérimentés pour les nombreux curieux comme toi, donc pour ne
pas gâcher leur temps on compte sur ta présence :)
</>
)}
</p> </p>
<p>Si tu as un contre-temps, écris-nous à benevoles@parisestludique.fr</p> <p>Si tu as un contre-temps, écris-nous à benevoles@parisestludique.fr</p>
<p>À très bientôt !</p> <p>À très bientôt !</p>
@ -406,20 +462,33 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
</dl> </dl>
)} )}
{firstMeeting === "" && ( <div
<div className={styles.inputWrapper}> className={classnames(
<div className={styles.commentWrapper}> styles.inputWrapper,
<textarea !(firstMeeting === "" || firstMeeting === "visio") && styles.hidden
name="commentFirstMeeting" )}
id="commentFirstMeeting" >
className={styles.inputWrapper} <div className={styles.commentWrapper}>
placeholder="Mince. Quelles dates t'arrangeraient ? Ou si c'est plus simple, quels jours sont à éviter ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?" <textarea
value={commentFirstMeeting} name="commentFirstMeeting"
onChange={sendTextareaDispatch(setCommentFirstMeeting)} id="commentFirstMeeting"
/> className={styles.inputWrapper}
</div> placeholder={
(hasMeetingDates && firstMeeting === ""
? "Mince. Quelles dates t'arrangeraient ? Ou si c'est plus simple, quels jours sont à éviter ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?"
: "") +
(!hasMeetingDates && firstMeeting === ""
? "As-tu des contraintes horaires les lundis ? Les mardis ?"
: "") +
(firstMeeting === "visio"
? "As-tu des contraites en terme de jours de la semaine ? D'horaire ?"
: "")
}
value={commentFirstMeeting}
onChange={sendTextareaDispatch(setCommentFirstMeeting)}
/>
</div> </div>
)} </div>
</> </>
) )
@ -572,4 +641,4 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
export default memo(RegisterForm) export default memo(RegisterForm)
// Fetch server-side data here // Fetch server-side data here
export const fetchFor = [fetchMiscMeetingDateListIfNeed] export const fetchFor = [fetchMiscFestivalDateListIfNeed, fetchMiscMeetingDateListIfNeed]

View File

@ -221,3 +221,7 @@
height: 112px; height: 112px;
} }
} }
.hidden {
display: none;
}

View File

@ -20,28 +20,29 @@ import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm
import { fetchFor as fetchForPersonalInfoForm } from "./PersonalInfoForm/PersonalInfoForm" import { fetchFor as fetchForPersonalInfoForm } from "./PersonalInfoForm/PersonalInfoForm"
import PersonalInfo from "./PersonalInfo/PersonalInfo" import PersonalInfo from "./PersonalInfo/PersonalInfo"
import PersonalInfoFormModal from "./PersonalInfoForm/PersonalInfoFormModal" import PersonalInfoFormModal from "./PersonalInfoForm/PersonalInfoFormModal"
import Brunch from "./Brunch/Brunch" // import Brunch from "./Brunch/Brunch"
import BrunchFormModal from "./BrunchForm/BrunchFormModal" // import BrunchFormModal from "./BrunchForm/BrunchFormModal"
import { fetchFor as fetchForBrunchForm } from "./BrunchForm/BrunchForm" // import { fetchFor as fetchForBrunchForm } from "./BrunchForm/BrunchForm"
import Retex from "./Retex/Retex" // import Retex from "./Retex/Retex"
import RetexFormModal from "./RetexForm/RetexFormModal" // import RetexFormModal from "./RetexForm/RetexFormModal"
import { fetchFor as fetchForRetexForm } from "./RetexForm/RetexForm" // import { fetchFor as fetchForRetexForm } from "./RetexForm/RetexForm"
import { useRetex } from "./retex.utils" // import { useRetex } from "./retex.utils"
const Board: FC = (): JSX.Element => { const Board: FC = (): JSX.Element => (
const [retex] = useRetex() // {
return ( // const [retex] = useRetex()
<> // return (
<ContentTitle title="Profil spécifique au festival" /> <>
<PersonalInfo /> <ContentTitle title="Profil spécifique au festival" />
<PersonalInfoFormModal /> <PersonalInfo />
{retex && <Brunch />} <PersonalInfoFormModal />
{/* {retex && <Brunch />}
{retex && <BrunchFormModal />} {retex && <BrunchFormModal />}
{retex && <Retex />} {retex && <Retex />}
{retex && <RetexFormModal />} {retex && <RetexFormModal />} */}
<DayWishes /> <DayWishes />
<DayWishesFormModal /> <DayWishesFormModal />
{/* <ParticipationDetails /> {/* <ParticipationDetails />
<ParticipationDetailsFormModal /> <ParticipationDetailsFormModal />
<TeamWishes /> <TeamWishes />
<TeamWishesFormModal /> <TeamWishesFormModal />
@ -50,16 +51,17 @@ const Board: FC = (): JSX.Element => {
<HostingFormModal /> <HostingFormModal />
<Meals /> <Meals />
<MealsFormModal /> */} <MealsFormModal /> */}
</> </>
) )
} // )
// }
export default memo(withUserConnected(Board)) export default memo(withUserConnected(Board))
export const fetchFor = [ export const fetchFor = [
...fetchForPersonalInfoForm, ...fetchForPersonalInfoForm,
...fetchForRetexForm, // ...fetchForRetexForm,
...fetchForBrunchForm, // ...fetchForBrunchForm,
...fetchForDayWishesForm, ...fetchForDayWishesForm,
// ...fetchForHostingForm, // ...fetchForHostingForm,
// ...fetchForMealsForm, // ...fetchForMealsForm,

View File

@ -7,6 +7,7 @@ import { displayModal, MODAL_IDS } from "../../../store/ui"
const DayWishes: FC = (): JSX.Element | null => { const DayWishes: FC = (): JSX.Element | null => {
const [userWishes] = useUserDayWishes() const [userWishes] = useUserDayWishes()
const charter = get(userWishes, "charter", false)
const participation = get(userWishes, "active", "inconnu") const participation = get(userWishes, "active", "inconnu")
const dayWishesString = get(userWishes, "dayWishes", []).map(getDayLabel).join(", ") const dayWishesString = get(userWishes, "dayWishes", []).map(getDayLabel).join(", ")
const comment = get(userWishes, "dayWishesComment", "") const comment = get(userWishes, "dayWishesComment", "")
@ -15,7 +16,13 @@ const DayWishes: FC = (): JSX.Element | null => {
return ( return (
<div className={styles.dayWishes}> <div className={styles.dayWishes}>
<div className={styles.title}>Mes présences</div> <div className={styles.title}>Mon engagement</div>
{!charter && (
<div className={styles.lineEmpty}>
Je n'ai pas encore accepté la charte du bénévole :(
</div>
)}
{participation === "non" && ( {participation === "non" && (
<div className={styles.participationLabel}> <div className={styles.participationLabel}>
Je <b>ne participerai pas</b> à PeL 2023 :( Je <b>ne participerai pas</b> à PeL 2023 :(

View File

@ -1,4 +1,5 @@
import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from "react" import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from "react"
import _ from "lodash"
import { useSelector } from "react-redux" import { useSelector } from "react-redux"
import classnames from "classnames" import classnames from "classnames"
import get from "lodash/get" import get from "lodash/get"
@ -17,6 +18,10 @@ import {
fetchMiscDiscordInvitationIfNeed, fetchMiscDiscordInvitationIfNeed,
selectMiscDiscordInvitation, selectMiscDiscordInvitation,
} from "../../../store/miscDiscordInvitation" } from "../../../store/miscDiscordInvitation"
import {
fetchMiscFestivalDateListIfNeed,
selectMiscFestivalDateList,
} from "../../../store/miscFestivalDateList"
type Props = { type Props = {
children?: ReactNode | undefined children?: ReactNode | undefined
@ -24,17 +29,24 @@ type Props = {
} }
const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => { const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
const [charterState, setCharter] = useState(false)
const [participationState, setParticipation] = useState("inconnu") const [participationState, setParticipation] = useState("inconnu")
const [selection, setSelection] = useState(daysChoiceSelectionDefaultState) const [selection, setSelection] = useState(daysChoiceSelectionDefaultState)
const commentRef = useRef<HTMLTextAreaElement | null>(null) const commentRef = useRef<HTMLTextAreaElement | null>(null)
const [userWishes, saveWishes] = useUserDayWishes() const [userWishes, saveWishes] = useUserDayWishes()
const discordInvitation = useSelector(selectMiscDiscordInvitation) const discordInvitation = useSelector(selectMiscDiscordInvitation)
const festivalDateList = useSelector(selectMiscFestivalDateList)
const onCharteChange = (e: React.ChangeEvent<HTMLInputElement>) =>
setCharter(e.target.value === "oui")
const onParticipationChange = (e: React.ChangeEvent<HTMLInputElement>) => const onParticipationChange = (e: React.ChangeEvent<HTMLInputElement>) =>
setParticipation(e.target.value) setParticipation(e.target.value)
const festivalShortDate = _.find(festivalDateList, { id: 2 })?.date
useEffect(() => { useEffect(() => {
if (!userWishes) return if (!userWishes) return
const charter = get(userWishes, "charter", false)
const participation = get(userWishes, "active", "inconnu") const participation = get(userWishes, "active", "inconnu")
const dayWishes = get(userWishes, "dayWishes", []) as string[] const dayWishes = get(userWishes, "dayWishes", []) as string[]
const newSelection = dayWishes.reduce( const newSelection = dayWishes.reduce(
@ -44,6 +56,7 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
}), }),
{} as SelectionChoices {} as SelectionChoices
) )
setCharter(charter)
setParticipation(participation) setParticipation(participation)
setSelection(newSelection) setSelection(newSelection)
set(commentRef, "current.value", get(userWishes, "dayWishesComment", "")) set(commentRef, "current.value", get(userWishes, "dayWishesComment", ""))
@ -61,94 +74,142 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
const onChoiceSubmit = useCallback(() => { const onChoiceSubmit = useCallback(() => {
const comment = get(commentRef, "current.value", "") const comment = get(commentRef, "current.value", "")
const days = daysChoice.map(({ id }) => id).filter((id) => selection[id]) const days = daysChoice.map(({ id }) => id).filter((id) => selection[id])
saveWishes(participationState, days, comment) saveWishes(charterState, participationState, days, comment)
if (afterSubmit) afterSubmit() if (afterSubmit) afterSubmit()
}, [participationState, selection, commentRef, saveWishes, afterSubmit]) }, [saveWishes, charterState, participationState, afterSubmit, selection])
return ( return (
<div> <div>
<div className={styles.title}>Mes jours de présence</div> <div className={styles.title}>Mon engagement</div>
<div className={classnames(styles.inputWrapper, styles.noBottomMargin)}>
<div className={styles.oneCol}>
En tant que bénévole, il nexiste <b>aucun lien de subordination</b> entre
lassociation et toi.
<br />
Il n'y a <b>pas de rémunération</b>, mais il est attendu que tu sois :<br />
<b>Accueillant et sincère</b> avec les autres bénévoles comme avec les visiteurs
et les exposants;
<br />
<b>Humble et à lécoute</b> pour permettre le travail en équipe et la
collaboration;
<br />
<b>Neutre et tolérant</b> politiquement et religieusement pour éviter la
discorde.
<br />
</div>
</div>
<div className={classnames(styles.inputWrapper, styles.noBottomMargin)}> <div className={classnames(styles.inputWrapper, styles.noBottomMargin)}>
<div className={styles.leftCol}> <div className={styles.leftCol}>
<div className={styles.participationTitle}> <div className={styles.charteTitle}>
Si les conditions sanitaires te le permettent, souhaites-tu être bénévole à Acceptes-tu de respecter cette charte ?
PeL les 24 & 25 juin 2023 ?
</div> </div>
</div> </div>
<div className={styles.rightCol}> <div className={styles.rightCol}>
<label className={styles.participationLabel}> <label className={styles.charteLabel}>
<input <input
type="radio" type="radio"
value="oui" value="oui"
name="participation" name="charte"
onChange={onParticipationChange} onChange={onCharteChange}
checked={participationState === "oui"} checked={charterState}
/>{" "} />{" "}
Oui Oui
</label> </label>
<label className={styles.participationLabel}> <label className={styles.charteLabel}>
<input <input
type="radio" type="radio"
value="non" value="non"
name="participation" name="charte"
onChange={onParticipationChange} onChange={onCharteChange}
checked={participationState === "non"} checked={!charterState}
/>{" "} />{" "}
Non Non
</label> </label>
<label className={styles.participationLabel}>
<input
type="radio"
value="peut-etre"
name="participation"
onChange={onParticipationChange}
checked={participationState === "peut-etre"}
/>{" "}
Je ne sais pas encore
</label>
</div> </div>
</div> </div>
{participationState === "peut-etre" ? ( {charterState ? (
<div> <>
On te le reproposera dans quelques temps. <div className={classnames(styles.inputWrapper, styles.noBottomMargin)}>
<br /> <div className={styles.leftCol}>
Si tu as besoin d&apos;infos, viens nous en parler sur le serveur Discord ! Pour <div className={styles.participationTitle}>
le rejoindre,{" "} Souhaites-tu être bénévole à PeL{" "}
<a href={discordInvitation} target="_blank" rel="noreferrer"> {festivalShortDate && `les ${festivalShortDate}`} ?
clique ici{" "} </div>
</a> </div>
. <div className={styles.rightCol}>
</div> <label className={styles.participationLabel}>
) : null} <input
<div className={styles.inputWrapper}> type="radio"
<div className={styles.leftCol}> value="oui"
<div className={styles.dayWishesTitle}> name="participation"
Quels jours viendras-tu ?<br /> onChange={onParticipationChange}
Il est tôt pour le dire, tu peux y repondre plus tard sur la page profil. checked={participationState === "oui"}
<br /> />{" "}
(Minimum 2 jours dont l'un sera samedi ou dimanche, idéalement samedi{" "} Oui
<b>et</b> dimanche ^^) </label>
<label className={styles.participationLabel}>
<input
type="radio"
value="non"
name="participation"
onChange={onParticipationChange}
checked={participationState === "non"}
/>{" "}
Non
</label>
<label className={styles.participationLabel}>
<input
type="radio"
value="peut-etre"
name="participation"
onChange={onParticipationChange}
checked={participationState === "peut-etre"}
/>{" "}
Je ne sais pas encore
</label>
</div>
</div> </div>
</div> {participationState === "peut-etre" ? (
<div className={styles.rightCol}> <div>
<ul className={styles.dayWishesList}> On te le reproposera dans quelques temps.
{daysChoice.map(({ id, label }) => ( <br />
<li key={id} className={styles.dayWishesItem}> Si tu as besoin d&apos;infos, viens nous en parler sur le serveur
<button Discord ! Pour le rejoindre,{" "}
type="button" <a href={discordInvitation} target="_blank" rel="noreferrer">
onClick={() => onChoiceClick(id)} clique ici{" "}
className={classnames( </a>
styles.dayWishesButton, .
selection[id] && styles.active </div>
)} ) : null}
> <div className={styles.inputWrapper}>
{label} <div className={styles.leftCol}>
</button> <div className={styles.dayWishesTitle}>
</li> Quels jours viendras-tu ?<br />
))} (Minimum 2 jours dont l'un sera samedi ou dimanche, idéalement
</ul> samedi <b>et</b> dimanche ^^)
</div> </div>
</div> </div>
<div className={styles.rightCol}>
<ul className={styles.dayWishesList}>
{daysChoice.map(({ id, label }) => (
<li key={id} className={styles.dayWishesItem}>
<button
type="button"
onClick={() => onChoiceClick(id)}
className={classnames(
styles.dayWishesButton,
selection[id] && styles.active
)}
>
{label}
</button>
</li>
))}
</ul>
</div>
</div>
</>
) : null}
<div className={styles.dayWishCommentWrapper}> <div className={styles.dayWishCommentWrapper}>
<label htmlFor="day-choice-comment">Un commentaire, une précision ?</label> <label htmlFor="day-choice-comment">Un commentaire, une précision ?</label>
<textarea id="day-choice-comment" ref={commentRef} /> <textarea id="day-choice-comment" ref={commentRef} />
@ -184,4 +245,8 @@ DayWishesForm.defaultProps = {
export default memo(DayWishesForm) export default memo(DayWishesForm)
// Fetch server-side data here // Fetch server-side data here
export const fetchFor = [fetchVolunteerDayWishesSetIfNeed, fetchMiscDiscordInvitationIfNeed] export const fetchFor = [
fetchVolunteerDayWishesSetIfNeed,
fetchMiscDiscordInvitationIfNeed,
fetchMiscFestivalDateListIfNeed,
]

View File

@ -28,13 +28,19 @@
text-align: center; text-align: center;
} }
.participationTitle { .oneCol {
width: 100%;
}
.participationTitle,
.charteTitle {
display: inline-block; display: inline-block;
width: 320px; width: 320px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.participationLabel { .participationLabel,
.charteLabel {
text-align: left; text-align: left;
display: inline-block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;

View File

@ -23,6 +23,7 @@ export const daysChoiceSelectionDefaultState = daysChoice.reduce((state, { id })
}, <SelectionChoices>{}) }, <SelectionChoices>{})
type SetFunction = ( type SetFunction = (
charter: VolunteerDayWishes["charter"],
active: VolunteerDayWishes["active"], active: VolunteerDayWishes["active"],
dayWishes: VolunteerDayWishes["dayWishes"], dayWishes: VolunteerDayWishes["dayWishes"],
dayWishesComment: VolunteerDayWishes["dayWishesComment"] dayWishesComment: VolunteerDayWishes["dayWishesComment"]
@ -37,10 +38,11 @@ export const useUserDayWishes = (): [VolunteerDayWishes | undefined, SetFunction
) )
const saveWishes: SetFunction = useCallback( const saveWishes: SetFunction = useCallback(
(active, dayWishes, dayWishesComment) => { (charter, active, dayWishes, dayWishesComment) => {
if (!userWishes) return if (!userWishes) return
save(jwtToken, 0, { save(jwtToken, 0, {
id: userWishes.id, id: userWishes.id,
charter,
active, active,
dayWishes, dayWishes,
dayWishesComment, dayWishesComment,

View File

@ -10,8 +10,8 @@ import AsyncAnnouncements, { loadData as loadAnnouncementsData } from "../pages/
import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../pages/TeamAssignment" import AsyncTeamAssignment, { loadData as loadTeamAssignmentData } from "../pages/TeamAssignment"
import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register" import AsyncRegisterPage, { loadData as loadRegisterPage } from "../pages/Register"
import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge" import AsyncKnowledge, { loadData as loadKnowledgeData } from "../pages/Knowledge"
import AsyncLoans, { loadData as loadLoansData } from "../pages/Loans" // import AsyncLoans, { loadData as loadLoansData } from "../pages/Loans"
import AsyncLoaning, { loadData as loadLoaningData } from "../pages/Loaning" // import AsyncLoaning, { loadData as loadLoaningData } from "../pages/Loaning"
import AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards" import AsyncKnowledgeCards, { loadData as loadCardKnowledgeData } from "../pages/KnowledgeCards"
import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams" import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams"
import AsyncBoard, { loadData as loadBoardData } from "../pages/Board" import AsyncBoard, { loadData as loadBoardData } from "../pages/Board"
@ -46,16 +46,16 @@ export default [
component: AsyncKnowledge, component: AsyncKnowledge,
loadData: loadKnowledgeData, loadData: loadKnowledgeData,
}, },
{ // {
path: "/emprunts", // path: "/emprunts",
component: AsyncLoans, // component: AsyncLoans,
loadData: loadLoansData, // loadData: loadLoansData,
}, // },
{ // {
path: "/emprunter", // path: "/emprunter",
component: AsyncLoaning, // component: AsyncLoaning,
loadData: loadLoaningData, // loadData: loadLoaningData,
}, // },
{ {
path: "/fiches", path: "/fiches",
component: AsyncKnowledgeCards, component: AsyncKnowledgeCards,

View File

@ -2,6 +2,7 @@
import path from "path" import path from "path"
import _, { assign } from "lodash" import _, { assign } from "lodash"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import { Misc } from "../../services/miscs"
import { Volunteer } from "../../services/volunteers" import { Volunteer } from "../../services/volunteers"
import { Postulant } from "../../services/postulants" import { Postulant } from "../../services/postulants"
import { Retex } from "../../services/retex" import { Retex } from "../../services/retex"
@ -281,6 +282,17 @@ function anonimizedDb(_s: States): States {
assign(r, new Retex(), { id: r.id, dayWishes: r.dayWishes }) assign(r, new Retex(), { id: r.id, dayWishes: r.dayWishes })
}) })
} }
if (s.Miscs) {
;(s.Miscs as Misc[]).forEach((r) => {
assign(r, new Retex(), {
id: r.id,
date: r.date,
meetingId: r.meetingId,
meetingTitle: r.meetingTitle,
meetingUrl: r.meetingUrl,
})
})
}
return s return s
} }

View File

@ -2,6 +2,7 @@ import ExpressAccessors from "./expressAccessors"
import { import {
Misc, Misc,
MiscDiscordInvitation, MiscDiscordInvitation,
MiscFestivalDate,
MiscMeetingDate, MiscMeetingDate,
MiscWithoutId, MiscWithoutId,
translationMisc, translationMisc,
@ -28,6 +29,18 @@ export const miscDiscordInvitation = expressAccessor.get(async (list, _body, id)
) )
}) })
export const miscFestivalDateListGet = expressAccessor.get(async (list) =>
list
.filter((misc) => !!misc.date)
.map(
(misc) =>
({
id: misc.id,
date: misc.date,
} as MiscFestivalDate)
)
)
export const miscMeetingDateListGet = expressAccessor.get(async (list) => export const miscMeetingDateListGet = expressAccessor.get(async (list) =>
list list
.filter((misc) => !!misc.meetingId) .filter((misc) => !!misc.meetingId)

View File

@ -62,17 +62,24 @@ async function sendMeetingEmail(
const miscSheet = await getSheet<MiscWithoutId, Misc>("Miscs", new Misc(), translationMisc) const miscSheet = await getSheet<MiscWithoutId, Misc>("Miscs", new Misc(), translationMisc)
const apiKey = process.env.SENDGRID_API_KEY || "" const apiKey = process.env.SENDGRID_API_KEY || ""
if (firstMeeting === "") { const miscList = await miscSheet.getList()
if (!miscList) {
throw Error("Unable to load miscList")
}
const hasMeetingDates = miscList?.[0]?.meetingId !== ""
if (!hasMeetingDates || firstMeeting === "") {
if (__DEV__ || apiKey === "") { if (__DEV__ || apiKey === "") {
console.error(`Fake sending meeting email to ${email}`) console.error(`Fake sending meeting email to ${email}`)
} else { } else {
sgMail.setApiKey(apiKey) sgMail.setApiKey(apiKey)
const visioComment = firstMeeting === "visio" ? " en visio" : ""
const msg = { const msg = {
to: email, to: email,
from: "contact@parisestludique.fr", from: "contact@parisestludique.fr",
subject: "Première rencontre Paris est Ludique", subject: "Première rencontre Paris est Ludique",
text: `Salut ${firstname},\n\nTon inscription est bien prise en compte !\n\nNous te contacterons pour trouver un moyen de se rencontrer.\n\nÀ bientôt :)\nPierre`, text: `Salut ${firstname},\n\nTon inscription est bien prise en compte !\n\nNous te contacterons pour trouver un moyen de se rencontrer${visioComment}.\n\nÀ bientôt :)\nPierre`,
html: `Salut ${firstname},<br /><br />Ton inscription est bien prise en compte !<br /><br />Nous te contacterons pour trouver un moyen de se rencontrer.<br /><br />À bientôt :)<br />Pierre`, html: `Salut ${firstname},<br /><br />Ton inscription est bien prise en compte !<br /><br />Nous te contacterons pour trouver un moyen de se rencontrer${visioComment}.<br /><br />À bientôt :)<br />Pierre`,
} }
await sgMail.send(msg) await sgMail.send(msg)
} }
@ -80,11 +87,6 @@ async function sendMeetingEmail(
} }
// else // else
const miscList = await miscSheet.getList()
if (!miscList) {
throw Error("Unable to load miscList")
}
const meetingLine = miscList.find((misc) => misc.meetingId === firstMeeting) const meetingLine = miscList.find((misc) => misc.meetingId === firstMeeting)
if (!meetingLine) { if (!meetingLine) {
throw Error(`Unable to find meeting ${firstMeeting}`) throw Error(`Unable to find meeting ${firstMeeting}`)

View File

@ -318,7 +318,16 @@ export const volunteerDayWishesSet = expressAccessor.set(async (list, body, id)
} }
const newVolunteer: Volunteer = cloneDeep(volunteer) const newVolunteer: Volunteer = cloneDeep(volunteer)
if (wishes.charter !== undefined) {
newVolunteer.charter = wishes.charter
if (!newVolunteer.charter) {
wishes.active = "non"
}
}
if (wishes.active !== undefined) { if (wishes.active !== undefined) {
if (!newVolunteer.charter && wishes.active === "oui") {
throw Error(`La charte doit être acceptée pour pouvoir devenir membre`)
}
newVolunteer.active = wishes.active newVolunteer.active = wishes.active
} }
if (wishes.dayWishes !== undefined) { if (wishes.dayWishes !== undefined) {
@ -332,6 +341,7 @@ export const volunteerDayWishesSet = expressAccessor.set(async (list, body, id)
toDatabase: newVolunteer, toDatabase: newVolunteer,
toCaller: { toCaller: {
id: newVolunteer.id, id: newVolunteer.id,
charter: newVolunteer.charter,
active: newVolunteer.active, active: newVolunteer.active,
dayWishes: newVolunteer.dayWishes, dayWishes: newVolunteer.dayWishes,
dayWishesComment: newVolunteer.dayWishesComment, dayWishesComment: newVolunteer.dayWishesComment,

View File

@ -53,7 +53,11 @@ import { notificationsSubscribe, notificationMain } from "./notifications"
import checkAccess from "./checkAccess" import checkAccess from "./checkAccess"
import { hasGSheetsAccess } from "./gsheets/accessors" import { hasGSheetsAccess } from "./gsheets/accessors"
import { addStatus, showStatusAt } from "./status" import { addStatus, showStatusAt } from "./status"
import { miscMeetingDateListGet, miscDiscordInvitation } from "./gsheets/miscs" import {
miscDiscordInvitation,
miscFestivalDateListGet,
miscMeetingDateListGet,
} from "./gsheets/miscs"
import { retexSet } from "./gsheets/retex" import { retexSet } from "./gsheets/retex"
checkAccess() checkAccess()
@ -106,6 +110,7 @@ app.get("/BoxDetailedListGet", detailedBoxListGet)
app.get("/GameListGet", gameListGet) app.get("/GameListGet", gameListGet)
app.get("/GamesToGiveListGet", gamesToGiveListGet) app.get("/GamesToGiveListGet", gamesToGiveListGet)
app.get("/GameTitleOrderCategories", gameTitleOrderCategories) app.get("/GameTitleOrderCategories", gameTitleOrderCategories)
app.get("/MiscFestivalDateListGet", miscFestivalDateListGet)
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet) app.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
app.get("/WishListGet", wishListGet) app.get("/WishListGet", wishListGet)
app.post("/WishAdd", wishAdd) app.post("/WishAdd", wishAdd)

View File

@ -2,6 +2,8 @@
export class Misc { export class Misc {
id = 0 id = 0
date = ""
meetingId = "" meetingId = ""
meetingTitle = "" meetingTitle = ""
@ -13,6 +15,7 @@ export class Misc {
export const translationMisc: { [k in keyof Misc]: string } = { export const translationMisc: { [k in keyof Misc]: string } = {
id: "id", id: "id",
date: "date",
meetingId: "rencontreId", meetingId: "rencontreId",
meetingTitle: "rencontreTitre", meetingTitle: "rencontreTitre",
meetingUrl: "rencontreUrl", meetingUrl: "rencontreUrl",
@ -23,14 +26,19 @@ export const elementName = "Misc"
export type MiscWithoutId = Omit<Misc, "id"> export type MiscWithoutId = Omit<Misc, "id">
export interface MiscDiscordInvitation {
id: Misc["id"]
discordInvitation: Misc["discordInvitation"]
}
export interface MiscFestivalDate {
id: Misc["id"]
date: Misc["date"]
}
export interface MiscMeetingDate { export interface MiscMeetingDate {
id: Misc["id"] id: Misc["id"]
meetingId: Misc["meetingId"] meetingId: Misc["meetingId"]
meetingTitle: Misc["meetingTitle"] meetingTitle: Misc["meetingTitle"]
meetingUrl: Misc["meetingUrl"] meetingUrl: Misc["meetingUrl"]
} }
export interface MiscDiscordInvitation {
id: Misc["id"]
discordInvitation: Misc["discordInvitation"]
}

View File

@ -1,5 +1,12 @@
import ServiceAccessors from "./accessors" import ServiceAccessors from "./accessors"
import { elementName, Misc, MiscDiscordInvitation, MiscMeetingDate, MiscWithoutId } from "./miscs" import {
elementName,
Misc,
MiscWithoutId,
MiscDiscordInvitation,
MiscFestivalDate,
MiscMeetingDate,
} from "./miscs"
const serviceAccessors = new ServiceAccessors<MiscWithoutId, Misc>(elementName) const serviceAccessors = new ServiceAccessors<MiscWithoutId, Misc>(elementName)
@ -7,6 +14,10 @@ export const miscDiscordInvitation = serviceAccessors.securedCustomGet<[], MiscD
"DiscordInvitationGet" "DiscordInvitationGet"
) )
export const miscFestivalDateListGet = serviceAccessors.customGet<[], MiscFestivalDate>(
"FestivalDateListGet"
)
export const miscMeetingDateListGet = serviceAccessors.customGet<[], MiscMeetingDate>( export const miscMeetingDateListGet = serviceAccessors.customGet<[], MiscMeetingDate>(
"MeetingDateListGet" "MeetingDateListGet"
) )

View File

@ -52,6 +52,8 @@ export class Volunteer implements VolunteerPartial {
acceptsNotifs = "" acceptsNotifs = ""
team2022 = 0
ok: number[] = [] ok: number[] = []
bof: number[] = [] bof: number[] = []
@ -75,6 +77,8 @@ export class Volunteer implements VolunteerPartial {
hostingComment = "" hostingComment = ""
meals: string[] = [] meals: string[] = []
charter = true
} }
export const translationVolunteer: { [k in keyof Volunteer]: string } = { export const translationVolunteer: { [k in keyof Volunteer]: string } = {
@ -104,6 +108,7 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
password2: "passe2", password2: "passe2",
pushNotifSubscription: "pushNotifSubscription", pushNotifSubscription: "pushNotifSubscription",
acceptsNotifs: "accepteLesNotifs", acceptsNotifs: "accepteLesNotifs",
team2022: "équipe2022",
ok: "OK", ok: "OK",
bof: "Bof", bof: "Bof",
niet: "Niet", niet: "Niet",
@ -116,6 +121,7 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
distanceToFestival: "distanceAuFestival", distanceToFestival: "distanceAuFestival",
hostingComment: "commentaireHébergement", hostingComment: "commentaireHébergement",
meals: "repas", meals: "repas",
charter: "charte",
} }
export class VolunteerPartial { export class VolunteerPartial {
@ -157,6 +163,7 @@ export const volunteerExample: Volunteer = {
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O", password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
pushNotifSubscription: "", pushNotifSubscription: "",
acceptsNotifs: "", acceptsNotifs: "",
team2022: 0,
ok: [5, 7, 24, 26, 31, 38, 50, 52, 54, 58], ok: [5, 7, 24, 26, 31, 38, 50, 52, 54, 58],
bof: [9, 12, 16, 27, 34, 35, 36], bof: [9, 12, 16, 27, 34, 35, 36],
niet: [13, 18, 19, 23, 47, 53, 59, 67], niet: [13, 18, 19, 23, 47, 53, 59, 67],
@ -169,6 +176,7 @@ export const volunteerExample: Volunteer = {
distanceToFestival: 0, distanceToFestival: 0,
hostingComment: "", hostingComment: "",
meals: [], meals: [],
charter: false,
} }
export const emailRegexp = export const emailRegexp =
@ -208,6 +216,7 @@ export interface VolunteerTeamWishes {
export interface VolunteerDayWishes { export interface VolunteerDayWishes {
id: Volunteer["id"] id: Volunteer["id"]
charter: Volunteer["charter"]
active: Volunteer["active"] active: Volunteer["active"]
dayWishes: Volunteer["dayWishes"] dayWishes: Volunteer["dayWishes"]
dayWishesComment: Volunteer["dayWishesComment"] dayWishesComment: Volunteer["dayWishesComment"]

View File

@ -0,0 +1,65 @@
import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit"
import { StateRequest, toastError, elementListFetch } from "./utils"
import { MiscFestivalDate } from "../services/miscs"
import { AppThunk, AppState, EntitiesRequest } from "."
import { miscFestivalDateListGet } from "../services/miscsAccessors"
const miscAdapter = createEntityAdapter<MiscFestivalDate>()
export const initialState = miscAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest)
const miscFestivalDateList = createSlice({
name: "miscFestivalDateList",
initialState,
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<MiscFestivalDate[]>) => {
state.readyStatus = "success"
miscAdapter.setAll(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default miscFestivalDateList.reducer
export const { getRequesting, getSuccess, getFailure } = miscFestivalDateList.actions
export const fetchMiscFestivalDateList = elementListFetch(
miscFestivalDateListGet,
getRequesting,
getSuccess,
getFailure,
(error: Error) => toastError(`Erreur lors du chargement des données diverses: ${error.message}`)
)
const shouldFetchMiscFestivalDateList = (state: AppState) =>
state.miscFestivalDateList.readyStatus !== "success"
export const fetchMiscFestivalDateListIfNeed = (): AppThunk => (dispatch, getState) => {
if (shouldFetchMiscFestivalDateList(getState())) return dispatch(fetchMiscFestivalDateList())
return null
}
export const refreshMiscFestivalDateList = (): AppThunk => (dispatch) =>
dispatch(fetchMiscFestivalDateList())
export const selectMiscFestivalDateListState = (
state: AppState
): EntitiesRequest<MiscFestivalDate> => state.miscFestivalDateList
export const selectMiscFestivalDateList = createSelector(
selectMiscFestivalDateListState,
({ ids, entities, readyStatus }) => {
if (readyStatus !== "success") return []
return ids.map((id) => entities[id]) as MiscFestivalDate[]
}
)

View File

@ -8,6 +8,7 @@ import gameList from "./gameList"
import gameWithVolunteersList from "./gameWithVolunteersList" import gameWithVolunteersList from "./gameWithVolunteersList"
import gameDetailsUpdate from "./gameDetailsUpdate" import gameDetailsUpdate from "./gameDetailsUpdate"
import miscDiscordInvitation from "./miscDiscordInvitation" import miscDiscordInvitation from "./miscDiscordInvitation"
import miscFestivalDateList from "./miscFestivalDateList"
import miscMeetingDateList from "./miscMeetingDateList" import miscMeetingDateList from "./miscMeetingDateList"
import postulantAdd from "./postulantAdd" import postulantAdd from "./postulantAdd"
import retexSet from "./retexSet" import retexSet from "./retexSet"
@ -43,6 +44,7 @@ export default (history: History) => ({
gameWithVolunteersList, gameWithVolunteersList,
gameDetailsUpdate, gameDetailsUpdate,
miscDiscordInvitation, miscDiscordInvitation,
miscFestivalDateList,
miscMeetingDateList, miscMeetingDateList,
postulantAdd, postulantAdd,
retexSet, retexSet,