Add /profil to notifications, improve responsiveness

This commit is contained in:
pikiou 2022-03-25 01:37:11 +01:00
parent f65963d91c
commit 8fb7b5287b
21 changed files with 538 additions and 280 deletions

View File

@ -0,0 +1,53 @@
import { FC, ReactNode, useState } from "react"
import styles from "./styles.module.scss"
type Props = {
children?: ReactNode | undefined
text: string
onClick?: () => void
}
const FormButton: FC<Props> = ({ children, text, onClick }): JSX.Element => {
const [doShowMessage, setDoShowMessage] = useState<boolean>(false)
const showMessage = () => {
setDoShowMessage(true)
}
const onIgnore = () => {
setDoShowMessage(true)
onClick?.()
}
const onCloseIgnore = () => {
setDoShowMessage(false)
}
return (
<>
<button
type="button"
className={styles.greyButton}
onClick={!children ? onClick : showMessage}
>
{text}
</button>
{doShowMessage && (
<div className={styles.infoBeforeIgnore}>
<button type="button" className={styles.closeButton} onClick={onCloseIgnore}>
&#10005;
</button>
<div>{children}</div>
<button type="button" className={styles.greyButton} onClick={onIgnore}>
Ok, ignorer
</button>
</div>
)}
</>
)
}
FormButton.defaultProps = {
children: undefined,
onClick: undefined,
}
export default FormButton

View File

@ -0,0 +1,30 @@
@import "../../../theme/mixins";
.greyButton {
@include form-grey-button;
margin-top: 10px;
}
.infoBeforeIgnore {
margin-top: 15px;
position: relative;
padding: 15px;
outline: 0;
background-color: $color-white;
}
.closeButton {
position: absolute;
padding: 0;
z-index: 10;
top: 10px;
right: 10px;
width: 20px;
height: 20px;
border-radius: 0;
line-height: 20px;
color: $color-grey-dark;
text-align: center;
background: none;
cursor: pointer;
}

View File

