mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-09 00:54:21 +02:00
Add sending notifications for new announcements
This commit is contained in:
parent
140891c4c0
commit
1e10f773fb
@ -1,23 +1,9 @@
|
|||||||
@import "../../theme/variables";
|
@import "../../theme/mixins";
|
||||||
|
|
||||||
.mainMenu {
|
.mainMenu {
|
||||||
margin: 0;
|
@include nav-menu;
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainMenuItem {
|
.mainMenuItem {
|
||||||
display: inline-block;
|
@include nav-menu-item;
|
||||||
margin: 0 4px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: $color-black;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: $color-grey-dark;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ import _ from "lodash"
|
|||||||
import React, { memo, useCallback, useEffect, useRef, useState } from "react"
|
import React, { memo, useCallback, useEffect, useRef, useState } from "react"
|
||||||
import isNode from "detect-node"
|
import isNode from "detect-node"
|
||||||
import { useDispatch, useSelector } from "react-redux"
|
import { useDispatch, useSelector } from "react-redux"
|
||||||
|
import classnames from "classnames"
|
||||||
import { fetchVolunteerNotifsSet } from "../../store/volunteerNotifsSet"
|
import { fetchVolunteerNotifsSet } from "../../store/volunteerNotifsSet"
|
||||||
import styles from "./styles.module.scss"
|
import styles from "./styles.module.scss"
|
||||||
import { selectUserJwtToken } from "../../store/auth"
|
import { selectUserJwtToken } from "../../store/auth"
|
||||||
import { VolunteerNotifs } from "../../services/volunteers"
|
import { VolunteerNotifs } from "../../services/volunteers"
|
||||||
import LogoutButton from "../LogoutButton/LogoutButton"
|
|
||||||
// import { TeamWishesForm } from ".."
|
// import { TeamWishesForm } from ".."
|
||||||
import { fetchFor as fetchForTeamWishesForm } from "../VolunteerBoard/TeamWishesForm/TeamWishesForm"
|
import { fetchFor as fetchForTeamWishesForm } from "../VolunteerBoard/TeamWishesForm/TeamWishesForm"
|
||||||
|
|
||||||
@ -144,6 +144,43 @@ const Notifications = ({ volunteerNotifs }: Props): JSX.Element | null => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.notificationsContent}>
|
||||||
|
<form onSubmit={onSubmit3}>
|
||||||
|
<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}>
|
||||||
|
<button type="submit">Ok, masquer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// const onSubmit3 = useCallback((): void => {
|
// const onSubmit3 = useCallback((): void => {
|
||||||
// dispatch(
|
// dispatch(
|
||||||
// fetchVolunteerNotifsSet(jwtToken, 0, {
|
// fetchVolunteerNotifsSet(jwtToken, 0, {
|
||||||
@ -154,7 +191,7 @@ const Notifications = ({ volunteerNotifs }: Props): JSX.Element | null => {
|
|||||||
|
|
||||||
// if (!_.includes(hidden, 3)) {
|
// if (!_.includes(hidden, 3)) {
|
||||||
// notifs.push(
|
// notifs.push(
|
||||||
// <div key="1">
|
// <div key="4">
|
||||||
// <div className={styles.notificationsPage}>
|
// <div className={styles.notificationsPage}>
|
||||||
// <div className={styles.notificationsContent}>
|
// <div className={styles.notificationsContent}>
|
||||||
// <TeamWishesForm afterSubmit={onSubmit3} />
|
// <TeamWishesForm afterSubmit={onSubmit3} />
|
||||||
@ -395,12 +432,7 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <div>{notifs.map<React.ReactNode>((t) => t).reduce((prev, curr) => [prev, curr])}</div>
|
||||||
<div>
|
|
||||||
{notifs.map<React.ReactNode>((t) => t).reduce((prev, curr) => [prev, curr])}
|
|
||||||
<LogoutButton />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(Notifications)
|
export default memo(Notifications)
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
.notifIntro {
|
.notifIntro {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
.notifCentered {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.formLine {
|
.formLine {
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
|
@ -32,13 +32,15 @@ import {
|
|||||||
} from "./gsheets/volunteers"
|
} from "./gsheets/volunteers"
|
||||||
import { wishListGet, wishAdd } from "./gsheets/wishes"
|
import { wishListGet, wishAdd } from "./gsheets/wishes"
|
||||||
import config from "../config"
|
import config from "../config"
|
||||||
import notificationsSubscribe from "./notificationsSubscribe"
|
import { notificationsSubscribe, notificationMain } from "./notifications"
|
||||||
import checkAccess from "./checkAccess"
|
import checkAccess from "./checkAccess"
|
||||||
import { hasGSheetsAccess } from "./gsheets/accessors"
|
import { hasGSheetsAccess } from "./gsheets/accessors"
|
||||||
import { addStatus, showStatusAt } from "./status"
|
import { addStatus, showStatusAt } from "./status"
|
||||||
|
|
||||||
checkAccess()
|
checkAccess()
|
||||||
|
|
||||||
|
notificationMain()
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
|
||||||
// Use helmet to secure Express with various HTTP headers
|
// Use helmet to secure Express with various HTTP headers
|
||||||
|
131
src/server/notifications.ts
Normal file
131
src/server/notifications.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express"
|
||||||
|
import webpush from "web-push"
|
||||||
|
import {
|
||||||
|
Announcement,
|
||||||
|
AnnouncementWithoutId,
|
||||||
|
translationAnnouncement,
|
||||||
|
} from "../services/announcement"
|
||||||
|
import { translationVolunteer, Volunteer, VolunteerWithoutId } from "../services/volunteers"
|
||||||
|
import { getSheet } from "./gsheets/accessors"
|
||||||
|
|
||||||
|
const publicKey = process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY
|
||||||
|
const privateKey = process.env.FORCE_ORANGE_PRIVATE_VAPID_KEY
|
||||||
|
const hasPushAccess = publicKey && privateKey && !__DEV__ && !__LOCAL__
|
||||||
|
if (hasPushAccess) {
|
||||||
|
webpush.setVapidDetails("mailto: contact@parisestludique.fr", publicKey, privateKey)
|
||||||
|
}
|
||||||
|
type Payload = {
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendNotifToVolunteer(volunteer: Volunteer, payload: Payload): void {
|
||||||
|
if (volunteer.acceptsNotifs !== "oui") {
|
||||||
|
console.error(`Volunteer refuses notifs`)
|
||||||
|
} else {
|
||||||
|
const subscription = JSON.parse(volunteer.pushNotifSubscription)
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
console.error(`Volunteer has no notif subscription`)
|
||||||
|
} else if (hasPushAccess) {
|
||||||
|
const stringifiedPayload = JSON.stringify(payload)
|
||||||
|
webpush
|
||||||
|
.sendNotification(subscription, stringifiedPayload)
|
||||||
|
.then((result) => console.error(result))
|
||||||
|
.catch((e) => console.error(e.stack))
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`Fake sending push notif to ${JSON.stringify(subscription)} of ${JSON.stringify(
|
||||||
|
payload
|
||||||
|
)})}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendNotifToSubscription(
|
||||||
|
subscription: webpush.PushSubscription,
|
||||||
|
payload: Payload
|
||||||
|
): void {
|
||||||
|
if (hasPushAccess) {
|
||||||
|
const stringifiedPayload = JSON.stringify(payload)
|
||||||
|
webpush
|
||||||
|
.sendNotification(subscription, stringifiedPayload)
|
||||||
|
.then((result) => console.error(result))
|
||||||
|
.catch((e) => console.error(e.stack))
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`Fake sending push notif to ${JSON.stringify(subscription)} of ${JSON.stringify(
|
||||||
|
payload
|
||||||
|
)})}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function notificationsSubscribe(
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
_next: NextFunction
|
||||||
|
): void {
|
||||||
|
sendNotifToSubscription(request.body, {
|
||||||
|
message:
|
||||||
|
"Clique-moi pour voir la gazette de février dans la page Annonces !\nFini les emails, cette notification sera notre seul moyen de te prévenir :)",
|
||||||
|
})
|
||||||
|
|
||||||
|
response.status(200).json({ success: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function notificationMain(): void {
|
||||||
|
setInterval(notifyAboutAnnouncement, 5 * 60 * 1000)
|
||||||
|
setTimeout(notifyAboutAnnouncement, 60 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function notifyAboutAnnouncement(): Promise<void> {
|
||||||
|
const announcementSheet = getSheet<AnnouncementWithoutId, Announcement>(
|
||||||
|
"Announcements",
|
||||||
|
new Announcement(),
|
||||||
|
translationAnnouncement
|
||||||
|
)
|
||||||
|
|
||||||
|
const announcementList = await announcementSheet.getList()
|
||||||
|
if (!announcementList) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const toSend = announcementList.find((a) => !a.informedWithNotif)
|
||||||
|
if (!toSend) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const volunteerSheet = getSheet<VolunteerWithoutId, Volunteer>(
|
||||||
|
"Volunteers",
|
||||||
|
new Volunteer(),
|
||||||
|
translationVolunteer
|
||||||
|
)
|
||||||
|
const volunteerList = await volunteerSheet.getList()
|
||||||
|
if (!volunteerList) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const audience = volunteerList.filter(
|
||||||
|
(v) =>
|
||||||
|
v.acceptsNotifs === "oui" &&
|
||||||
|
!v.discordId &&
|
||||||
|
(v.active === "oui" || v.active === "peut-etre")
|
||||||
|
)
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
`Sending announcement ${toSend.title} to ${audience
|
||||||
|
.map((v) => `${v.firstname} #${v.id}`)
|
||||||
|
.join(", ")}`
|
||||||
|
)
|
||||||
|
|
||||||
|
audience.forEach((v) => {
|
||||||
|
sendNotifToVolunteer(v, {
|
||||||
|
message:
|
||||||
|
"Clique-moi pour voir la gazette de février dans la page Annonces !\n\nFini les emails/spam, cette notification est notre nouveau moyen de te prévenir :)",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
toSend.informedWithNotif = true
|
||||||
|
await announcementSheet.set(toSend)
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
import { Request, Response, NextFunction } from "express"
|
|
||||||
import webpush from "web-push"
|
|
||||||
|
|
||||||
const publicKey = process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY
|
|
||||||
const privateKey = process.env.FORCE_ORANGE_PRIVATE_VAPID_KEY
|
|
||||||
const hasPushAccess = publicKey && privateKey
|
|
||||||
if (hasPushAccess) {
|
|
||||||
webpush.setVapidDetails("mailto: contact@parisestludique.fr", publicKey, privateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function notificationsSubscribe(
|
|
||||||
request: Request,
|
|
||||||
response: Response,
|
|
||||||
_next: NextFunction
|
|
||||||
): void {
|
|
||||||
const subscription = request.body
|
|
||||||
|
|
||||||
const payload = JSON.stringify({
|
|
||||||
title: "Hello!",
|
|
||||||
body: "It works.",
|
|
||||||
})
|
|
||||||
|
|
||||||
if (hasPushAccess) {
|
|
||||||
webpush
|
|
||||||
.sendNotification(subscription, payload)
|
|
||||||
.then((result) => console.log(result))
|
|
||||||
.catch((e) => console.log(e.stack))
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
`Fake sending push notif to ${JSON.stringify(subscription)} of ${JSON.stringify(
|
|
||||||
payload
|
|
||||||
)})}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.status(200).json({ success: true })
|
|
||||||
}
|
|
@ -8,6 +8,8 @@ export class Announcement {
|
|||||||
title = ""
|
title = ""
|
||||||
|
|
||||||
url = ""
|
url = ""
|
||||||
|
|
||||||
|
informedWithNotif = false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const translationAnnouncement: { [k in keyof Announcement]: string } = {
|
export const translationAnnouncement: { [k in keyof Announcement]: string } = {
|
||||||
@ -16,6 +18,7 @@ export const translationAnnouncement: { [k in keyof Announcement]: string } = {
|
|||||||
type: "type",
|
type: "type",
|
||||||
title: "titre",
|
title: "titre",
|
||||||
url: "url",
|
url: "url",
|
||||||
|
informedWithNotif: "informéAvecUneNotif",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const elementName = "Announcement"
|
export const elementName = "Announcement"
|
||||||
|
@ -36,6 +36,36 @@
|
|||||||
background-color: $color-grey-lighter;
|
background-color: $color-grey-lighter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin nav-menu($desktopWidth: 520px) {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin nav-menu-item($desktopWidth: 520px) {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 4px;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $color-black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: $color-grey-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@mixin clear-ul-style {
|
@mixin clear-ul-style {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user