diff --git a/.eslintignore b/.eslintignore
index 157c89f..d70ebaa 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,2 +1 @@
-public
-announces/*
\ No newline at end of file
+public
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 0ec8880..b1644c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,7 +24,6 @@ access/*
*.log
.idea
announces/*
-/styles.module.css
# VS Code stuff
.vscode/settings.json
diff --git a/.prettierignore b/.prettierignore
index d5651d6..e666141 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -9,4 +9,3 @@ public/*
*.log
node_modules/*
access/*
-announces/*
diff --git a/package.json b/package.json
index c2494c4..3b41ed0 100644
--- a/package.json
+++ b/package.json
@@ -84,6 +84,7 @@
"autoprefixer": "^10.2.6",
"axios": "^0.21.1",
"bcrypt": "^5.0.1",
+ "body-parser": "^1.20.0",
"chalk": "^4.1.1",
"classnames": "^2.3.1",
"compression": "^1.7.4",
diff --git a/src/components/Asks/AskPersonalInfo.tsx b/src/components/Asks/AskPersonalInfo.tsx
new file mode 100644
index 0000000..9498e48
--- /dev/null
+++ b/src/components/Asks/AskPersonalInfo.tsx
@@ -0,0 +1,36 @@
+import { get } from "lodash"
+import { useCallback } from "react"
+import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
+import { useAskTools, addAsk, answerLaterOnProfile } from "./utils"
+import PersonalInfoForm, {
+ fetchFor as fetchForPersonalInfoForm,
+} from "../VolunteerBoard/PersonalInfoForm/PersonalInfoForm"
+import { useUserPersonalInfo } from "../VolunteerBoard/personalInfo.utils"
+
+export function AskPersonalInfo(asks: JSX.Element[], id: number): void {
+ const { dispatch, jwtToken, volunteerAsks } = useAskTools()
+
+ const onSubmit = useCallback((): void => {
+ dispatch(
+ fetchVolunteerAsksSet(jwtToken, 0, {
+ hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
+ })
+ )
+ }, [dispatch, id, jwtToken, volunteerAsks?.hiddenAsks])
+
+ const [personalInfo] = useUserPersonalInfo()
+ const photo = get(personalInfo, "photo", false)
+ const needToShow = !/^[0-9]/.test(photo || "")
+
+ addAsk(
+ asks,
+ id,
+ volunteerAsks,
+ false,
+ needToShow,
+ {answerLaterOnProfile}
+ )
+}
+
+// Fetch server-side data here
+export const fetchFor = [...fetchForPersonalInfoForm]
diff --git a/src/components/Asks/index.tsx b/src/components/Asks/index.tsx
index 7aad7b0..79acb2d 100644
--- a/src/components/Asks/index.tsx
+++ b/src/components/Asks/index.tsx
@@ -6,7 +6,8 @@ import { AskWelcome } from "./AskWelcome"
import { AskDiscord, fetchFor as fetchForDiscord } from "./AskDiscord"
import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
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 { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
import {
AskParticipationDetails,
@@ -24,8 +25,9 @@ const Asks = (): JSX.Element | null => {
AskDayWishes(asks, 10)
AskTeamWishes(asks, 11)
AskParticipationDetails(asks, 12)
+ AskPersonalInfo(asks, 15)
AskHosting(asks, 20)
- AskMeals(asks, 22)
+ // AskMeals(asks, 22)
AskPushNotif(asks, 99)
@@ -63,7 +65,8 @@ export const fetchFor = [
...fetchForDiscord,
...fetchForDayWishes,
...fetchForHosting,
- ...fetchForMeals,
+ // ...fetchForMeals,
...fetchForTeamWishes,
...fetchForParticipationDetails,
+ ...fetchForPersonalInfo,
]
diff --git a/src/components/Modal/styles.module.scss b/src/components/Modal/styles.module.scss
index 0dd7a0c..9adae36 100755
--- a/src/components/Modal/styles.module.scss
+++ b/src/components/Modal/styles.module.scss
@@ -30,6 +30,7 @@
max-height: 80vh;
border-radius: 15px;
border: $border-large;
+ overflow-y: auto;
}
}
diff --git a/src/components/RegisterForm/index.tsx b/src/components/RegisterForm/index.tsx
index 5bcb36a..1e5095d 100644
--- a/src/components/RegisterForm/index.tsx
+++ b/src/components/RegisterForm/index.tsx
@@ -48,7 +48,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const [firstMeeting, setFirstMeeting] = useState("")
const [commentFirstMeeting, setCommentFirstMeeting] = useState("")
const [canHelpBefore, setCanHelpBefore] = useState("")
- const [pelMember, setPelMember] = useState(false)
const [howToContact, setHowToContact] = useState("Email")
const [sending, setSending] = useState(false)
const [changingBackground, setChangingBackground] = useState(0)
@@ -107,7 +106,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
mobile,
howToContact,
canHelpBefore,
- pelMember,
})
)
dispatch(
@@ -623,54 +621,20 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
>
)
- const pelMemberQuestion = enableRegistering && !potentialVolunteer && (
+ const pelMember = enableRegistering && (
<>
Association Paris est Ludique
Légalement il faut que le festival soit organisé par une structure, et c'est
- l'association Paris est Ludique ! qui s'en charge. Pour aider à
- organiser bénévolement le festival il faut donc en faire partie. Ça n'engage
- à rien et c'est gratuit, mais absolument nécessaire.
+ l'association Paris est Ludique ! qui s'en charge. Pour avoir un
+ droit de regard dessus, devenir bénévole à cette édition implique
+ automatiquement d'en devenir membre jusqu'à septembre prochain. Ça n'engage
+ à rien et c'est gratuit !
-
-
-
- Acceptes-tu de devenir membre de l'association Paris est Ludique ! ?
-
-
-
-
- {["Oui", "Non"].map((option) => (
-
- {" "}
- {option}
-
- ))}
-
-
-
- {!pelMember && (
-
-
-
- Tant que tu n'as pas accepté cette condition je suis désolé on ne peut
- pas continuer.
-
-
-
- )}
>
)
@@ -803,14 +767,10 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
{cameAsVisitor}
{meeting}
{helpBefore}
- {pelMemberQuestion}
- {(potentialVolunteer || pelMember) && (
- <>
- {nameMobileEmail}
- {!enableRegistering && commentQuestion}
- {howToContact !== "Aucun" && submitButton}
- >
- )}
+ {pelMember}
+ {nameMobileEmail}
+ {!enableRegistering && commentQuestion}
+ {howToContact !== "Aucun" && submitButton}
>
)}
diff --git a/src/components/TeamMembers/TeamMembers.tsx b/src/components/TeamMembers/TeamMembers.tsx
index 4a150c1..e133cfb 100644
--- a/src/components/TeamMembers/TeamMembers.tsx
+++ b/src/components/TeamMembers/TeamMembers.tsx
@@ -34,7 +34,7 @@ type VolunteerEmailProps = {
const VolunteerEmail: FC = withUserRole(ROLES.TEAMLEAD, ({ email }) => (
{email}
-))
+), null)
type DaysAvailabilityProps = {
volunteer: Volunteer
diff --git a/src/components/VolunteerBoard/Board.tsx b/src/components/VolunteerBoard/Board.tsx
index 678c246..a33ca43 100644
--- a/src/components/VolunteerBoard/Board.tsx
+++ b/src/components/VolunteerBoard/Board.tsx
@@ -15,12 +15,17 @@ import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm
import { fetchFor as fetchForHostingForm } from "./HostingForm/HostingForm"
import { fetchFor as fetchForMealsForm } from "./MealsForm/MealsForm"
import { fetchFor as fetchForParticipationDetailsForm } from "./ParticipationDetailsForm/ParticipationDetailsForm"
+import { fetchFor as fetchForPersonalInfoForm } from "./PersonalInfoForm/PersonalInfoForm"
import { fetchFor as fetchForTeamWishesForm } from "./TeamWishesForm/TeamWishesForm"
import VolunteerTeam from "./VolunteerTeam/VolunteerTeam"
+import PersonalInfo from "./PersonalInfo/PersonalInfo"
+import PersonalInfoFormModal from "./PersonalInfoForm/PersonalInfoFormModal"
const Board: FC = (): JSX.Element => (
<>
+
+
@@ -42,5 +47,6 @@ export const fetchFor = [
...fetchForHostingForm,
...fetchForMealsForm,
...fetchForParticipationDetailsForm,
+ ...fetchForPersonalInfoForm,
...fetchForTeamWishesForm,
]
diff --git a/src/components/VolunteerBoard/Meals/Meals.tsx b/src/components/VolunteerBoard/Meals/Meals.tsx
index fd6e155..210fcf5 100644
--- a/src/components/VolunteerBoard/Meals/Meals.tsx
+++ b/src/components/VolunteerBoard/Meals/Meals.tsx
@@ -1,9 +1,9 @@
-import { FC, memo, useCallback } from "react"
+import { FC, memo } from "react"
import { find, get } from "lodash"
import styles from "./styles.module.scss"
import { useUserMeals, mealDays, MealOption } from "../meals.utils"
-import useAction from "../../../utils/useAction"
-import { displayModal, MODAL_IDS } from "../../../store/ui"
+// import useAction from "../../../utils/useAction"
+// import { displayModal, MODAL_IDS } from "../../../store/ui"
import { useUserDayWishes } from "../daysWishes.utils"
const Meals: FC = (): JSX.Element | null => {
@@ -11,8 +11,8 @@ const Meals: FC = (): JSX.Element | null => {
const [userWishes] = useUserDayWishes()
const meals = get(userMeals, "meals", [])
const dayWishesString = get(userWishes, "dayWishes", [])
- const execDisplayModal = useAction(displayModal)
- const onEdit = useCallback(() => execDisplayModal(MODAL_IDS.MEALS), [execDisplayModal])
+ // const execDisplayModal = useAction(displayModal)
+ // const onEdit = useCallback(() => execDisplayModal(MODAL_IDS.MEALS), [execDisplayModal])
const mealChoices = mealDays.map((meal, i: number) =>
find(meal.options, { abbr: meals[i] || "" })
) as MealOption[]
@@ -53,10 +53,13 @@ const Meals: FC = (): JSX.Element | null => {
)}
-
+ {/*
Modifier
+
*/}
+
+ Plus modifiable
)
diff --git a/src/components/VolunteerBoard/MealsForm/MealsForm.tsx b/src/components/VolunteerBoard/MealsForm/MealsForm.tsx
index 122d941..dff4477 100644
--- a/src/components/VolunteerBoard/MealsForm/MealsForm.tsx
+++ b/src/components/VolunteerBoard/MealsForm/MealsForm.tsx
@@ -46,11 +46,8 @@ const MealsForm: FC = ({ children, afterSubmit }): JSX.Element => {
{mealDays[i].name}
- {i === 0 && <>, accompagné d'un délicieux brownie tout chocolat>}
- {i === 1 && (
- <>, accompagné du même brownie. Enfin, un autre que celui de la veille>
- )}{" "}
- :
+ {(i === 0 || i === 1) && <>, accompagné d'un délicieux brownie tout chocolat>}
+ {i === 2 && <>, accompagné d'une part de tarte indéterminée>} :
@@ -89,6 +86,13 @@ const MealsForm: FC
= ({ children, afterSubmit }): JSX.Element => {
return (
Mes repas
+
+
+ La composition exacte des repas ne sera pas connues avant le festival (risque de
+ changement d'ingrédient au dernier moment). Elle sera disponible au moment de
+ récupérer ton repas pour que tu puisses contrôler l'absence d'allergène.
+
+
{dayWishesString.includes("S") ? (
<>
{getBreakfeastElement("Samedi")}
diff --git a/src/components/VolunteerBoard/PersonalInfo/PersonalInfo.tsx b/src/components/VolunteerBoard/PersonalInfo/PersonalInfo.tsx
new file mode 100644
index 0000000..15a92fc
--- /dev/null
+++ b/src/components/VolunteerBoard/PersonalInfo/PersonalInfo.tsx
@@ -0,0 +1,40 @@
+import { FC, memo, useCallback } from "react"
+import get from "lodash/get"
+import styles from "./styles.module.scss"
+import { useUserPersonalInfo } from "../personalInfo.utils"
+import useAction from "../../../utils/useAction"
+import { displayModal, MODAL_IDS } from "../../../store/ui"
+
+const PersonalInfo: FC = (): JSX.Element | null => {
+ const [userWishes] = useUserPersonalInfo()
+ const firstname = get(userWishes, "firstname", "")
+ const lastname = get(userWishes, "lastname", "")
+ const photo = get(userWishes, "photo", "")
+ const execDisplayModal = useAction(displayModal)
+ const onEdit = useCallback(() => execDisplayModal(MODAL_IDS.PERSONALINFO), [execDisplayModal])
+
+ return (
+
+
Mes infos personnelles
+
+ {firstname || "Aucun prénom"} {lastname || "Aucun nom de famille"}
+
+
+ {/^[0-9]/.test(photo || "") && (
+
Ta photo est bien renseignée merci !
+ )}
+
+ {!/^[0-9]/.test(photo || "") && (
+
Il faudrait renseigner ta photo !
+ )}
+
+
+
+ Modifier
+
+
+
+ )
+}
+
+export default memo(PersonalInfo)
diff --git a/src/components/VolunteerBoard/PersonalInfo/styles.module.scss b/src/components/VolunteerBoard/PersonalInfo/styles.module.scss
new file mode 100755
index 0000000..7bf852d
--- /dev/null
+++ b/src/components/VolunteerBoard/PersonalInfo/styles.module.scss
@@ -0,0 +1,53 @@
+@import "../../../theme/variables";
+@import "../../../theme/mixins";
+
+.title {
+ padding-bottom: 10px;
+ font-weight: bold;
+}
+
+.personalInfoLabel {
+ margin-right: 5px;
+ font-style: bold;
+}
+
+.personalInfo {
+ @include inner-content-wrapper();
+
+ position: relative;
+ padding-right: 90px;
+}
+
+.personalInfoLabel,
+.commentLine {
+ margin-bottom: 5px;
+ span {
+ display: inline-block;
+ }
+}
+
+.lineEmpty {
+ color: $color-red;
+ font-style: italic;
+}
+
+.commentLineTitle {
+ padding-right: 5px;
+}
+
+.commentLineText {
+ font-style: italic;
+}
+
+.editButton {
+ @include vertical-center();
+
+ position: absolute;
+ right: 20px;
+
+ button {
+ color: $color-green;
+ font-weight: bold;
+ cursor: pointer;
+ }
+}
diff --git a/src/components/VolunteerBoard/PersonalInfoForm/PersonalInfoForm.tsx b/src/components/VolunteerBoard/PersonalInfoForm/PersonalInfoForm.tsx
new file mode 100644
index 0000000..4af8957
--- /dev/null
+++ b/src/components/VolunteerBoard/PersonalInfoForm/PersonalInfoForm.tsx
@@ -0,0 +1,155 @@
+import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from "react"
+import classnames from "classnames"
+import { get, set } from "lodash"
+import styles from "./styles.module.scss"
+import { useUserPersonalInfo } from "../personalInfo.utils"
+import FormButton from "../../Form/FormButton/FormButton"
+import { fetchVolunteerPersonalInfoSetIfNeed } from "../../../store/volunteerPersonalInfoSet"
+import IgnoreButton from "../../Form/IgnoreButton/IgnoreButton"
+
+type Props = {
+ children?: ReactNode | undefined
+ afterSubmit?: () => void | undefined
+}
+
+const PersonalInfoForm: FC
= ({ children, afterSubmit }): JSX.Element => {
+ const firstnameRef = useRef(null)
+ const lastnameRef = useRef(null)
+ const [photo, setPhoto] = useState("")
+ const [selectedImage, setSelectedImage] = useState(null)
+ const [userWishes, saveWishes] = useUserPersonalInfo()
+
+ useEffect(() => {
+ if (!userWishes) return
+ set(firstnameRef, "current.value", get(userWishes, "firstname", ""))
+ set(lastnameRef, "current.value", get(userWishes, "lastname", ""))
+ setPhoto(get(userWishes, "photo", ""))
+ }, [userWishes])
+
+ const onChoiceSubmit = useCallback(() => {
+ const firstname = get(firstnameRef, "current.value", "")
+ const lastname = get(lastnameRef, "current.value", "")
+ const reader = new FileReader()
+ reader.onload = async (event) => {
+ const photoData = event?.target?.result as string
+ if (!photoData) {
+ throw Error("Ce n'est pas une photo valide")
+ }
+ saveWishes(firstname, lastname, photoData)
+ if (afterSubmit) afterSubmit()
+ }
+ if (selectedImage) {
+ reader.readAsDataURL(selectedImage)
+ } else {
+ saveWishes(firstname, lastname, undefined)
+ if (afterSubmit) afterSubmit()
+ }
+ }, [selectedImage, saveWishes, afterSubmit])
+
+ return (
+
+
Mes infos personnelles
+
+
+
+
+
+
+
+
+ Ta photo de profil pour le trombinoscope de l'association :
+
+
+
+
+ {/^[0-9]/.test(photo || "") && !selectedImage && (
+
+
+
+ )}
+ {selectedImage && (
+
+
+
+ )}
+
+
+ {
+ console.log(event?.target?.files?.[0])
+ setSelectedImage(event?.target?.files?.[0] || null)
+ }}
+ />
+
+
+
+
+
+
+ En tant que bénévole pour le festival, tu es automatiquement membre adhérent de
+ l'association Paris est Ludique ! pour avoir droit de regard sur son
+ fonctionnement. Aucune cotisation n'est demandée et aucun engagement autre
+ qu'être bénévole pendant le festival n'est nécessaire. Les statuts sont{" "}
+
+ accessibles ici
+
+ .
+
+
+
+
+ Enregistrer
+ {children === undefined && (
+ <>
+ {" "}
+
+ Annuler
+ {" "}
+ >
+ )}
+ {children !== undefined && (
+ <>
+ {" "}
+
+ {children}
+ {" "}
+ >
+ )}
+
+
+ )
+}
+
+PersonalInfoForm.defaultProps = {
+ children: undefined,
+ afterSubmit: undefined,
+}
+
+export default memo(PersonalInfoForm)
+
+// Fetch server-side data here
+export const fetchFor = [fetchVolunteerPersonalInfoSetIfNeed]
diff --git a/src/components/VolunteerBoard/PersonalInfoForm/PersonalInfoFormModal.tsx b/src/components/VolunteerBoard/PersonalInfoForm/PersonalInfoFormModal.tsx
new file mode 100644
index 0000000..b9fd074
--- /dev/null
+++ b/src/components/VolunteerBoard/PersonalInfoForm/PersonalInfoFormModal.tsx
@@ -0,0 +1,18 @@
+import { FC, memo, useCallback } from "react"
+import { hideModal, MODAL_IDS } from "../../../store/ui"
+import Modal from "../../Modal/Modal"
+import useAction from "../../../utils/useAction"
+import PersonalInfoForm from "./PersonalInfoForm"
+
+const PersonalInfoFormModal: FC = (): JSX.Element => {
+ const execHideModal = useAction(hideModal)
+ const afterFormSubmit = useCallback(() => execHideModal(), [execHideModal])
+
+ return (
+
+
+
+ )
+}
+
+export default memo(PersonalInfoFormModal)
diff --git a/src/components/VolunteerBoard/PersonalInfoForm/styles.module.scss b/src/components/VolunteerBoard/PersonalInfoForm/styles.module.scss
new file mode 100755
index 0000000..8cbdc37
--- /dev/null
+++ b/src/components/VolunteerBoard/PersonalInfoForm/styles.module.scss
@@ -0,0 +1,123 @@
+@import "../../../theme/variables";
+@import "../../../theme/mixins";
+
+.title {
+ padding: 15px 0;
+ font-weight: bold;
+ text-align: center;
+}
+
+.inputWrapper {
+ margin: 25px 0;
+
+ @include desktop {
+ display: flex;
+ }
+}
+
+.noTopMargin {
+ margin-top: 0;
+}
+
+.noBottomMargin {
+ margin-bottom: 0;
+}
+
+.leftCol {
+ flex: 0 0 220px;
+}
+
+.rightCol {
+ width: 100%;
+ text-align: center;
+}
+
+.firstnameTitle,
+.lastnameTitle {
+ display: inline-block;
+ width: 80px;
+ margin-bottom: 10px;
+}
+
+.firstnameLabel,
+.lastnameLabel {
+ text-align: left;
+ display: inline-block;
+ margin-bottom: 10px;
+ width: 120px;
+}
+
+.photoTitle {
+ display: inline-block;
+ width: 180px;
+ margin-bottom: 10px;
+}
+
+.photoLabel {
+ text-align: left;
+ display: inline-block;
+ margin-bottom: 10px;
+ width: 120px;
+}
+
+.personalInfoTitle {
+ display: inline-block;
+ width: 320px;
+ margin-bottom: 10px;
+}
+
+.personalInfoList {
+ @include clear-ul-style;
+
+ display: inline-block;
+ width: 204px;
+ text-align: center;
+}
+
+.personalInfoItem {
+ display: inline-block;
+ margin: 3px;
+}
+
+.personalInfoButton {
+ margin: 0;
+ padding: 7px 2px 6px;
+ border: 0;
+ border-radius: 0;
+ width: 90px;
+ text-align: center;
+ color: $color-grey-dark;
+ background-color: $color-grey-light;
+ cursor: pointer;
+
+ &.active {
+ color: $color-yellow;
+ background-color: $color-black;
+ }
+}
+
+.personalInfoCommentWrapper {
+ margin: 6px 0 14px;
+
+ label {
+ display: block;
+ padding: 6px 0 2px 4px;
+ }
+ textarea {
+ width: 100%;
+ height: 50px;
+ padding: 5px;
+ border: 1px solid $color-grey-light;
+ background-color: $color-grey-lighter;
+ outline: 0;
+ }
+}
+
+.buttonWrapper {
+ margin-bottom: 10px;
+ text-align: center;
+}
+
+.yesMembership {
+ color: $color-orange;
+}
diff --git a/src/components/VolunteerBoard/personalInfo.utils.ts b/src/components/VolunteerBoard/personalInfo.utils.ts
new file mode 100644
index 0000000..629dcb4
--- /dev/null
+++ b/src/components/VolunteerBoard/personalInfo.utils.ts
@@ -0,0 +1,37 @@
+import { shallowEqual, useSelector } from "react-redux"
+import { useCallback } from "react"
+import { selectUserJwtToken } from "../../store/auth"
+import { AppState } from "../../store"
+import { fetchVolunteerPersonalInfoSet } from "../../store/volunteerPersonalInfoSet"
+import useAction from "../../utils/useAction"
+import { VolunteerPersonalInfo } from "../../services/volunteers"
+
+type SetFunction = (
+ firstname: VolunteerPersonalInfo["firstname"],
+ lastname: VolunteerPersonalInfo["lastname"],
+ photo: VolunteerPersonalInfo["photo"] | undefined
+) => void
+
+export const useUserPersonalInfo = (): [VolunteerPersonalInfo | undefined, SetFunction] => {
+ const save = useAction(fetchVolunteerPersonalInfoSet)
+ const jwtToken = useSelector(selectUserJwtToken)
+ const userWishes = useSelector(
+ (state: AppState) => state.volunteerPersonalInfoSet?.entity,
+ shallowEqual
+ )
+
+ const saveWishes = useCallback(
+ (firstname, lastname, photo) => {
+ if (!userWishes) return
+ save(jwtToken, 0, {
+ id: userWishes.id,
+ firstname,
+ lastname,
+ photo,
+ })
+ },
+ [userWishes, save, jwtToken]
+ )
+
+ return [userWishes, saveWishes]
+}
diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts
index 1ca81da..3eceb07 100644
--- a/src/server/gsheets/volunteers.ts
+++ b/src/server/gsheets/volunteers.ts
@@ -1,3 +1,5 @@
+import path from "path"
+import * as fs from "fs"
import { assign, cloneDeep, max, omit, pick } from "lodash"
import bcrypt from "bcrypt"
import sgMail from "@sendgrid/mail"
@@ -16,6 +18,7 @@ import {
VolunteerParticipationDetails,
VolunteerTeamAssign,
VolunteerKnowledge,
+ VolunteerPersonalInfo,
} from "../../services/volunteers"
import { canonicalEmail, canonicalMobile, trim, validMobile } from "../../utils/standardization"
import { getJwt } from "../secure"
@@ -113,7 +116,6 @@ export const volunteerPartialAdd = expressAccessor.add(async (list, body) => {
mobile: canonicalMobile(params.mobile),
howToContact: trim(params.howToContact),
canHelpBefore: trim(params.canHelpBefore),
- pelMember: params.pelMember === true,
password1: passwordHash,
password2: passwordHash,
})
@@ -181,7 +183,7 @@ export const volunteerForgot = expressAccessor.set(async (list, bodyArray) => {
if (!volunteer) {
throw Error("Il n'y a aucun bénévole avec cet email")
}
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
const now = +new Date()
const timeSinceLastSent = now - lastForgot[volunteer.id]
@@ -238,11 +240,11 @@ export const volunteerAsksSet = expressAccessor.set(async (list, body, id) => {
throw Error(`On ne peut acceder qu'à ses propres questions`)
}
const notifChanges = body[1]
- const volunteer = list.find((v) => v.id === requestedId)
+ const volunteer: Volunteer | undefined = list.find((v) => v.id === requestedId)
if (!volunteer) {
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
}
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
if (notifChanges.hiddenAsks !== undefined) newVolunteer.hiddenAsks = notifChanges.hiddenAsks
if (notifChanges.acceptsNotifs !== undefined)
@@ -303,7 +305,7 @@ export const volunteerDayWishesSet = expressAccessor.set(async (list, body, id)
if (!volunteer) {
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
}
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
if (wishes.active !== undefined) {
newVolunteer.active = wishes.active
@@ -336,7 +338,7 @@ export const volunteerHostingSet = expressAccessor.set(async (list, body, id) =>
if (!volunteer) {
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
}
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
if (wishes.needsHosting !== undefined) {
newVolunteer.needsHosting = wishes.needsHosting
@@ -363,6 +365,62 @@ export const volunteerHostingSet = expressAccessor.set(async (list, body, id) =>
}
})
+export const volunteerPersonalInfoSet = expressAccessor.set(async (list, body, id) => {
+ const requestedId = +body[0] || id
+ if (requestedId !== id && requestedId !== 0) {
+ throw Error(`On ne peut acceder qu'à ses propres infos d'hébergement`)
+ }
+ const wishes = body[1] as VolunteerPersonalInfo
+ const volunteer: Volunteer | undefined = list.find((v) => v.id === requestedId)
+ if (!volunteer) {
+ throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
+ }
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
+
+ if (wishes.firstname !== undefined) {
+ newVolunteer.firstname = wishes.firstname
+ }
+ if (wishes.lastname !== undefined) {
+ newVolunteer.lastname = wishes.lastname
+ }
+ if (wishes.photo !== undefined) {
+ const filename = setNewPhoto(
+ requestedId,
+ wishes.photo,
+ /^[0-9]/.test(volunteer.photo) ? volunteer.photo : undefined
+ )
+ newVolunteer.photo = filename
+ }
+
+ return {
+ toDatabase: newVolunteer,
+ toCaller: {
+ id: newVolunteer.id,
+ firstname: newVolunteer.firstname,
+ lastname: newVolunteer.lastname,
+ photo: newVolunteer.photo,
+ } as VolunteerPersonalInfo,
+ }
+})
+
+function setNewPhoto(id: number, photoData: string, prevFilename: string | undefined): string {
+ const matches = photoData.match(/^data:.+\/([a-z0-9]+);base64,(.*)$/)
+ if (!matches) {
+ throw Error("Not image data ><")
+ }
+ const ext = matches[1]
+ const base64Data = matches[2]
+ const buffer = Buffer.from(base64Data, "base64")
+ const filename = `${id}.${ext}`
+ const filePath = path.resolve(process.cwd(), `public/photos/${filename}`)
+ if (prevFilename) {
+ const prevFilePath = path.resolve(process.cwd(), `public/photos/${prevFilename}`)
+ fs.unlinkSync(prevFilePath)
+ }
+ fs.writeFileSync(filePath, buffer)
+ return filename
+}
+
export const volunteerMealsSet = expressAccessor.set(async (list, body, id) => {
const requestedId = +body[0] || id
if (requestedId !== id && requestedId !== 0) {
@@ -373,7 +431,7 @@ export const volunteerMealsSet = expressAccessor.set(async (list, body, id) => {
if (!volunteer) {
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
}
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
if (wishes.meals !== undefined) {
newVolunteer.meals = wishes.meals
@@ -395,11 +453,11 @@ export const volunteerParticipationDetailsSet = expressAccessor.set(async (list,
)
}
const wishes = body[1] as VolunteerParticipationDetails
- const volunteer = list.find((v) => v.id === requestedId)
+ const volunteer: Volunteer | undefined = list.find((v) => v.id === requestedId)
if (!volunteer) {
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
}
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
if (wishes.tshirtSize !== undefined) {
newVolunteer.tshirtSize = wishes.tshirtSize
@@ -432,11 +490,11 @@ export const volunteerTeamAssignSet = expressAccessor.set(async (list, body, _id
}
const teamAssign = body[1] as VolunteerTeamAssign
- const volunteer = list.find((v) => v.id === teamAssign.volunteer)
+ const volunteer: Volunteer | undefined = list.find((v) => v.id === teamAssign.volunteer)
if (!volunteer) {
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${teamAssign.volunteer}`)
}
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
newVolunteer.team = teamAssign.team
return {
@@ -455,12 +513,12 @@ function getByEmail(list: T[], rawEmail: string): T
export const volunteerKnowledgeSet = expressAccessor.set(async (list, body, id) => {
const requestedId = +body[0] || id
- const volunteer = list.find((v) => v.id === requestedId)
+ const volunteer: Volunteer | undefined = list.find((v) => v.id === requestedId)
if (!volunteer) {
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
}
const knowledge = body[1] as VolunteerKnowledge
- const newVolunteer = cloneDeep(volunteer)
+ const newVolunteer: Volunteer = cloneDeep(volunteer)
if (knowledge?.ok !== undefined) newVolunteer.ok = knowledge.ok
if (knowledge?.bof !== undefined) newVolunteer.bof = knowledge.bof
if (knowledge?.niet !== undefined) newVolunteer.niet = knowledge.niet
diff --git a/src/server/index.ts b/src/server/index.ts
index 32c1274..ba42158 100755
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -31,6 +31,7 @@ import {
volunteerDiscordId,
volunteerLogin,
volunteerParticipationDetailsSet,
+ volunteerPersonalInfoSet,
volunteerSet,
volunteerTeamWishesSet,
volunteerTeamAssignSet,
@@ -52,13 +53,16 @@ notificationMain()
const app = express()
+// Allow receiving big images
+app.use(express.json({ limit: "200mb" }))
+app.use(express.urlencoded({ limit: "200mb" }))
+
// Use helmet to secure Express with various HTTP headers
app.use(helmet({ contentSecurityPolicy: false }))
// Prevent HTTP parameter pollution
app.use(hpp())
// Compress all requests
app.use(compression())
-
// Https with certbot and Let's Encrypt
if (!__DEV__) {
app.use("/.well-known/acme-challenge", certbotRouter)
@@ -114,6 +118,7 @@ app.post(
app.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet)
app.post("/VolunteerHostingSet", secure as RequestHandler, volunteerHostingSet)
app.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet)
+app.post("/VolunteerPersonalInfoSet", secure as RequestHandler, volunteerPersonalInfoSet)
app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet)
app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts
index b6f7511..0154ea4 100644
--- a/src/services/volunteers.ts
+++ b/src/services/volunteers.ts
@@ -2,10 +2,10 @@
export class Volunteer implements VolunteerPartial {
id = 0
- lastname = ""
-
firstname = ""
+ lastname = ""
+
email = ""
mobile = ""
@@ -40,8 +40,6 @@ export class Volunteer implements VolunteerPartial {
canHelpBefore = ""
- pelMember = false
-
hiddenAsks: number[] = []
created = new Date()
@@ -73,8 +71,8 @@ export class Volunteer implements VolunteerPartial {
export const translationVolunteer: { [k in keyof Volunteer]: string } = {
id: "id",
- lastname: "nom",
firstname: "prenom",
+ lastname: "nom",
email: "mail",
mobile: "telephone",
photo: "photo",
@@ -92,7 +90,6 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
teamWishesComment: "commentaireEnviesEquipe",
howToContact: "commentContacter",
canHelpBefore: "aideEnAmont",
- pelMember: "membrePel",
hiddenAsks: "questionsCachees",
created: "creation",
password1: "passe1",
@@ -110,10 +107,10 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
}
export class VolunteerPartial {
- lastname = ""
-
firstname = ""
+ lastname = ""
+
email = ""
mobile = ""
@@ -142,7 +139,6 @@ export const volunteerExample: Volunteer = {
teamWishesComment: "",
howToContact: "",
canHelpBefore: "",
- pelMember: false,
hiddenAsks: [],
created: new Date(0),
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
@@ -222,6 +218,13 @@ export interface VolunteerParticipationDetails {
food: Volunteer["food"]
}
+export interface VolunteerPersonalInfo {
+ id: Volunteer["id"]
+ firstname: Volunteer["firstname"]
+ lastname: Volunteer["lastname"]
+ photo: string
+}
+
export interface VolunteerTeamAssign {
id: Volunteer["id"]
volunteer: number
diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts
index 40a963e..f1904be 100644
--- a/src/services/volunteersAccessors.ts
+++ b/src/services/volunteersAccessors.ts
@@ -12,6 +12,7 @@ import {
VolunteerDiscordId,
VolunteerKnowledge,
VolunteerMeals,
+ VolunteerPersonalInfo,
} from "./volunteers"
const serviceAccessors = new ServiceAccessors(elementName)
@@ -51,6 +52,9 @@ export const volunteerParticipationDetailsSet =
"ParticipationDetailsSet"
)
+export const volunteerPersonalInfoSet =
+ serviceAccessors.securedCustomPost<[number, Partial]>("PersonalInfoSet")
+
export const volunteerTeamAssignSet =
serviceAccessors.securedCustomPost<[number, Partial]>("TeamAssignSet")
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index 7642271..79f0fe4 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -22,6 +22,7 @@ import volunteerList from "./volunteerList"
import volunteerLogin from "./volunteerLogin"
import volunteerKnowledgeSet from "./volunteerKnowledgeSet"
import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet"
+import volunteerPersonalInfoSet from "./volunteerPersonalInfoSet"
import volunteerSet from "./volunteerSet"
import volunteerTeamAssignSet from "./volunteerTeamAssignSet"
import volunteerTeamWishesSet from "./volunteerTeamWishesSet"
@@ -52,6 +53,7 @@ export default (history: History) => ({
volunteerLogin,
volunteerKnowledgeSet,
volunteerParticipationDetailsSet,
+ volunteerPersonalInfoSet,
volunteerSet,
volunteerTeamAssignSet,
volunteerTeamWishesSet,
diff --git a/src/store/ui.ts b/src/store/ui.ts
index 2de7700..a951e8c 100644
--- a/src/store/ui.ts
+++ b/src/store/ui.ts
@@ -31,5 +31,6 @@ export const MODAL_IDS = {
HOSTING: "HOSTING",
MEALS: "MEALS",
PARTICIPATIONDETAILS: "PARTICIPATIONDETAILS",
+ PERSONALINFO: "PERSONALINFO",
TEAMWISHES: "TEAMWISHES",
}
diff --git a/src/store/volunteerPersonalInfoSet.ts b/src/store/volunteerPersonalInfoSet.ts
new file mode 100644
index 0000000..a0ff054
--- /dev/null
+++ b/src/store/volunteerPersonalInfoSet.ts
@@ -0,0 +1,60 @@
+import { PayloadAction, createSlice } from "@reduxjs/toolkit"
+
+import { StateRequest, toastError, elementFetch } from "./utils"
+import { VolunteerPersonalInfo } from "../services/volunteers"
+import { AppThunk, AppState } from "."
+import { volunteerPersonalInfoSet } from "../services/volunteersAccessors"
+
+type StateVolunteerPersonalInfoSet = { entity?: VolunteerPersonalInfo } & StateRequest
+
+export const initialState: StateVolunteerPersonalInfoSet = {
+ readyStatus: "idle",
+}
+
+const volunteerPersonalInfoSetSlice = createSlice({
+ name: "volunteerPersonalInfoSet",
+ initialState,
+ reducers: {
+ getRequesting: (_) => ({
+ readyStatus: "request",
+ }),
+ getSuccess: (_, { payload }: PayloadAction) => ({
+ readyStatus: "success",
+ entity: payload,
+ }),
+ getFailure: (_, { payload }: PayloadAction) => ({
+ readyStatus: "failure",
+ error: payload,
+ }),
+ },
+})
+
+export default volunteerPersonalInfoSetSlice.reducer
+export const { getRequesting, getSuccess, getFailure } = volunteerPersonalInfoSetSlice.actions
+
+export const fetchVolunteerPersonalInfoSet = elementFetch(
+ volunteerPersonalInfoSet,
+ getRequesting,
+ getSuccess,
+ getFailure,
+ (error: Error) =>
+ toastError(`Erreur lors du chargement des choix de jours de présence: ${error.message}`)
+)
+
+const shouldFetchVolunteerPersonalInfoSet = (state: AppState, id: number) =>
+ state.volunteerPersonalInfoSet?.readyStatus !== "success" ||
+ (state.volunteerPersonalInfoSet?.entity && state.volunteerPersonalInfoSet?.entity?.id !== id)
+
+export const fetchVolunteerPersonalInfoSetIfNeed =
+ (id = 0, wishes: Partial = {}): AppThunk =>
+ (dispatch, getState) => {
+ let jwt = ""
+
+ if (!id) {
+ ;({ jwt, id } = getState().auth)
+ }
+ if (shouldFetchVolunteerPersonalInfoSet(getState(), id))
+ return dispatch(fetchVolunteerPersonalInfoSet(jwt, id, wishes))
+
+ return null
+ }
diff --git a/src/utils/withUserRole.tsx b/src/utils/withUserRole.tsx
index 7e43068..813d860 100644
--- a/src/utils/withUserRole.tsx
+++ b/src/utils/withUserRole.tsx
@@ -2,13 +2,17 @@ import React from "react"
import { useSelector } from "react-redux"
import { selectUserRoles } from "../store/auth"
-function withUserRole(requiredRole: string, Component: React.ComponentType) {
- return (props: T): JSX.Element => {
+function withUserRole(
+ requiredRole: string,
+ Component: React.ComponentType,
+ doShowMissingRole: true | null = true
+) {
+ return (props: T): JSX.Element | null => {
const roles = useSelector(selectUserRoles)
return roles.includes(requiredRole) ? (
) : (
- Missing role {requiredRole}
+ doShowMissingRole && Missing role {requiredRole}
)
}
}
diff --git a/yarn.lock b/yarn.lock
index 9321b01..953adde 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2907,6 +2907,24 @@ body-parser@1.19.0:
raw-body "2.4.0"
type-is "~1.6.17"
+body-parser@^1.20.0:
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5"
+ integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
+ dependencies:
+ bytes "3.1.2"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "2.0.0"
+ destroy "1.2.0"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ on-finished "2.4.1"
+ qs "6.10.3"
+ raw-body "2.5.1"
+ type-is "~1.6.18"
+ unpipe "1.0.0"
+
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@@ -3022,6 +3040,11 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+bytes@3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+ integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+
cacheable-request@^2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
@@ -3933,15 +3956,20 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+depd@2.0.0, depd@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
depd@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
-depd@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
- integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+destroy@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+ integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
destroy@~1.0.4:
version "1.0.4"
@@ -5588,6 +5616,17 @@ http-errors@1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
+http-errors@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+ integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
+ dependencies:
+ depd "2.0.0"
+ inherits "2.0.4"
+ setprototypeof "1.2.0"
+ statuses "2.0.1"
+ toidentifier "1.0.1"
+
http-errors@~1.7.2:
version "1.7.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
@@ -7772,6 +7811,13 @@ object.values@^1.1.5:
define-properties "^1.1.3"
es-abstract "^1.19.1"
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@@ -8640,6 +8686,13 @@ pupa@^2.1.1:
dependencies:
escape-goat "^2.0.0"
+qs@6.10.3:
+ version "6.10.3"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
+ integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
+ dependencies:
+ side-channel "^1.0.4"
+
qs@6.7.0:
version "6.7.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
@@ -8698,6 +8751,16 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
+raw-body@2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
+ integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
+ dependencies:
+ bytes "3.1.2"
+ http-errors "2.0.0"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
rc@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -9416,6 +9479,11 @@ setprototypeof@1.1.1:
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+setprototypeof@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+ integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
+
shallow-clone@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
@@ -9632,6 +9700,11 @@ stackframe@^1.1.1:
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
+statuses@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+ integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
@@ -10149,6 +10222,11 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+toidentifier@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+ integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
+
totalist@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"