mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-09 17:14:21 +02:00
Refactor Notifications into Asks, replace all <Link> by <a>
This commit is contained in:
parent
70b53c5af8
commit
4273ea1603
@ -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 => (
|
||||
<div className={styles.logo} />
|
||||
<div>
|
||||
<h1 className={styles.siteName}>
|
||||
<Link to="/">{config.APP.title}</Link>
|
||||
<a href="/">{config.APP.title}</a>
|
||||
</h1>
|
||||
<div className={styles.siteDescription}>{config.APP.description}</div>
|
||||
</div>
|
||||
|
38
src/components/Asks/AskDayWishes.tsx
Normal file
38
src/components/Asks/AskDayWishes.tsx
Normal file
@ -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,
|
||||
<DayWishesForm afterSubmit={onSubmit}>{answerLaterOnProfile}</DayWishesForm>
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch server-side data here
|
||||
export const fetchFor = [...fetchForDayWishesForm]
|
41
src/components/Asks/AskGazette.tsx
Normal file
41
src/components/Asks/AskGazette.tsx
Normal file
@ -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,
|
||||
<form>
|
||||
<div className={classnames(styles.notifIntro, styles.notifCentered)} key="login-intro">
|
||||
La{" "}
|
||||
<a
|
||||
href="https://mailchi.mp/3c75c3b3a20f/gazette_2020_02-8978118"
|
||||
onClick={onSubmit}
|
||||
>
|
||||
gazette de février
|
||||
</a>{" "}
|
||||
est disponible !<br />
|
||||
<div className={styles.formButtons}>
|
||||
<FormButton onClick={onSubmit}>Ok, masquer</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
39
src/components/Asks/AskParticipationDetails.tsx
Normal file
39
src/components/Asks/AskParticipationDetails.tsx
Normal file
@ -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,
|
||||
<ParticipationDetailsForm afterSubmit={onSubmit}>
|
||||
{answerLaterOnProfile}
|
||||
</ParticipationDetailsForm>
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch server-side data here
|
||||
export const fetchFor = [...fetchForParticipationDetailsForm]
|
229
src/components/Asks/AskPushNotif.tsx
Normal file
229
src/components/Asks/AskPushNotif.tsx
Normal file
@ -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<HTMLInputElement>) =>
|
||||
setAcceptsNotifs(e.target.value)
|
||||
|
||||
const onSubmit = useCallback(async (): Promise<void> => {
|
||||
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,
|
||||
<div className={styles.formLine} key="line-participation">
|
||||
<label>
|
||||
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en aura
|
||||
d'autres à t'afficher ici ?<br />
|
||||
<span className={styles.sousMessage}>
|
||||
(Ça nous simplifierait la vie, on a des soucis à contacter les bénévoles par
|
||||
email.)
|
||||
</span>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
value="oui"
|
||||
name="notifs"
|
||||
checked={acceptsNotifs === "oui"}
|
||||
onChange={onChangeValue}
|
||||
/>{" "}
|
||||
Oui
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
value="non"
|
||||
name="notifs"
|
||||
checked={acceptsNotifs === "non"}
|
||||
onChange={onChangeValue}
|
||||
/>{" "}
|
||||
Non
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<div className={styles.formButtons}>
|
||||
<FormButton onClick={onSubmit}>Enregistrer</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
37
src/components/Asks/AskTeamWishes.tsx
Normal file
37
src/components/Asks/AskTeamWishes.tsx
Normal file
@ -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,
|
||||
<TeamWishesForm afterSubmit={onSubmit}>{answerLaterOnProfile}</TeamWishesForm>
|
||||
)
|
||||
}
|
||||
|
||||
// Fetch server-side data here
|
||||
export const fetchFor = [...fetchForTeamWishesForm]
|
35
src/components/Asks/AskWelcome.tsx
Normal file
35
src/components/Asks/AskWelcome.tsx
Normal file
@ -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,
|
||||
<form>
|
||||
Salut {volunteerAsks?.firstname} !
|
||||
<div className={styles.notifIntro} key="login-intro">
|
||||
Ici tu seras notifié(e) des nouvelles importantes et des questions pour lesquelles
|
||||
il nous faudrait absolument ta réponse.
|
||||
<div className={styles.formButtons}>
|
||||
<FormButton onClick={onSubmit}>Ok, continuer</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
60
src/components/Asks/index.tsx
Normal file
60
src/components/Asks/index.tsx
Normal file
@ -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(
|
||||
<div key="pushNotifs">
|
||||
<div className={styles.notificationsPage}>
|
||||
<div className={styles.notificationsContent}>
|
||||
<div className={styles.formLine} key="line-participation">
|
||||
<label>
|
||||
Tu as fait le tour des dernières infos ou questions importantes,
|
||||
merci ! :)
|
||||
<br />
|
||||
Nous te préviendrons quand il y en aura de nouvelles.
|
||||
<br />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (volunteerAsks === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div>{asks.map<React.ReactNode>((t) => t).reduce((prev, curr) => [prev, curr])}</div>
|
||||
}
|
||||
|
||||
export default memo(Asks)
|
||||
|
||||
// Fetch server-side data here
|
||||
export const fetchFor = [
|
||||
...fetchForDayWishes,
|
||||
...fetchForTeamWishes,
|
||||
...fetchForParticipationDetails,
|
||||
]
|
63
src/components/Asks/utils.tsx
Normal file
63
src/components/Asks/utils.tsx
Normal file
@ -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<any>
|
||||
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(
|
||||
<div key={id}>
|
||||
<div className={styles.notificationsPage}>
|
||||
<div
|
||||
className={
|
||||
isNarrow ? styles.notificationsContentNarrow : styles.notificationsContent
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const answerLaterOnProfile = (
|
||||
<>
|
||||
Tu pourras y répondre plus tard sur la page <a href="/profil">Mon profil</a>.
|
||||
</>
|
||||
)
|
@ -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 => {
|
||||
<div className={styles.error}>{error}</div>
|
||||
<div className={styles.message}>{message}</div>
|
||||
<div className={styles.link}>
|
||||
<Link to="/"> S'identifier </Link>
|
||||
<a href="/"> S'identifier </a>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
@ -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 => {
|
||||
</div>
|
||||
{loginError && <div className={styles.error}>{loginError}</div>}
|
||||
<div className={styles.link}>
|
||||
<Link to="/oubli"> Demander un nouveau mot de passe </Link>
|
||||
<a href="/oubli"> Demander un nouveau mot de passe </a>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
@ -37,7 +37,7 @@ const MainMenu: FC = (): JSX.Element | null => {
|
||||
<ul className={classnames(styles.mainMenu, opened && styles.opened)}>
|
||||
{createMenuItem("Questions", "/")}
|
||||
{createMenuItem("Annonces", "/annonces")}
|
||||
createMenuItem("Mon profil", "/profil")
|
||||
{createMenuItem("Mon profil", "/profil")}
|
||||
<button type="button" className={styles.close} onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
|
@ -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(
|
||||
<div key="1">
|
||||
<div className={styles.notificationsPage}>
|
||||
<div className={styles.notificationsContentNarrow}>
|
||||
<form>
|
||||
Salut {volunteerNotifs?.firstname} !
|
||||
<div className={styles.notifIntro} key="login-intro">
|
||||
Ici tu seras notifié(e) des nouvelles importantes et des questions
|
||||
pour lesquelles il nous faudrait absolument ta réponse.
|
||||
<div className={styles.formButtons}>
|
||||
<FormButton onClick={onSubmit1}>Ok, continuer</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// const [participation, setParticipation] = useState(volunteerNotifs?.active || "inconnu")
|
||||
// const [participationMessage, setParticipationMessage] = useState("")
|
||||
// const onChangeValue2 = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
// 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(
|
||||
// <div key="2">
|
||||
// <div className={styles.notificationsPage}>
|
||||
// <div className={styles.notificationsContentNarrow}>
|
||||
// <div className={styles.formLine} key="line-participation">
|
||||
// <form>
|
||||
// Si les conditions sanitaires te le permettent, souhaites-tu être
|
||||
// bénévole à PeL 2022 ?<br />
|
||||
// <label>
|
||||
// <input
|
||||
// type="radio"
|
||||
// value="oui"
|
||||
// name="participation"
|
||||
// checked={participation === "oui"}
|
||||
// onChange={onChangeValue2}
|
||||
// />{" "}
|
||||
// Oui
|
||||
// </label>
|
||||
// <label>
|
||||
// <input
|
||||
// type="radio"
|
||||
// value="non"
|
||||
// name="participation"
|
||||
// checked={participation === "non"}
|
||||
// onChange={onChangeValue2}
|
||||
// />{" "}
|
||||
// Non
|
||||
// </label>
|
||||
// <label>
|
||||
// <input
|
||||
// type="radio"
|
||||
// value="peut-etre"
|
||||
// name="participation"
|
||||
// checked={participation === "peut-etre"}
|
||||
// onChange={onChangeValue2}
|
||||
// />{" "}
|
||||
// Je ne sais pas encore
|
||||
// </label>
|
||||
// {participation === "peut-etre" ? (
|
||||
// <div>
|
||||
// On te le reproposera dans quelques temps.
|
||||
// <br />
|
||||
// Si tu as besoin d'infos, viens nous en parler sur le
|
||||
// serveur Discord ! Pour le rejoindre,{" "}
|
||||
// <a
|
||||
// href="https://discord.com/invite/eXhjKxSBB4"
|
||||
// target="_blank"
|
||||
// rel="noreferrer"
|
||||
// >
|
||||
// clique ici{" "}
|
||||
// </a>
|
||||
// .
|
||||
// </div>
|
||||
// ) : null}
|
||||
// <div className={styles.formButtons}>
|
||||
// <FormButton onClick={onSubmit2}>Confirmer</FormButton>
|
||||
// </div>
|
||||
// <div className={styles.message}>{participationMessage}</div>
|
||||
// </form>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
// const onSubmit3 = useCallback((): void => {
|
||||
// dispatch(
|
||||
// fetchVolunteerNotifsSet(jwtToken, 0, {
|
||||
// hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 3],
|
||||
// })
|
||||
// )
|
||||
// }, [dispatch, jwtToken, volunteerNotifs])
|
||||
|
||||
// if (!_.includes(hidden, 3)) {
|
||||
// notifs.push(
|
||||
// <div key="3">
|
||||
// <div className={styles.notificationsPage}>
|
||||
// <div className={styles.notificationsContentNarrow}>
|
||||
// <form>
|
||||
// <div
|
||||
// className={classnames(styles.notifIntro, styles.notifCentered)}
|
||||
// key="login-intro"
|
||||
// >
|
||||
// La{" "}
|
||||
// <a
|
||||
// href="https://mailchi.mp/3c75c3b3a20f/gazette_2020_02-8978118"
|
||||
// onClick={onSubmit3}
|
||||
// >
|
||||
// gazette de février
|
||||
// </a>{" "}
|
||||
// est disponible !<br />
|
||||
// <div className={styles.formButtons}>
|
||||
// <FormButton onClick={onSubmit3}>Ok, masquer</FormButton>
|
||||
// </div>
|
||||
// </div>
|
||||
// </form>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
|
||||
const answerLaterOnProfile = (
|
||||
<>
|
||||
Tu pourras y répondre plus tard sur la page <a href="/profil">Mon profil</a>.
|
||||
</>
|
||||
)
|
||||
|
||||
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(
|
||||
<div key="10">
|
||||
<div className={styles.notificationsPage}>
|
||||
<div className={styles.notificationsContent}>
|
||||
<DayWishesForm afterSubmit={onSubmit10}>
|
||||
{answerLaterOnProfile}
|
||||
</DayWishesForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
<div key="11">
|
||||
<div className={styles.notificationsPage}>
|
||||
<div className={styles.notificationsContent}>
|
||||
<TeamWishesForm afterSubmit={onSubmit11}>
|
||||
{answerLaterOnProfile}
|
||||
</TeamWishesForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
<div key="12">
|
||||
<div className={styles.notificationsPage}>
|
||||
<div className={styles.notificationsContent}>
|
||||
<ParticipationDetailsForm afterSubmit={onSubmit12}>
|
||||
{answerLaterOnProfile}
|
||||
</ParticipationDetailsForm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* 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<HTMLInputElement>) =>
|
||||
setAcceptsNotifs(e.target.value)
|
||||
|
||||
const onSubmit99 = useCallback(async (): Promise<void> => {
|
||||
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(
|
||||
<div key="pushNotifs">
|
||||
<div className={styles.notificationsPage}>
|
||||
<div className={styles.notificationsContent}>
|
||||
<div className={styles.formLine} key="line-participation">
|
||||
<label>
|
||||
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en
|
||||
aura d'autres à t'afficher ici ?<br />
|
||||
<span className={styles.sousMessage}>
|
||||
(Ça nous simplifierait la vie, on a des soucis à contacter les
|
||||
bénévoles par email.)
|
||||
</span>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
value="oui"
|
||||
name="gender"
|
||||
checked={acceptsNotifs === "oui"}
|
||||
onChange={onChangeValue99}
|
||||
/>{" "}
|
||||
Oui
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
value="non"
|
||||
name="gender"
|
||||
checked={acceptsNotifs === "non"}
|
||||
onChange={onChangeValue99}
|
||||
/>{" "}
|
||||
Non
|
||||
</label>
|
||||
</label>
|
||||
|
||||
<div className={styles.formButtons}>
|
||||
<FormButton onClick={onSubmit99}>Enregistrer</FormButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (_.isEmpty(notifs)) {
|
||||
notifs.push(
|
||||
<div key="pushNotifs">
|
||||
<div className={styles.notificationsPage}>
|
||||
<div className={styles.notificationsContent}>
|
||||
<div className={styles.formLine} key="line-participation">
|
||||
<label>
|
||||
Tu as fait le tour des dernières infos ou questions importantes,
|
||||
merci ! :)
|
||||
<br />
|
||||
Nous te préviendrons quand il y en aura de nouvelles.
|
||||
<br />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (volunteerNotifs === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div>{notifs.map<React.ReactNode>((t) => t).reduce((prev, curr) => [prev, curr])}</div>
|
||||
}
|
||||
|
||||
export default memo(Notifications)
|
||||
|
||||
// Fetch server-side data here
|
||||
export const fetchFor = [
|
||||
...fetchForTeamWishesForm,
|
||||
...fetchForarticipationDetailsForm,
|
||||
...fetchForDayWishesForm,
|
||||
]
|
@ -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) => (
|
||||
<ul>
|
||||
{items.map(({ id, lastname, firstname }) => (
|
||||
<li key={id}>
|
||||
<Link to={`/Volunteer/${id}`}>
|
||||
<a href={`/Volunteer/${id}`}>
|
||||
<b>{firstname}</b> {lastname}
|
||||
</Link>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
@ -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,
|
||||
|
@ -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<Props> = (): JSX.Element => {
|
||||
</div>
|
||||
<div className={styles.announcements}>
|
||||
<div className={styles.navigationLink}>
|
||||
<Link to="/sinscrire"> S'informer sur le bénévolat </Link>
|
||||
<a href="/sinscrire"> S'informer sur le bénévolat </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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<Props> = (): JSX.Element => {
|
||||
if (jwtToken === undefined) return <p>Loading...</p>
|
||||
|
||||
if (jwtToken) {
|
||||
return <Notifications />
|
||||
return <Asks />
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
@ -29,7 +29,7 @@ const HomePage: FC<Props> = (): JSX.Element => {
|
||||
</div>
|
||||
<div className={styles.homePage}>
|
||||
<div className={styles.navigationLink}>
|
||||
<Link to="/sinscrire"> S'informer sur le bénévolat </Link>
|
||||
<a href="/sinscrire"> S'informer sur le bénévolat </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -38,8 +38,8 @@ const HomePage: FC<Props> = (): 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)
|
||||
|
@ -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<void> {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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"]
|
||||
}
|
||||
|
@ -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<VolunteerNotifs>]>("NotifsSet")
|
||||
export const volunteerAsksSet =
|
||||
serviceAccessors.securedCustomPost<[number, Partial<VolunteerAsks>]>("AsksSet")
|
||||
|
||||
export const volunteerTeamWishesSet =
|
||||
serviceAccessors.securedCustomPost<[number, Partial<VolunteerTeamWishes>]>("TeamWishesSet")
|
||||
|
@ -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,
|
||||
|
59
src/store/volunteerAsksSet.ts
Normal file
59
src/store/volunteerAsksSet.ts
Normal file
@ -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<VolunteerAsks>) => ({
|
||||
readyStatus: "success",
|
||||
entity: payload,
|
||||
}),
|
||||
getFailure: (_, { payload }: PayloadAction<string>) => ({
|
||||
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<VolunteerAsks> = {}): AppThunk =>
|
||||
(dispatch, getState) => {
|
||||
let jwt = ""
|
||||
|
||||
if (!id) {
|
||||
;({ jwt, id } = getState().auth)
|
||||
}
|
||||
if (shouldFetchVolunteerAsksSet(getState(), id))
|
||||
return dispatch(fetchVolunteerAsksSet(jwt, id, notif))
|
||||
|
||||
return 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<VolunteerNotifs>) => ({
|
||||
readyStatus: "success",
|
||||
entity: payload,
|
||||
}),
|
||||
getFailure: (_, { payload }: PayloadAction<string>) => ({
|
||||
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<VolunteerNotifs> = {}): 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
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user