mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-06-08 08:34:20 +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 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
@include nav-menu;
|
||||
}
|
||||
|
||||
.mainMenuItem {
|
||||
display: inline-block;
|
||||
margin: 0 4px;
|
||||
|
||||
a {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
color: $color-black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $color-grey-dark;
|
||||
}
|
||||
@include nav-menu-item;
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import _ from "lodash"
|
||||
import React, { memo, useCallback, useEffect, useRef, useState } from "react"
|
||||
import isNode from "detect-node"
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
import classnames from "classnames"
|
||||
import { fetchVolunteerNotifsSet } from "../../store/volunteerNotifsSet"
|
||||
import styles from "./styles.module.scss"
|
||||
import { selectUserJwtToken } from "../../store/auth"
|
||||
import { VolunteerNotifs } from "../../services/volunteers"
|
||||
import LogoutButton from "../LogoutButton/LogoutButton"
|
||||
// import { TeamWishesForm } from ".."
|
||||
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 => {
|
||||
// dispatch(
|
||||
// fetchVolunteerNotifsSet(jwtToken, 0, {
|
||||
@ -154,7 +191,7 @@ const Notifications = ({ volunteerNotifs }: Props): JSX.Element | null => {
|
||||
|
||||
// if (!_.includes(hidden, 3)) {
|
||||
// notifs.push(
|
||||
// <div key="1">
|
||||
// <div key="4">
|
||||
// <div className={styles.notificationsPage}>
|
||||
// <div className={styles.notificationsContent}>
|
||||
// <TeamWishesForm afterSubmit={onSubmit3} />
|
||||
@ -395,12 +432,7 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{notifs.map<React.ReactNode>((t) => t).reduce((prev, curr) => [prev, curr])}
|
||||
<LogoutButton />
|
||||
</div>
|
||||
)
|
||||
return <div>{notifs.map<React.ReactNode>((t) => t).reduce((prev, curr) => [prev, curr])}</div>
|
||||
}
|
||||
|
||||
export default memo(Notifications)
|
||||
|
@ -22,6 +22,9 @@
|
||||
.notifIntro {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.notifCentered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.formLine {
|
||||
padding: 5px 0;
|
||||
|
@ -32,13 +32,15 @@ import {
|
||||
} from "./gsheets/volunteers"
|
||||
import { wishListGet, wishAdd } from "./gsheets/wishes"
|
||||
import config from "../config"
|
||||
import notificationsSubscribe from "./notificationsSubscribe"
|
||||
import { notificationsSubscribe, notificationMain } from "./notifications"
|
||||
import checkAccess from "./checkAccess"
|
||||
import { hasGSheetsAccess } from "./gsheets/accessors"
|
||||
import { addStatus, showStatusAt } from "./status"
|
||||
|
||||
checkAccess()
|
||||
|
||||
notificationMain()
|
||||
|
||||
const app = express()
|
||||
|
||||
// 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 = ""
|
||||
|
||||
url = ""
|
||||
|
||||
informedWithNotif = false
|
||||
}
|
||||
|
||||
export const translationAnnouncement: { [k in keyof Announcement]: string } = {
|
||||
@ -16,6 +18,7 @@ export const translationAnnouncement: { [k in keyof Announcement]: string } = {
|
||||
type: "type",
|
||||
title: "titre",
|
||||
url: "url",
|
||||
informedWithNotif: "informéAvecUneNotif",
|
||||
}
|
||||
|
||||
export const elementName = "Announcement"
|
||||
|
@ -36,6 +36,36 @@
|
||||
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 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
Loading…
x
Reference in New Issue
Block a user