diff --git a/src/app/index.tsx b/src/app/index.tsx
index a1ad843..bc6a5b9 100755
--- a/src/app/index.tsx
+++ b/src/app/index.tsx
@@ -1,4 +1,3 @@
-import { Link } from "react-router-dom"
import { RouteConfig, renderRoutes } from "react-router-config"
import { Helmet } from "react-helmet"
import { ToastContainer } from "react-toastify"
@@ -29,7 +28,7 @@ const App = ({ route }: Route): JSX.Element => (
diff --git a/src/components/Asks/AskDayWishes.tsx b/src/components/Asks/AskDayWishes.tsx
new file mode 100644
index 0000000..b634a1b
--- /dev/null
+++ b/src/components/Asks/AskDayWishes.tsx
@@ -0,0 +1,38 @@
+import { get } from "lodash"
+import { useCallback } from "react"
+import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
+import { useAskTools, addAsk, answerLaterOnProfile } from "./utils"
+import { useUserDayWishes } from "../VolunteerBoard/daysWishes.utils"
+import DayWishesForm, {
+ fetchFor as fetchForDayWishesForm,
+} from "../VolunteerBoard/DayWishesForm/DayWishesForm"
+
+export function AskDayWishes(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 [userWishes] = useUserDayWishes()
+ const participation = get(userWishes, "active", "inconnu")
+ const newSelection = get(userWishes, "dayWishes", [])
+ const comment = get(userWishes, "dayWishesComment", "")
+ const needToShow = participation === "inconnu" || (newSelection.length === 0 && !comment)
+
+ addAsk(
+ asks,
+ id,
+ volunteerAsks,
+ false,
+ needToShow,
+ {answerLaterOnProfile}
+ )
+}
+
+// Fetch server-side data here
+export const fetchFor = [...fetchForDayWishesForm]
diff --git a/src/components/Asks/AskGazette.tsx b/src/components/Asks/AskGazette.tsx
new file mode 100644
index 0000000..8be33a8
--- /dev/null
+++ b/src/components/Asks/AskGazette.tsx
@@ -0,0 +1,41 @@
+import { useCallback } from "react"
+import classnames from "classnames"
+import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
+import styles from "./styles.module.scss"
+import { useAskTools, addAsk } from "./utils"
+import FormButton from "../Form/FormButton/FormButton"
+
+export function AskWelcome(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])
+
+ addAsk(
+ asks,
+ id,
+ volunteerAsks,
+ true,
+ true,
+
+ )
+}
diff --git a/src/components/Asks/AskParticipationDetails.tsx b/src/components/Asks/AskParticipationDetails.tsx
new file mode 100644
index 0000000..132a07f
--- /dev/null
+++ b/src/components/Asks/AskParticipationDetails.tsx
@@ -0,0 +1,39 @@
+import { get } from "lodash"
+import { useCallback } from "react"
+import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
+import { useAskTools, addAsk, answerLaterOnProfile } from "./utils"
+import ParticipationDetailsForm, {
+ fetchFor as fetchForParticipationDetailsForm,
+} from "../VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
+import { useUserParticipationDetails } from "../VolunteerBoard/participationDetails.utils"
+
+export function AskParticipationDetails(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 [participationDetails] = useUserParticipationDetails()
+ const tshirtSize = get(participationDetails, "tshirtSize", "")
+ const food = get(participationDetails, "food", "")
+ const needToShow = !tshirtSize && !food
+
+ addAsk(
+ asks,
+ id,
+ volunteerAsks,
+ false,
+ needToShow,
+
+ {answerLaterOnProfile}
+
+ )
+}
+
+// Fetch server-side data here
+export const fetchFor = [...fetchForParticipationDetailsForm]
diff --git a/src/components/Asks/AskPushNotif.tsx b/src/components/Asks/AskPushNotif.tsx
new file mode 100644
index 0000000..9676c19
--- /dev/null
+++ b/src/components/Asks/AskPushNotif.tsx
@@ -0,0 +1,229 @@
+import _ from "lodash"
+import React, { useCallback, useEffect, useRef, useState } from "react"
+import isNode from "detect-node"
+import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
+import styles from "./styles.module.scss"
+import { useAskTools, addAsk } from "./utils"
+import FormButton from "../Form/FormButton/FormButton"
+import { toastError, toastSuccess } from "../../store/utils"
+
+export function AskPushNotif(asks: JSX.Element[], id: number): void {
+ const { dispatch, jwtToken, volunteerAsks } = useAskTools()
+
+ const [acceptsNotifs, setAcceptsNotifs] = useState("")
+
+ const mounted = useRef(false)
+ useEffect(() => {
+ if (mounted.current) {
+ return
+ }
+ mounted.current = true
+ if (!isNode) {
+ if (volunteerAsks?.acceptsNotifs === "oui") {
+ navigator.serviceWorker.ready.then((registration) =>
+ registration.pushManager.getSubscription().then((existedSubscription) => {
+ const doesAcceptNotifs =
+ _.isEqual(
+ JSON.parse(JSON.stringify(existedSubscription)),
+ JSON.parse(volunteerAsks?.pushNotifSubscription)
+ ) && volunteerAsks?.acceptsNotifs === "oui"
+ setAcceptsNotifs(doesAcceptNotifs ? "oui" : "non")
+ })
+ )
+ } else {
+ setAcceptsNotifs("non")
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ const onChangeValue = (e: React.ChangeEvent) =>
+ setAcceptsNotifs(e.target.value)
+
+ const onSubmit = useCallback(async (): Promise => {
+ if (isNode) {
+ return
+ }
+
+ if (!("serviceWorker" in navigator)) {
+ return
+ }
+
+ if (acceptsNotifs === "non") {
+ dispatch(
+ fetchVolunteerAsksSet(jwtToken, 0, {
+ hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
+ acceptsNotifs: "non",
+ })
+ )
+ return
+ }
+
+ const registration = await navigator.serviceWorker.ready
+
+ if (!registration.pushManager) {
+ toastError(
+ "Il y a un problème avec le push manager. Il faudrait utiliser un navigateur plus récent !"
+ )
+ dispatch(
+ fetchVolunteerAsksSet(jwtToken, 0, {
+ hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
+ })
+ )
+ return
+ }
+
+ const convertedVapidKey = urlBase64ToUint8Array(process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY)
+
+ function urlBase64ToUint8Array(base64String?: string) {
+ if (!base64String) {
+ return ""
+ }
+ const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
+ // eslint-disable-next-line
+ const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/")
+
+ const rawData = atob(base64)
+ const outputArray = new Uint8Array(rawData.length)
+
+ for (let i = 0; i < rawData.length; i += 1) {
+ outputArray[i] = rawData.charCodeAt(i)
+ }
+ return outputArray
+ }
+
+ if (!convertedVapidKey) {
+ toastError("No convertedVapidKey available")
+ }
+
+ try {
+ const existedSubscription = await registration.pushManager.getSubscription()
+
+ if (existedSubscription === null) {
+ // No subscription detected, make a request
+ try {
+ const newSubscription = await registration.pushManager.subscribe({
+ applicationServerKey: convertedVapidKey,
+ userVisibleOnly: true,
+ })
+ // New subscription added
+ if (
+ volunteerAsks?.acceptsNotifs === "oui" &&
+ !subscriptionEqualsSave(
+ newSubscription,
+ volunteerAsks?.pushNotifSubscription
+ )
+ ) {
+ toastSuccess(
+ "Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
+ )
+ }
+
+ dispatch(
+ fetchVolunteerAsksSet(jwtToken, 0, {
+ pushNotifSubscription: JSON.stringify(newSubscription),
+ acceptsNotifs: "oui",
+ hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
+ })
+ )
+ } catch (_e) {
+ if (Notification.permission !== "granted") {
+ toastError(
+ "Mince tu as bloqué les notifications pour le site des bénévoles ! En haut juste à gauche de la barre d'adresse, il y a une icone de cadenas ou de message barré sur lequel cliquer pour annuler ce blocage.",
+ false
+ )
+ } else {
+ toastError(
+ "Il y a eu une erreur avec l'enregistrement avec le Service Worker. Il faudrait utiliser un navigateur plus récent !"
+ )
+ }
+ }
+ } else {
+ // Existed subscription detected
+ if (
+ volunteerAsks?.acceptsNotifs === "oui" &&
+ !subscriptionEqualsSave(
+ existedSubscription,
+ volunteerAsks?.pushNotifSubscription
+ )
+ ) {
+ toastSuccess(
+ "Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
+ )
+ }
+
+ dispatch(
+ fetchVolunteerAsksSet(jwtToken, 0, {
+ pushNotifSubscription: JSON.stringify(existedSubscription),
+ acceptsNotifs: "oui",
+ hiddenAsks: [...(volunteerAsks?.hiddenAsks || []), id],
+ })
+ )
+ }
+ } catch (_e) {
+ toastError(
+ "Il y a eu une erreur avec l'enregistrement avec le Service Worker. Il faudrait utiliser un navigateur plus récent !"
+ )
+ }
+ }, [
+ acceptsNotifs,
+ dispatch,
+ id,
+ jwtToken,
+ volunteerAsks?.acceptsNotifs,
+ volunteerAsks?.hiddenAsks,
+ volunteerAsks?.pushNotifSubscription,
+ ])
+
+ function subscriptionEqualsSave(toCheck: PushSubscription, save: string | undefined): boolean {
+ if (!save) {
+ return !toCheck
+ }
+ return _.isEqual(JSON.parse(JSON.stringify(toCheck)), JSON.parse(save))
+ }
+
+ const needToShow =
+ volunteerAsks?.acceptsNotifs !== "oui" && volunteerAsks?.acceptsNotifs !== "non"
+
+ addAsk(
+ asks,
+ id,
+ volunteerAsks,
+ true,
+ needToShow,
+
+ )
+}
diff --git a/src/components/Asks/AskTeamWishes.tsx b/src/components/Asks/AskTeamWishes.tsx
new file mode 100644
index 0000000..528f621
--- /dev/null
+++ b/src/components/Asks/AskTeamWishes.tsx
@@ -0,0 +1,37 @@
+import { get } from "lodash"
+import { useCallback } from "react"
+import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
+import { useAskTools, addAsk, answerLaterOnProfile } from "./utils"
+import { useUserTeamWishes } from "../VolunteerBoard/teamWishes.utils"
+import TeamWishesForm, {
+ fetchFor as fetchForTeamWishesForm,
+} from "../VolunteerBoard/TeamWishesForm/TeamWishesForm"
+
+export function AskTeamWishes(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 [teamWishesData] = useUserTeamWishes()
+ const teamWishesString = get(teamWishesData, "teamWishes", [])
+ const comment = get(teamWishesData, "teamWishesComment", "")
+ const needToShow = teamWishesString.length === 0 && !comment
+
+ addAsk(
+ asks,
+ id,
+ volunteerAsks,
+ false,
+ needToShow,
+ {answerLaterOnProfile}
+ )
+}
+
+// Fetch server-side data here
+export const fetchFor = [...fetchForTeamWishesForm]
diff --git a/src/components/Asks/AskWelcome.tsx b/src/components/Asks/AskWelcome.tsx
new file mode 100644
index 0000000..f8e373c
--- /dev/null
+++ b/src/components/Asks/AskWelcome.tsx
@@ -0,0 +1,35 @@
+import { useCallback } from "react"
+import { fetchVolunteerAsksSet } from "../../store/volunteerAsksSet"
+import styles from "./styles.module.scss"
+import { useAskTools, addAsk } from "./utils"
+import FormButton from "../Form/FormButton/FormButton"
+
+export function AskWelcome(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])
+
+ addAsk(
+ asks,
+ id,
+ volunteerAsks,
+ true,
+ true,
+
+ )
+}
diff --git a/src/components/Asks/index.tsx b/src/components/Asks/index.tsx
new file mode 100644
index 0000000..e4877d5
--- /dev/null
+++ b/src/components/Asks/index.tsx
@@ -0,0 +1,60 @@
+import _ from "lodash"
+import React, { memo } from "react"
+import styles from "./styles.module.scss"
+import { useAskTools } from "./utils"
+import { AskWelcome } from "./AskWelcome"
+import { AskPushNotif } from "./AskPushNotif"
+import { AskDayWishes, fetchFor as fetchForDayWishes } from "./AskDayWishes"
+import { AskTeamWishes, fetchFor as fetchForTeamWishes } from "./AskTeamWishes"
+import {
+ AskParticipationDetails,
+ fetchFor as fetchForParticipationDetails,
+} from "./AskParticipationDetails"
+
+const Asks = (): JSX.Element | null => {
+ const { volunteerAsks } = useAskTools()
+ const asks: JSX.Element[] = []
+
+ AskWelcome(asks, 1)
+
+ AskDayWishes(asks, 10)
+ AskTeamWishes(asks, 11)
+ AskParticipationDetails(asks, 12)
+
+ AskPushNotif(asks, 99)
+
+ if (_.isEmpty(asks)) {
+ asks.push(
+
+
+
+
+
+ Tu as fait le tour des dernières infos ou questions importantes,
+ merci ! :)
+
+ Nous te préviendrons quand il y en aura de nouvelles.
+
+
+
+
+
+
+ )
+ }
+
+ if (volunteerAsks === undefined) {
+ return null
+ }
+
+ return {asks.map((t) => t).reduce((prev, curr) => [prev, curr])}
+}
+
+export default memo(Asks)
+
+// Fetch server-side data here
+export const fetchFor = [
+ ...fetchForDayWishes,
+ ...fetchForTeamWishes,
+ ...fetchForParticipationDetails,
+]
diff --git a/src/components/Notifications/styles.module.scss b/src/components/Asks/styles.module.scss
similarity index 100%
rename from src/components/Notifications/styles.module.scss
rename to src/components/Asks/styles.module.scss
diff --git a/src/components/Asks/utils.tsx b/src/components/Asks/utils.tsx
new file mode 100644
index 0000000..aca7104
--- /dev/null
+++ b/src/components/Asks/utils.tsx
@@ -0,0 +1,63 @@
+import _ from "lodash"
+import { Dispatch } from "react"
+import { shallowEqual, useDispatch, useSelector } from "react-redux"
+import { VolunteerAsks } from "../../services/volunteers"
+import { AppState } from "../../store"
+import { selectUserJwtToken } from "../../store/auth"
+import styles from "./styles.module.scss"
+
+let prevVolunteerAsks: VolunteerAsks | undefined
+
+type AskTools = {
+ dispatch: Dispatch
+ jwtToken: string
+ volunteerAsks: VolunteerAsks | undefined
+}
+
+export function useAskTools(): AskTools {
+ const dispatch = useDispatch()
+ const jwtToken = useSelector(selectUserJwtToken)
+ const volunteerAsks = useSelector((state: AppState) => {
+ const vAsks = state.volunteerAsksSet?.entity
+ if (vAsks) {
+ prevVolunteerAsks = vAsks
+ return vAsks
+ }
+ return prevVolunteerAsks
+ }, shallowEqual)
+ return { dispatch, jwtToken, volunteerAsks }
+}
+
+export function addAsk(
+ asks: JSX.Element[],
+ id: number,
+ volunteerAsks: VolunteerAsks | undefined,
+ isNarrow: boolean,
+ needToShow: boolean,
+ children: JSX.Element
+): void {
+ const hidden = volunteerAsks?.hiddenAsks || []
+ if (_.includes(hidden, id) || !_.isEmpty(asks) || !needToShow) {
+ return
+ }
+
+ asks.push(
+
+ )
+}
+
+export const answerLaterOnProfile = (
+ <>
+ Tu pourras y répondre plus tard sur la page Mon profil .
+ >
+)
diff --git a/src/components/ForgotForm/ForgotForm.tsx b/src/components/ForgotForm/ForgotForm.tsx
index 72ad494..eea33d4 100644
--- a/src/components/ForgotForm/ForgotForm.tsx
+++ b/src/components/ForgotForm/ForgotForm.tsx
@@ -1,5 +1,4 @@
import React, { memo, useCallback } from "react"
-import { Link } from "react-router-dom"
import { AppDispatch } from "../../store"
import { fetchVolunteerForgot } from "../../store/volunteerForgot"
import styles from "./styles.module.scss"
@@ -40,7 +39,7 @@ const ForgotForm = ({ dispatch, error, message }: Props): JSX.Element => {
{error}
{message}
)
diff --git a/src/components/LoginForm/index.tsx b/src/components/LoginForm/index.tsx
index ff729bc..a7c397c 100644
--- a/src/components/LoginForm/index.tsx
+++ b/src/components/LoginForm/index.tsx
@@ -1,5 +1,4 @@
import React, { memo, useCallback } from "react"
-import { Link } from "react-router-dom"
import { shallowEqual, useDispatch, useSelector } from "react-redux"
import { AppState } from "../../store"
import { fetchVolunteerLogin } from "../../store/volunteerLogin"
@@ -43,7 +42,7 @@ const LoginForm = (): JSX.Element => {
{loginError && {loginError}
}
)
diff --git a/src/components/Navigation/MainMenu.tsx b/src/components/Navigation/MainMenu.tsx
index cb8b6e0..9a519b1 100644
--- a/src/components/Navigation/MainMenu.tsx
+++ b/src/components/Navigation/MainMenu.tsx
@@ -37,7 +37,7 @@ const MainMenu: FC = (): JSX.Element | null => {
{createMenuItem("Questions", "/")}
{createMenuItem("Annonces", "/annonces")}
- createMenuItem("Mon profil", "/profil")
+ {createMenuItem("Mon profil", "/profil")}
×
diff --git a/src/components/Notifications/index.tsx b/src/components/Notifications/index.tsx
deleted file mode 100644
index 86db167..0000000
--- a/src/components/Notifications/index.tsx
+++ /dev/null
@@ -1,546 +0,0 @@
-import _, { get } from "lodash"
-import React, { memo, useCallback, useEffect, useRef, useState } from "react"
-import isNode from "detect-node"
-import { shallowEqual, useDispatch, useSelector } from "react-redux"
-import { fetchVolunteerNotifsSet } from "../../store/volunteerNotifsSet"
-import styles from "./styles.module.scss"
-import { selectUserJwtToken } from "../../store/auth"
-import { VolunteerNotifs } from "../../services/volunteers"
-import TeamWishesForm, {
- fetchFor as fetchForTeamWishesForm,
-} from "../VolunteerBoard/TeamWishesForm/TeamWishesForm"
-import { AppState } from "../../store"
-import { useUserTeamWishes } from "../VolunteerBoard/teamWishes.utils"
-import { useUserDayWishes } from "../VolunteerBoard/daysWishes.utils"
-import ParticipationDetailsForm, {
- fetchFor as fetchForarticipationDetailsForm,
-} from "../VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
-import DayWishesForm, {
- fetchFor as fetchForDayWishesForm,
-} from "../VolunteerBoard/DayWishesForm/DayWishesForm"
-import { useUserParticipationDetails } from "../VolunteerBoard/participationDetails.utils"
-import FormButton from "../Form/FormButton/FormButton"
-import { toastError, toastSuccess } from "../../store/utils"
-
-let prevNotifs: VolunteerNotifs | undefined
-
-const Notifications = (): JSX.Element | null => {
- const dispatch = useDispatch()
- const jwtToken = useSelector(selectUserJwtToken)
- const volunteerNotifs = useSelector((state: AppState) => {
- const notifs = state.volunteerNotifsSet?.entity
- if (notifs) {
- prevNotifs = notifs
- return notifs
- }
- return prevNotifs
- }, shallowEqual)
- const hidden = volunteerNotifs?.hiddenNotifs || []
- const notifs: JSX.Element[] = []
-
- const onSubmit1 = useCallback((): void => {
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 1],
- })
- )
- }, [dispatch, jwtToken, volunteerNotifs])
-
- if (!_.includes(hidden, 1)) {
- notifs.push(
-
- )
- }
-
- // const [participation, setParticipation] = useState(volunteerNotifs?.active || "inconnu")
- // const [participationMessage, setParticipationMessage] = useState("")
- // const onChangeValue2 = (e: React.ChangeEvent) =>
- // setParticipation(e.target.value)
-
- // const onSubmit2 = useCallback(
- // (): void => {
- // if (participation === "inconnu") {
- // setParticipationMessage("Il nous faudrait une réponse ^^")
- // return
- // }
-
- // dispatch(
- // fetchVolunteerNotifsSet(jwtToken, 0, {
- // hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 2],
- // active: participation,
- // })
- // )
- // },
- // [dispatch, jwtToken, volunteerNotifs, participation]
- // )
-
- // if (!_.includes(hidden, 2)) {
- // notifs.push(
- //
- // )
- // }
-
- // const onSubmit3 = useCallback((): void => {
- // dispatch(
- // fetchVolunteerNotifsSet(jwtToken, 0, {
- // hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 3],
- // })
- // )
- // }, [dispatch, jwtToken, volunteerNotifs])
-
- // if (!_.includes(hidden, 3)) {
- // notifs.push(
- //
- // )
- // }
-
- const answerLaterOnProfile = (
- <>
- Tu pourras y répondre plus tard sur la page Mon profil .
- >
- )
-
- const onSubmit10 = useCallback((): void => {
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 10],
- })
- )
- }, [dispatch, jwtToken, volunteerNotifs])
-
- const [userWishes] = useUserDayWishes()
- const participation10 = get(userWishes, "active", "inconnu")
- const newSelection = get(userWishes, "dayWishes", [])
- const comment10 = get(userWishes, "dayWishesComment", "")
- const needToShow10 = participation10 === "inconnu" || (newSelection.length === 0 && !comment10)
-
- if (!_.includes(hidden, 10) && _.isEmpty(notifs) && needToShow10) {
- notifs.push(
-
-
-
-
- {answerLaterOnProfile}
-
-
-
-
- )
- }
-
- const onSubmit11 = useCallback((): void => {
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 11],
- })
- )
- }, [dispatch, jwtToken, volunteerNotifs])
-
- const [teamWishesData] = useUserTeamWishes()
- const teamWishesString = get(teamWishesData, "teamWishes", [])
- const comment = get(teamWishesData, "teamWishesComment", "")
- const needToShow11 = teamWishesString.length === 0 && !comment
-
- if (!_.includes(hidden, 11) && _.isEmpty(notifs) && needToShow11) {
- notifs.push(
-
-
-
-
- {answerLaterOnProfile}
-
-
-
-
- )
- }
-
- const onSubmit12 = useCallback((): void => {
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 12],
- })
- )
- }, [dispatch, jwtToken, volunteerNotifs])
-
- const [participationDetails] = useUserParticipationDetails()
- const tshirtSize = get(participationDetails, "tshirtSize", "")
- const food = get(participationDetails, "food", "")
- const needToShow12 = !tshirtSize && !food
-
- if (!_.includes(hidden, 12) && _.isEmpty(notifs) && needToShow12) {
- notifs.push(
-
-
-
-
- {answerLaterOnProfile}
-
-
-
-
- )
- }
-
- /* DISCORD
- Discord nous donne à tous la parole via nos téléphone ou navigateurs, pour organiser le meilleur des festivals !
-Il permet de discuter sujet par sujet entre tous les bénévoles, entre les membres d'une même équipe, ou avec ton référent.
-Il permet de choisir les sujets spécifiques sur lesquels être notifié de nouveaux messages.
-
-Rejoindre les 86 bénévoles déjà présents sur le serveur se fait en cliquant ici.
-Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
-*/
-
- const [acceptsNotifs, setAcceptsNotifs] = useState("")
-
- const mounted = useRef(false)
- useEffect(() => {
- if (mounted.current) {
- return
- }
- mounted.current = true
- if (!isNode) {
- if (volunteerNotifs?.acceptsNotifs === "oui") {
- navigator.serviceWorker.ready.then((registration) =>
- registration.pushManager.getSubscription().then((existedSubscription) => {
- const doesAcceptNotifs =
- _.isEqual(
- JSON.parse(JSON.stringify(existedSubscription)),
- JSON.parse(volunteerNotifs?.pushNotifSubscription)
- ) && volunteerNotifs?.acceptsNotifs === "oui"
- setAcceptsNotifs(doesAcceptNotifs ? "oui" : "non")
- })
- )
- } else {
- setAcceptsNotifs("non")
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
-
- const onChangeValue99 = (e: React.ChangeEvent) =>
- setAcceptsNotifs(e.target.value)
-
- const onSubmit99 = useCallback(async (): Promise => {
- if (isNode) {
- return
- }
-
- if (!("serviceWorker" in navigator)) {
- return
- }
-
- if (acceptsNotifs === "non") {
- setAcceptsNotifs("non")
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 99],
- acceptsNotifs: "non",
- })
- )
- return
- }
-
- const registration = await navigator.serviceWorker.ready
-
- if (!registration.pushManager) {
- toastError(
- "Il y a un problème avec le push manager. Il faudrait utiliser un navigateur plus récent !"
- )
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 99],
- })
- )
- return
- }
-
- const convertedVapidKey = urlBase64ToUint8Array(process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY)
-
- function urlBase64ToUint8Array(base64String?: string) {
- if (!base64String) {
- return ""
- }
- const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
- // eslint-disable-next-line
- const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/")
-
- const rawData = atob(base64)
- const outputArray = new Uint8Array(rawData.length)
-
- for (let i = 0; i < rawData.length; i += 1) {
- outputArray[i] = rawData.charCodeAt(i)
- }
- return outputArray
- }
-
- if (!convertedVapidKey) {
- toastError("No convertedVapidKey available")
- }
-
- try {
- const existedSubscription = await registration.pushManager.getSubscription()
-
- if (existedSubscription === null) {
- // No subscription detected, make a request
- try {
- const newSubscription = await registration.pushManager.subscribe({
- applicationServerKey: convertedVapidKey,
- userVisibleOnly: true,
- })
- // New subscription added
- if (
- volunteerNotifs?.acceptsNotifs === "oui" &&
- !subscriptionEqualsSave(
- newSubscription,
- volunteerNotifs?.pushNotifSubscription
- )
- ) {
- toastSuccess(
- "Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
- )
- }
-
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- pushNotifSubscription: JSON.stringify(newSubscription),
- acceptsNotifs: "oui",
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 99],
- })
- )
- } catch (_e) {
- if (Notification.permission !== "granted") {
- toastError(
- "Mince tu as bloqué les notifications pour le site des bénévoles ! En haut juste à gauche de la barre d'adresse, il y a une icone de cadenas ou de message barré sur lequel cliquer pour annuler ce blocage.",
- false
- )
- } else {
- toastError(
- "Il y a eu une erreur avec l'enregistrement avec le Service Worker. Il faudrait utiliser un navigateur plus récent !"
- )
- }
- }
- } else {
- // Existed subscription detected
- if (
- volunteerNotifs?.acceptsNotifs === "oui" &&
- !subscriptionEqualsSave(
- existedSubscription,
- volunteerNotifs?.pushNotifSubscription
- )
- ) {
- toastSuccess(
- "Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
- )
- }
-
- dispatch(
- fetchVolunteerNotifsSet(jwtToken, 0, {
- pushNotifSubscription: JSON.stringify(existedSubscription),
- acceptsNotifs: "oui",
- hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 99],
- })
- )
- }
- } catch (_e) {
- toastError(
- "Il y a eu une erreur avec l'enregistrement avec le Service Worker. Il faudrait utiliser un navigateur plus récent !"
- )
- }
- }, [
- acceptsNotifs,
- dispatch,
- jwtToken,
- volunteerNotifs?.acceptsNotifs,
- volunteerNotifs?.hiddenNotifs,
- volunteerNotifs?.pushNotifSubscription,
- ])
-
- function subscriptionEqualsSave(toCheck: PushSubscription, save: string | undefined): boolean {
- if (!save) {
- return !toCheck
- }
- return _.isEqual(JSON.parse(JSON.stringify(toCheck)), JSON.parse(save))
- }
-
- const needToShow99 = volunteerNotifs?.acceptsNotifs !== "oui"
-
- if (!_.includes(hidden, 99) && _.isEmpty(notifs) && needToShow99) {
- notifs.push(
-
- )
- }
-
- if (_.isEmpty(notifs)) {
- notifs.push(
-
-
-
-
-
- Tu as fait le tour des dernières infos ou questions importantes,
- merci ! :)
-
- Nous te préviendrons quand il y en aura de nouvelles.
-
-
-
-
-
-
- )
- }
-
- if (volunteerNotifs === undefined) {
- return null
- }
-
- return {notifs.map((t) => t).reduce((prev, curr) => [prev, curr])}
-}
-
-export default memo(Notifications)
-
-// Fetch server-side data here
-export const fetchFor = [
- ...fetchForTeamWishesForm,
- ...fetchForarticipationDetailsForm,
- ...fetchForDayWishesForm,
-]
diff --git a/src/components/VolunteerList/index.tsx b/src/components/VolunteerList/index.tsx
index 5b6f414..32f8f07 100755
--- a/src/components/VolunteerList/index.tsx
+++ b/src/components/VolunteerList/index.tsx
@@ -1,5 +1,4 @@
import { memo } from "react"
-import { Link } from "react-router-dom"
import { Volunteer } from "../../services/volunteers"
import styles from "./styles.module.scss"
@@ -14,9 +13,9 @@ const VolunteerList = ({ items }: Props) => (
diff --git a/src/components/index.ts b/src/components/index.ts
index f7e7a54..4368455 100755
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -7,7 +7,7 @@ import ErrorBoundary from "./ErrorBoundary"
import GameList from "./GameList"
import Loading from "./Loading"
import LoginForm from "./LoginForm"
-import Notifications, { fetchFor as fetchForNotifications } from "./Notifications"
+import Asks, { fetchFor as fetchForAsks } from "./Asks"
import ParticipationDetailsForm, {
fetchFor as fetchForParticipationDetailsForm,
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
@@ -30,8 +30,8 @@ export {
GameList,
Loading,
LoginForm,
- Notifications,
- fetchForNotifications,
+ Asks,
+ fetchForAsks,
ParticipationDetailsForm,
fetchForParticipationDetailsForm,
PreRegisterForm,
diff --git a/src/pages/Announcements/Announcements.tsx b/src/pages/Announcements/Announcements.tsx
index f111003..263bba5 100644
--- a/src/pages/Announcements/Announcements.tsx
+++ b/src/pages/Announcements/Announcements.tsx
@@ -1,6 +1,6 @@
import { FC, memo } from "react"
import * as _ from "lodash"
-import { RouteComponentProps, Link } from "react-router-dom"
+import { RouteComponentProps } from "react-router-dom"
import { useSelector, shallowEqual } from "react-redux"
import { Helmet } from "react-helmet"
import { EntityState } from "@reduxjs/toolkit"
@@ -60,7 +60,7 @@ const AnnouncementsPage: FC = (): JSX.Element => {
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
index 5342591..442ac66 100644
--- a/src/pages/Home/Home.tsx
+++ b/src/pages/Home/Home.tsx
@@ -1,12 +1,12 @@
import { FC, memo } from "react"
-import { RouteComponentProps, Link } from "react-router-dom"
+import { RouteComponentProps } from "react-router-dom"
import { useSelector } from "react-redux"
import { Helmet } from "react-helmet"
import { AppThunk } from "../../store"
-import { fetchForNotifications, LoginForm, Notifications } from "../../components"
+import { fetchForAsks, LoginForm, Asks } from "../../components"
import styles from "./styles.module.scss"
-import { fetchVolunteerNotifsSetIfNeed } from "../../store/volunteerNotifsSet"
+import { fetchVolunteerAsksSetIfNeed } from "../../store/volunteerAsksSet"
import { selectUserJwtToken } from "../../store/auth"
export type Props = RouteComponentProps
@@ -17,7 +17,7 @@ const HomePage: FC = (): JSX.Element => {
if (jwtToken === undefined) return Loading...
if (jwtToken) {
- return
+ return
}
return (
@@ -29,7 +29,7 @@ const HomePage: FC
= (): JSX.Element => {
@@ -38,8 +38,8 @@ const HomePage: FC = (): JSX.Element => {
// Fetch server-side data here
export const loadData = (): AppThunk[] => [
- fetchVolunteerNotifsSetIfNeed(),
- ...fetchForNotifications.map((f) => f()),
+ fetchVolunteerAsksSetIfNeed(),
+ ...fetchForAsks.map((f) => f()),
]
export default memo(HomePage)
diff --git a/src/server/gsheets/volunteers.ts b/src/server/gsheets/volunteers.ts
index 6e61923..1e0b96b 100644
--- a/src/server/gsheets/volunteers.ts
+++ b/src/server/gsheets/volunteers.ts
@@ -7,7 +7,7 @@ import {
Volunteer,
VolunteerWithoutId,
VolunteerLogin,
- VolunteerNotifs,
+ VolunteerAsks,
VolunteerTeamWishes,
translationVolunteer,
VolunteerDayWishes,
@@ -115,10 +115,10 @@ async function sendForgetEmail(email: string, password: string): Promise {
}
}
-export const volunteerNotifsSet = expressAccessor.set(async (list, body, id) => {
+export const volunteerAsksSet = 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 notifs`)
+ throw Error(`On ne peut acceder qu'à ses propres questions`)
}
const notifChanges = body[1]
const volunteer = list.find((v) => v.id === requestedId)
@@ -136,10 +136,10 @@ export const volunteerNotifsSet = expressAccessor.set(async (list, body, id) =>
firstname: newVolunteer.firstname,
adult: newVolunteer.adult,
active: newVolunteer.active,
- hiddenNotifs: newVolunteer.hiddenNotifs,
+ hiddenAsks: newVolunteer.hiddenAsks,
pushNotifSubscription: newVolunteer.pushNotifSubscription,
acceptsNotifs: newVolunteer.acceptsNotifs,
- } as VolunteerNotifs,
+ } as VolunteerAsks,
}
})
diff --git a/src/server/index.ts b/src/server/index.ts
index d65524f..bd5a953 100755
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -25,7 +25,7 @@ import {
volunteerSet,
volunteerLogin,
volunteerForgot,
- volunteerNotifsSet,
+ volunteerAsksSet,
volunteerParticipationDetailsSet,
volunteerTeamWishesSet,
volunteerDayWishesSet,
@@ -92,7 +92,7 @@ app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
app.get("/TeamListGet", teamListGet)
// UNSAFE app.post("/VolunteerGet", secure as RequestHandler, volunteerGet)
-app.post("/VolunteerNotifsSet", secure as RequestHandler, volunteerNotifsSet)
+app.post("/VolunteerAsksSet", secure as RequestHandler, volunteerAsksSet)
app.post(
"/VolunteerParticipationDetailsSet",
secure as RequestHandler,
diff --git a/src/services/volunteers.ts b/src/services/volunteers.ts
index e2fc8a6..372c704 100644
--- a/src/services/volunteers.ts
+++ b/src/services/volunteers.ts
@@ -33,7 +33,7 @@ export class Volunteer {
teamWishesComment = ""
- hiddenNotifs: number[] = []
+ hiddenAsks: number[] = []
created = new Date()
@@ -64,7 +64,7 @@ export const translationVolunteer: { [k in keyof Volunteer]: string } = {
food: "alimentation",
teamWishes: "enviesEquipe",
teamWishesComment: "commentaireEnviesEquipe",
- hiddenNotifs: "notifsCachees",
+ hiddenAsks: "questionsCachees",
created: "creation",
password1: "passe1",
password2: "passe2",
@@ -92,7 +92,7 @@ export const volunteerExample: Volunteer = {
food: "Végétarien",
teamWishes: [],
teamWishesComment: "",
- hiddenNotifs: [],
+ hiddenAsks: [],
created: new Date(0),
password1: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
password2: "$2y$10$fSxY9AIuxSiEjwF.J3eXGubIxUPkdq9d5fqpbl8ASimSjNj4SR.9O",
@@ -116,12 +116,12 @@ export interface VolunteerForgot {
message: string
}
-export interface VolunteerNotifs {
+export interface VolunteerAsks {
id: Volunteer["id"]
firstname: Volunteer["firstname"]
adult: Volunteer["adult"]
active: Volunteer["active"]
- hiddenNotifs: Volunteer["hiddenNotifs"]
+ hiddenAsks: Volunteer["hiddenAsks"]
pushNotifSubscription: Volunteer["pushNotifSubscription"]
acceptsNotifs: Volunteer["acceptsNotifs"]
}
diff --git a/src/services/volunteersAccessors.ts b/src/services/volunteersAccessors.ts
index d6dc3d0..e7ec52d 100644
--- a/src/services/volunteersAccessors.ts
+++ b/src/services/volunteersAccessors.ts
@@ -3,7 +3,7 @@ import {
elementName,
Volunteer,
VolunteerDayWishes,
- VolunteerNotifs,
+ VolunteerAsks,
VolunteerParticipationDetails,
VolunteerTeamWishes,
VolunteerWithoutId,
@@ -21,8 +21,8 @@ export const volunteerLogin =
export const volunteerForgot = serviceAccessors.customPost<[{ email: string }]>("Forgot")
-export const volunteerNotifsSet =
- serviceAccessors.securedCustomPost<[number, Partial]>("NotifsSet")
+export const volunteerAsksSet =
+ serviceAccessors.securedCustomPost<[number, Partial]>("AsksSet")
export const volunteerTeamWishesSet =
serviceAccessors.securedCustomPost<[number, Partial]>("TeamWishesSet")
diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts
index 7b3b653..2b6cbf7 100644
--- a/src/store/rootReducer.ts
+++ b/src/store/rootReducer.ts
@@ -14,7 +14,7 @@ import volunteerList from "./volunteerList"
import volunteerSet from "./volunteerSet"
import volunteerLogin from "./volunteerLogin"
import volunteerForgot from "./volunteerForgot"
-import volunteerNotifsSet from "./volunteerNotifsSet"
+import volunteerAsksSet from "./volunteerAsksSet"
import volunteerParticipationDetailsSet from "./volunteerParticipationDetailsSet"
import volunteerDayWishesSet from "./volunteerDayWishesSet"
import volunteerTeamWishesSet from "./volunteerTeamWishesSet"
@@ -37,7 +37,7 @@ export default (history: History) => ({
volunteerSet,
volunteerLogin,
volunteerForgot,
- volunteerNotifsSet,
+ volunteerAsksSet,
volunteerParticipationDetailsSet,
volunteerDayWishesSet,
volunteerTeamWishesSet,
diff --git a/src/store/volunteerAsksSet.ts b/src/store/volunteerAsksSet.ts
new file mode 100644
index 0000000..0b2d9b8
--- /dev/null
+++ b/src/store/volunteerAsksSet.ts
@@ -0,0 +1,59 @@
+import { PayloadAction, createSlice } from "@reduxjs/toolkit"
+
+import { StateRequest, toastError, elementFetch } from "./utils"
+import { VolunteerAsks } from "../services/volunteers"
+import { AppThunk, AppState } from "."
+import { volunteerAsksSet } from "../services/volunteersAccessors"
+
+type StateVolunteerAsksSet = { entity?: VolunteerAsks } & StateRequest
+
+export const initialState: StateVolunteerAsksSet = {
+ readyStatus: "idle",
+}
+
+const volunteerAsksSetSlice = createSlice({
+ name: "volunteerAsksSet",
+ initialState,
+ reducers: {
+ getRequesting: (_) => ({
+ readyStatus: "request",
+ }),
+ getSuccess: (_, { payload }: PayloadAction) => ({
+ readyStatus: "success",
+ entity: payload,
+ }),
+ getFailure: (_, { payload }: PayloadAction) => ({
+ readyStatus: "failure",
+ error: payload,
+ }),
+ },
+})
+
+export default volunteerAsksSetSlice.reducer
+export const { getRequesting, getSuccess, getFailure } = volunteerAsksSetSlice.actions
+
+export const fetchVolunteerAsksSet = elementFetch(
+ volunteerAsksSet,
+ getRequesting,
+ getSuccess,
+ getFailure,
+ (error: Error) => toastError(`Erreur lors du chargement des notifications: ${error.message}`)
+)
+
+const shouldFetchVolunteerAsksSet = (state: AppState, id: number) =>
+ state.volunteerAsksSet?.readyStatus !== "success" ||
+ (state.volunteerAsksSet?.entity && state.volunteerAsksSet?.entity?.id !== id)
+
+export const fetchVolunteerAsksSetIfNeed =
+ (id = 0, notif: Partial = {}): AppThunk =>
+ (dispatch, getState) => {
+ let jwt = ""
+
+ if (!id) {
+ ;({ jwt, id } = getState().auth)
+ }
+ if (shouldFetchVolunteerAsksSet(getState(), id))
+ return dispatch(fetchVolunteerAsksSet(jwt, id, notif))
+
+ return null
+ }
diff --git a/src/store/volunteerNotifsSet.ts b/src/store/volunteerNotifsSet.ts
deleted file mode 100644
index 4200652..0000000
--- a/src/store/volunteerNotifsSet.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { PayloadAction, createSlice, createSelector } from "@reduxjs/toolkit"
-import get from "lodash/get"
-
-import { StateRequest, toastError, elementFetch } from "./utils"
-import { VolunteerNotifs } from "../services/volunteers"
-import { AppThunk, AppState } from "."
-import { volunteerNotifsSet } from "../services/volunteersAccessors"
-
-type StateVolunteerNotifsSet = { entity?: VolunteerNotifs } & StateRequest
-
-export const initialState: StateVolunteerNotifsSet = {
- readyStatus: "idle",
-}
-
-const volunteerNotifsSetSlice = createSlice({
- name: "volunteerNotifsSet",
- initialState,
- reducers: {
- getRequesting: (_) => ({
- readyStatus: "request",
- }),
- getSuccess: (_, { payload }: PayloadAction) => ({
- readyStatus: "success",
- entity: payload,
- }),
- getFailure: (_, { payload }: PayloadAction) => ({
- readyStatus: "failure",
- error: payload,
- }),
- },
-})
-
-export default volunteerNotifsSetSlice.reducer
-export const { getRequesting, getSuccess, getFailure } = volunteerNotifsSetSlice.actions
-
-export const fetchVolunteerNotifsSet = elementFetch(
- volunteerNotifsSet,
- getRequesting,
- getSuccess,
- getFailure,
- (error: Error) => toastError(`Erreur lors du chargement des notifications: ${error.message}`)
-)
-
-const shouldFetchVolunteerNotifsSet = (state: AppState, id: number) =>
- state.volunteerNotifsSet?.readyStatus !== "success" ||
- (state.volunteerNotifsSet?.entity && state.volunteerNotifsSet?.entity?.id !== id)
-
-export const fetchVolunteerNotifsSetIfNeed =
- (id = 0, notif: Partial = {}): AppThunk =>
- (dispatch, getState) => {
- let jwt = ""
-
- if (!id) {
- ;({ jwt, id } = getState().auth)
- }
- if (shouldFetchVolunteerNotifsSet(getState(), id))
- return dispatch(fetchVolunteerNotifsSet(jwt, id, notif))
-
- return null
- }
-
-export const openedNotifsIds = [1, 2, 3]
-
-export const selectVolunteerNotifsSetState = (state: AppState): StateVolunteerNotifsSet =>
- state.volunteerNotifsSet
-
-export const selectHiddenNotifs = createSelector(selectVolunteerNotifsSetState, (notifState) =>
- get(notifState, "entity.hiddenNotifs", [])
-)
-
-export const selectWaitingsNotifs = createSelector(selectHiddenNotifs, (hidden) =>
- openedNotifsIds.filter((id) => !hidden.find((hiddenId: number) => hiddenId === id))
-)
-
-export const hasWaitingNotifs = createSelector(
- selectWaitingsNotifs,
- (waiting) => waiting.length > 0
-)