@ -6,6 +6,7 @@ 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 [opened, setOpened] = useState(false) const [opened, setOpened] = useState(false)
const onOpen = useCallback(() => { const onOpen = useCallback(() => {
@ -24,6 +25,9 @@ const MainMenu: FC = (): JSX.Element | null => {
</button> </button>
<ul className={classnames(styles.mainMenu, opened && styles.opened)}> <ul className={classnames(styles.mainMenu, opened && styles.opened)}>
<li className={styles.mainMenuItem}>
<a href="/">Page d'accueil</a>
</li>
<li className={styles.mainMenuItem}> <li className={styles.mainMenuItem}>
<a href="/annonces">Annonces</a> <a href="/annonces">Annonces</a>
</li> </li>

View File

@ -57,6 +57,9 @@
text-align: center; text-align: center;
font-size: 22px; font-size: 22px;
color: $color-black; color: $color-black;
background-color: $color-white;
border-radius: 4px;
box-shadow: 1px 1px 2px $color-black;
} }
} }

View File

@ -1,16 +1,25 @@
import _ from "lodash" import _, { get } 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 { shallowEqual, useDispatch, useSelector } from "react-redux" import { shallowEqual, 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 { TeamWishesForm } from ".." import TeamWishesForm, {
import { fetchFor as fetchForTeamWishesForm } from "../VolunteerBoard/TeamWishesForm/TeamWishesForm" fetchFor as fetchForTeamWishesForm,
} from "../VolunteerBoard/TeamWishesForm/TeamWishesForm"
import { AppState } from "../../store" import { AppState } from "../../store"
import Block from "../ui/Content/ContentBlock" import { useUserTeamWishes } from "../VolunteerBoard/teamWishes.utils"
import { useUserDayWishes } from "../VolunteerBoard/daysWishes.utils"
import ParticipationDetailsForm, {
fetchFor as fetchForarticipationDetailsForm,
} from "../VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
import DayWishesForm, {
fetchFor as fetchForDayWishesForm,
} from "../VolunteerBoard/DayWishesForm/DayWishesForm"
import { useUserParticipationDetails } from "../VolunteerBoard/participationDetails.utils"
import FormButton from "../Form/FormButton/FormButton"
let prevNotifs: VolunteerNotifs | undefined let prevNotifs: VolunteerNotifs | undefined
@ -28,118 +37,28 @@ const Notifications = (): JSX.Element | null => {
const hidden = volunteerNotifs?.hiddenNotifs || [] const hidden = volunteerNotifs?.hiddenNotifs || []
const notifs: JSX.Element[] = [] const notifs: JSX.Element[] = []
const onSubmit1 = useCallback( const onSubmit1 = useCallback((): void => {
(event: React.SyntheticEvent): void => { dispatch(
event.preventDefault() fetchVolunteerNotifsSet(jwtToken, 0, {
dispatch( hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 1],
fetchVolunteerNotifsSet(jwtToken, 0, { })
hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 1], )
}) }, [dispatch, jwtToken, volunteerNotifs])
)
},
[dispatch, jwtToken, volunteerNotifs]
)
if (!_.includes(hidden, 1)) { if (!_.includes(hidden, 1)) {
notifs.push( notifs.push(
<div key="1"> <div key="1">
<div className={styles.notificationsContent}> <div className={styles.notificationsPage}>
<form onSubmit={onSubmit1}> <div className={styles.notificationsContentNarrow}>
Salut {volunteerNotifs?.firstname} ! <form>
<div className={styles.notifIntro} key="login-intro"> Salut {volunteerNotifs?.firstname} !
Ici tu seras notifié(e) des nouvelles importantes et des questions pour <div className={styles.notifIntro} key="login-intro">
lesquelles il nous faudrait absolument ta réponse. Ici tu seras notifié(e) des nouvelles importantes et des questions
<div className={styles.formButtons}> pour lesquelles il nous faudrait absolument ta réponse.
<button type="submit">Ok, continuer</button> <div className={styles.formButtons}>
</div> <FormButton onClick={onSubmit1}>Ok, continuer</FormButton>
</div>
</form>
</div>
</div>
)
}
const [participation, setParticipation] = useState(volunteerNotifs?.active || "inconnu")
const [participationMessage, setParticipationMessage] = useState("")
const onChangeValue2 = (e: React.ChangeEvent<HTMLInputElement>) =>
setParticipation(e.target.value)
const onSubmit2 = useCallback(
(event: React.SyntheticEvent): void => {
event.preventDefault()
if (participation === "inconnu") {
setParticipationMessage("Il nous faudrait une réponse ^^")
return
}
dispatch(
fetchVolunteerNotifsSet(jwtToken, 0, {
hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 2],
active: participation,
})
)
},
[dispatch, jwtToken, volunteerNotifs, participation]
)
if (!_.includes(hidden, 2)) {
notifs.push(
<div key="2">
<div className={styles.notificationsContent}>
<div className={styles.formLine} key="line-participation">
<form onSubmit={onSubmit2}>
Si les conditions sanitaires te le permettent, souhaites-tu être
bénévole à PeL 2022 ?<br />
<label>
<input
type="radio"
value="oui"
name="participation"
checked={participation === "oui"}
onChange={onChangeValue2}
/>{" "}
Oui
</label>
<label>
<input
type="radio"
value="non"
name="participation"
checked={participation === "non"}
onChange={onChangeValue2}
/>{" "}
Non
</label>
<label>
<input
type="radio"
value="peut-etre"
name="participation"
checked={participation === "peut-etre"}
onChange={onChangeValue2}
/>{" "}
Je ne sais pas encore
</label>
{participation === "peut-etre" ? (
<div>
On te le reproposera dans quelques temps.
<br />
Si tu as besoin d&apos;infos, viens nous en parler sur le
serveur Discord ! Pour le rejoindre,{" "}
<a
href="https://discord.com/invite/eXhjKxSBB4"
target="_blank"
rel="noreferrer"
>
clique ici{" "}
</a>
.
</div> </div>
) : null}
<div className={styles.formButtons}>
<button type="submit">Confirmer</button>
</div> </div>
<div className={styles.message}>{participationMessage}</div>
</form> </form>
</div> </div>
</div> </div>
@ -147,40 +66,94 @@ const Notifications = (): JSX.Element | null => {
) )
} }
const onSubmit3 = useCallback((): void => { // const [participation, setParticipation] = useState(volunteerNotifs?.active || "inconnu")
dispatch( // const [participationMessage, setParticipationMessage] = useState("")
fetchVolunteerNotifsSet(jwtToken, 0, { // const onChangeValue2 = (e: React.ChangeEvent<HTMLInputElement>) =>
hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 3], // setParticipation(e.target.value)
})
)
}, [dispatch, jwtToken, volunteerNotifs])
if (!_.includes(hidden, 3)) { // const onSubmit2 = useCallback(
notifs.push( // (): void => {
<div key="3"> // if (participation === "inconnu") {
<div className={styles.notificationsContent}> // setParticipationMessage("Il nous faudrait une réponse ^^")
<form onSubmit={onSubmit3}> // return
<div // }
className={classnames(styles.notifIntro, styles.notifCentered)}
key="login-intro" // dispatch(
> // fetchVolunteerNotifsSet(jwtToken, 0, {
La{" "} // hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 2],
<a // active: participation,
href="https://mailchi.mp/3c75c3b3a20f/gazette_2020_02-8978118" // })
onClick={onSubmit3} // )
> // },
gazette de février // [dispatch, jwtToken, volunteerNotifs, participation]
</a>{" "} // )
est disponible !<br />
<div className={styles.formButtons}> // if (!_.includes(hidden, 2)) {
<button type="submit">Ok, masquer</button> // notifs.push(
</div> // <div key="2">
</div> // <div className={styles.notificationsPage}>
</form> // <div className={styles.notificationsContentNarrow}>
</div> // <div className={styles.formLine} key="line-participation">
</div> // <form>
) // Si les conditions sanitaires te le permettent, souhaites-tu être
} // bénévole à PeL 2022 ?<br />
// <label>
// <input
// type="radio"
// value="oui"
// name="participation"
// checked={participation === "oui"}
// onChange={onChangeValue2}
// />{" "}
// Oui
// </label>
// <label>
// <input
// type="radio"
// value="non"
// name="participation"
// checked={participation === "non"}
// onChange={onChangeValue2}
// />{" "}
// Non
// </label>
// <label>
// <input
// type="radio"
// value="peut-etre"
// name="participation"
// checked={participation === "peut-etre"}
// onChange={onChangeValue2}
// />{" "}
// Je ne sais pas encore
// </label>
// {participation === "peut-etre" ? (
// <div>
// On te le reproposera dans quelques temps.
// <br />
// Si tu as besoin d&apos;infos, viens nous en parler sur le
// serveur Discord ! Pour le rejoindre,{" "}
// <a
// href="https://discord.com/invite/eXhjKxSBB4"
// target="_blank"
// rel="noreferrer"
// >
// clique ici{" "}
// </a>
// .
// </div>
// ) : null}
// <div className={styles.formButtons}>
// <FormButton onClick={onSubmit2}>Confirmer</FormButton>
// </div>
// <div className={styles.message}>{participationMessage}</div>
// </form>
// </div>
// </div>
// </div>
// </div>
// )
// }
// const onSubmit3 = useCallback((): void => { // const onSubmit3 = useCallback((): void => {
// dispatch( // dispatch(
@ -192,16 +165,121 @@ const Notifications = (): JSX.Element | null => {
// if (!_.includes(hidden, 3)) { // if (!_.includes(hidden, 3)) {
// notifs.push( // notifs.push(
// <div key="4"> // <div key="3">
// <div className={styles.notificationsPage}> // <div className={styles.notificationsPage}>
// <div className={styles.notificationsContent}> // <div className={styles.notificationsContentNarrow}>
// <TeamWishesForm afterSubmit={onSubmit3} /> // <form>
// <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}>
// <FormButton onClick={onSubmit3}>Ok, masquer</FormButton>
// </div>
// </div>
// </form>
// </div> // </div>
// </div> // </div>
// </div> // </div>
// ) // )
// } // }
const answerLaterOnProfile = (
<>
Tu pourras y répondre plus tard sur la page <a href="/profil">Mon profil</a>.
</>
)
const onSubmit10 = useCallback((): void => {
dispatch(
fetchVolunteerNotifsSet(jwtToken, 0, {
hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 10],
})
)
}, [dispatch, jwtToken, volunteerNotifs])
const [userWishes] = useUserDayWishes()
const participation10 = get(userWishes, "active", "inconnu")
const newSelection = get(userWishes, "dayWishes", [])
const comment10 = get(userWishes, "dayWishesComment", "")
const needToShow10 = participation10 === "inconnu" || (newSelection.length === 0 && !comment10)
if (!_.includes(hidden, 10) && needToShow10) {
notifs.push(
<div key="10">
<div className={styles.notificationsPage}>
<div className={styles.notificationsContent}>
<DayWishesForm afterSubmit={onSubmit10}>
{answerLaterOnProfile}
</DayWishesForm>
</div>
</div>
</div>
)
}
const onSubmit11 = useCallback((): void => {
dispatch(
fetchVolunteerNotifsSet(jwtToken, 0, {
hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 11],
})
)
}, [dispatch, jwtToken, volunteerNotifs])
const [teamWishesData] = useUserTeamWishes()
const teamWishesString = get(teamWishesData, "teamWishes", [])
const comment = get(teamWishesData, "teamWishesComment", "")
const needToShow11 = teamWishesString.length === 0 && !comment
if (!_.includes(hidden, 11) && needToShow11) {
notifs.push(
<div key="11">
<div className={styles.notificationsPage}>
<div className={styles.notificationsContent}>
<TeamWishesForm afterSubmit={onSubmit11}>
{answerLaterOnProfile}
</TeamWishesForm>
</div>
</div>
</div>
)
}
const onSubmit12 = useCallback((): void => {
dispatch(
fetchVolunteerNotifsSet(jwtToken, 0, {
hiddenNotifs: [...(volunteerNotifs?.hiddenNotifs || []), 12],
})
)
}, [dispatch, jwtToken, volunteerNotifs])
const [participationDetails] = useUserParticipationDetails()
const tshirtSize = get(participationDetails, "tshirtSize", "")
const food = get(participationDetails, "food", "")
const needToShow12 = !tshirtSize && !food
if (!_.includes(hidden, 12) && needToShow12) {
notifs.push(
<div key="12">
<div className={styles.notificationsPage}>
<div className={styles.notificationsContent}>
<ParticipationDetailsForm afterSubmit={onSubmit12}>
{answerLaterOnProfile}
</ParticipationDetailsForm>
</div>
</div>
</div>
)
}
/* DISCORD /* DISCORD
Discord nous donne à tous la parole via nos téléphone ou navigateurs, pour organiser le meilleur des festivals ! Discord nous donne à tous la parole via nos téléphone ou navigateurs, pour organiser le meilleur des festivals !
Il permet de discuter sujet par sujet entre tous les bénévoles, entre les membres d'une même équipe, ou avec ton référent. Il permet de discuter sujet par sujet entre tous les bénévoles, entre les membres d'une même équipe, ou avec ton référent.
@ -382,47 +460,49 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
if (notifs.length === 0) { if (notifs.length === 0) {
notifs.push( notifs.push(
<div key="pushNotifs"> <div key="pushNotifs">
<Block title="Notifications"> <div className={styles.notificationsPage}>
<div className={styles.formLine} key="line-participation"> <div className={styles.notificationsContent}>
<label> <div className={styles.formLine} key="line-participation">
Tu as fait le tour des dernières infos ou questions importantes, merci ! <label>
:) Tu as fait le tour des dernières infos ou questions importantes,
<br /> merci ! :)
<br /> <br />
Acceptes-tu de recevoir une alerte dans ton navigateur quand on en aura <br />
d&apos;autres spécifiquement pour toi ?<br /> 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="gender"
checked={acceptsNotifs === "oui"}
onChange={onChangePushNotifs}
/>{" "}
Oui
</label>
<label>
<input
type="radio"
value="non"
name="gender"
checked={acceptsNotifs === "non"}
onChange={onChangePushNotifs}
/>{" "}
Non
</label>
</label>
<div className={styles.message}>{notifMessage}</div>
<span className={styles.sousMessage}> <span className={styles.sousMessage}>
(Ça nous simplifierait la vie, on a des soucis à contacter les Pas besoin de valider, le site mémorise automatiquement si tu
bénévoles par email.) changes ta réponse.
</span> </span>
<label> </div>
<input
type="radio"
value="oui"
name="gender"
checked={acceptsNotifs === "oui"}
onChange={onChangePushNotifs}
/>{" "}
Oui
</label>
<label>
<input
type="radio"
value="non"
name="gender"
checked={acceptsNotifs === "non"}
onChange={onChangePushNotifs}
/>{" "}
Non
</label>
</label>
<div className={styles.message}>{notifMessage}</div>
<span className={styles.sousMessage}>
Pas besoin de valider, le site mémorise automatiquement si tu changes ta
réponse.
</span>
</div> </div>
</Block> </div>
</div> </div>
) )
} }
@ -437,4 +517,8 @@ Tu n'y es absolument pas obligé(e) ! C'est juste plus pratique.
export default memo(Notifications) export default memo(Notifications)
// Fetch server-side data here // Fetch server-side data here
export const fetchFor = [...fetchForTeamWishesForm] export const fetchFor = [
...fetchForTeamWishesForm,
...fetchForarticipationDetailsForm,
...fetchForDayWishesForm,
]

View File

@ -6,7 +6,11 @@
} }
.notificationsContent { .notificationsContent {
@include inner-content-wrapper; @include page-content-wrapper;
}
.notificationsContentNarrow {
@include page-content-wrapper(520px);
} }
.pushNotificationsPage { .pushNotificationsPage {
@ -14,7 +18,7 @@
} }
.pushNotificationsContent { .pushNotificationsContent {
@include inner-content-wrapper; @include page-content-wrapper;
} }
.notifIntro { .notifIntro {

View File

@ -6,11 +6,10 @@ import ParticipationDetailsFormModal from "./ParticipationDetailsForm/Participat
import TeamWishes from "./TeamWishes/TeamWishes" import TeamWishes from "./TeamWishes/TeamWishes"
import TeamWishesFormModal from "./TeamWishesForm/TeamWishesFormModal" import TeamWishesFormModal from "./TeamWishesForm/TeamWishesFormModal"
import withUserConnected from "../../utils/withUserConnected" import withUserConnected from "../../utils/withUserConnected"
import { fetchVolunteerDayWishesSetIfNeed } from "../../store/volunteerDayWishesSet"
import { fetchVolunteerParticipationDetailsSetIfNeed } from "../../store/volunteerParticipationDetailsSet"
import { fetchTeamListIfNeed } from "../../store/teamList"
import { fetchVolunteerTeamWishesSetIfNeed } from "../../store/volunteerTeamWishesSet"
import ContentTitle from "../ui/Content/ContentTitle" import ContentTitle from "../ui/Content/ContentTitle"
import { fetchFor as fetchForDayWishesForm } from "./DayWishesForm/DayWishesForm"
import { fetchFor as fetchForParticipationDetailsForm } from "./ParticipationDetailsForm/ParticipationDetailsForm"
import { fetchFor as fetchForTeamWishesForm } from "./TeamWishesForm/TeamWishesForm"
const Board: FC = (): JSX.Element => ( const Board: FC = (): JSX.Element => (
<> <>
@ -27,8 +26,7 @@ const Board: FC = (): JSX.Element => (
export default memo(withUserConnected(Board)) export default memo(withUserConnected(Board))
export const fetchFor = [ export const fetchFor = [
fetchVolunteerDayWishesSetIfNeed, ...fetchForDayWishesForm,
fetchVolunteerParticipationDetailsSetIfNeed, ...fetchForParticipationDetailsForm,
fetchTeamListIfNeed, ...fetchForTeamWishesForm,
fetchVolunteerTeamWishesSetIfNeed,
] ]

View File

@ -32,16 +32,17 @@ const DayWishes: FC = (): JSX.Element | null => {
</div> </div>
)} )}
{participation === "inconnu" && ( {participation === "inconnu" && (
<div className={styles.lineEmpty}>Participation à PeL 2022 non renseignée</div> <div className={styles.lineEmpty}>
Participation à PeL 2022{" "}
<span className={styles.lineEmpty}>non renseignées</span>
</div>
)} )}
{participation !== "non" && ( {participation !== "non" && (
<div className={styles.daysLine}> <div className={styles.daysLine}>
<span className={styles.dayLineTitle}>Mes jours :</span> <span className={styles.dayLineTitle}>Mes jours :</span>
{dayWishesString && <b>{dayWishesString}</b>} {dayWishesString && <b>{dayWishesString}</b>}
{!dayWishesString && ( {!dayWishesString && <span className={styles.lineEmpty}>Non renseignés</span>}
<span className={styles.dayLineEmpty}>Non renseignés</span>
)}
</div> </div>
)} )}
{comment && ( {comment && (

View File

@ -36,7 +36,6 @@
} }
.lineEmpty { .lineEmpty {
margin: 0 5px 5px 0;
color: $color-red; color: $color-red;
font-style: italic; font-style: italic;
} }

View File

@ -1,4 +1,4 @@
import { FC, memo, useCallback, useEffect, useRef, useState } from "react" import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from "react"
import classnames from "classnames" import classnames from "classnames"
import get from "lodash/get" import get from "lodash/get"
import set from "lodash/set" import set from "lodash/set"
@ -10,12 +10,15 @@ import {
useUserDayWishes, useUserDayWishes,
} from "../daysWishes.utils" } from "../daysWishes.utils"
import FormButton from "../../Form/FormButton/FormButton" import FormButton from "../../Form/FormButton/FormButton"
import { fetchVolunteerDayWishesSetIfNeed } from "../../../store/volunteerDayWishesSet"
import IgnoreButton from "../../Form/IgnoreButton/IgnoreButton"
type Props = { type Props = {
children?: ReactNode | undefined
afterSubmit?: () => void | undefined afterSubmit?: () => void | undefined
} }
const DayWishesForm: FC<Props> = ({ afterSubmit }): JSX.Element => { const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
const [participationState, setParticipation] = useState("inconnu") const [participationState, setParticipation] = useState("inconnu")
const [selection, setSelection] = useState(daysChoiceSelectionDefaultState) const [selection, setSelection] = useState(daysChoiceSelectionDefaultState)
const commentRef = useRef<HTMLTextAreaElement | null>(null) const commentRef = useRef<HTMLTextAreaElement | null>(null)
@ -145,18 +148,35 @@ const DayWishesForm: FC<Props> = ({ afterSubmit }): JSX.Element => {
<label htmlFor="day-choice-comment">Un commentaire, une précision ?</label> <label htmlFor="day-choice-comment">Un commentaire, une précision ?</label>
<textarea id="day-choice-comment" ref={commentRef} /> <textarea id="day-choice-comment" ref={commentRef} />
</div> </div>
<div className={styles.dayWishesButtonWrapper}> <div className={styles.buttonWrapper}>
<FormButton onClick={onChoiceSubmit}>Enregistrer</FormButton>{" "} <FormButton onClick={onChoiceSubmit}>Enregistrer</FormButton>
<FormButton onClick={afterSubmit} type="grey"> {children === undefined && (
Annuler <>
</FormButton> {" "}
<FormButton onClick={afterSubmit} type="grey">
Annuler
</FormButton>{" "}
</>
)}
{children !== undefined && (
<>
{" "}
<IgnoreButton onClick={afterSubmit} text="Ignorer">
{children}
</IgnoreButton>{" "}
</>
)}
</div> </div>
</div> </div>
) )
} }
DayWishesForm.defaultProps = { DayWishesForm.defaultProps = {
children: undefined,
afterSubmit: undefined, afterSubmit: undefined,
} }
export default memo(DayWishesForm) export default memo(DayWishesForm)
// Fetch server-side data here
export const fetchFor = [fetchVolunteerDayWishesSetIfNeed]

View File

@ -24,6 +24,7 @@
} }
.rightCol { .rightCol {
width: 100%;
text-align: center; text-align: center;
} }
@ -37,7 +38,7 @@
text-align: left; text-align: left;
display: inline-block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
width: 180px; width: 280px;
} }
.dayWishesTitle { .dayWishesTitle {
@ -93,7 +94,7 @@
} }
} }
.dayWishesButtonWrapper { .buttonWrapper {
margin-bottom: 10px; margin-bottom: 10px;
text-align: center; text-align: center;
} }

View File

@ -26,12 +26,30 @@ const ParticipationDetails: FC<Props> = (): JSX.Element | null => {
<div className={styles.title}>Mes infos logistiques</div> <div className={styles.title}>Mes infos logistiques</div>
{tshirtCount === 0 && ( {tshirtCount === 0 && (
<div className={styles.line}> <div className={styles.line}>
Je n'ai <b>aucun t-shirt</b> et je suis taillé·e <b>{tshirtSize}</b> Je n'ai <b>aucun t-shirt</b> et{" "}
{tshirtSize ? (
<>
je suis taillé·e <b>{tshirtSize}</b>
</>
) : (
<>
ma taille est <span className={styles.lineEmpty}>non renseignées</span>
</>
)}
</div> </div>
)} )}
{tshirtCount === 1 && ( {tshirtCount === 1 && (
<div className={styles.line}> <div className={styles.line}>
J'ai <b>un seul t-shirt</b> et je suis taillé·e <b>{tshirtSize}</b> J'ai <b>un seul t-shirt</b> et{" "}
{tshirtSize ? (
<>
je suis taillé·e <b>{tshirtSize}</b>
</>
) : (
<>
ma taille est <span className={styles.lineEmpty}>non renseignées</span>
</>
)}
</div> </div>
)} )}
{tshirtCount === 2 && ( {tshirtCount === 2 && (

View File

@ -17,6 +17,11 @@
margin-bottom: 5px; margin-bottom: 5px;
} }
.lineEmpty {
color: $color-red;
font-style: italic;
}
.editButton { .editButton {
@include vertical-center(); @include vertical-center();

View File

@ -1,19 +1,18 @@
import { FC, memo, useCallback, useEffect, useRef, useState } from "react" import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from "react"
import get from "lodash/get" import get from "lodash/get"
import set from "lodash/set" import set from "lodash/set"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import { import { tshirtSizes, useUserParticipationDetails } from "../participationDetails.utils"
foodDefaultValue,
tshirtSizes,
useUserParticipationDetails,
} from "../participationDetails.utils"
import FormButton from "../../Form/FormButton/FormButton" import FormButton from "../../Form/FormButton/FormButton"
import { fetchVolunteerParticipationDetailsSetIfNeed } from "../../../store/volunteerParticipationDetailsSet"
import IgnoreButton from "../../Form/IgnoreButton/IgnoreButton"
type Props = { type Props = {
children?: ReactNode | undefined
afterSubmit?: () => void | undefined afterSubmit?: () => void | undefined
} }
const ParticipationDetailsForm: FC<Props> = ({ afterSubmit }): JSX.Element | null => { const ParticipationDetailsForm: FC<Props> = ({ children, afterSubmit }): JSX.Element | null => {
const sizeRef = useRef<HTMLSelectElement | null>(null) const sizeRef = useRef<HTMLSelectElement | null>(null)
const dietRef = useRef<HTMLTextAreaElement | null>(null) const dietRef = useRef<HTMLTextAreaElement | null>(null)
const [tshirtCountState, setTshirtCount] = useState<number>(0) const [tshirtCountState, setTshirtCount] = useState<number>(0)
@ -23,7 +22,7 @@ const ParticipationDetailsForm: FC<Props> = ({ afterSubmit }): JSX.Element | nul
const onSubmit = useCallback(() => { const onSubmit = useCallback(() => {
const tshirtSize = get(sizeRef, "current.value", "") const tshirtSize = get(sizeRef, "current.value", "")
const food = get(dietRef, "current.value", "") || foodDefaultValue const food = get(dietRef, "current.value", "")
saveParticipationDetails({ saveParticipationDetails({
tshirtSize, tshirtSize,
tshirtCount: tshirtCountState, tshirtCount: tshirtCountState,
@ -103,13 +102,15 @@ const ParticipationDetailsForm: FC<Props> = ({ afterSubmit }): JSX.Element | nul
</label> </label>
</div> </div>
<div className={styles.rightCol}> <div className={styles.rightCol}>
<select id="tshirtSize" ref={sizeRef} className={styles.tshirtCountSelect}> <label className={styles.tshirtSizeLabel}>
{tshirtSizes.map((size) => ( <select id="tshirtSize" ref={sizeRef} className={styles.tshirtCountSelect}>
<option key={size} value={size}> {tshirtSizes.map((size) => (
{size} <option key={size} value={size}>
</option> {size}
))} </option>
</select> ))}
</select>
</label>
</div> </div>
</div> </div>
@ -143,17 +144,34 @@ const ParticipationDetailsForm: FC<Props> = ({ afterSubmit }): JSX.Element | nul
<textarea id="diet" ref={dietRef} placeholder="végétarien ? halal ? ..." /> <textarea id="diet" ref={dietRef} placeholder="végétarien ? halal ? ..." />
</div> </div>
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<FormButton onClick={onSubmit}>Enregistrer</FormButton>{" "} <FormButton onClick={onSubmit}>Enregistrer</FormButton>
<FormButton onClick={afterSubmit} type="grey"> {children === undefined && (
Annuler <>
</FormButton> {" "}
<FormButton onClick={afterSubmit} type="grey">
Annuler
</FormButton>{" "}
</>
)}
{children !== undefined && (
<>
{" "}
<IgnoreButton onClick={afterSubmit} text="Ignorer">
{children}
</IgnoreButton>{" "}
</>
)}
</div> </div>
</div> </div>
) )
} }
ParticipationDetailsForm.defaultProps = { ParticipationDetailsForm.defaultProps = {
children: undefined,
afterSubmit: undefined, afterSubmit: undefined,
} }
export default memo(ParticipationDetailsForm) export default memo(ParticipationDetailsForm)
// Fetch server-side data here
export const fetchFor = [fetchVolunteerParticipationDetailsSetIfNeed]

View File

@ -27,6 +27,7 @@
} }
.rightCol { .rightCol {
width: 100%;
text-align: center; text-align: center;
} }
@ -42,13 +43,17 @@
text-align: left; text-align: left;
display: inline-block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
width: 180px; width: 220px;
} }
.tshirtCountSelect { .tshirtSizeLabel {
width: 120px; text-align: left;
display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
margin-right: 60px; width: 220px;
}
.tshirtSizeSelect {
border: 1px solid $color-grey-medium; border: 1px solid $color-grey-medium;
border-radius: 4px; border-radius: 4px;
background-color: $color-white; background-color: $color-white;
@ -59,12 +64,7 @@
text-align: left; text-align: left;
display: inline-block; display: inline-block;
margin-bottom: 10px; margin-bottom: 10px;
width: 180px; width: 220px;
}
.buttonWrapper {
margin-bottom: 10px;
text-align: center;
} }
.dietWrapper { .dietWrapper {
@ -83,3 +83,8 @@
outline: 0; outline: 0;
} }
} }
.buttonWrapper {
margin-bottom: 10px;
text-align: center;
}

View File

@ -1,4 +1,4 @@
import { FC, memo, useCallback, useEffect, useRef } from "react" import { FC, memo, ReactNode, useCallback, useEffect, useRef } from "react"
import { useSelector } from "react-redux" import { useSelector } from "react-redux"
import get from "lodash/get" import get from "lodash/get"
import set from "lodash/set" import set from "lodash/set"
@ -9,12 +9,14 @@ import { fetchTeamListIfNeed, selectSortedActiveTeams } from "../../../store/tea
import useSelection from "../useSelection" import useSelection from "../useSelection"
import { fetchVolunteerTeamWishesSetIfNeed } from "../../../store/volunteerTeamWishesSet" import { fetchVolunteerTeamWishesSetIfNeed } from "../../../store/volunteerTeamWishesSet"
import FormButton from "../../Form/FormButton/FormButton" import FormButton from "../../Form/FormButton/FormButton"
import IgnoreButton from "../../Form/IgnoreButton/IgnoreButton"
type Props = { type Props = {
children?: ReactNode | undefined
afterSubmit?: () => void | undefined afterSubmit?: () => void | undefined
} }
const TeamWishesForm: FC<Props> = ({ afterSubmit }): JSX.Element | null => { const TeamWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element | null => {
const teams = useSelector(selectSortedActiveTeams) const teams = useSelector(selectSortedActiveTeams)
const { selection, setSelection, toggleToSelection, isInSelection } = useSelection() const { selection, setSelection, toggleToSelection, isInSelection } = useSelection()
const commentRef = useRef<HTMLTextAreaElement | null>(null) const commentRef = useRef<HTMLTextAreaElement | null>(null)
@ -35,7 +37,7 @@ const TeamWishesForm: FC<Props> = ({ afterSubmit }): JSX.Element | null => {
}, [selection, saveWishes, afterSubmit]) }, [selection, saveWishes, afterSubmit])
return ( return (
<div className={styles.root}> <div>
<div className={styles.title}>Mon choix d'équipe</div> <div className={styles.title}>Mon choix d'équipe</div>
<div className={styles.intro}> <div className={styles.intro}>
<p> <p>
@ -73,7 +75,7 @@ const TeamWishesForm: FC<Props> = ({ afterSubmit }): JSX.Element | null => {
<textarea id="day-choice-comment" ref={commentRef} /> <textarea id="day-choice-comment" ref={commentRef} />
</div> </div>
</div> </div>
<div> <div className={styles.rightCol}>
<ul className={styles.teamList}> <ul className={styles.teamList}>
{teams.map((team: any) => ( {teams.map((team: any) => (
<li <li
@ -96,16 +98,30 @@ const TeamWishesForm: FC<Props> = ({ afterSubmit }): JSX.Element | null => {
</div> </div>
</div> </div>
<div className={styles.buttonWrapper}> <div className={styles.buttonWrapper}>
<FormButton onClick={onSubmit}>Enregistrer</FormButton>{" "} <FormButton onClick={onSubmit}>Enregistrer</FormButton>
<FormButton onClick={afterSubmit} type="grey"> {children === undefined && (
Annuler <>
</FormButton> {" "}
<FormButton onClick={afterSubmit} type="grey">
Annuler
</FormButton>{" "}
</>
)}
{children !== undefined && (
<>
{" "}
<IgnoreButton onClick={afterSubmit} text="Ignorer">
{children}
</IgnoreButton>{" "}
</>
)}
</div> </div>
</div> </div>
) )
} }
TeamWishesForm.defaultProps = { TeamWishesForm.defaultProps = {
children: undefined,
afterSubmit: undefined, afterSubmit: undefined,
} }

View File

@ -1,12 +1,6 @@
@import "../../../theme/variables"; @import "../../../theme/variables";
@import "../../../theme/mixins"; @import "../../../theme/mixins";
.root {
@include desktop {
width: 680px;
}
}
.title { .title {
padding: 4px; padding: 4px;
font-weight: bold; font-weight: bold;
@ -31,10 +25,15 @@
} }
.leftCol { .leftCol {
flex: 0 0 280px; flex: 0 0 240px;
margin-right: 20px; margin-right: 20px;
} }
.rightCol {
width: 280px;
text-align: center;
}
.choiceList { .choiceList {
margin: 5px 0 15px; margin: 5px 0 15px;
padding: 0 0 0 30px; padding: 0 0 0 30px;
@ -95,7 +94,7 @@
} }
textarea { textarea {
width: 100%; width: 100%;
height: 50px; height: 60px;
padding: 5px; padding: 5px;
border: 1px solid $color-grey-light; border: 1px solid $color-grey-light;
background-color: $color-grey-lighter; background-color: $color-grey-lighter;

View File

@ -1,9 +1,16 @@
import AnnouncementLink from "./AnnouncementLink" import AnnouncementLink from "./AnnouncementLink"
import Board, { fetchFor as fetchForBoard } from "./VolunteerBoard/Board"
import DayWishesForm, {
fetchFor as fetchForDayWishesForm,
} from "./VolunteerBoard/DayWishesForm/DayWishesForm"
import ErrorBoundary from "./ErrorBoundary" import ErrorBoundary from "./ErrorBoundary"
import GameList from "./GameList" import GameList from "./GameList"
import Loading from "./Loading" import Loading from "./Loading"
import LoginForm from "./LoginForm" import LoginForm from "./LoginForm"
import Notifications, { fetchFor as fetchForNotifications } from "./Notifications" import Notifications, { fetchFor as fetchForNotifications } from "./Notifications"
import ParticipationDetailsForm, {
fetchFor as fetchForParticipationDetailsForm,
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
import PreRegisterForm from "./PreRegisterForm" import PreRegisterForm from "./PreRegisterForm"
import TeamWishesForm, { import TeamWishesForm, {
fetchFor as fetchForTeamWishesForm, fetchFor as fetchForTeamWishesForm,
@ -12,16 +19,21 @@ import VolunteerList from "./VolunteerList"
import VolunteerInfo from "./VolunteerInfo" import VolunteerInfo from "./VolunteerInfo"
import VolunteerSet from "./VolunteerSet" import VolunteerSet from "./VolunteerSet"
import WishAdd from "./WishAdd" import WishAdd from "./WishAdd"
import { fetchFor as fetchForBoardForms } from "./VolunteerBoard/Board"
export { export {
AnnouncementLink, AnnouncementLink,
Board,
fetchForBoard,
DayWishesForm,
fetchForDayWishesForm,
ErrorBoundary, ErrorBoundary,
GameList, GameList,
Loading, Loading,
LoginForm, LoginForm,
Notifications, Notifications,
fetchForNotifications, fetchForNotifications,
ParticipationDetailsForm,
fetchForParticipationDetailsForm,
PreRegisterForm, PreRegisterForm,
TeamWishesForm, TeamWishesForm,
fetchForTeamWishesForm, fetchForTeamWishesForm,
@ -29,5 +41,4 @@ export {
VolunteerList, VolunteerList,
VolunteerSet, VolunteerSet,
WishAdd, WishAdd,
fetchForBoardForms,
} }

View File

@ -5,10 +5,7 @@ import { useSelector } from "react-redux"
import { AppThunk } from "../../store" import { AppThunk } from "../../store"
import { selectUserJwtToken } from "../../store/auth" import { selectUserJwtToken } from "../../store/auth"
import Page from "../../components/ui/Page/Page" import Page from "../../components/ui/Page/Page"
import Board from "../../components/VolunteerBoard/Board" import { Board, fetchForBoard } from "../../components"
import { fetchVolunteerDayWishesSetIfNeed } from "../../store/volunteerDayWishesSet"
import { fetchVolunteerParticipationDetailsSetIfNeed } from "../../store/volunteerParticipationDetailsSet"
import { fetchForTeamWishesForm } from "../../components"
export type Props = RouteComponentProps export type Props = RouteComponentProps
@ -27,10 +24,6 @@ const BoardPage: FC<Props> = (): JSX.Element => {
} }
// Fetch server-side data here // Fetch server-side data here
export const loadData = (): AppThunk[] => [ export const loadData = (): AppThunk[] => [...fetchForBoard.map((f) => f())]
fetchVolunteerDayWishesSetIfNeed(),
fetchVolunteerParticipationDetailsSetIfNeed(),
...fetchForTeamWishesForm.map((f) => f()),
]
export default memo(BoardPage) export default memo(BoardPage)

View File

@ -4,11 +4,10 @@ import { useSelector } from "react-redux"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
import { AppThunk } from "../../store" import { AppThunk } from "../../store"
import { LoginForm, Notifications, fetchForBoardForms } from "../../components" import { fetchForNotifications, LoginForm, Notifications } from "../../components"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import { fetchVolunteerNotifsSetIfNeed } from "../../store/volunteerNotifsSet" import { fetchVolunteerNotifsSetIfNeed } from "../../store/volunteerNotifsSet"
import { selectUserJwtToken } from "../../store/auth" import { selectUserJwtToken } from "../../store/auth"
import Page from "../../components/ui/Page/Page"
export type Props = RouteComponentProps export type Props = RouteComponentProps
@ -18,11 +17,7 @@ const HomePage: FC<Props> = (): JSX.Element => {
if (jwtToken === undefined) return <p>Loading...</p> if (jwtToken === undefined) return <p>Loading...</p>
if (jwtToken) { if (jwtToken) {
return ( return <Notifications />
<Page>
<Notifications />
</Page>
)
} }
return ( return (
<div> <div>
@ -44,7 +39,7 @@ const HomePage: FC<Props> = (): JSX.Element => {
// Fetch server-side data here // Fetch server-side data here
export const loadData = (): AppThunk[] => [ export const loadData = (): AppThunk[] => [
fetchVolunteerNotifsSetIfNeed(), fetchVolunteerNotifsSetIfNeed(),
...fetchForBoardForms.map((f) => f()), ...fetchForNotifications.map((f) => f()),
] ]
export default memo(HomePage) export default memo(HomePage)

View File

@ -22,8 +22,8 @@
@include flex-center; @include flex-center;
} }
@mixin page-content-wrapper($desktopWidth: 520px) { @mixin page-content-wrapper($desktopWidth: 720px) {
margin: 10px; margin: 10px 0;
padding: 10px; padding: 10px;
background-color: $color-white; background-color: $color-white;
border-radius: 15px; border-radius: 15px;
@ -31,6 +31,7 @@
width: 100%; width: 100%;
@include desktop { @include desktop {
margin: 10px;
padding: 20px; padding: 20px;
width: $desktopWidth; width: $desktopWidth;
} }
@ -43,7 +44,7 @@
background-color: $color-grey-lighter; background-color: $color-grey-lighter;
} }
@mixin nav-menu($desktopWidth: 520px) { @mixin nav-menu($desktopWidth: 720px) {
margin: 0; margin: 0;
padding: 0; padding: 0;
list-style: none; list-style: none;
@ -53,7 +54,7 @@
} }
} }
@mixin nav-menu-item($desktopWidth: 520px) { @mixin nav-menu-item($desktopWidth: 720px) {
display: inline-block; display: inline-block;
margin: 0 4px; margin: 0 4px;