Change menu into tabs, change push notif into temporary question

This commit is contained in:
pikiou 2022-03-29 00:47:02 +02:00
parent 8fb7b5287b
commit 70b53c5af8
10 changed files with 205 additions and 138 deletions

View File

@ -9,6 +9,7 @@
background-color: $color-white; background-color: $color-white;
border-top: $border-large; border-top: $border-large;
border-bottom: $border-large; border-bottom: $border-large;
height: 52px;
@include desktop { @include desktop {
margin: 20px 0 35px; margin: 20px 0 35px;
@ -63,7 +64,7 @@
@include desktop { @include desktop {
top: auto; top: auto;
bottom: 5px; bottom: 0;
left: 420px; left: 420px;
right: 10px; right: 10px;
text-align: center; text-align: center;

View File

@ -43,7 +43,7 @@ const LoginForm = (): JSX.Element => {
</div> </div>
{loginError && <div className={styles.error}>{loginError}</div>} {loginError && <div className={styles.error}>{loginError}</div>}
<div className={styles.link}> <div className={styles.link}>
<Link to="/forgot"> Demander un nouveau mot de passe </Link> <Link to="/oubli"> Demander un nouveau mot de passe </Link>
</div> </div>
</form> </form>
) )

View File

@ -1,11 +1,12 @@
import { FC, useCallback, useState } from "react" import { FC, useCallback, useState } from "react"
import { useSelector } from "react-redux" import { useSelector } from "react-redux"
import classnames from "classnames" import classnames from "classnames"
import { isUserConnected } from "../../store/auth" import { isUserConnected, routerSelector } from "../../store/auth"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
const MainMenu: FC = (): JSX.Element | null => { const MainMenu: FC = (): JSX.Element | null => {
const connected = useSelector(isUserConnected) const connected = useSelector(isUserConnected)
const router = useSelector(routerSelector)
const [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)
@ -19,21 +20,24 @@ const MainMenu: FC = (): JSX.Element | null => {
if (!connected) return null if (!connected) return null
function createMenuItem(name: string, pathname: string): JSX.Element {
const isActive = (router as any)?.location?.pathname === pathname
return (
<li className={classnames(styles.mainMenuItem, isActive ? styles.active : null)}>
<a href={pathname}>{name}</a>
</li>
)
}
return ( return (
<nav> <nav>
<button type="button" className={styles.burger} onClick={onOpen}> <button type="button" className={styles.burger} onClick={onOpen}>
</button> </button>
<ul className={classnames(styles.mainMenu, opened && styles.opened)}> <ul className={classnames(styles.mainMenu, opened && styles.opened)}>
<li className={styles.mainMenuItem}> {createMenuItem("Questions", "/")}
<a href="/">Page d'accueil</a> {createMenuItem("Annonces", "/annonces")}
</li> createMenuItem("Mon profil", "/profil")
<li className={styles.mainMenuItem}>
<a href="/annonces">Annonces</a>
</li>
<li className={styles.mainMenuItem}>
<a href="/profil">Mon profil</a>
</li>
<button type="button" className={styles.close} onClick={onClose}> <button type="button" className={styles.close} onClick={onClose}>
× ×
</button> </button>
@ -43,3 +47,8 @@ const MainMenu: FC = (): JSX.Element | null => {
} }
export default MainMenu export default MainMenu
// // Fetch server-side data here
// export const fetchFor = [
// ...fetchForTeamWishesForm,
// ]

View File

@ -65,7 +65,15 @@
.mainMenuItem { .mainMenuItem {
display: inline-block; display: inline-block;
margin: 0 6px; margin: 0;
padding: 7px 6px 6px;
border: 1px solid $color-black;
border-width: 1px 1px 0;
border-radius: 10px 10px 0 0;
text-align: center;
background-color: $color-grey-light;
cursor: pointer;
color: $color-grey-dark;
@include mobile { @include mobile {
display: block; display: block;
@ -76,11 +84,16 @@
} }
} }
&.active {
color: $color-yellow;
background-color: $color-black;
}
a { a {
font-size: 0.9em; font-size: 0.9em;
font-weight: bold; font-weight: bold;
color: $color-black;
text-decoration: none; text-decoration: none;
color: inherit;
@include mobile { @include mobile {
display: block; display: block;
@ -88,7 +101,7 @@
} }
} }
a:hover { &:not(.active) a:hover {
color: $color-grey-dark; color: $color-white;
} }
} }

View File

@ -20,6 +20,7 @@ import DayWishesForm, {
} from "../VolunteerBoard/DayWishesForm/DayWishesForm" } from "../VolunteerBoard/DayWishesForm/DayWishesForm"
import { useUserParticipationDetails } from "../VolunteerBoard/participationDetails.utils" import { useUserParticipationDetails } from "../VolunteerBoard/participationDetails.utils"
import FormButton from "../Form/FormButton/FormButton" import FormButton from "../Form/FormButton/FormButton"
import { toastError, toastSuccess } from "../../store/utils"
let prevNotifs: VolunteerNotifs | undefined let prevNotifs: VolunteerNotifs | undefined
@ -212,7 +213,7 @@ const Notifications = (): JSX.Element | null => {
const comment10 = get(userWishes, "dayWishesComment", "") const comment10 = get(userWishes, "dayWishesComment", "")
const needToShow10 = participation10 === "inconnu" || (newSelection.length === 0 && !comment10) const needToShow10 = participation10 === "inconnu" || (newSelection.length === 0 && !comment10)
if (!_.includes(hidden, 10) && needToShow10) { if (!_.includes(hidden, 10) && _.isEmpty(notifs) && needToShow10) {
notifs.push( notifs.push(
<div key="10"> <div key="10">
<div className={styles.notificationsPage}> <div className={styles.notificationsPage}>
@ -239,7 +240,7 @@ const Notifications = (): JSX.Element | null => {
const comment = get(teamWishesData, "teamWishesComment", "") const comment = get(teamWishesData, "teamWishesComment", "")
const needToShow11 = teamWishesString.length === 0 && !comment const needToShow11 = teamWishesString.length === 0 && !comment
if (!_.includes(hidden, 11) && needToShow11) { if (!_.includes(hidden, 11) && _.isEmpty(notifs) && needToShow11) {
notifs.push( notifs.push(
<div key="11"> <div key="11">
<div className={styles.notificationsPage}> <div className={styles.notificationsPage}>
@ -266,7 +267,7 @@ const Notifications = (): JSX.Element | null => {
const food = get(participationDetails, "food", "") const food = get(participationDetails, "food", "")
const needToShow12 = !tshirtSize && !food const needToShow12 = !tshirtSize && !food
if (!_.includes(hidden, 12) && needToShow12) { if (!_.includes(hidden, 12) && _.isEmpty(notifs) && needToShow12) {
notifs.push( notifs.push(
<div key="12"> <div key="12">
<div className={styles.notificationsPage}> <div className={styles.notificationsPage}>
@ -290,7 +291,6 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
*/ */
const [acceptsNotifs, setAcceptsNotifs] = useState("") const [acceptsNotifs, setAcceptsNotifs] = useState("")
const [notifMessage, setNotifMessage] = useState("")
const mounted = useRef(false) const mounted = useRef(false)
useEffect(() => { useEffect(() => {
@ -317,138 +317,143 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) }, [])
const onChangePushNotifs = useCallback( const onChangeValue99 = (e: React.ChangeEvent<HTMLInputElement>) =>
async (event: React.ChangeEvent<HTMLInputElement>): Promise<void> => { setAcceptsNotifs(e.target.value)
event.preventDefault()
if (isNode) {
return
}
if (!("serviceWorker" in navigator)) { const onSubmit99 = useCallback(async (): Promise<void> => {
return if (isNode) {
} return
}
const isChecked = event.target.value === "oui" if (!("serviceWorker" in navigator)) {
if (!isChecked) { return
setNotifMessage("Réponse mémorisée.") }
setAcceptsNotifs("non")
dispatch(
fetchVolunteerNotifsSet(jwtToken, 0, {
acceptsNotifs: "non",
})
)
return
}
const registration = await navigator.serviceWorker.ready if (acceptsNotifs === "non") {
setAcceptsNotifs("non")
if (!registration.pushManager) { dispatch(
setNotifMessage( fetchVolunteerNotifsSet(jwtToken, 0, {
"Il y a un problème avec le push manager. Il faudrait utiliser un navigateur plus récent !" hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 99],
) acceptsNotifs: "non",
return })
}
const convertedVapidKey = urlBase64ToUint8Array(
process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY
) )
return
}
function urlBase64ToUint8Array(base64String?: string) { const registration = await navigator.serviceWorker.ready
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) if (!registration.pushManager) {
const outputArray = new Uint8Array(rawData.length) 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
}
for (let i = 0; i < rawData.length; i += 1) { const convertedVapidKey = urlBase64ToUint8Array(process.env.FORCE_ORANGE_PUBLIC_VAPID_KEY)
outputArray[i] = rawData.charCodeAt(i)
} function urlBase64ToUint8Array(base64String?: string) {
return outputArray if (!base64String) {
return ""
} }
const padding = "=".repeat((4 - (base64String.length % 4)) % 4)
// eslint-disable-next-line
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/")
if (!convertedVapidKey) { const rawData = atob(base64)
console.error("No convertedVapidKey available") const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; i += 1) {
outputArray[i] = rawData.charCodeAt(i)
} }
return outputArray
}
try { if (!convertedVapidKey) {
const existedSubscription = await registration.pushManager.getSubscription() toastError("No convertedVapidKey available")
}
if (existedSubscription === null) { try {
// No subscription detected, make a request const existedSubscription = await registration.pushManager.getSubscription()
try {
const newSubscription = await registration.pushManager.subscribe({
applicationServerKey: convertedVapidKey,
userVisibleOnly: true,
})
// New subscription added
if (
volunteerNotifs?.acceptsNotifs === "oui" &&
!subscriptionEqualsSave(
newSubscription,
volunteerNotifs?.pushNotifSubscription
)
) {
setNotifMessage(
"Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
)
} else {
setNotifMessage("C'est mémorisé !")
}
setAcceptsNotifs("oui") if (existedSubscription === null) {
dispatch( // No subscription detected, make a request
fetchVolunteerNotifsSet(jwtToken, 0, { try {
pushNotifSubscription: JSON.stringify(newSubscription), const newSubscription = await registration.pushManager.subscribe({
acceptsNotifs: "oui", applicationServerKey: convertedVapidKey,
}) userVisibleOnly: true,
) })
} catch (_e) { // New subscription added
if (Notification.permission !== "granted") {
setNotifMessage(
"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."
)
} else {
setNotifMessage(
"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 ( if (
volunteerNotifs?.acceptsNotifs === "oui" && volunteerNotifs?.acceptsNotifs === "oui" &&
!subscriptionEqualsSave( !subscriptionEqualsSave(
existedSubscription, newSubscription,
volunteerNotifs?.pushNotifSubscription volunteerNotifs?.pushNotifSubscription
) )
) { ) {
setNotifMessage( toastSuccess(
"Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera." "Un autre navigateur était notifié, mais c'est maintenant celui-ci qui le sera."
) )
} else {
setNotifMessage("C'est mémorisé !")
} }
setAcceptsNotifs("oui")
dispatch( dispatch(
fetchVolunteerNotifsSet(jwtToken, 0, { fetchVolunteerNotifsSet(jwtToken, 0, {
pushNotifSubscription: JSON.stringify(existedSubscription), pushNotifSubscription: JSON.stringify(newSubscription),
acceptsNotifs: "oui", 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 !"
)
}
} }
} catch (_e) { } else {
setNotifMessage( // Existed subscription detected
"Il y a eu une erreur avec l'enregistrement avec le Service Worker. Il faudrait utiliser un navigateur plus récent !" 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) {
[dispatch, jwtToken, volunteerNotifs] 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 { function subscriptionEqualsSave(toCheck: PushSubscription, save: string | undefined): boolean {
if (!save) { if (!save) {
@ -457,17 +462,15 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
return _.isEqual(JSON.parse(JSON.stringify(toCheck)), JSON.parse(save)) return _.isEqual(JSON.parse(JSON.stringify(toCheck)), JSON.parse(save))
} }
if (notifs.length === 0) { const needToShow99 = volunteerNotifs?.acceptsNotifs !== "oui"
if (!_.includes(hidden, 99) && _.isEmpty(notifs) && needToShow99) {
notifs.push( notifs.push(
<div key="pushNotifs"> <div key="pushNotifs">
<div className={styles.notificationsPage}> <div className={styles.notificationsPage}>
<div className={styles.notificationsContent}> <div className={styles.notificationsContent}>
<div className={styles.formLine} key="line-participation"> <div className={styles.formLine} key="line-participation">
<label> <label>
Tu as fait le tour des dernières infos ou questions importantes,
merci ! :)
<br />
<br />
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en Acceptes-tu de recevoir une alerte dans ton navigateur quand on en
aura d&apos;autres à t'afficher ici ?<br /> aura d&apos;autres à t'afficher ici ?<br />
<span className={styles.sousMessage}> <span className={styles.sousMessage}>
@ -480,7 +483,7 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
value="oui" value="oui"
name="gender" name="gender"
checked={acceptsNotifs === "oui"} checked={acceptsNotifs === "oui"}
onChange={onChangePushNotifs} onChange={onChangeValue99}
/>{" "} />{" "}
Oui Oui
</label> </label>
@ -490,16 +493,35 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
value="non" value="non"
name="gender" name="gender"
checked={acceptsNotifs === "non"} checked={acceptsNotifs === "non"}
onChange={onChangePushNotifs} onChange={onChangeValue99}
/>{" "} />{" "}
Non Non
</label> </label>
</label> </label>
<div className={styles.message}>{notifMessage}</div>
<span className={styles.sousMessage}> <div className={styles.formButtons}>
Pas besoin de valider, le site mémorise automatiquement si tu <FormButton onClick={onSubmit99}>Enregistrer</FormButton>
changes ta réponse. </div>
</span> </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>
</div> </div>

View File

@ -5,6 +5,7 @@
padding: 0; padding: 0;
font-weight: 600; font-weight: 600;
font-size: 1.1em; font-size: 1.1em;
text-align: center;
} }
.content { .content {

View File

@ -11,6 +11,7 @@ import styles from "./styles.module.scss"
import { fetchAnnouncementListIfNeed } from "../../store/announcementList" import { fetchAnnouncementListIfNeed } from "../../store/announcementList"
import { Announcement } from "../../services/announcement" import { Announcement } from "../../services/announcement"
import { selectUserJwtToken } from "../../store/auth" import { selectUserJwtToken } from "../../store/auth"
import ContentTitle from "../../components/ui/Content/ContentTitle"
export type Props = RouteComponentProps export type Props = RouteComponentProps
@ -42,7 +43,10 @@ const AnnouncementsPage: FC<Props> = (): JSX.Element => {
return ( return (
<div className={styles.announcements}> <div className={styles.announcements}>
<div className={styles.announcementsContent}>{listElements}</div> <div className={styles.announcementsContent}>
<ContentTitle title="Annonces" />
{listElements}
</div>
</div> </div>
) )
} }

View File

@ -42,15 +42,28 @@ export default [
path: "/login", path: "/login",
component: Login, component: Login,
}, },
{
path: "/sidentifier",
component: Login,
},
{ {
path: "/forgot", path: "/forgot",
component: Forgot, component: Forgot,
}, },
{
path: "/oubli",
component: Forgot,
},
{ {
path: "/equipes", path: "/equipes",
component: AsyncTeams, component: AsyncTeams,
loadData: loadTeamsData, loadData: loadTeamsData,
}, },
{
path: "/teams",
component: AsyncTeams,
loadData: loadTeamsData,
},
{ {
path: "/wish", path: "/wish",
component: AsyncWish, component: AsyncWish,

View File

@ -37,8 +37,12 @@ export const { setCurrentUser, logoutUser } = auth.actions
export const selectAuthData = (state: AppState): AuthState => state.auth export const selectAuthData = (state: AppState): AuthState => state.auth
export const selectRouter = (state: AppState): AppState["router"] => state.router
export const selectUserJwtToken = createSelector(selectAuthData, (authData) => authData.jwt) export const selectUserJwtToken = createSelector(selectAuthData, (authData) => authData.jwt)
export const routerSelector = createSelector(selectRouter, (authData) => authData)
export const selectUserRoles = createSelector(selectAuthData, (authData) => authData.roles) export const selectUserRoles = createSelector(selectAuthData, (authData) => authData.roles)
export const isUserConnected = createSelector(selectUserJwtToken, (token) => !!token) export const isUserConnected = createSelector(selectUserJwtToken, (token) => !!token)

View File

@ -13,10 +13,10 @@ export interface StateRequest {
error?: string error?: string
} }
export function toastError(message: string): void { export function toastError(message: string, autoClose: number | false = 6000): void {
toast.error(message, { toast.error(message, {
position: "top-center", position: "top-center",
autoClose: 6000, ...(autoClose ? { autoClose } : {}),
hideProgressBar: true, hideProgressBar: true,
closeOnClick: true, closeOnClick: true,
pauseOnHover: true, pauseOnHover: true,
@ -25,10 +25,10 @@ export function toastError(message: string): void {
}) })
} }
export function toastSuccess(message: string): void { export function toastSuccess(message: string, autoClose: number | false = 5000): void {
toast.success(message, { toast.success(message, {
position: "top-center", position: "top-center",
autoClose: 5000, ...(autoClose ? { autoClose } : {}),
hideProgressBar: true, hideProgressBar: true,
closeOnClick: true, closeOnClick: true,
pauseOnHover: true, pauseOnHover: true,