Refactor Notifications into Asks, replace all <Link> by <a>

This commit is contained in:
pikiou
2022-03-29 06:31:03 +02:00
parent 70b53c5af8
commit 4273ea1603
25 changed files with 636 additions and 663 deletions

View 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]

View 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>
)
}

View 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]

View 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&apos;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>
)
}

View 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]

View 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>
)
}

View 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,
]

View File

@@ -0,0 +1,67 @@
@import "../../theme/variables";
@import "../../theme/mixins";
.notificationsPage {
@include page-wrapper-center;
}
.notificationsContent {
@include page-content-wrapper;
}
.notificationsContentNarrow {
@include page-content-wrapper(520px);
}
.pushNotificationsPage {
@include page-wrapper-center;
}
.pushNotificationsContent {
@include page-content-wrapper;
}
.notifIntro {
margin-bottom: 10px;
}
.notifCentered {
text-align: center;
}
.formLine {
padding: 5px 0;
label {
display: block;
margin-left: 5px;
margin-top: 7px;
}
.sousMessage {
font-size: 95%;
}
select,
input {
width: 10%;
border: 1px solid #333;
border-radius: 4px;
}
}
.formButtons {
margin-top: 10px;
padding: 5px 0;
text-align: center;
}
.message {
margin-top: 10px;
color: rgb(11, 138, 0);
text-align: center;
}
.error {
margin-top: 10px;
color: rgb(255, 0, 0);
text-align: center;
}

View 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>.
</>
)