mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-08 08:34:20 +02:00
Add PersonalInfo form and ask; Fix Meals
This commit is contained in:
parent
ff05fed345
commit
88549bf42d
@ -1,2 +1 @@
|
|||||||
public
|
public
|
||||||
announces/*
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,7 +24,6 @@ access/*
|
|||||||
*.log
|
*.log
|
||||||
.idea
|
.idea
|
||||||
announces/*
|
announces/*
|
||||||
/styles.module.css
|
|
||||||
|
|
||||||
# VS Code stuff
|
# VS Code stuff
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
@ -9,4 +9,3 @@ public/*
|
|||||||
*.log
|
*.log
|
||||||
node_modules/*
|
node_modules/*
|
||||||
access/*
|
access/*
|
||||||
announces/*
|
|
||||||
|
@ -84,6 +84,7 @@
|
|||||||
"autoprefixer": "^10.2.6",
|
"autoprefixer": "^10.2.6",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
|
"body-parser": "^1.20.0",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
|
36
src/components/Asks/AskPersonalInfo.tsx
Normal file
36
src/components/Asks/AskPersonalInfo.tsx
Normal file
@ -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,
|
||||||
|
<PersonalInfoForm afterSubmit={onSubmit}>{answerLaterOnProfile}</PersonalInfoForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch server-side data here
|
||||||
|
export const fetchFor = [...fetchForPersonalInfoForm]
|
@ -6,7 +6,8 @@ import { AskWelcome } from "./AskWelcome"
|
|||||||
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 { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
|
import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
|
||||||
import {
|
import {
|
||||||
AskParticipationDetails,
|
AskParticipationDetails,
|
||||||
@ -24,8 +25,9 @@ const Asks = (): JSX.Element | null => {
|
|||||||
AskDayWishes(asks, 10)
|
AskDayWishes(asks, 10)
|
||||||
AskTeamWishes(asks, 11)
|
AskTeamWishes(asks, 11)
|
||||||
AskParticipationDetails(asks, 12)
|
AskParticipationDetails(asks, 12)
|
||||||
|
AskPersonalInfo(asks, 15)
|
||||||
AskHosting(asks, 20)
|
AskHosting(asks, 20)
|
||||||
AskMeals(asks, 22)
|
// AskMeals(asks, 22)
|
||||||
|
|
||||||
AskPushNotif(asks, 99)
|
AskPushNotif(asks, 99)
|
||||||
|
|
||||||
@ -63,7 +65,8 @@ export const fetchFor = [
|
|||||||
...fetchForDiscord,
|
...fetchForDiscord,
|
||||||
...fetchForDayWishes,
|
...fetchForDayWishes,
|
||||||
...fetchForHosting,
|
...fetchForHosting,
|
||||||
...fetchForMeals,
|
// ...fetchForMeals,
|
||||||
...fetchForTeamWishes,
|
...fetchForTeamWishes,
|
||||||
...fetchForParticipationDetails,
|
...fetchForParticipationDetails,
|
||||||
|
...fetchForPersonalInfo,
|
||||||
]
|
]
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
border: $border-large;
|
border: $border-large;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
|||||||
const [firstMeeting, setFirstMeeting] = useState("")
|
const [firstMeeting, setFirstMeeting] = useState("")
|
||||||
const [commentFirstMeeting, setCommentFirstMeeting] = useState("")
|
const [commentFirstMeeting, setCommentFirstMeeting] = useState("")
|
||||||
const [canHelpBefore, setCanHelpBefore] = useState("")
|
const [canHelpBefore, setCanHelpBefore] = useState("")
|
||||||
const [pelMember, setPelMember] = useState(false)
|
|
||||||
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)
|
||||||
@ -107,7 +106,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
|||||||
mobile,
|
mobile,
|
||||||
howToContact,
|
howToContact,
|
||||||
canHelpBefore,
|
canHelpBefore,
|
||||||
pelMember,
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -623,54 +621,20 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
const pelMemberQuestion = enableRegistering && !potentialVolunteer && (
|
const pelMember = enableRegistering && (
|
||||||
<>
|
<>
|
||||||
<dl className={styles.registerIntro}>
|
<dl className={styles.registerIntro}>
|
||||||
<dt>Association Paris est Ludique</dt>
|
<dt>Association Paris est Ludique</dt>
|
||||||
<dd>
|
<dd>
|
||||||
<p>
|
<p>
|
||||||
Légalement il faut que le festival soit organisé par une structure, et c'est
|
Légalement il faut que le festival soit organisé par une structure, et c'est
|
||||||
l'association <i>Paris est Ludique !</i> qui s'en charge. Pour aider à
|
l'association <i>Paris est Ludique !</i> qui s'en charge. Pour avoir un
|
||||||
organiser bénévolement le festival il faut donc en faire partie. Ça n'engage
|
droit de regard dessus, devenir bénévole à cette édition implique
|
||||||
à rien et c'est gratuit, mais absolument nécessaire.
|
automatiquement d'en devenir membre jusqu'à septembre prochain. Ça n'engage
|
||||||
|
à rien et c'est gratuit !
|
||||||
</p>
|
</p>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<div className={styles.inputWrapper}>
|
|
||||||
<div className={styles.leftCol}>
|
|
||||||
<div className={styles.multipleChoiceTitle}>
|
|
||||||
Acceptes-tu de devenir membre de l'association <i>Paris est Ludique !</i> ?
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={styles.rightCol}>
|
|
||||||
<div className={styles.rightColContainer}>
|
|
||||||
{["Oui", "Non"].map((option) => (
|
|
||||||
<label className={styles.shortAnswerLabel} key={option}>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="pelMember"
|
|
||||||
onChange={sendBooleanRadioboxDispatch(
|
|
||||||
setPelMember,
|
|
||||||
option === "Oui"
|
|
||||||
)}
|
|
||||||
checked={pelMember === (option === "Oui")}
|
|
||||||
/>{" "}
|
|
||||||
{option}
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{!pelMember && (
|
|
||||||
<dl className={styles.registerIntro}>
|
|
||||||
<dd>
|
|
||||||
<p>
|
|
||||||
Tant que tu n'as pas accepté cette condition je suis désolé on ne peut
|
|
||||||
pas continuer.
|
|
||||||
</p>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -803,14 +767,10 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
|
|||||||
{cameAsVisitor}
|
{cameAsVisitor}
|
||||||
{meeting}
|
{meeting}
|
||||||
{helpBefore}
|
{helpBefore}
|
||||||
{pelMemberQuestion}
|
{pelMember}
|
||||||
{(potentialVolunteer || pelMember) && (
|
{nameMobileEmail}
|
||||||
<>
|
{!enableRegistering && commentQuestion}
|
||||||
{nameMobileEmail}
|
{howToContact !== "Aucun" && submitButton}
|
||||||
{!enableRegistering && commentQuestion}
|
|
||||||
{howToContact !== "Aucun" && submitButton}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
|
@ -34,7 +34,7 @@ type VolunteerEmailProps = {
|
|||||||
|
|
||||||
const VolunteerEmail: FC<VolunteerEmailProps> = withUserRole(ROLES.TEAMLEAD, ({ email }) => (
|
const VolunteerEmail: FC<VolunteerEmailProps> = withUserRole(ROLES.TEAMLEAD, ({ email }) => (
|
||||||
<td> {email}</td>
|
<td> {email}</td>
|
||||||
))
|
), null)
|
||||||
|
|
||||||
type DaysAvailabilityProps = {
|
type DaysAvailabilityProps = {
|
||||||
volunteer: Volunteer
|
volunteer: Volunteer
|
||||||
|
@ -15,12 +15,17 @@ 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"
|
||||||
|
import { fetchFor as fetchForPersonalInfoForm } from "./PersonalInfoForm/PersonalInfoForm"
|
||||||
import { fetchFor as fetchForTeamWishesForm } from "./TeamWishesForm/TeamWishesForm"
|
import { fetchFor as fetchForTeamWishesForm } from "./TeamWishesForm/TeamWishesForm"
|
||||||
import VolunteerTeam from "./VolunteerTeam/VolunteerTeam"
|
import VolunteerTeam from "./VolunteerTeam/VolunteerTeam"
|
||||||
|
import PersonalInfo from "./PersonalInfo/PersonalInfo"
|
||||||
|
import PersonalInfoFormModal from "./PersonalInfoForm/PersonalInfoFormModal"
|
||||||
|
|
||||||
const Board: FC = (): JSX.Element => (
|
const Board: FC = (): JSX.Element => (
|
||||||
<>
|
<>
|
||||||
<ContentTitle title="Profil spécifique au festival" />
|
<ContentTitle title="Profil spécifique au festival" />
|
||||||
|
<PersonalInfo />
|
||||||
|
<PersonalInfoFormModal />
|
||||||
<DayWishes />
|
<DayWishes />
|
||||||
<DayWishesFormModal />
|
<DayWishesFormModal />
|
||||||
<ParticipationDetails />
|
<ParticipationDetails />
|
||||||
@ -42,5 +47,6 @@ export const fetchFor = [
|
|||||||
...fetchForHostingForm,
|
...fetchForHostingForm,
|
||||||
...fetchForMealsForm,
|
...fetchForMealsForm,
|
||||||
...fetchForParticipationDetailsForm,
|
...fetchForParticipationDetailsForm,
|
||||||
|
...fetchForPersonalInfoForm,
|
||||||
...fetchForTeamWishesForm,
|
...fetchForTeamWishesForm,
|
||||||
]
|
]
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { FC, memo, useCallback } from "react"
|
import { FC, memo } from "react"
|
||||||
import { find, get } from "lodash"
|
import { find, get } from "lodash"
|
||||||
import styles from "./styles.module.scss"
|
import styles from "./styles.module.scss"
|
||||||
import { useUserMeals, mealDays, MealOption } from "../meals.utils"
|
import { useUserMeals, mealDays, MealOption } from "../meals.utils"
|
||||||
import useAction from "../../../utils/useAction"
|
// import useAction from "../../../utils/useAction"
|
||||||
import { displayModal, MODAL_IDS } from "../../../store/ui"
|
// import { displayModal, MODAL_IDS } from "../../../store/ui"
|
||||||
import { useUserDayWishes } from "../daysWishes.utils"
|
import { useUserDayWishes } from "../daysWishes.utils"
|
||||||
|
|
||||||
const Meals: FC = (): JSX.Element | null => {
|
const Meals: FC = (): JSX.Element | null => {
|
||||||
@ -11,8 +11,8 @@ const Meals: FC = (): JSX.Element | null => {
|
|||||||
const [userWishes] = useUserDayWishes()
|
const [userWishes] = useUserDayWishes()
|
||||||
const meals = get(userMeals, "meals", [])
|
const meals = get(userMeals, "meals", [])
|
||||||
const dayWishesString = get(userWishes, "dayWishes", [])
|
const dayWishesString = get(userWishes, "dayWishes", [])
|
||||||
const execDisplayModal = useAction(displayModal)
|
// const execDisplayModal = useAction(displayModal)
|
||||||
const onEdit = useCallback(() => execDisplayModal(MODAL_IDS.MEALS), [execDisplayModal])
|
// const onEdit = useCallback(() => execDisplayModal(MODAL_IDS.MEALS), [execDisplayModal])
|
||||||
const mealChoices = mealDays.map((meal, i: number) =>
|
const mealChoices = mealDays.map((meal, i: number) =>
|
||||||
find(meal.options, { abbr: meals[i] || "" })
|
find(meal.options, { abbr: meals[i] || "" })
|
||||||
) as MealOption[]
|
) as MealOption[]
|
||||||
@ -53,10 +53,13 @@ const Meals: FC = (): JSX.Element | null => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.editButton} key="edit">
|
{/* <div className={styles.editButton} key="edit">
|
||||||
<button type="button" onClick={onEdit}>
|
<button type="button" onClick={onEdit}>
|
||||||
Modifier
|
Modifier
|
||||||
</button>
|
</button>
|
||||||
|
</div> */}
|
||||||
|
<div className={styles.editButton} key="edit">
|
||||||
|
Plus modifiable
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -46,11 +46,8 @@ const MealsForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
|
|||||||
<div className={styles.leftCol}>
|
<div className={styles.leftCol}>
|
||||||
<div className={styles.needsMealsTitle}>
|
<div className={styles.needsMealsTitle}>
|
||||||
<b>{mealDays[i].name}</b>
|
<b>{mealDays[i].name}</b>
|
||||||
{i === 0 && <>, accompagné d'un délicieux brownie tout chocolat</>}
|
{(i === 0 || i === 1) && <>, accompagné d'un délicieux brownie tout chocolat</>}
|
||||||
{i === 1 && (
|
{i === 2 && <>, accompagné d'une part de tarte indéterminée</>} :
|
||||||
<>, accompagné du même brownie. Enfin, un autre que celui de la veille</>
|
|
||||||
)}{" "}
|
|
||||||
:
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.rightCol}>
|
<div className={styles.rightCol}>
|
||||||
@ -89,6 +86,13 @@ const MealsForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.title}>Mes repas</div>
|
<div className={styles.title}>Mes repas</div>
|
||||||
|
<div className={classnames(styles.inputWrapper)}>
|
||||||
|
<div>
|
||||||
|
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.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{dayWishesString.includes("S") ? (
|
{dayWishesString.includes("S") ? (
|
||||||
<>
|
<>
|
||||||
{getBreakfeastElement("Samedi")}
|
{getBreakfeastElement("Samedi")}
|
||||||
|
40
src/components/VolunteerBoard/PersonalInfo/PersonalInfo.tsx
Normal file
40
src/components/VolunteerBoard/PersonalInfo/PersonalInfo.tsx
Normal file
@ -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 (
|
||||||
|
<div className={styles.personalInfo}>
|
||||||
|
<div className={styles.title}>Mes infos personnelles</div>
|
||||||
|
<div className={styles.personalInfoLabel}>
|
||||||
|
{firstname || "Aucun prénom"} {lastname || "Aucun nom de famille"}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/^[0-9]/.test(photo || "") && (
|
||||||
|
<div className={styles.personalInfoLabel}>Ta photo est bien renseignée merci !</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!/^[0-9]/.test(photo || "") && (
|
||||||
|
<div className={styles.personalInfoLabel}>Il faudrait renseigner ta photo !</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.editButton}>
|
||||||
|
<button type="button" onClick={onEdit}>
|
||||||
|
Modifier
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(PersonalInfo)
|
53
src/components/VolunteerBoard/PersonalInfo/styles.module.scss
Executable file
53
src/components/VolunteerBoard/PersonalInfo/styles.module.scss
Executable file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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<Props> = ({ children, afterSubmit }): JSX.Element => {
|
||||||
|
const firstnameRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
const lastnameRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
const [photo, setPhoto] = useState("")
|
||||||
|
const [selectedImage, setSelectedImage] = useState<File | null>(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 (
|
||||||
|
<div>
|
||||||
|
<div className={styles.title}>Mes infos personnelles</div>
|
||||||
|
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.firstnameTitle}>Prénom :</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<input className={styles.firstnameLabel} type="text" ref={firstnameRef} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.lastnameTitle}>Nom :</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<input className={styles.lastnameLabel} type="text" ref={lastnameRef} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={classnames(styles.inputWrapper, styles.noBottomMargin)}>
|
||||||
|
<div className={styles.leftCol}>
|
||||||
|
<div className={styles.photoTitle}>
|
||||||
|
Ta photo de profil pour le trombinoscope de l'association :
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.rightCol}>
|
||||||
|
<label className={styles.photoLabel}>
|
||||||
|
{/^[0-9]/.test(photo || "") && !selectedImage && (
|
||||||
|
<div>
|
||||||
|
<img alt="actuelle" width="100px" src={`/photos/${photo}`} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedImage && (
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
alt="remplacement"
|
||||||
|
width="100px"
|
||||||
|
src={URL.createObjectURL(selectedImage)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name="myImage"
|
||||||
|
onChange={(event) => {
|
||||||
|
console.log(event?.target?.files?.[0])
|
||||||
|
setSelectedImage(event?.target?.files?.[0] || null)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<div>
|
||||||
|
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{" "}
|
||||||
|
<a
|
||||||
|
href="https://drive.google.com/file/d/1KJIJxZmDdJ_PBKJSh_W3yrHLzH0-awsE/view?usp=sharing"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
accessibles ici
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.buttonWrapper}>
|
||||||
|
<FormButton onClick={onChoiceSubmit}>Enregistrer</FormButton>
|
||||||
|
{children === undefined && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<FormButton onClick={afterSubmit} type="grey">
|
||||||
|
Annuler
|
||||||
|
</FormButton>{" "}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{children !== undefined && (
|
||||||
|
<>
|
||||||
|
{" "}
|
||||||
|
<IgnoreButton onClick={afterSubmit} text="Ignorer">
|
||||||
|
{children}
|
||||||
|
</IgnoreButton>{" "}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PersonalInfoForm.defaultProps = {
|
||||||
|
children: undefined,
|
||||||
|
afterSubmit: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(PersonalInfoForm)
|
||||||
|
|
||||||
|
// Fetch server-side data here
|
||||||
|
export const fetchFor = [fetchVolunteerPersonalInfoSetIfNeed]
|
@ -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 (
|
||||||
|
<Modal modalId={MODAL_IDS.PERSONALINFO}>
|
||||||
|
<PersonalInfoForm afterSubmit={afterFormSubmit} />
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(PersonalInfoFormModal)
|
123
src/components/VolunteerBoard/PersonalInfoForm/styles.module.scss
Executable file
123
src/components/VolunteerBoard/PersonalInfoForm/styles.module.scss
Executable file
@ -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;
|
||||||
|
}
|
37
src/components/VolunteerBoard/personalInfo.utils.ts
Normal file
37
src/components/VolunteerBoard/personalInfo.utils.ts
Normal file
@ -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]
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import path from "path"
|
||||||
|
import * as fs from "fs"
|
||||||
import { assign, cloneDeep, max, omit, pick } 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"
|
||||||
@ -16,6 +18,7 @@ import {
|
|||||||
VolunteerParticipationDetails,
|
VolunteerParticipationDetails,
|
||||||
VolunteerTeamAssign,
|
VolunteerTeamAssign,
|
||||||
VolunteerKnowledge,
|
VolunteerKnowledge,
|
||||||
|
VolunteerPersonalInfo,
|
||||||
} 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"
|
||||||
@ -113,7 +116,6 @@ export const volunteerPartialAdd = expressAccessor.add(async (list, body) => {
|
|||||||
mobile: canonicalMobile(params.mobile),
|
mobile: canonicalMobile(params.mobile),
|
||||||
howToContact: trim(params.howToContact),
|
howToContact: trim(params.howToContact),
|
||||||
canHelpBefore: trim(params.canHelpBefore),
|
canHelpBefore: trim(params.canHelpBefore),
|
||||||
pelMember: params.pelMember === true,
|
|
||||||
password1: passwordHash,
|
password1: passwordHash,
|
||||||
password2: passwordHash,
|
password2: passwordHash,
|
||||||
})
|
})
|
||||||
@ -181,7 +183,7 @@ export const volunteerForgot = expressAccessor.set(async (list, bodyArray) => {
|
|||||||
if (!volunteer) {
|
if (!volunteer) {
|
||||||
throw Error("Il n'y a aucun bénévole avec cet email")
|
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 now = +new Date()
|
||||||
const timeSinceLastSent = now - lastForgot[volunteer.id]
|
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`)
|
throw Error(`On ne peut acceder qu'à ses propres questions`)
|
||||||
}
|
}
|
||||||
const notifChanges = body[1]
|
const notifChanges = body[1]
|
||||||
const volunteer = list.find((v) => v.id === requestedId)
|
const volunteer: Volunteer | undefined = list.find((v) => v.id === requestedId)
|
||||||
if (!volunteer) {
|
if (!volunteer) {
|
||||||
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
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.hiddenAsks !== undefined) newVolunteer.hiddenAsks = notifChanges.hiddenAsks
|
||||||
if (notifChanges.acceptsNotifs !== undefined)
|
if (notifChanges.acceptsNotifs !== undefined)
|
||||||
@ -303,7 +305,7 @@ export const volunteerDayWishesSet = expressAccessor.set(async (list, body, id)
|
|||||||
if (!volunteer) {
|
if (!volunteer) {
|
||||||
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
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) {
|
if (wishes.active !== undefined) {
|
||||||
newVolunteer.active = wishes.active
|
newVolunteer.active = wishes.active
|
||||||
@ -336,7 +338,7 @@ export const volunteerHostingSet = expressAccessor.set(async (list, body, id) =>
|
|||||||
if (!volunteer) {
|
if (!volunteer) {
|
||||||
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
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) {
|
if (wishes.needsHosting !== undefined) {
|
||||||
newVolunteer.needsHosting = wishes.needsHosting
|
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) => {
|
export const volunteerMealsSet = expressAccessor.set(async (list, body, id) => {
|
||||||
const requestedId = +body[0] || id
|
const requestedId = +body[0] || id
|
||||||
if (requestedId !== id && requestedId !== 0) {
|
if (requestedId !== id && requestedId !== 0) {
|
||||||
@ -373,7 +431,7 @@ export const volunteerMealsSet = expressAccessor.set(async (list, body, id) => {
|
|||||||
if (!volunteer) {
|
if (!volunteer) {
|
||||||
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
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) {
|
if (wishes.meals !== undefined) {
|
||||||
newVolunteer.meals = wishes.meals
|
newVolunteer.meals = wishes.meals
|
||||||
@ -395,11 +453,11 @@ export const volunteerParticipationDetailsSet = expressAccessor.set(async (list,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
const wishes = body[1] as VolunteerParticipationDetails
|
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) {
|
if (!volunteer) {
|
||||||
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
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) {
|
if (wishes.tshirtSize !== undefined) {
|
||||||
newVolunteer.tshirtSize = wishes.tshirtSize
|
newVolunteer.tshirtSize = wishes.tshirtSize
|
||||||
@ -432,11 +490,11 @@ export const volunteerTeamAssignSet = expressAccessor.set(async (list, body, _id
|
|||||||
}
|
}
|
||||||
|
|
||||||
const teamAssign = body[1] as VolunteerTeamAssign
|
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) {
|
if (!volunteer) {
|
||||||
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${teamAssign.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
|
newVolunteer.team = teamAssign.team
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -455,12 +513,12 @@ function getByEmail<T extends { email: string }>(list: T[], rawEmail: string): T
|
|||||||
|
|
||||||
export const volunteerKnowledgeSet = expressAccessor.set(async (list, body, id) => {
|
export const volunteerKnowledgeSet = expressAccessor.set(async (list, body, id) => {
|
||||||
const requestedId = +body[0] || 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) {
|
if (!volunteer) {
|
||||||
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
throw Error(`Il n'y a aucun bénévole avec cet identifiant ${requestedId}`)
|
||||||
}
|
}
|
||||||
const knowledge = body[1] as VolunteerKnowledge
|
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?.ok !== undefined) newVolunteer.ok = knowledge.ok
|
||||||
if (knowledge?.bof !== undefined) newVolunteer.bof = knowledge.bof
|
if (knowledge?.bof !== undefined) newVolunteer.bof = knowledge.bof
|
||||||
if (knowledge?.niet !== undefined) newVolunteer.niet = knowledge.niet
|
if (knowledge?.niet !== undefined) newVolunteer.niet = knowledge.niet
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
volunteerDiscordId,
|
volunteerDiscordId,
|
||||||
volunteerLogin,
|
volunteerLogin,
|
||||||
volunteerParticipationDetailsSet,
|
volunteerParticipationDetailsSet,
|
||||||
|
volunteerPersonalInfoSet,
|
||||||
volunteerSet,
|
volunteerSet,
|
||||||
volunteerTeamWishesSet,
|
volunteerTeamWishesSet,
|
||||||
volunteerTeamAssignSet,
|
volunteerTeamAssignSet,
|
||||||
@ -52,13 +53,16 @@ notificationMain()
|
|||||||
|
|
||||||
const app = express()
|
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
|
// Use helmet to secure Express with various HTTP headers
|
||||||
app.use(helmet({ contentSecurityPolicy: false }))
|
app.use(helmet({ contentSecurityPolicy: false }))
|
||||||
// Prevent HTTP parameter pollution
|
// Prevent HTTP parameter pollution
|
||||||
app.use(hpp())
|
app.use(hpp())
|
||||||
// Compress all requests
|
// Compress all requests
|
||||||
app.use(compression())
|
app.use(compression())
|
||||||
|
|
||||||
// Https with certbot and Let's Encrypt
|
// Https with certbot and Let's Encrypt
|
||||||
if (!__DEV__) {
|
if (!__DEV__) {
|
||||||
app.use("/.well-known/acme-challenge", certbotRouter)
|
app.use("/.well-known/acme-challenge", certbotRouter)
|
||||||
@ -114,6 +118,7 @@ app.post(
|
|||||||
app.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet)
|
app.post("/VolunteerDayWishesSet", secure as RequestHandler, volunteerDayWishesSet)
|
||||||
app.post("/VolunteerHostingSet", secure as RequestHandler, volunteerHostingSet)
|
app.post("/VolunteerHostingSet", secure as RequestHandler, volunteerHostingSet)
|
||||||
app.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet)
|
app.post("/VolunteerMealsSet", secure as RequestHandler, volunteerMealsSet)
|
||||||
|
app.post("/VolunteerPersonalInfoSet", secure as RequestHandler, volunteerPersonalInfoSet)
|
||||||
app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet)
|
app.post("/VolunteerTeamWishesSet", secure as RequestHandler, volunteerTeamWishesSet)
|
||||||
app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
|
app.post("/VolunteerTeamAssignSet", secure as RequestHandler, volunteerTeamAssignSet)
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
export class Volunteer implements VolunteerPartial {
|
export class Volunteer implements VolunteerPartial {
|
||||||
id = 0
|
id = 0
|
||||||
|
|
||||||
lastname = ""
|
|
||||||
|
|
||||||
firstname = ""
|
firstname = ""
|
||||||
|
|
||||||
|
lastname = ""
|
||||||
|
|
||||||
email = ""
|
email = ""
|
||||||
|
|
||||||
mobile = ""
|
mobile = ""
|
||||||
@ -40,8 +40,6 @@ export class Volunteer implements VolunteerPartial {
|
|||||||
|
|
||||||
canHelpBefore = ""
|
canHelpBefore = ""
|
||||||
|
|
||||||
pelMember = false
|
|
||||||
|
|
||||||
hiddenAsks: number[] = []
|
hiddenAsks: number[] = []
|
||||||
|
|
||||||
created = new Date()
|
created = new Date()
|
||||||
@ -73,8 +71,8 @@ export class Volunteer implements VolunteerPartial {
|
|||||||
|
|
||||||
export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
||||||
id: "id",
|
id: "id",
|
||||||
lastname: "nom",
|
|
||||||
firstname: "prenom",
|
firstname: "prenom",
|
||||||
|
lastname: "nom",
|
||||||
email: "mail",
|
email: "mail",
|
||||||
mobile: "telephone",
|
mobile: "telephone",
|
||||||
photo: "photo",
|
photo: "photo",
|
||||||
@ -92,7 +90,6 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
|||||||
teamWishesComment: "commentaireEnviesEquipe",
|
teamWishesComment: "commentaireEnviesEquipe",
|
||||||
howToContact: "commentContacter",
|
howToContact: "commentContacter",
|
||||||
canHelpBefore: "aideEnAmont",
|
canHelpBefore: "aideEnAmont",
|
||||||
pelMember: "membrePel",
|
|
||||||
hiddenAsks: "questionsCachees",
|
hiddenAsks: "questionsCachees",
|
||||||
created: "creation",
|
created: "creation",
|
||||||
password1: "passe1",
|
password1: "passe1",
|
||||||
@ -110,10 +107,10 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class VolunteerPartial {
|
export class VolunteerPartial {
|
||||||
lastname = ""
|
|
||||||
|
|
||||||
firstname = ""
|
firstname = ""
|
||||||
|
|
||||||
|
lastname = ""
|
||||||
|
|
||||||
email = ""
|
email = ""
|
||||||
|
|
||||||
mobile = ""
|
mobile = ""
|
||||||
@ -142,7 +139,6 @@ export const volunteerExample: Volunteer = {
|
|||||||
teamWishesComment: "",
|
teamWishesComment: "",
|
||||||
howToContact: "",
|
howToContact: "",
|
||||||
canHelpBefore: "",
|
canHelpBefore: "",
|
||||||
pelMember: false,
|
|
||||||
hiddenAsks: [],
|
hiddenAsks: [],
|
||||||
created: new Date(0),
|
created: new Date(0),
|
||||||
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
|
||||||
@ -222,6 +218,13 @@ export interface VolunteerParticipationDetails {
|
|||||||
food: Volunteer["food"]
|
food: Volunteer["food"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VolunteerPersonalInfo {
|
||||||
|
id: Volunteer["id"]
|
||||||
|
firstname: Volunteer["firstname"]
|
||||||
|
lastname: Volunteer["lastname"]
|
||||||
|
photo: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface VolunteerTeamAssign {
|
export interface VolunteerTeamAssign {
|
||||||
id: Volunteer["id"]
|
id: Volunteer["id"]
|
||||||
volunteer: number
|
volunteer: number
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
VolunteerDiscordId,
|
VolunteerDiscordId,
|
||||||
VolunteerKnowledge,
|
VolunteerKnowledge,
|
||||||
VolunteerMeals,
|
VolunteerMeals,
|
||||||
|
VolunteerPersonalInfo,
|
||||||
} from "./volunteers"
|
} from "./volunteers"
|
||||||
|
|
||||||
const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
|
const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
|
||||||
@ -51,6 +52,9 @@ export const volunteerParticipationDetailsSet =
|
|||||||
"ParticipationDetailsSet"
|
"ParticipationDetailsSet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const volunteerPersonalInfoSet =
|
||||||
|
serviceAccessors.securedCustomPost<[number, Partial<VolunteerPersonalInfo>]>("PersonalInfoSet")
|
||||||
|
|
||||||
export const volunteerTeamAssignSet =
|
export const volunteerTeamAssignSet =
|
||||||
serviceAccessors.securedCustomPost<[number, Partial<VolunteerTeamAssign>]>("TeamAssignSet")
|
serviceAccessors.securedCustomPost<[number, Partial<VolunteerTeamAssign>]>("TeamAssignSet")
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import volunteerList from "./volunteerList"
|
|||||||
import volunteerLogin from "./volunteerLogin"
|
import volunteerLogin from "./volunteerLogin"
|
||||||
import volunteerKnowledgeSet from "./volunteerKnowledgeSet"
|
import volunteerKnowledgeSet from "./volunteerKnowledgeSet"
|
||||||
import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet"
|
import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet"
|
||||||
|
import volunteerPersonalInfoSet from "./volunteerPersonalInfoSet"
|
||||||
import volunteerSet from "./volunteerSet"
|
import volunteerSet from "./volunteerSet"
|
||||||
import volunteerTeamAssignSet from "./volunteerTeamAssignSet"
|
import volunteerTeamAssignSet from "./volunteerTeamAssignSet"
|
||||||
import volunteerTeamWishesSet from "./volunteerTeamWishesSet"
|
import volunteerTeamWishesSet from "./volunteerTeamWishesSet"
|
||||||
@ -52,6 +53,7 @@ export default (history: History) => ({
|
|||||||
volunteerLogin,
|
volunteerLogin,
|
||||||
volunteerKnowledgeSet,
|
volunteerKnowledgeSet,
|
||||||
volunteerParticipationDetailsSet,
|
volunteerParticipationDetailsSet,
|
||||||
|
volunteerPersonalInfoSet,
|
||||||
volunteerSet,
|
volunteerSet,
|
||||||
volunteerTeamAssignSet,
|
volunteerTeamAssignSet,
|
||||||
volunteerTeamWishesSet,
|
volunteerTeamWishesSet,
|
||||||
|
@ -31,5 +31,6 @@ export const MODAL_IDS = {
|
|||||||
HOSTING: "HOSTING",
|
HOSTING: "HOSTING",
|
||||||
MEALS: "MEALS",
|
MEALS: "MEALS",
|
||||||
PARTICIPATIONDETAILS: "PARTICIPATIONDETAILS",
|
PARTICIPATIONDETAILS: "PARTICIPATIONDETAILS",
|
||||||
|
PERSONALINFO: "PERSONALINFO",
|
||||||
TEAMWISHES: "TEAMWISHES",
|
TEAMWISHES: "TEAMWISHES",
|
||||||
}
|
}
|
||||||
|
60
src/store/volunteerPersonalInfoSet.ts
Normal file
60
src/store/volunteerPersonalInfoSet.ts
Normal file
@ -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<VolunteerPersonalInfo>) => ({
|
||||||
|
readyStatus: "success",
|
||||||
|
entity: payload,
|
||||||
|
}),
|
||||||
|
getFailure: (_, { payload }: PayloadAction<string>) => ({
|
||||||
|
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<VolunteerPersonalInfo> = {}): AppThunk =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
let jwt = ""
|
||||||
|
|
||||||
|
if (!id) {
|
||||||
|
;({ jwt, id } = getState().auth)
|
||||||
|
}
|
||||||
|
if (shouldFetchVolunteerPersonalInfoSet(getState(), id))
|
||||||
|
return dispatch(fetchVolunteerPersonalInfoSet(jwt, id, wishes))
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
@ -2,13 +2,17 @@ import React from "react"
|
|||||||
import { useSelector } from "react-redux"
|
import { useSelector } from "react-redux"
|
||||||
import { selectUserRoles } from "../store/auth"
|
import { selectUserRoles } from "../store/auth"
|
||||||
|
|
||||||
function withUserRole<T>(requiredRole: string, Component: React.ComponentType<T>) {
|
function withUserRole<T>(
|
||||||
return (props: T): JSX.Element => {
|
requiredRole: string,
|
||||||
|
Component: React.ComponentType<T>,
|
||||||
|
doShowMissingRole: true | null = true
|
||||||
|
) {
|
||||||
|
return (props: T): JSX.Element | null => {
|
||||||
const roles = useSelector(selectUserRoles)
|
const roles = useSelector(selectUserRoles)
|
||||||
return roles.includes(requiredRole) ? (
|
return roles.includes(requiredRole) ? (
|
||||||
<Component {...props} />
|
<Component {...props} />
|
||||||
) : (
|
) : (
|
||||||
<div>Missing role {requiredRole}</div>
|
doShowMissingRole && <div>Missing role {requiredRole}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
86
yarn.lock
86
yarn.lock
@ -2907,6 +2907,24 @@ body-parser@1.19.0:
|
|||||||
raw-body "2.4.0"
|
raw-body "2.4.0"
|
||||||
type-is "~1.6.17"
|
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:
|
boolbase@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
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"
|
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||||
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
|
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:
|
cacheable-request@^2.1.1:
|
||||||
version "2.1.4"
|
version "2.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
|
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"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
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:
|
depd@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||||
|
|
||||||
depd@~2.0.0:
|
destroy@1.2.0:
|
||||||
version "2.0.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
|
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
|
||||||
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
|
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
|
||||||
|
|
||||||
destroy@~1.0.4:
|
destroy@~1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
@ -5588,6 +5616,17 @@ http-errors@1.7.2:
|
|||||||
statuses ">= 1.5.0 < 2"
|
statuses ">= 1.5.0 < 2"
|
||||||
toidentifier "1.0.0"
|
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:
|
http-errors@~1.7.2:
|
||||||
version "1.7.3"
|
version "1.7.3"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
|
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"
|
define-properties "^1.1.3"
|
||||||
es-abstract "^1.19.1"
|
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:
|
on-finished@~2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||||
@ -8640,6 +8686,13 @@ pupa@^2.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-goat "^2.0.0"
|
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:
|
qs@6.7.0:
|
||||||
version "6.7.0"
|
version "6.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
|
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"
|
iconv-lite "0.4.24"
|
||||||
unpipe "1.0.0"
|
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:
|
rc@^1.2.8:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
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"
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
|
||||||
integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
|
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:
|
shallow-clone@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
|
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"
|
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
|
||||||
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
|
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:
|
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
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"
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
|
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:
|
totalist@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user