diff --git a/src/components/Navigation/styles.module.scss b/src/components/Navigation/styles.module.scss index 108db20..25c8255 100644 --- a/src/components/Navigation/styles.module.scss +++ b/src/components/Navigation/styles.module.scss @@ -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; } diff --git a/src/components/Notifications/index.tsx b/src/components/Notifications/index.tsx index 9524dc0..5366f87 100644 --- a/src/components/Notifications/index.tsx +++ b/src/components/Notifications/index.tsx @@ -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( +
+
+
+
+
+ La{" "} + + gazette de février + {" "} + est disponible !
+
+ +
+
+
+
+
+
+ ) + } + // 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( - //
+ //
//
//
// @@ -395,12 +432,7 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique. return null } - return ( -
- {notifs.map((t) => t).reduce((prev, curr) => [prev, curr])} - -
- ) + return
{notifs.map((t) => t).reduce((prev, curr) => [prev, curr])}
} export default memo(Notifications) diff --git a/src/components/Notifications/styles.module.scss b/src/components/Notifications/styles.module.scss index 6856a83..379b293 100755 --- a/src/components/Notifications/styles.module.scss +++ b/src/components/Notifications/styles.module.scss @@ -22,6 +22,9 @@ .notifIntro { margin-bottom: 10px; } +.notifCentered { + text-align: center; +} .formLine { padding: 5px 0; diff --git a/src/server/index.ts b/src/server/index.ts index 085f7c2..5e884f5 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -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 diff --git a/src/server/notifications.ts b/src/server/notifications.ts new file mode 100644 index 0000000..2b02d42 --- /dev/null +++ b/src/server/notifications.ts @@ -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 { + const announcementSheet = getSheet( + "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( + "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) +} diff --git a/src/server/notificationsSubscribe.ts b/src/server/notificationsSubscribe.ts deleted file mode 100644 index bd276e7..0000000 --- a/src/server/notificationsSubscribe.ts +++ /dev/null @@ -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 }) -} diff --git a/src/services/announcement.ts b/src/services/announcement.ts index f3ebb54..615583d 100644 --- a/src/services/announcement.ts +++ b/src/services/announcement.ts @@ -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" diff --git a/src/theme/mixins.scss b/src/theme/mixins.scss index 6163950..b62bc28 100644 --- a/src/theme/mixins.scss +++ b/src/theme/mixins.scss @@ -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;