Update sign up message and available meetup location

This commit is contained in:
pikiou 2022-10-29 01:01:11 +02:00
parent 2a1d2ef49b
commit e6a049d0d1
39 changed files with 1014 additions and 498 deletions

View File

@ -40,6 +40,7 @@
}, },
"scripts": { "scripts": {
"dev": "yarn dev:build && nodemon ./public/server", "dev": "yarn dev:build && nodemon ./public/server",
"ser": "yarn dev:build && node ./public/server",
"dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts", "dev:build": "cross-env NODE_ENV=development webpack --config ./webpack/server.config.ts",
"local-start": "cross-env LOCAL=true yarn build && node ./public/server", "local-start": "cross-env LOCAL=true yarn build && node ./public/server",
"start": "node ./public/server", "start": "node ./public/server",

View File

@ -6,7 +6,7 @@ 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"
// import { AskMeals, fetchFor as fetchForMeals } from "./AskMeals" // import { AskMeals, fetchFor as fetchForMeals } from "./AskMeals"
// import { AskPersonalInfo, fetchFor as fetchForPersonalInfo } from "./AskPersonalInfo" // import { AskPersonalInfo, fetchFor as fetchForPersonalInfo } from "./AskPersonalInfo"
@ -26,7 +26,7 @@ const Asks = (): JSX.Element | null => {
AskRetex(asks, 3) AskRetex(asks, 3)
AskDiscord(asks, 5) AskDiscord(asks, 5)
// AskDayWishes(asks, 10) AskDayWishes(asks, 10)
// AskTeamWishes(asks, 11) // AskTeamWishes(asks, 11)
// AskParticipationDetails(asks, 12) // AskParticipationDetails(asks, 12)
// AskPersonalInfo(asks, 15) // AskPersonalInfo(asks, 15)
@ -69,7 +69,7 @@ export const fetchFor = [
...fetchForBrunch, ...fetchForBrunch,
...fetchForRetex, ...fetchForRetex,
...fetchForDiscord, ...fetchForDiscord,
// ...fetchForDayWishes, ...fetchForDayWishes,
// ...fetchForHosting, // ...fetchForHosting,
// ...fetchForMeals, // ...fetchForMeals,
// ...fetchForTeamWishes, // ...fetchForTeamWishes,

View File

@ -37,7 +37,7 @@ const KnowledgeBoxList: React.FC = (): JSX.Element | null => {
onChange={onShowUnknownOnly} onChange={onShowUnknownOnly}
checked={showUnknownOnly} checked={showUnknownOnly}
/>{" "} />{" "}
Uniquement les non-renseignés Afficher uniquement les non-renseignés
</label> </label>
<ul className={styles.boxList}> <ul className={styles.boxList}>
{boxesToShow.map((detailedBox: any) => ( {boxesToShow.map((detailedBox: any) => (

View File

@ -5,7 +5,6 @@
text-align: left; text-align: left;
display: inline-block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
width: 280px;
} }
.boxList { .boxList {

View File

@ -1,156 +0,0 @@
import { cloneDeep, pull, sortBy, uniq } from "lodash"
import React, { memo, useCallback, useEffect, useState } from "react"
import classnames from "classnames"
import styles from "./styles.module.scss"
import { DetailedBox } from "../../services/boxes"
import { VolunteerLoan, VolunteerLoanWithoutId } from "../../services/volunteers"
interface Props {
detailedBox: DetailedBox
volunteerLoan: VolunteerLoan | undefined
saveVolunteerLoan: (newVolunteerLoan: VolunteerLoan) => void
}
const BoxItem: React.FC<Props> = ({
detailedBox,
volunteerLoan,
saveVolunteerLoan,
}): JSX.Element => {
type ChoiceValue = keyof VolunteerLoanWithoutId
const [loanable, setLoanable] = useState(false)
const [playable, setPlayable] = useState(false)
const [giftable, setGiftable] = useState(false)
const [noOpinion, setNoOpinion] = useState(false)
const { gameId, bggPhoto, title, bggId, poufpaf, bggIdAlternative } = detailedBox
const isBggPhoto = !/^[0-9]+\.[a-zA-Z]+$/.test(bggPhoto)
const loanChoices: { name: string; value: ChoiceValue }[] = [
{ name: "Empruntable", value: "loanable" },
{ name: "Jouable", value: "playable" },
{ name: "Offrable", value: "giftable" },
{ name: "SansAvis", value: "noOpinion" },
]
const loanValues = {
loanable,
playable,
giftable,
noOpinion,
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const loanSetters = {
loanable: setLoanable,
playable: setPlayable,
giftable: setGiftable,
noOpinion: setNoOpinion,
}
useEffect(() => {
if (!volunteerLoan) return
setLoanable(volunteerLoan.loanable.includes(gameId))
setPlayable(volunteerLoan.playable.includes(gameId))
setGiftable(volunteerLoan.giftable.includes(gameId))
setNoOpinion(volunteerLoan.noOpinion.includes(gameId))
}, [gameId, volunteerLoan])
const onChoiceClick = useCallback(
(value: ChoiceValue) => {
if (!volunteerLoan) return
const adding = !volunteerLoan[value].includes(gameId)
const newVolunteerLoan: VolunteerLoan = cloneDeep(volunteerLoan)
if (adding) {
newVolunteerLoan[value] = sortBy(uniq([...newVolunteerLoan[value], gameId]))
} else {
pull(newVolunteerLoan[value], gameId)
}
if (adding && value === "noOpinion") {
pull(newVolunteerLoan.loanable, gameId)
pull(newVolunteerLoan.playable, gameId)
pull(newVolunteerLoan.giftable, gameId)
setLoanable(volunteerLoan.loanable.includes(gameId))
setPlayable(volunteerLoan.playable.includes(gameId))
setGiftable(volunteerLoan.giftable.includes(gameId))
setNoOpinion(true)
} else {
pull(newVolunteerLoan.noOpinion, gameId)
setNoOpinion(false)
loanSetters[value](adding)
}
saveVolunteerLoan(newVolunteerLoan)
},
[volunteerLoan, gameId, saveVolunteerLoan, loanSetters]
)
const onCheckboxChange = useCallback(() => {
// Do nothing
}, [])
return (
<li className={styles.boxItem}>
<div className={styles.photoContainer}>
{isBggPhoto && <img className={styles.photo} src={bggPhoto} alt="" />}
{!isBggPhoto && (
<div
className={classnames(
styles.alternateBox,
styles[`a${bggPhoto.replace(/\..*/, "")}`]
)}
>
{" "}
</div>
)}
</div>
<div className={classnames(styles.titleContainer, poufpaf && styles.shorterTitle)}>
<a
href={
bggId > 0
? `https://boardgamegeek.com/boardgame/${bggId}`
: bggIdAlternative
}
target="_blank"
rel="noreferrer"
className={styles.title}
>
{title}
</a>
</div>
<a
href={`https://sites.google.com/site/poufpafpasteque/fiches_alpha/${poufpaf}`}
target="_blank"
rel="noreferrer"
>
<div className={poufpaf ? styles.poufpaf : styles.noPoufpaf}> </div>
</a>
<ul className={styles.loanList}>
{loanChoices.map(({ name, value }) => (
<li key={value} className={styles.loanItem}>
<button
type="button"
onClick={() => onChoiceClick(value)}
className={classnames(
styles.loanButton,
styles[value],
loanValues[value] && styles.active
)}
data-message={name}
>
<input
type="checkbox"
className={styles.loanCheckbox}
checked={loanValues[value]}
onChange={() => onCheckboxChange()}
/>
<div
className={classnames(
styles.loanCheckboxImg,
styles[`${value}Checkbox`]
)}
/>
</button>
</li>
))}
</ul>
</li>
)
}
export default memo(BoxItem)

View File

@ -0,0 +1,164 @@
// import { cloneDeep, pull, sortBy, uniq } from "lodash"
import { toast } from "react-toastify"
import React, { memo, useCallback, useEffect, useState } from "react"
import classnames from "classnames"
import styles from "./styles.module.scss"
import { DetailedBox } from "../../services/boxes"
import { VolunteerLoan, VolunteerLoanWithoutId } from "../../services/volunteers"
interface Props {
detailedGame: DetailedBox
volunteerLoan: VolunteerLoan | undefined
saveVolunteerLoan: (newVolunteerLoan: VolunteerLoan) => void
}
const DetailedGameItem: React.FC<Props> = ({
detailedGame,
volunteerLoan,
saveVolunteerLoan,
}): JSX.Element => {
type ChoiceValue = keyof VolunteerLoanWithoutId
const [loanable, setLoanable] = useState(false)
const [playable, setPlayable] = useState(false)
const [giftable, setGiftable] = useState(false)
const [noOpinion, setNoOpinion] = useState(false)
const { gameId, bggPhoto, title, bggId, poufpaf, bggIdAlternative } = detailedGame
const isBggPhoto = !/^[0-9]+\.[a-zA-Z]+$/.test(bggPhoto)
const loanChoices: { name: string; value: ChoiceValue }[] = [
{ name: "Empruntable", value: "loanable" },
{ name: "Jouable", value: "playable" },
{ name: "Offrable", value: "giftable" },
{ name: "SansAvis", value: "noOpinion" },
]
const loanValues = {
loanable,
playable,
giftable,
noOpinion,
}
useEffect(() => {
if (!volunteerLoan) return
setLoanable(volunteerLoan.loanable.includes(gameId))
setPlayable(volunteerLoan.playable.includes(gameId))
setGiftable(volunteerLoan.giftable.includes(gameId))
setNoOpinion(volunteerLoan.noOpinion.includes(gameId))
}, [gameId, volunteerLoan])
const onChoiceClick = useCallback(
(_value: ChoiceValue) => {
toast.warning("Il n'est plus possible de changer ses choix", {
position: "top-center",
autoClose: 500,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
})
},
[]
// (value: ChoiceValue) => {
// if (!volunteerLoan) return
// const adding = !volunteerLoan[value].includes(gameId)
// const newVolunteerLoan: VolunteerLoan = cloneDeep(volunteerLoan)
// if (adding) {
// newVolunteerLoan[value] = sortBy(uniq([...newVolunteerLoan[value], gameId]))
// } else {
// pull(newVolunteerLoan[value], gameId)
// }
// if (adding && value === "noOpinion") {
// pull(newVolunteerLoan.loanable, gameId)
// pull(newVolunteerLoan.playable, gameId)
// pull(newVolunteerLoan.giftable, gameId)
// } else {
// pull(newVolunteerLoan.noOpinion, gameId)
// }
// saveVolunteerLoan(newVolunteerLoan)
// },
// [volunteerLoan, gameId, saveVolunteerLoan]
)
// Just to keep saveVolunteerLoan necessary to prevent errors while changes are prevented
if (!saveVolunteerLoan) {
console.log("")
}
const onCheckboxChange = useCallback(() => {
// Do nothing
}, [])
return (
<>
<li className={classnames(styles.boxItem, !volunteerLoan && styles.hide)}>
<div className={styles.photoContainer}>
{isBggPhoto && <img className={styles.photo} src={bggPhoto} alt="" />}
{!isBggPhoto && (
<div
className={classnames(
styles.alternateBox,
styles[`a${bggPhoto.replace(/\..*/, "")}`]
)}
>
{" "}
</div>
)}
</div>
<div className={classnames(styles.titleContainer, poufpaf && styles.shorterTitle)}>
<a
href={
bggId > 0
? `https://boardgamegeek.com/boardgame/${bggId}`
: bggIdAlternative
}
target="_blank"
rel="noreferrer"
className={styles.title}
>
{title}
</a>
</div>
<a
href={`https://sites.google.com/site/poufpafpasteque/fiches_alpha/${poufpaf}`}
target="_blank"
rel="noreferrer"
>
<div className={poufpaf ? styles.poufpaf : styles.noPoufpaf}> </div>
</a>
<ul className={styles.loanList}>
{loanChoices.map(({ name, value }) => (
<li key={value} className={styles.loanItem}>
<button
type="button"
onClick={() => onChoiceClick(value)}
className={classnames(
styles.loanButton,
styles[value],
loanValues[value] && styles.active
)}
data-message={name}
>
<input
type="checkbox"
className={styles.loanCheckbox}
checked={loanValues[value]}
onChange={() => onCheckboxChange()}
/>
<div
className={classnames(
styles.loanCheckboxImg,
styles[`${value}Checkbox`]
)}
/>
</button>
</li>
))}
</ul>
</li>
</>
)
}
export default memo(DetailedGameItem)

View File

@ -0,0 +1,64 @@
import React, { memo } from "react"
import classnames from "classnames"
import styles from "./styles.module.scss"
import { GameWithVolunteers } from "../../services/games"
import { gameTitleExactCategory } from "../../store/utils"
interface Props {
gameWithLoans: GameWithVolunteers
}
const LoanGamesItem: React.FC<Props> = ({ gameWithLoans }): JSX.Element => {
const { bggPhoto, title, bggId, poufpaf, bggIdAlternative, volunteerNicknames, boxCount } =
gameWithLoans
const isBggPhoto = !/^[0-9]+\.[a-zA-Z]+$/.test(bggPhoto)
return (
<li className={styles.boxItem}>
<div className={styles.photoContainer}>
{isBggPhoto && <img className={styles.photo} src={bggPhoto} alt="" />}
{!isBggPhoto && (
<div
className={classnames(
styles.alternateBox,
styles[`a${bggPhoto.replace(/\..*/, "")}`]
)}
>
{" "}
</div>
)}
</div>
<div className={classnames(styles.titleContainer, poufpaf && styles.shorterTitle)}>
<a
href={
bggId > 0
? `https://boardgamegeek.com/boardgame/${bggId}`
: bggIdAlternative
}
target="_blank"
rel="noreferrer"
className={styles.title}
>
{title}
</a>
</div>
<a
href={`https://sites.google.com/site/poufpafpasteque/fiches_alpha/${poufpaf}`}
target="_blank"
rel="noreferrer"
>
<div className={poufpaf ? styles.poufpaf : styles.noPoufpaf}> </div>
</a>
<div className={styles.boxCount}>
{boxCount > 1 ? `${boxCount} boîtes` : ""}
<br />
{gameTitleExactCategory({ title })}
</div>
<div className={styles.volunteerList}>
Bénévoles intéressés: {volunteerNicknames.join(", ")}
</div>
</li>
)
}
export default memo(LoanGamesItem)

View File

@ -1,23 +1,21 @@
import React, { memo, useState } from "react" import React, { memo, useState } from "react"
import { useSelector } from "react-redux" import { useSelector } from "react-redux"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import BoxItem from "./BoxItem"
import { fetchBoxListIfNeed, selectSortedUniqueDetailedBoxes } from "../../store/boxList" import { fetchBoxListIfNeed, selectSortedUniqueDetailedBoxes } from "../../store/boxList"
import { fetchVolunteerLoanSetIfNeed, useVolunteerLoan } from "../../store/volunteerLoanSet" import { fetchVolunteerLoanSetIfNeed, useVolunteerLoan } from "../../store/volunteerLoanSet"
import { DetailedBox } from "../../services/boxes"
import DetailedGameItem from "./DetailedGameItem"
const LoanBoxList: React.FC = (): JSX.Element | null => { const Loaning: React.FC = (): JSX.Element | null => {
const detailedBoxes = useSelector(selectSortedUniqueDetailedBoxes) const detailedBoxes: DetailedBox[] = useSelector(selectSortedUniqueDetailedBoxes)
const [volunteerLoan, saveVolunteerLoan] = useVolunteerLoan() const [volunteerLoan, saveVolunteerLoan] = useVolunteerLoan()
const [showUnknownOnly, setShowUnknownOnly] = useState(false) const [showUnknownOnly, setShowUnknownOnly] = useState(false)
const onShowUnknownOnly = (e: React.ChangeEvent<HTMLInputElement>) => const onShowUnknownOnly = (e: React.ChangeEvent<HTMLInputElement>) =>
setShowUnknownOnly(e.target.checked) setShowUnknownOnly(e.target.checked)
if (!detailedBoxes || detailedBoxes.length === 0) return null if (!detailedBoxes || detailedBoxes.length === 0) return null
const boxesToShow = detailedBoxes.filter( const boxesToShow = detailedBoxes.filter(
(box) => (box) =>
!box ||
!showUnknownOnly || !showUnknownOnly ||
!volunteerLoan || !volunteerLoan ||
(!volunteerLoan.loanable.includes(box.gameId) && (!volunteerLoan.loanable.includes(box.gameId) &&
@ -28,22 +26,22 @@ const LoanBoxList: React.FC = (): JSX.Element | null => {
return ( return (
<div className={styles.loanThings}> <div className={styles.loanThings}>
<label className={styles.showUnknownOnlyLabel}> <label className={styles.showCheckboxLabel}>
<input <input
type="checkbox" type="checkbox"
name="showUnknownOnly" name="showUnknownOnly"
onChange={onShowUnknownOnly} onChange={onShowUnknownOnly}
checked={showUnknownOnly} checked={showUnknownOnly}
/>{" "} />{" "}
Uniquement les non-renseignés Afficher uniquement les non-renseignés
</label> </label>
<ul className={styles.boxList}> <ul className={styles.boxList}>
{boxesToShow.map((detailedBox: any) => ( {boxesToShow.map((game) => (
<BoxItem <DetailedGameItem
detailedBox={detailedBox} detailedGame={game}
volunteerLoan={volunteerLoan} volunteerLoan={volunteerLoan}
saveVolunteerLoan={saveVolunteerLoan} saveVolunteerLoan={saveVolunteerLoan}
key={detailedBox.id} key={game.id}
/> />
))} ))}
</ul> </ul>
@ -51,6 +49,6 @@ const LoanBoxList: React.FC = (): JSX.Element | null => {
) )
} }
export default memo(LoanBoxList) export default memo(Loaning)
export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerLoanSetIfNeed] export const fetchFor = [fetchBoxListIfNeed, fetchVolunteerLoanSetIfNeed]

View File

@ -2,7 +2,7 @@ import classnames from "classnames"
import React from "react" import React from "react"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
const LoanIntro: React.FC = (): JSX.Element => ( const LoaningIntro: React.FC = (): JSX.Element => (
<div className={styles.loanThings}> <div className={styles.loanThings}>
<h1>Emprunt et tri des jeux</h1> <h1>Emprunt et tri des jeux</h1>
<p> <p>
@ -54,4 +54,4 @@ const LoanIntro: React.FC = (): JSX.Element => (
</div> </div>
) )
export default LoanIntro export default LoaningIntro

View File

@ -0,0 +1,46 @@
import React, { memo } from "react"
import { useSelector } from "react-redux"
import styles from "./styles.module.scss"
import {
fetchGameWithVolunteersListIfNeed,
selectSortedUniqueGamesWithVolunteers,
} from "../../store/gameWithVolunteersList"
import { GameWithVolunteers } from "../../services/games"
import GamesWithVolunteersItem from "./GamesWithVolunteersItem"
const Loans: React.FC = (): JSX.Element | null => {
const gameWithLoans: GameWithVolunteers[] = useSelector(selectSortedUniqueGamesWithVolunteers)
const loanGames = gameWithLoans.filter((game) => game.toLoan)
// const giftGames = gameWithLoans.filter(game => !game.toLoan)
return (
<div>
<h3>Jeux que tu veux emprunter</h3>
{loanGames.length > 0 && (
<ul className={styles.boxList}>
{loanGames.map((game) => (
<GamesWithVolunteersItem gameWithLoans={game} key={game.id} />
))}
</ul>
)}
{loanGames.length === 0 && <>Aucun jeu.</>}
{/* {giftGames.length > 0 && <>
<h3>Jeux que tu voudrais récupérer définitivement</h3>
<ul className={styles.boxList}>
{giftGames.map(game => (
<GamesWithVolunteersItem
gameWithLoans={game}
key={game.id}
/>
))}
</ul>
</>} */}
</div>
)
}
export default memo(Loans)
export const fetchFor = [fetchGameWithVolunteersListIfNeed]

View File

@ -0,0 +1,14 @@
import React from "react"
import styles from "./styles.module.scss"
const LoansIntro: React.FC = (): JSX.Element => (
<div className={styles.loanThings}>
<h1>Jeux à emprunter</h1>
<p>
Si un jeu qui t'intéresse est emprunté par un autre bénévole, son nom sera affiché ici.
Vous pourrez peut-être envisager d'y jouer ensemble !
</p>
</div>
)
export default LoansIntro

View File

@ -1,11 +1,14 @@
@import "../../theme/variables"; @import "../../theme/variables";
@import "../../theme/mixins"; @import "../../theme/mixins";
.showUnknownOnlyLabel { .showCheckboxLabel {
text-align: left; text-align: left;
display: inline-block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
width: 280px; }
.hide {
display: none;
} }
.boxList { .boxList {
@ -200,6 +203,22 @@
display: none; display: none;
} }
.boxCount {
height: 50px;
width: 73px;
position: relative;
display: inline-block;
vertical-align: middle;
}
.volunteerList {
height: 50px;
width: 198px;
position: relative;
display: inline-block;
vertical-align: middle;
}
.loanList { .loanList {
@include clear-ul-style; @include clear-ul-style;

View File

@ -52,6 +52,7 @@ 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="Emprunts" pathname="/emprunts" /> <MenuItem name="Emprunts" pathname="/emprunts" />
{/* <MenuItem name="Mes connaissances" pathname="/connaissances" /> */} {/* <MenuItem name="Mes connaissances" pathname="/connaissances" /> */}
<RestrictMenuItem <RestrictMenuItem

View File

@ -8,7 +8,6 @@ import styles from "./styles.module.scss"
import { fetchPostulantAdd } from "../../store/postulantAdd" import { fetchPostulantAdd } from "../../store/postulantAdd"
import { AppDispatch, AppState } from "../../store" import { AppDispatch, AppState } from "../../store"
import { fetchVolunteerPartialAdd } from "../../store/volunteerPartialAdd"
import FormButton from "../Form/FormButton/FormButton" import FormButton from "../Form/FormButton/FormButton"
import { validEmail } from "../../utils/standardization" import { validEmail } from "../../utils/standardization"
import { toastError } from "../../store/utils" import { toastError } from "../../store/utils"
@ -38,24 +37,21 @@ const animations = [
[styles.imgTransitionShow, styles.imgTransitionAbouToShow], [styles.imgTransitionShow, styles.imgTransitionAbouToShow],
] ]
const RegisterForm = ({ dispatch }: Props): JSX.Element => { const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const [potentialVolunteer, setPotentialVolunteer] = useState(true)
const [firstname, setFirstname] = useState("") const [firstname, setFirstname] = useState("")
const [lastname, setLastname] = useState("") const [lastname, setLastname] = useState("")
const [email, setEmail] = useState("") const [email, setEmail] = useState("")
const [mobile, setMobile] = useState("") const [mobile, setMobile] = useState("")
const [alreadyVolunteer, setAlreadyVolunteer] = useState(false)
const [comment, setComment] = useState("") const [comment, setComment] = useState("")
const [alreadyCame, setAlreadyCame] = useState(true) const [alreadyCame, setAlreadyCame] = useState(true)
const [firstMeeting, setFirstMeeting] = useState("") const [firstMeeting, setFirstMeeting] = useState("")
const [commentFirstMeeting, setCommentFirstMeeting] = useState("") const [commentFirstMeeting, setCommentFirstMeeting] = useState("")
const [canHelpBefore, setCanHelpBefore] = useState("")
const [howToContact, setHowToContact] = useState("Email") const [howToContact, setHowToContact] = useState("Email")
const [sending, setSending] = useState(false) const [sending, setSending] = useState(false)
const [changingBackground, setChangingBackground] = useState(0) const [changingBackground, setChangingBackground] = useState(0)
const meetingDateList = useSelector(selectMiscMeetingDateList) const meetingDateList = useSelector(selectMiscMeetingDateList)
const enableRegistering = false const enableRegistering = true
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
@ -83,47 +79,29 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
return return
} }
if (potentialVolunteer) { dispatch(
dispatch( fetchPostulantAdd({
fetchPostulantAdd({ potential: true,
potential: true, firstname,
firstname, lastname,
lastname, email,
email, mobile,
mobile, howToContact,
howToContact, alreadyCame,
alreadyCame, firstMeeting,
firstMeeting, commentFirstMeeting: firstMeeting ? "" : commentFirstMeeting,
commentFirstMeeting: firstMeeting ? "" : commentFirstMeeting, comment,
comment, })
}) )
) // dispatch(
} else { // fetchVolunteerPartialAdd({
dispatch( // firstname,
fetchVolunteerPartialAdd({ // lastname,
firstname, // email,
lastname, // mobile,
email, // howToContact,
mobile, // })
howToContact, // )
canHelpBefore,
})
)
dispatch(
fetchPostulantAdd({
potential: false,
firstname,
lastname,
email,
mobile,
howToContact,
alreadyCame,
firstMeeting,
commentFirstMeeting: firstMeeting ? "" : commentFirstMeeting,
comment,
})
)
}
setSending(true) setSending(true)
} }
@ -141,11 +119,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
let sendSuccess let sendSuccess
let sendError let sendError
let sendingElement let sendingElement
if ( if (!postulantError && !_.isEmpty(postulant)) {
!postulantError &&
!_.isEmpty(postulant) &&
(potentialVolunteer || (!volunteerError && !_.isEmpty(volunteer)))
) {
if (sending) { if (sending) {
setSending(false) setSending(false)
} }
@ -181,11 +155,11 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
<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 2 et dimanche 3 juillet 2022 ! leurs formes. Les samedi 24 et dimanche 25 juin 2023 !
</p> </p>
<p> <p>
En 2019 lors de la dernière édition, ce sont <b>16 000</b> joueurs qui se sont En 2022, ce sont <b>18 000</b> visiteurs qui sont venus sous 300 chapiteaux et 2
réunis sous 300 chapiteaux et 2 000 tables. 000 tables.
</p> </p>
<p> <p>
Les 2 jours que durent le festival sont entièrement dédiés à ce que le public{" "} Les 2 jours que durent le festival sont entièrement dédiés à ce que le public{" "}
@ -211,7 +185,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
toute l&apos;année ^^ toute l&apos;année ^^
</p> </p>
<p> <p>
Pendant le festival de 2019, nous étions <b>187 bénévoles</b> organisés en Pendant le festival de 2022, nous étions <b>196 bénévoles</b> organisés en
équipes qui chouchoutent les visiteurs en les accueillant, en s&apos;assurant équipes qui chouchoutent les visiteurs en les accueillant, en s&apos;assurant
que tout se passe bien, ou encore en expliquant des règles de jeux. que tout se passe bien, ou encore en expliquant des règles de jeux.
</p> </p>
@ -224,9 +198,9 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
<p> <p>
La majorité d'entre nous sommes bénévoles les <b>samedi et dimanche</b>, mais 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 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, à la d'aider à la mise en place mercredi, jeudi ou vendredi, ou au rangement le
place d'un des jours du weekend. Bref, chacun participe comme il peut mais deux lundi, à la place d'un des jours du weekend. Bref, chacun participe comme il
jours minimum ! peut mais <b>deux jours minimum</b> !
</p> </p>
<p> <p>
Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en Le samedi soir quand les visiteurs sont partis, nous prolongeons la fête en
@ -238,8 +212,8 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
{!enableRegistering && ( {!enableRegistering && (
<dt> <dt>
L'inscription est clôturée pour l'édition 2022, mais si l'expérience vous tente, L'inscription est clôturée pour l'édition 2023, mais si l'expérience te tente,
remplissez le formulaire suivant pour devenir bénévole à PeL 2023 !<br /> remplis le formulaire suivant pour devenir bénévole à PeL 2024 !<br />
Dès septembre on se rencontrera sur Paris en petits groupes pour discuter du Dès septembre on se rencontrera sur Paris en petits groupes pour discuter du
festival, du bénévolat et surtout faire connaissance :) festival, du bénévolat et surtout faire connaissance :)
<br /> <br />
@ -248,98 +222,15 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
{enableRegistering && ( {enableRegistering && (
<dt> <dt>
Si l&apos;expérience vous tente, remplissez le formulaire suivant pour devenir Si tu as envie de donner le sourire à des milliers de gens, remplis le
bénévole !<br /> formulaire suivant pour rencontrer une poignée d'entre nous dans un bar/resto
Vous pouvez aussi juste nous rencontrer avant de vous décider à devenir près de Châtelet, avant de te décider ou non à devenir bénévole :)
bénévole, on comprend qu&apos;un saut pareil dans l&apos;inconnu soit difficile.
<br />
Dans les deux cas, venez rencontrer une poignée d'entre nous dans un bar/resto
près de Châtelet ! :) Sur inscription uniquement...
<br /> <br />
</dt> </dt>
)} )}
</dl> </dl>
) )
const potentialVolunteerQuestion = enableRegistering && (
<div className={styles.inputWrapper}>
<div className={styles.leftCol}>
<div className={styles.multipleChoiceTitle}>Je veux devenir bénévole :</div>
</div>
<div className={styles.rightCol}>
{["Tout de suite !", "Peut-être après une rencontre avec des bénévoles"].map(
(option) => (
<label className={styles.longAnswerLabel} key={option}>
<input
type="radio"
name="potentialVolunteer"
onChange={sendBooleanRadioboxDispatch(
setPotentialVolunteer,
option !== "Tout de suite !"
)}
checked={potentialVolunteer === (option !== "Tout de suite !")}
/>{" "}
{option}
</label>
)
)}
</div>
</div>
)
const alreadyVolunteerQuestion = !potentialVolunteer && (
<>
<div className={styles.inputWrapper}>
<div className={styles.leftCol}>
<div className={styles.multipleChoiceTitle}>J'ai déjà é bénévole à PeL</div>
</div>
<div className={styles.rightCol}>
<div className={styles.rightColContainer}>
{["Oui", "Non"].map((option) => (
<label className={styles.shortAnswerLabel} key={option}>
<input
type="radio"
name="alreadyVolunteer"
onChange={sendBooleanRadioboxDispatch(
setAlreadyVolunteer,
option === "Oui"
)}
checked={alreadyVolunteer === (option === "Oui")}
/>{" "}
{option}
</label>
))}
</div>
</div>
</div>
{alreadyVolunteer && (
<dl className={styles.registerIntro}>
<dd>
<p>Dans ce cas pourquoi t'inscris-tu ici ? ^^</p>
<p>
Si tu te rappelles de l'email que tu avais utilisé à ta dernière
inscription sur le site Force Orange des bénévoles (même sur l'ancienne
version) tu peux{" "}
<a href="/sidentifier" target="_blank" rel="noreferrer">
t'identifier ici
</a>{" "}
avec ton ancien mot de passe, ou en{" "}
<a href="/sinscrire" target="_blank" rel="noreferrer">
demander un nouveau ici
</a>
.
</p>
<p>
Autrement, si tu as changé d'email, mieux vaut nous le communiquer à
benevoles@parisestludique.fr en précisant bien tes nom et prénom :)
</p>
</dd>
</dl>
)}
</>
)
const commentQuestion = ( const commentQuestion = (
<dl className={styles.inputWrapper}> <dl className={styles.inputWrapper}>
<dd className={styles.commentWrapper}> <dd className={styles.commentWrapper}>
@ -448,10 +339,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const meeting = enableRegistering && ( const meeting = enableRegistering && (
<> <>
<dl className={styles.registerIntro}> <dl className={styles.registerIntro}>
{!potentialVolunteer && <dt>Faisons connaissance !</dt>} <dt>Faisons connaissance !</dt>
{potentialVolunteer && (
<dt>Se rencontrer avant de se décider à devenir bénévole ?</dt>
)}
<dd> <dd>
<p> <p>
On organise des rencontres entre de nouvelles personnes comme toi, et des On organise des rencontres entre de nouvelles personnes comme toi, et des
@ -461,23 +349,13 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
<p> <p>
Ces rencontres ont lieu à 19h dans un bar/resto calme à Châtelet, le{" "} Ces rencontres ont lieu à 19h dans un bar/resto calme à Châtelet, le{" "}
<a <a
href="https://goo.gl/maps/N5NYWDF66vNQDFMh8" href="https://g.page/lerhinocerosparis?share"
id="sfmMap" id="sfmMap"
key="sfmMap" key="sfmMap"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
Street Food Market Rhinocéros
</a>
, ou à une soirée festive à 2 pas du lieu du festival, aux{" "}
<a
href="https://www.captainturtle.fr/aperos-petanque-paris/"
id="petanque"
key="petanque"
target="_blank"
rel="noreferrer"
>
apéros de la pétanque
</a> </a>
. .
</p> </p>
@ -535,11 +413,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
name="commentFirstMeeting" name="commentFirstMeeting"
id="commentFirstMeeting" id="commentFirstMeeting"
className={styles.inputWrapper} className={styles.inputWrapper}
placeholder={ 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 ?"
potentialVolunteer
? "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 ?"
: "Ce n'est pas obligé mais ça aurait été top ! Manques-tu de temps ? Préfères-tu une autre date ? Est-ce trop loin de chez toi ? Préfères-tu nous rencontrer en visio ?"
}
value={commentFirstMeeting} value={commentFirstMeeting}
onChange={sendTextareaDispatch(setCommentFirstMeeting)} onChange={sendTextareaDispatch(setCommentFirstMeeting)}
/> />
@ -549,79 +423,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
</> </>
) )
const helpBefore = enableRegistering && !potentialVolunteer && (
<>
<dl className={styles.registerIntro}>
<dt>Bénévolat en amont du festival</dt>
<dd>
<p>
En tant que bénévole, tu fais selon tes envies, tes disponibilités, ton
énergie. Si personne ne veut faire quelque chose de primordial pour le
festival, on paye quelqu'un de l'extérieur. Par exemple le transport+montage
des tentes+tables, ou la sécurité de nuit sont délégués à des prestataires.
Et si ce quelque chose n'est pas primordiale et que personne ne veut s'en
occuper, bah tant pis on le fait pas ^^
</p>
<p>
Après on essaye de faire plein de choses sans aide extérieure. Pour le
plaisir de collaborer à un projet entre bénévoles parfois devenus amis, pour
acquérir de nouvelles compétences, parce que chaque économie d'argent fait
baisser le prix d'entrée au festival et contribue à le rendre plus
accessible.
</p>
</dd>
</dl>
<div className={styles.inputWrapper}>
<div className={styles.leftCol}>
<div className={styles.multipleChoiceTitle}>
Bref, as-tu le temps et l'envie de voir si tu peux aider en amont du
festival ?
</div>
</div>
<div className={styles.rightCol}>
<div className={styles.rightColContainer}>
{[
{ value: "oui", desc: "Oui" },
{ value: "non", desc: "Non" },
{ value: "", desc: "Ne sais pas" },
].map((option) => (
<label className={styles.shortAnswerLabel} key={option.value}>
<input
type="radio"
name="canHelpBefore"
value={option.value}
onChange={sendRadioboxDispatch(setCanHelpBefore)}
checked={canHelpBefore === option.value}
/>{" "}
{option.desc}
</label>
))}
</div>
</div>
</div>
<dl className={styles.registerIntro}>
<dd>
{canHelpBefore === "oui" && (
<p>
Génial ! Quand tu auras fini de t'inscrire et que tu seras identifié sur
le site, nous t'en parlerons plus en détail.
</p>
)}
{canHelpBefore === "non" && (
<p>
Aucun souci tu nous seras d'une aide précieuse le jour J c'est déjà
énorme !
</p>
)}
<p>
Si tu changes d'avis, il sera possible de revenir sur cette décision dans
ton profil sur le site.
</p>
</dd>
</dl>
</>
)
const pelMember = enableRegistering && ( const pelMember = enableRegistering && (
<> <>
<dl className={styles.registerIntro}> <dl className={styles.registerIntro}>
@ -744,9 +545,7 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const submitButton = ( const submitButton = (
<> <>
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<FormButton onClick={onSubmit} disabled={!potentialVolunteer && alreadyVolunteer}> <FormButton onClick={onSubmit}>Envoyer</FormButton>
Envoyer
</FormButton>
</div> </div>
<div className={styles.formReactions}> <div className={styles.formReactions}>
{sendingElement} {sendingElement}
@ -759,21 +558,13 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
return ( return (
<form> <form>
{intro} {intro}
{potentialVolunteerQuestion} {enableRegistering && commentQuestion}
{alreadyVolunteerQuestion} {cameAsVisitor}
{meeting}
{(potentialVolunteer || !alreadyVolunteer) && ( {pelMember}
<> {nameMobileEmail}
{enableRegistering && commentQuestion} {!enableRegistering && commentQuestion}
{cameAsVisitor} {howToContact !== "Aucun" && submitButton}
{meeting}
{helpBefore}
{pelMember}
{nameMobileEmail}
{!enableRegistering && commentQuestion}
{howToContact !== "Aucun" && submitButton}
</>
)}
</form> </form>
) )
} }

View File

@ -1,6 +1,6 @@
import { FC, memo } from "react" import { FC, memo } from "react"
// import DayWishes from "./DayWishes/DayWishes" import DayWishes from "./DayWishes/DayWishes"
// import DayWishesFormModal from "./DayWishesForm/DayWishesFormModal" import DayWishesFormModal from "./DayWishesForm/DayWishesFormModal"
// import Hosting from "./Hosting/Hosting" // import Hosting from "./Hosting/Hosting"
// import HostingFormModal from "./HostingForm/HostingFormModal" // import HostingFormModal from "./HostingForm/HostingFormModal"
// import Meals from "./Meals/Meals" // import Meals from "./Meals/Meals"
@ -12,7 +12,7 @@ import { FC, memo } from "react"
// import VolunteerTeam from "./VolunteerTeam/VolunteerTeam" // import VolunteerTeam from "./VolunteerTeam/VolunteerTeam"
import withUserConnected from "../../utils/withUserConnected" import withUserConnected from "../../utils/withUserConnected"
import ContentTitle from "../ui/Content/ContentTitle" import ContentTitle from "../ui/Content/ContentTitle"
// import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm" import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm"
// import { fetchFor as fetchForHostingForm } from "./HostingForm/HostingForm" // import { fetchFor as fetchForHostingForm } from "./HostingForm/HostingForm"
// import { fetchFor as fetchForMealsForm } from "./MealsForm/MealsForm" // import { fetchFor as fetchForMealsForm } from "./MealsForm/MealsForm"
// import { fetchFor as fetchForParticipationDetailsForm } from "./ParticipationDetailsForm/ParticipationDetailsForm" // import { fetchFor as fetchForParticipationDetailsForm } from "./ParticipationDetailsForm/ParticipationDetailsForm"
@ -39,9 +39,9 @@ const Board: FC = (): JSX.Element => {
{retex && <BrunchFormModal />} {retex && <BrunchFormModal />}
{retex && <Retex />} {retex && <Retex />}
{retex && <RetexFormModal />} {retex && <RetexFormModal />}
{/* <DayWishes /> <DayWishes />
<DayWishesFormModal /> <DayWishesFormModal />
<ParticipationDetails /> {/* <ParticipationDetails />
<ParticipationDetailsFormModal /> <ParticipationDetailsFormModal />
<TeamWishes /> <TeamWishes />
<TeamWishesFormModal /> <TeamWishesFormModal />
@ -60,7 +60,7 @@ export const fetchFor = [
...fetchForPersonalInfoForm, ...fetchForPersonalInfoForm,
...fetchForRetexForm, ...fetchForRetexForm,
...fetchForBrunchForm, ...fetchForBrunchForm,
// ...fetchForDayWishesForm, ...fetchForDayWishesForm,
// ...fetchForHostingForm, // ...fetchForHostingForm,
// ...fetchForMealsForm, // ...fetchForMealsForm,
// ...fetchForParticipationDetailsForm, // ...fetchForParticipationDetailsForm,

View File

@ -27,7 +27,10 @@ const Brunch: FC<Props> = (): JSX.Element | null => {
)} )}
{question1 === 0 && <>Je ne viendrai pas au brunch </>} {question1 === 0 && <>Je ne viendrai pas au brunch </>}
{question1 > 0 && ( {question1 > 0 && (
<>Je viendrai au brunch{question1 > 1 && <> avec {question1} personne(s)</>}</> <>
Je viendrai au brunch
{question1 > 1 && <> avec {+question1 - 1} personne(s)</>}
</>
)} )}
</div> </div>
<div className={styles.editButton}> <div className={styles.editButton}>

View File

@ -18,22 +18,22 @@ const DayWishes: FC = (): JSX.Element | null => {
<div className={styles.title}>Mes présences</div> <div className={styles.title}>Mes présences</div>
{participation === "non" && ( {participation === "non" && (
<div className={styles.participationLabel}> <div className={styles.participationLabel}>
Je <b>ne participerai pas</b> à PeL 2022 :( Je <b>ne participerai pas</b> à PeL 2023 :(
</div> </div>
)} )}
{participation === "oui" && ( {participation === "oui" && (
<div className={styles.participationLabel}> <div className={styles.participationLabel}>
Je <b className={styles.yesParticipation}>participerai</b> à PeL 2022 ! Je <b className={styles.yesParticipation}>participerai</b> à PeL 2023 !
</div> </div>
)} )}
{participation === "peut-etre" && ( {participation === "peut-etre" && (
<div className={styles.participationLabel}> <div className={styles.participationLabel}>
Je <b>ne sais pas encore</b> si je participerai à PeL 2022 Je <b>ne sais pas encore</b> si je participerai à PeL 2023
</div> </div>
)} )}
{participation === "inconnu" && ( {participation === "inconnu" && (
<div className={styles.lineEmpty}> <div className={styles.lineEmpty}>
Participation à PeL 2022{" "} Participation à PeL 2023{" "}
<span className={styles.lineEmpty}>non renseignées</span> <span className={styles.lineEmpty}>non renseignées</span>
</div> </div>
)} )}

View File

@ -72,7 +72,7 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
<div className={styles.leftCol}> <div className={styles.leftCol}>
<div className={styles.participationTitle}> <div className={styles.participationTitle}>
Si les conditions sanitaires te le permettent, souhaites-tu être bénévole à Si les conditions sanitaires te le permettent, souhaites-tu être bénévole à
PeL 2022 ? PeL les 24 & 25 juin 2023 ?
</div> </div>
</div> </div>
<div className={styles.rightCol}> <div className={styles.rightCol}>
@ -124,6 +124,8 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
<div className={styles.leftCol}> <div className={styles.leftCol}>
<div className={styles.dayWishesTitle}> <div className={styles.dayWishesTitle}>
Quels jours viendras-tu ?<br /> Quels jours viendras-tu ?<br />
Il est tôt pour le dire, tu peux y repondre plus tard sur la page profil.
<br />
(Minimum 2 jours dont l'un sera samedi ou dimanche, idéalement samedi{" "} (Minimum 2 jours dont l'un sera samedi ou dimanche, idéalement samedi{" "}
<b>et</b> dimanche ^^) <b>et</b> dimanche ^^)
</div> </div>

View File

@ -8,8 +8,10 @@ import DayWishesForm, {
import ErrorBoundary from "./ErrorBoundary" import ErrorBoundary from "./ErrorBoundary"
import GameList from "./GameList" import GameList from "./GameList"
import Loading from "./Loading" import Loading from "./Loading"
import LoanBoxList, { fetchFor as fetchForLoan } from "./Loan/LoanBoxList" import LoaningIntro from "./Loan/LoaningIntro"
import LoanIntro from "./Loan/LoanIntro" import Loaning, { fetchFor as fetchForLoaning } from "./Loan/Loaning"
import LoansIntro from "./Loan/LoansIntro"
import Loans, { fetchFor as fetchForLoans } from "./Loan/Loans"
import LoginForm from "./LoginForm" import LoginForm from "./LoginForm"
import KnowledgeBoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/KnowledgeBoxList" import KnowledgeBoxList, { fetchFor as fetchForKnowledge } from "./Knowledge/KnowledgeBoxList"
import KnowledgeCard, { fetchFor as fetchForKnowledgeCard } from "./Knowledge/KnowledgeCard" import KnowledgeCard, { fetchFor as fetchForKnowledgeCard } from "./Knowledge/KnowledgeCard"
@ -45,9 +47,12 @@ export {
fetchForKnowledge, fetchForKnowledge,
KnowledgeIntro, KnowledgeIntro,
Loading, Loading,
LoanBoxList, LoaningIntro,
fetchForLoan, Loaning,
LoanIntro, fetchForLoaning,
LoansIntro,
Loans,
fetchForLoans,
LoginForm, LoginForm,
Asks, Asks,
fetchForAsks, fetchForAsks,

View File

@ -0,0 +1,33 @@
import { FC, memo } from "react"
import { useSelector } from "react-redux"
import { RouteComponentProps } from "react-router-dom"
import { Helmet } from "react-helmet"
import { AppThunk } from "../../store"
import styles from "./styles.module.scss"
import { LoaningIntro, Loaning, fetchForLoaning } from "../../components"
import { selectUserJwtToken } from "../../store/auth"
export type Props = RouteComponentProps
const LoaningPage: FC<Props> = (): JSX.Element => {
const jwtToken = useSelector(selectUserJwtToken)
if (jwtToken === undefined) return <p>Loading...</p>
if (!jwtToken) {
return <div>Besoin d'être identifié</div>
}
return (
<div className={styles.loaningPage}>
<div className={styles.loaningContent}>
<Helmet title="LoaningPage" />
<LoaningIntro />
<Loaning />
</div>
</div>
)
}
// Fetch server-side data here
export const loadData = (): AppThunk[] => [...fetchForLoaning.map((f) => f())]
export default memo(LoaningPage)

16
src/pages/Loaning/index.tsx Executable file
View File

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

View File

@ -1,9 +1,13 @@
@import "../../theme/mixins"; @import "../../theme/mixins";
.loanPage { .loaningPage {
@include page-wrapper-center; @include page-wrapper-center;
} }
.loanContent { .loaningContent {
@include page-content-wrapper(700px); @include page-content-wrapper(700px);
} }
.hide {
display: none;
}

View File

@ -5,29 +5,29 @@ import { Helmet } from "react-helmet"
import { AppThunk } from "../../store" import { AppThunk } from "../../store"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import { LoanBoxList, LoanIntro, fetchForLoan } from "../../components" import { LoansIntro, Loans, fetchForLoans } from "../../components"
import { selectUserJwtToken } from "../../store/auth" import { selectUserJwtToken } from "../../store/auth"
export type Props = RouteComponentProps export type Props = RouteComponentProps
const LoanPage: FC<Props> = (): JSX.Element => { const LoansPage: FC<Props> = (): JSX.Element => {
const jwtToken = useSelector(selectUserJwtToken) const jwtToken = useSelector(selectUserJwtToken)
if (jwtToken === undefined) return <p>Loading...</p> if (jwtToken === undefined) return <p>Loading...</p>
if (!jwtToken) { if (!jwtToken) {
return <div>Besoin d'être identifié</div> return <div>Besoin d'être identifié</div>
} }
return ( return (
<div className={styles.loanPage}> <div className={styles.loaningPage}>
<div className={styles.loanContent}> <div className={styles.loaningContent}>
<Helmet title="LoanPage" /> <Helmet title="LoansPage" />
<LoanIntro /> <LoansIntro />
<LoanBoxList /> <Loans />
</div> </div>
</div> </div>
) )
} }
// Fetch server-side data here // Fetch server-side data here
export const loadData = (): AppThunk[] => [...fetchForLoan.map((f) => f())] export const loadData = (): AppThunk[] => [...fetchForLoans.map((f) => f())]
export default memo(LoanPage) export default memo(LoansPage)

View File

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

View File

@ -0,0 +1,13 @@
@import "../../theme/mixins";
.loaningPage {
@include page-wrapper-center;
}
.loaningContent {
@include page-content-wrapper(700px);
}
.hide {
display: none;
}

View File

@ -10,7 +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 AsyncLoan, { loadData as loadLoanData } from "../pages/Loan" import AsyncLoans, { loadData as loadLoansData } from "../pages/Loans"
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"
@ -47,8 +48,13 @@ export default [
}, },
{ {
path: "/emprunts", path: "/emprunts",
component: AsyncLoan, component: AsyncLoans,
loadData: loadLoanData, loadData: loadLoansData,
},
{
path: "/emprunter",
component: AsyncLoaning,
loadData: loadLoaningData,
}, },
{ {
path: "/fiches", path: "/fiches",

View File

@ -16,7 +16,7 @@ export const detailedBoxListGet = expressAccessor.get(async (list) => {
const toBeAsked: DetailedBox[] = [] const toBeAsked: DetailedBox[] = []
gameList.forEach((game) => { gameList.forEach((game) => {
const box: Box | undefined = list.find((g) => g.gameId === game.id) const box: Box | undefined = list.find((b) => b.gameId === game.id)
if ((box && box.unplayable) || (!box && !game.toBeKnown)) { if ((box && box.unplayable) || (!box && !game.toBeKnown)) {
return return
} }

View File

@ -1,8 +1,24 @@
import axios from "axios" import axios from "axios"
import { Parser } from "xml2js" import { Parser } from "xml2js"
import { assign, cloneDeep, find } from "lodash" import {
assign,
cloneDeep,
every,
find,
groupBy,
last,
mapValues,
reduce,
some,
sortBy,
} from "lodash"
import ExpressAccessors from "./expressAccessors" import ExpressAccessors from "./expressAccessors"
import { Game, GameWithoutId, translationGame } from "../../services/games" import { getSheet } from "./accessors"
import { Game, GameWithoutId, GameWithVolunteers, translationGame } from "../../services/games"
import { translationVolunteer, Volunteer, VolunteerWithoutId } from "../../services/volunteers"
import { translationBox, Box, BoxWithoutId } from "../../services/boxes"
import { getUniqueNickname } from "./tools"
import { gameTitleCategory, gameTitleExactCategory, gameTitleOrder } from "../../store/utils"
const expressAccessor = new ExpressAccessors<GameWithoutId, Game>( const expressAccessor = new ExpressAccessors<GameWithoutId, Game>(
"Games", "Games",
@ -10,7 +26,7 @@ const expressAccessor = new ExpressAccessors<GameWithoutId, Game>(
translationGame translationGame
) )
export const gameListGet = expressAccessor.listGet() // export const gameListGet = expressAccessor.listGet()
// export const gameGet = expressAccessor.get() // export const gameGet = expressAccessor.get()
// export const gameAdd = expressAccessor.add() // export const gameAdd = expressAccessor.add()
// export const gameSet = expressAccessor.set() // export const gameSet = expressAccessor.set()
@ -18,7 +34,7 @@ export const gameListGet = expressAccessor.listGet()
export const gameDetailsUpdate = expressAccessor.listSet( export const gameDetailsUpdate = expressAccessor.listSet(
async (list, _body, _id, _roles, request) => { async (list, _body, _id, _roles, request) => {
request.setTimeout(500000) request.setTimeout(500000)
const newList = cloneDeep(list) const newList: Game[] = cloneDeep(list)
const data = await getData() const data = await getData()
const parser = new Parser() const parser = new Parser()
@ -98,3 +114,214 @@ async function getData(): Promise<string> {
} }
return data return data
} }
export const gameListGet = expressAccessor.get(async (list) => {
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
const boxList = await boxSheet.getList()
if (!boxList) {
throw Error("Unable to load boxList")
}
const toBeAsked: Game[] = []
list.forEach((game) => {
const box: Box | undefined = boxList.find((b) => b.gameId === game.id)
if (!box) {
return
}
toBeAsked.push({
id: game.id,
title: game.title,
bggId: game.bggId,
bggIdAlternative: game.bggIdAlternative,
bggPhoto: game.bggPhoto,
poufpaf: game.poufpaf,
playersMin: game.playersMin,
playersMax: game.playersMax,
duration: game.duration,
type: game.type,
} as Game)
})
return toBeAsked
})
export const gameWithVolunteersListGet = expressAccessor.get(
async (list, _body, id): Promise<GameWithVolunteers[]> => {
if (id <= 0) {
throw Error(`L'accès est réservé aux utilisateurs identifiés`)
}
const volunteerSheet = await getSheet<VolunteerWithoutId, Volunteer>(
"Volunteers",
new Volunteer(),
translationVolunteer
)
const volunteerList = await volunteerSheet.getList()
if (!volunteerList) {
throw Error("Unable to load volunteers")
}
const currentVolunteer: Volunteer | undefined = volunteerList.find((v) => v.id === id)
if (!currentVolunteer) {
throw Error(`Unknown volunteer ${id}`)
}
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
const boxList = await boxSheet.getList()
if (!boxList) {
throw Error("Unable to load boxes")
}
const giftGameIds = list
.filter((game) =>
every(
volunteerList,
(v) => !v.loanable.includes(game.id) && !v.playable.includes(game.id)
)
)
.map((game) => game.id)
const gamesToLoan = list
.filter((game) => currentVolunteer.loanable.includes(game.id))
.map((game) => ({
...cloneDeep(game),
volunteerNicknames: volunteerToNicknameList(
volunteerList.filter((v) => v.loanable.includes(game.id)),
volunteerList
),
toLoan: true,
boxCount: boxList.filter((b) => b.gameId === game.id).length,
}))
const gamesToGift = list
.filter(
(game) =>
giftGameIds.includes(game.id) && currentVolunteer.loanable.includes(game.id)
)
.map((game) => ({
...cloneDeep(game),
volunteerNicknames: volunteerToNicknameList(
volunteerList.filter((v) => v.loanable.includes(game.id)),
volunteerList
),
toLoan: false,
boxCount: boxList.filter((b) => b.gameId === game.id).length,
}))
return [...gamesToLoan, ...gamesToGift]
}
)
function volunteerToNicknameList(volunteers: Volunteer[], allVolunteers: Volunteer[]): string[] {
return volunteers
.map((v) => getUniqueNickname(allVolunteers, v))
.sort((a, b) => a.localeCompare(b))
}
export const gamesToGiveListGet = expressAccessor.get(async (list): Promise<string[]> => {
const volunteerSheet = await getSheet<VolunteerWithoutId, Volunteer>(
"Volunteers",
new Volunteer(),
translationVolunteer
)
const volunteerList = await volunteerSheet.getList()
if (!volunteerList) {
throw Error("Unable to load volunteers")
}
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
const boxList = await boxSheet.getList()
if (!boxList) {
throw Error("Unable to load boxes")
}
volunteerList[105].playable = []
const giftGameTitles = list
.filter((game) =>
every(
volunteerList,
(v) => !v.loanable.includes(game.id) && !v.playable.includes(game.id)
)
)
.map((game) => game.title)
return giftGameTitles
})
type GameCategory = { start: string; end: string; titles: string[] }
export const gameTitleOrderCategories = expressAccessor.get(
async (list): Promise<GameCategory[]> => {
const volunteerSheet = await getSheet<VolunteerWithoutId, Volunteer>(
"Volunteers",
new Volunteer(),
translationVolunteer
)
const volunteerList = await volunteerSheet.getList()
if (!volunteerList) {
throw Error("Unable to load volunteers")
}
const boxSheet = await getSheet<BoxWithoutId, Box>("Boxes", new Box(), translationBox)
const boxList = await boxSheet.getList()
if (!boxList) {
throw Error("Unable to load boxes")
}
volunteerList[105].playable = []
const giftGameIds = list
.filter((game) =>
every(
volunteerList,
(v) => !v.loanable.includes(game.id) && !v.playable.includes(game.id)
)
)
.map((game) => game.id)
const gamesToLoan = sortBy(
list.filter((game) => !giftGameIds.includes(game.id)).map((game) => cloneDeep(game)),
gameTitleOrder
)
const exaustiveCats = mapValues(groupBy(gamesToLoan, gameTitleCategory), (gameList) =>
gameList.map((game) => game.title)
)
const cats: GameCategory[] = reduce(
Object.entries(exaustiveCats),
(res, [exaustiveCat, catTitles]) => {
const prevCat = last(res)
const prevCount = prevCat?.titles.length || 0
if (prevCount === 0 || prevCount + catTitles.length > 10) {
const newCat = {
start: exaustiveCat,
end: exaustiveCat,
titles: [...catTitles],
}
res.push(newCat)
} else if (prevCat !== undefined) {
prevCat.end = exaustiveCat
catTitles.forEach((title) => prevCat.titles.push(title))
}
return res
},
[] as GameCategory[]
)
const catList: string[] = cats.map((cat) => `${cat.start}-${cat.end}`)
console.log(catList.join("\n"))
const availableGiftIds = giftGameIds.filter((id) =>
some(volunteerList, (v: Volunteer) => v.giftable.includes(id))
)
console.log("availableGiftIds", availableGiftIds)
gamesToLoan.forEach((g) => gameTitleExactCategory(g))
return cats
}
)
// _.mapValues(list, cat => cat.length)

View File

@ -1,9 +1,10 @@
// eslint-disable-next-line max-classes-per-file // eslint-disable-next-line max-classes-per-file
import path from "path" import path from "path"
import _ from "lodash" import _, { assign } from "lodash"
import { promises as fs } from "fs" import { promises as fs } from "fs"
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"
const DB_PATH = path.resolve(process.cwd(), "access/db.json") const DB_PATH = path.resolve(process.cwd(), "access/db.json")
const DB_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json") const DB_TO_LOAD_PATH = path.resolve(process.cwd(), "access/dbToLoad.json")
@ -275,6 +276,11 @@ function anonimizedDb(_s: States): States {
v.comment = v.id % 3 === 0 ? "Bonjour, j'adore l'initiative!" : "" v.comment = v.id % 3 === 0 ? "Bonjour, j'adore l'initiative!" : ""
}) })
} }
if (s.Retex) {
;(s.Retex as Retex[]).forEach((r) => {
assign(r, new Retex(), { id: r.id, dayWishes: r.dayWishes })
})
}
return s return s
} }

View File

@ -0,0 +1,16 @@
import { remove } from "lodash"
import { Volunteer } from "../../services/volunteers"
export function getUniqueNickname(list: Volunteer[], volunteer: Volunteer): string {
const lastnameList = list
.filter((v) => v.firstname === volunteer.firstname)
.map((v) => v.lastname)
let lastnamePrefix = ""
while (lastnameList.length > 1) {
lastnamePrefix += volunteer.lastname.charAt(lastnamePrefix.length)
// eslint-disable-next-line no-loop-func
remove(lastnameList, (lastname) => !lastname.startsWith(lastnamePrefix))
}
const nickname = `${volunteer.firstname}${lastnamePrefix ? ` ${lastnamePrefix}.` : ""}`
return nickname
}

View File

@ -1,6 +1,6 @@
import path from "path" import path from "path"
import * as fs from "fs" import * as fs from "fs"
import { assign, cloneDeep, max, omit, pick, remove } from "lodash" import { assign, cloneDeep, max, omit, pick } from "lodash"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import sgMail from "@sendgrid/mail" import sgMail from "@sendgrid/mail"
@ -24,6 +24,7 @@ import {
} from "../../services/volunteers" } from "../../services/volunteers"
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization" import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
import { getJwt } from "../secure" import { getJwt } from "../secure"
import { getUniqueNickname } from "./tools"
const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>( const expressAccessor = new ExpressAccessors<VolunteerWithoutId, Volunteer>(
"Volunteers", "Volunteers",
@ -577,17 +578,3 @@ export const volunteerLoanSet = expressAccessor.set(async (list, body, id) => {
} as VolunteerLoan, } as VolunteerLoan,
} }
}) })
function getUniqueNickname(list: Volunteer[], volunteer: Volunteer): string {
const lastnameList = list
.filter((v) => v.firstname === volunteer.firstname)
.map((v) => v.lastname)
let lastnamePrefix = ""
while (lastnameList.length > 1) {
lastnamePrefix += volunteer.lastname.charAt(lastnamePrefix.length)
// eslint-disable-next-line no-loop-func
remove(lastnameList, (lastname) => !lastname.startsWith(lastnamePrefix))
}
const nickname = `${volunteer.firstname}${lastnamePrefix ? ` ${lastnamePrefix}.` : ""}`
return nickname
}

View File

@ -19,7 +19,13 @@ import certbotRouter from "../routes/certbot"
import { hasSecret, secure } from "./secure" import { hasSecret, secure } from "./secure"
import { announcementListGet } from "./gsheets/announcements" import { announcementListGet } from "./gsheets/announcements"
import { detailedBoxListGet } from "./gsheets/boxes" import { detailedBoxListGet } from "./gsheets/boxes"
import { gameListGet, gameDetailsUpdate } from "./gsheets/games" import {
gameListGet,
gameDetailsUpdate,
gameWithVolunteersListGet,
gamesToGiveListGet,
gameTitleOrderCategories,
} from "./gsheets/games"
import { postulantAdd } from "./gsheets/postulants" import { postulantAdd } from "./gsheets/postulants"
import { teamListGet } from "./gsheets/teams" import { teamListGet } from "./gsheets/teams"
import { import {
@ -98,6 +104,8 @@ app.get(
app.get("/GameDetailsUpdate", gameDetailsUpdate) app.get("/GameDetailsUpdate", gameDetailsUpdate)
app.get("/BoxDetailedListGet", detailedBoxListGet) app.get("/BoxDetailedListGet", detailedBoxListGet)
app.get("/GameListGet", gameListGet) app.get("/GameListGet", gameListGet)
app.get("/GamesToGiveListGet", gamesToGiveListGet)
app.get("/GameTitleOrderCategories", gameTitleOrderCategories)
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet) app.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
app.get("/WishListGet", wishListGet) app.get("/WishListGet", wishListGet)
app.post("/WishAdd", wishAdd) app.post("/WishAdd", wishAdd)
@ -109,6 +117,7 @@ app.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet)
// Secured APIs // Secured APIs
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet) app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
app.get("/GameWithVolunteersListGet", secure as RequestHandler, gameWithVolunteersListGet)
app.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation) app.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation)
app.post("/RetexSet", secure as RequestHandler, retexSet) app.post("/RetexSet", secure as RequestHandler, retexSet)
app.get("/TeamListGet", teamListGet) app.get("/TeamListGet", teamListGet)

View File

@ -90,6 +90,37 @@ export default class ServiceAccessors<
} }
} }
securedCustomListGet<InputElements extends Array<any>, OutputType>(
apiName: string
): (
jwt: string,
...params: InputElements
) => Promise<{
data?: any
error?: Error
}> {
interface ElementGetResponse {
data?: any
error?: Error
}
return async (jwt: string, ...params: InputElements): Promise<ElementGetResponse> => {
try {
const auth = { headers: { Authorization: `Bearer ${jwt}` } }
const fullAxiosConfig = _.defaultsDeep(auth, axiosConfig)
const { data } = await axios.get(
`${config.API_URL}/${this.elementName}${apiName}`,
{ ...fullAxiosConfig, params }
)
if (data.error) {
throw Error(data.error)
}
return { data } as { data: OutputType }
} catch (error) {
return { error: error as Error }
}
}
}
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
add(): (volunteerWithoutId: ElementNoId) => Promise<{ add(): (volunteerWithoutId: ElementNoId) => Promise<{
data?: Element data?: Element

View File

@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file */
export class Game { export class Game {
id = 0 id = 0
@ -26,6 +27,14 @@ export class Game {
toBeKnown = false toBeKnown = false
} }
export class GameWithVolunteers extends Game {
volunteerNicknames: string[] = []
toLoan = true
boxCount = 0
}
export const translationGame: { [k in keyof Game]: string } = { export const translationGame: { [k in keyof Game]: string } = {
id: "id", id: "id",
title: "titre", title: "titre",

View File

@ -1,5 +1,5 @@
import ServiceAccessors from "./accessors" import ServiceAccessors from "./accessors"
import { elementName, Game, GameWithoutId } from "./games" import { elementName, Game, GameWithVolunteers, GameWithoutId } from "./games"
const serviceAccessors = new ServiceAccessors<GameWithoutId, Game>(elementName) const serviceAccessors = new ServiceAccessors<GameWithoutId, Game>(elementName)
@ -9,3 +9,8 @@ export const gameListGet = serviceAccessors.listGet()
// export const gameSet = serviceAccessors.set() // export const gameSet = serviceAccessors.set()
export const gameDetailsUpdate = serviceAccessors.securedCustomGet<[], Game[]>("DetailsUpdate") export const gameDetailsUpdate = serviceAccessors.securedCustomGet<[], Game[]>("DetailsUpdate")
export const gameWithVolunteersListGet = serviceAccessors.securedCustomListGet<
[],
GameWithVolunteers[]
>("WithVolunteersListGet")

View File

@ -1,7 +1,7 @@
import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit" import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit"
import { sortedUniqBy, sortBy } from "lodash" import { sortedUniqBy, sortBy } from "lodash"
import { StateRequest, toastError, elementListFetch } from "./utils" import { StateRequest, toastError, elementListFetch, gameTitleOrder } from "./utils"
import { DetailedBox } from "../services/boxes" import { DetailedBox } from "../services/boxes"
import { AppThunk, AppState, EntitiesRequest } from "." import { AppThunk, AppState, EntitiesRequest } from "."
import { detailedBoxListGet } from "../services/boxesAccessors" import { detailedBoxListGet } from "../services/boxesAccessors"
@ -59,9 +59,10 @@ export const selectBoxList = createSelector(
} }
) )
export const selectSortedUniqueDetailedBoxes = createSelector(selectBoxList, (boxes) => export const selectSortedUniqueDetailedBoxes = createSelector(selectBoxList, (boxes) => {
sortedUniqBy(sortBy(boxes, "title"), "title") const validBoxes = boxes.filter((box) => box) as DetailedBox[]
) return sortedUniqBy(sortBy(validBoxes, gameTitleOrder), gameTitleOrder)
})
export const selectContainerSortedDetailedBoxes = createSelector(selectBoxList, (boxes) => export const selectContainerSortedDetailedBoxes = createSelector(selectBoxList, (boxes) =>
sortBy(boxes, "container") sortBy(boxes, "container")

View File

@ -0,0 +1,73 @@
import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit"
import { sortedUniqBy, sortBy } from "lodash"
import { StateRequest, toastError, elementListFetch, gameTitleOrder } from "./utils"
import { GameWithVolunteers } from "../services/games"
import { AppThunk, AppState, EntitiesRequest } from "."
import { gameWithVolunteersListGet } from "../services/gamesAccessors"
const gameAdapter = createEntityAdapter<GameWithVolunteers>()
export const initialState = gameAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest)
const gameWithVolunteersList = createSlice({
name: "gameWithVolunteersList",
initialState,
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<GameWithVolunteers[]>) => {
state.readyStatus = "success"
gameAdapter.setAll(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default gameWithVolunteersList.reducer
export const { getRequesting, getSuccess, getFailure } = gameWithVolunteersList.actions
export const fetchGameWithVolunteersList = elementListFetch(
gameWithVolunteersListGet,
getRequesting,
getSuccess,
getFailure,
(error: Error) => toastError(`Erreur lors du chargement des jeux JAV: ${error.message}`)
)
const shouldFetchGameWithVolunteersList = (state: AppState) =>
state.gameWithVolunteersList.readyStatus !== "success"
export const fetchGameWithVolunteersListIfNeed = (): AppThunk => (dispatch, getState) => {
const { jwt } = getState().auth
if (shouldFetchGameWithVolunteersList(getState()))
return dispatch(fetchGameWithVolunteersList(jwt))
return null
}
export const selectGameWithVolunteersListState = (
state: AppState
): EntitiesRequest<GameWithVolunteers> => state.gameWithVolunteersList
export const selectGameWithVolunteersList = createSelector(
selectGameWithVolunteersListState,
({ ids, entities, readyStatus }) => {
if (readyStatus !== "success") return []
return ids.map((id) => entities[id])
}
)
export const selectSortedUniqueGamesWithVolunteers = createSelector(
selectGameWithVolunteersList,
(games) => {
const gameWithVolunteers = games.filter((game) => game) as GameWithVolunteers[]
return sortedUniqBy(sortBy(gameWithVolunteers, gameTitleOrder), gameTitleOrder)
}
)

View File

@ -5,6 +5,7 @@ import announcementList from "./announcementList"
import auth from "./auth" import auth from "./auth"
import boxList from "./boxList" import boxList from "./boxList"
import gameList from "./gameList" import gameList from "./gameList"
import gameWithVolunteersList from "./gameWithVolunteersList"
import gameDetailsUpdate from "./gameDetailsUpdate" import gameDetailsUpdate from "./gameDetailsUpdate"
import miscDiscordInvitation from "./miscDiscordInvitation" import miscDiscordInvitation from "./miscDiscordInvitation"
import miscMeetingDateList from "./miscMeetingDateList" import miscMeetingDateList from "./miscMeetingDateList"
@ -39,6 +40,7 @@ export default (history: History) => ({
auth, auth,
boxList, boxList,
gameList, gameList,
gameWithVolunteersList,
gameDetailsUpdate, gameDetailsUpdate,
miscDiscordInvitation, miscDiscordInvitation,
miscMeetingDateList, miscMeetingDateList,

View File

@ -4,6 +4,7 @@ import {
ActionCreatorWithPayload, ActionCreatorWithPayload,
ThunkDispatch, ThunkDispatch,
} from "@reduxjs/toolkit" } from "@reduxjs/toolkit"
import { find } from "lodash"
import { toast } from "react-toastify" import { toast } from "react-toastify"
import { AppState, AppThunk } from "." import { AppState, AppThunk } from "."
@ -143,3 +144,129 @@ export function elementValueFetch<Element>(
} }
} }
} }
export function gameTitleOrder(boxOrGame: { title: string }): string {
return boxOrGame.title
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.toLowerCase()
.replace(/^[^a-z0-9]+/, "")
.replace(/^(le |la |les |l'|the )/, "")
.replace(/[^a-z0-9]/g, "")
}
export function gameTitleCategory(boxOrGame: { title: string }, length = 3): string {
return gameTitleOrder(boxOrGame)
.substring(0, length)
.replace(/(?<=[0-9]).+/, "")
.toUpperCase()
}
export function gameTitleExactCategory(boxOrGame: { title: string }): string {
const cats = [
["1", "6"],
["7", "AB"],
["AC", "AK"],
["ALA", "ALO"],
["ALP", "AP"],
["AQ", "ARG"],
["ARH", "AT"],
["AU", "AV"],
["AW", "BAR"],
["BAS", "BER"],
["BES", "BLI"],
["BLJ", "BRE"],
["BRF", "CAC"],
["CAD", "CAP"],
["CAQ", "CEM"],
["CEN", "CHIN"],
["CHJ", "CK"],
["CL"],
["COA", "COL"],
["COM"],
["CON", "COP"],
["COQ", "CRA"],
["CRB", "DEB"],
["DEC", "DES"],
["DET", "DIC"],
["DID", "DOM"],
["DON", "DR"],
["DS", "ELG"],
["ELH", "EVE"],
["EVD", "FAM"],
["FAN", "FIC"],
["FID", "FOL"],
["FOM", "FUM"],
["FUN", "GEE"],
["GED", "GLA"],
["GLB", "GQ"],
["GR", "HAN"],
["HAO", "HIM"],
["HIN", "IL"],
["IM", "IS"],
["IT", "JE"],
["JD", "KAB"],
["KAC", "KEM"],
["KEN", "KILL"],
["KIM", "KOD"],
["KOE", "LAB"],
["LAC", "LIC"],
["LID", "LIP"],
["LIQ", "LOQ"],
["LOR", "LZ"],
["MAA", "MAN"],
["MAO", "MED"],
["MEE", "MIM"],
["MINE", "MOR"],
["MOS", "MYS"],
["MYT", "NE"],
["ND", "ODD"],
["ODE", "ONE"],
["ONF", "OU"],
["OV", "PAN"],
["PAO", "PEL"],
["PEM", "PIO"],
["PIP", "PLZ"],
["PO", "PRI"],
["PRJ", "PZ"],
["QI", "RAT"],
["RAU", "RI"],
["RJ", "ROO"],
["ROP", "SAL"],
["SAM", "SEI"],
["SEJ", "SIC"],
["SID", "SL"],
["SM", "SO"],
["SPA", "SPX"],
["SPY", "STE"],
["STD", "SUN"],
["SUO", "TAF"],
["TAG", "TD"],
["TE", "TIC"],
["TID", "TIL"],
["TIM"],
["TIN", "TO"],
["TP", "TRO"],
["TRP", "UNE"],
["UND", "VEM"],
["VEN", "WAT"],
["WAU", "WH"],
["WI", "YE"],
["YOGA", "ZOO"],
]
const gameCat = gameTitleCategory(boxOrGame, 4)
const foundCat = find(cats, (cat) =>
cat[1]
? gameCat.substring(0, cat[0].length) >= cat[0] &&
gameCat.substring(0, cat[1].length) <= `${cat[1]}`
: gameCat.substring(0, cat[0].length) === cat[0]
)
if (!foundCat) {
console.log("no game found for ", foundCat, boxOrGame.title)
}
return foundCat ? `${foundCat[0]}-${foundCat[1]}` : gameCat
}