Add global variables for meeting dates

This commit is contained in:
pikiou 2022-04-29 01:52:08 +02:00
parent 514cf1b833
commit 18c6f75511
16 changed files with 311 additions and 25 deletions

View File

@ -8,10 +8,15 @@ import {
fetchVolunteerDiscordIdIfNeed, fetchVolunteerDiscordIdIfNeed,
selectVolunteerDiscordId, selectVolunteerDiscordId,
} from "../../store/volunteerDiscordId" } from "../../store/volunteerDiscordId"
import {
fetchMiscDiscordInvitationIfNeed,
selectMiscDiscordInvitation,
} from "../../store/miscDiscordInvitation"
export function AskDiscord(asks: JSX.Element[], id: number): void { export function AskDiscord(asks: JSX.Element[], id: number): void {
const { dispatch, jwtToken, volunteerAsks } = useAskTools() const { dispatch, jwtToken, volunteerAsks } = useAskTools()
const discordId: string | undefined = useSelector(selectVolunteerDiscordId) const discordId: string | undefined = useSelector(selectVolunteerDiscordId)
const discordInvitation = useSelector(selectMiscDiscordInvitation)
const onSubmit = useCallback((): void => { const onSubmit = useCallback((): void => {
dispatch( dispatch(
@ -38,8 +43,8 @@ export function AskDiscord(asks: JSX.Element[], id: number): void {
totalement via la gestion des notifications. totalement via la gestion des notifications.
<br /> <br />
Pour rejoindre le serveur PeL, voici le lien d'invitation à cliquer :{" "} Pour rejoindre le serveur PeL, voici le lien d'invitation à cliquer :{" "}
<a href="https://discord.gg/eXhjKxSBB4" onClick={onSubmit}> <a href={discordInvitation} onClick={onSubmit}>
https://discord.gg/eXhjKxSBB4 {discordInvitation}
</a>{" "} </a>{" "}
! !
</p> </p>
@ -64,4 +69,4 @@ export function AskDiscord(asks: JSX.Element[], id: number): void {
} }
// Fetch server-side data here // Fetch server-side data here
export const fetchFor = [fetchVolunteerDiscordIdIfNeed] export const fetchFor = [fetchVolunteerDiscordIdIfNeed, fetchMiscDiscordInvitationIfNeed]

View File

@ -11,6 +11,10 @@ import { fetchVolunteerPartialAdd } from "../../store/volunteerPartialAdd"
import FormButton from "../Form/FormButton/FormButton" import FormButton from "../Form/FormButton/FormButton"
import { validEmail } from "../../utils/standardization" import { validEmail } from "../../utils/standardization"
import { toastError } from "../../store/utils" import { toastError } from "../../store/utils"
import {
fetchMiscMeetingDateListIfNeed,
selectMiscMeetingDateList,
} from "../../store/miscMeetingDateList"
interface Props { interface Props {
dispatch: AppDispatch dispatch: AppDispatch
@ -43,6 +47,8 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const [sending, setSending] = useState(false) const [sending, setSending] = useState(false)
const [changingBackground, setChangingBackground] = useState(0) const [changingBackground, setChangingBackground] = useState(0)
const meetingDateList = useSelector(selectMiscMeetingDateList)
useEffect(() => { useEffect(() => {
const timer = setInterval(() => { const timer = setInterval(() => {
setChangingBackground((changingBackground + 1) % animations.length) setChangingBackground((changingBackground + 1) % animations.length)
@ -456,6 +462,14 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
> >
Street Food Market Street Food Market
</a> </a>
, ou à une soirée festive à 2 pas du lieu du festival, aux{" "}
<a
href="https://www.captainturtle.fr/aperos-petanque-paris/"
target="_blank"
rel="noreferrer"
>
apéros de la pétanque
</a>
. .
</p> </p>
</dd> </dd>
@ -469,13 +483,13 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
</div> </div>
<div className={styles.rightCol}> <div className={styles.rightCol}>
<div className={styles.rightColContainer}> <div className={styles.rightColContainer}>
{[ {_.concat(
{ value: "13mai", desc: "Vendredi 13 mai" }, meetingDateList.map((meetingDetails) => ({
{ value: "24mai", desc: "Mardi 24 mai" }, value: meetingDetails.meetingId,
{ value: "1juin", desc: "Mercredi 1er juin" }, desc: meetingDetails.meetingTitle,
{ value: "9juin", desc: "Jeudi 9 juin" }, })),
{ value: "", desc: "Aucune date possible" }, { value: "", desc: "Aucune date possible" }
].map((option) => ( ).map((option) => (
<label className={styles.longAnswerLabel} key={option.value}> <label className={styles.longAnswerLabel} key={option.value}>
<input <input
type="radio" type="radio"
@ -793,3 +807,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
} }
export default memo(RegisterForm) export default memo(RegisterForm)
// Fetch server-side data here
export const fetchFor = [fetchMiscMeetingDateListIfNeed]

View File

@ -1,4 +1,5 @@
import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from "react" import { FC, memo, ReactNode, useCallback, useEffect, useRef, useState } from "react"
import { useSelector } from "react-redux"
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"
@ -12,6 +13,10 @@ import {
import FormButton from "../../Form/FormButton/FormButton" import FormButton from "../../Form/FormButton/FormButton"
import { fetchVolunteerDayWishesSetIfNeed } from "../../../store/volunteerDayWishesSet" import { fetchVolunteerDayWishesSetIfNeed } from "../../../store/volunteerDayWishesSet"
import IgnoreButton from "../../Form/IgnoreButton/IgnoreButton" import IgnoreButton from "../../Form/IgnoreButton/IgnoreButton"
import {
fetchMiscDiscordInvitationIfNeed,
selectMiscDiscordInvitation,
} from "../../../store/miscDiscordInvitation"
type Props = { type Props = {
children?: ReactNode | undefined children?: ReactNode | undefined
@ -23,6 +28,7 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
const [selection, setSelection] = useState(daysChoiceSelectionDefaultState) const [selection, setSelection] = useState(daysChoiceSelectionDefaultState)
const commentRef = useRef<HTMLTextAreaElement | null>(null) const commentRef = useRef<HTMLTextAreaElement | null>(null)
const [userWishes, saveWishes] = useUserDayWishes() const [userWishes, saveWishes] = useUserDayWishes()
const discordInvitation = useSelector(selectMiscDiscordInvitation)
const onParticipationChange = (e: React.ChangeEvent<HTMLInputElement>) => const onParticipationChange = (e: React.ChangeEvent<HTMLInputElement>) =>
setParticipation(e.target.value) setParticipation(e.target.value)
@ -107,11 +113,7 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
<br /> <br />
Si tu as besoin d&apos;infos, viens nous en parler sur le serveur Discord ! Pour Si tu as besoin d&apos;infos, viens nous en parler sur le serveur Discord ! Pour
le rejoindre,{" "} le rejoindre,{" "}
<a <a href={discordInvitation} target="_blank" rel="noreferrer">
href="https://discord.com/invite/eXhjKxSBB4"
target="_blank"
rel="noreferrer"
>
clique ici{" "} clique ici{" "}
</a> </a>
. .
@ -179,4 +181,4 @@ DayWishesForm.defaultProps = {
export default memo(DayWishesForm) export default memo(DayWishesForm)
// Fetch server-side data here // Fetch server-side data here
export const fetchFor = [fetchVolunteerDayWishesSetIfNeed] export const fetchFor = [fetchVolunteerDayWishesSetIfNeed, fetchMiscDiscordInvitationIfNeed]

View File

@ -12,7 +12,7 @@ import ParticipationDetailsForm, {
fetchFor as fetchForParticipationDetailsForm, fetchFor as fetchForParticipationDetailsForm,
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm" } from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
import TeamAssignment, { fetchFor as fetchForTeamAssignment } from "./TeamAssignment/TeamAssignment" import TeamAssignment, { fetchFor as fetchForTeamAssignment } from "./TeamAssignment/TeamAssignment"
import RegisterForm from "./RegisterForm" import RegisterForm, { fetchFor as fetchForRegisterForm } from "./RegisterForm"
import TeamWishesForm, { import TeamWishesForm, {
fetchFor as fetchForTeamWishesForm, fetchFor as fetchForTeamWishesForm,
} from "./VolunteerBoard/TeamWishesForm/TeamWishesForm" } from "./VolunteerBoard/TeamWishesForm/TeamWishesForm"
@ -38,6 +38,7 @@ export {
TeamAssignment, TeamAssignment,
fetchForTeamAssignment, fetchForTeamAssignment,
RegisterForm, RegisterForm,
fetchForRegisterForm,
TeamWishesForm, TeamWishesForm,
fetchForTeamWishesForm, fetchForTeamWishesForm,
VolunteerInfo, VolunteerInfo,

View File

@ -4,7 +4,7 @@ import { useDispatch } from "react-redux"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
import { AppThunk } from "../../store" import { AppThunk } from "../../store"
import { RegisterForm } from "../../components" import { RegisterForm, fetchForRegisterForm } from "../../components"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
export type Props = RouteComponentProps export type Props = RouteComponentProps
@ -22,6 +22,6 @@ const RegisterPage: FC<Props> = (): JSX.Element => {
} }
// Fetch server-side data here // Fetch server-side data here
export const loadData = (): AppThunk[] => [] export const loadData = (): AppThunk[] => [...fetchForRegisterForm.map((f) => f())]
export default memo(RegisterPage) export default memo(RegisterPage)

View File

@ -14,6 +14,8 @@ export class SheetNames {
Games = "Jeux" Games = "Jeux"
Miscs = "Divers"
Postulants = "Postulants" Postulants = "Postulants"
Teams = "Equipes" Teams = "Equipes"

View File

@ -0,0 +1,42 @@
import ExpressAccessors from "./expressAccessors"
import {
Misc,
MiscDiscordInvitation,
MiscMeetingDate,
MiscWithoutId,
translationMisc,
} from "../../services/miscs"
const expressAccessor = new ExpressAccessors<MiscWithoutId, Misc>(
"Miscs",
new Misc(),
translationMisc
)
export const miscDiscordInvitation = expressAccessor.get(async (list, _body, id) => {
if (id <= 0) {
throw Error(`L'accès est réservé aux utilisateurs identifiés`)
}
return list
.filter((misc) => !!misc.discordInvitation)
.map(
(misc) =>
({
id: misc.id,
discordInvitation: misc.discordInvitation,
} as MiscDiscordInvitation)
)
})
export const miscMeetingDateListGet = expressAccessor.get(async (list) =>
list
.filter((misc) => !!misc.meetingId)
.map(
(misc) =>
({
id: misc.id,
meetingId: misc.meetingId,
meetingTitle: misc.meetingTitle,
} as MiscMeetingDate)
)
)

View File

@ -155,7 +155,7 @@ export const volunteerForgot = expressAccessor.set(async (list, bodyArray) => {
return { return {
toDatabase: newVolunteer, toDatabase: newVolunteer,
toCaller: { toCaller: {
message: `Un nouveau mot de passe t'a été envoyé par email. Regarde bien dans les spams, il pourrait y être :/`, message: `Un nouveau mot de passe t'a été envoyé par email. Regarde bien dans les spams, il pourrait y être ><`,
}, },
} }
}) })

View File

@ -40,6 +40,7 @@ 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"
import { miscMeetingDateListGet, miscDiscordInvitation } from "./gsheets/miscs"
checkAccess() checkAccess()
@ -84,6 +85,7 @@ app.get(
*/ */
// Google Sheets API // Google Sheets API
app.get("/GameListGet", gameListGet) app.get("/GameListGet", gameListGet)
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
app.get("/WishListGet", wishListGet) app.get("/WishListGet", wishListGet)
app.post("/WishAdd", wishAdd) app.post("/WishAdd", wishAdd)
app.post("/PostulantAdd", postulantAdd) app.post("/PostulantAdd", postulantAdd)
@ -94,6 +96,7 @@ app.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet)
// Secured APIs // Secured APIs
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet) app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
app.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet) app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
app.get("/TeamListGet", teamListGet) app.get("/TeamListGet", teamListGet)
app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId) app.get("/VolunteerDiscordId", secure as RequestHandler, volunteerDiscordId)

View File

@ -165,6 +165,32 @@ export default class ServiceAccessors<
} }
} }
customGet<InputElements extends Array<any>, OutputType>(
apiName: string
): (...params: InputElements) => Promise<{
data?: any
error?: Error
}> {
interface ElementGetResponse {
data?: any
error?: Error
}
return async (...params: InputElements): Promise<ElementGetResponse> => {
try {
const { data } = await axios.get(
`${config.API_URL}/${this.elementName}${apiName}`,
{ ...axiosConfig, params }
)
if (data.error) {
throw Error(data.error)
}
return { data } as { data: OutputType }
} catch (error) {
return { error: error as Error }
}
}
}
customPost<InputElements extends Array<any>>( customPost<InputElements extends Array<any>>(
apiName: string apiName: string
): (...params: InputElements) => Promise<{ ): (...params: InputElements) => Promise<{
@ -192,7 +218,7 @@ export default class ServiceAccessors<
} }
} }
securedCustomGet<InputElements extends Array<any>>( securedCustomGet<InputElements extends Array<any>, OutputType>(
apiName: string apiName: string
): ( ): (
jwt: string, jwt: string,
@ -216,7 +242,7 @@ export default class ServiceAccessors<
if (data.error) { if (data.error) {
throw Error(data.error) throw Error(data.error)
} }
return { data } return { data } as { data: OutputType }
} catch (error) { } catch (error) {
return { error: error as Error } return { error: error as Error }
} }

32
src/services/miscs.ts Normal file
View File

@ -0,0 +1,32 @@
/* eslint-disable max-classes-per-file */
export class Misc {
id = 0
meetingId = ""
meetingTitle = ""
discordInvitation = ""
}
export const translationMisc: { [k in keyof Misc]: string } = {
id: "id",
meetingId: "rencontreId",
meetingTitle: "rencontreTitre",
discordInvitation: "invitationDiscord",
}
export const elementName = "Misc"
export type MiscWithoutId = Omit<Misc, "id">
export interface MiscMeetingDate {
id: Misc["id"]
meetingId: Misc["meetingId"]
meetingTitle: Misc["meetingTitle"]
}
export interface MiscDiscordInvitation {
id: Misc["id"]
discordInvitation: Misc["discordInvitation"]
}

View File

@ -0,0 +1,12 @@
import ServiceAccessors from "./accessors"
import { elementName, Misc, MiscDiscordInvitation, MiscMeetingDate, MiscWithoutId } from "./miscs"
const serviceAccessors = new ServiceAccessors<MiscWithoutId, Misc>(elementName)
export const miscDiscordInvitation = serviceAccessors.securedCustomGet<[], MiscDiscordInvitation>(
"DiscordInvitationGet"
)
export const miscMeetingDateListGet = serviceAccessors.customGet<[], MiscMeetingDate>(
"MeetingDateListGet"
)

View File

@ -8,12 +8,16 @@ import {
VolunteerTeamWishes, VolunteerTeamWishes,
VolunteerTeamAssign, VolunteerTeamAssign,
VolunteerWithoutId, VolunteerWithoutId,
VolunteerDiscordId,
} from "./volunteers" } from "./volunteers"
const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName) const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
export const volunteerListGet = serviceAccessors.securedListGet() export const volunteerListGet = serviceAccessors.securedListGet()
export const volunteerDiscordIdGet = serviceAccessors.securedCustomGet<[number]>("DiscordId") export const volunteerDiscordIdGet = serviceAccessors.securedCustomGet<
[number],
VolunteerDiscordId
>("DiscordId")
export const volunteerPartialAdd = serviceAccessors.customPost<[Partial<Volunteer>]>("PartialAdd") export const volunteerPartialAdd = serviceAccessors.customPost<[Partial<Volunteer>]>("PartialAdd")
export const volunteerSet = serviceAccessors.set() export const volunteerSet = serviceAccessors.set()

View File

@ -0,0 +1,72 @@
import _ from "lodash"
import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit"
import { StateRequest, toastError, elementListFetch } from "./utils"
import { MiscDiscordInvitation } from "../services/miscs"
import { AppThunk, AppState, EntitiesRequest } from "."
import { miscDiscordInvitation } from "../services/miscsAccessors"
const miscAdapter = createEntityAdapter<MiscDiscordInvitation>()
export const initialState = miscAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest)
const miscDiscordInvitationSlice = createSlice({
name: "miscDiscordInvitation",
initialState,
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<MiscDiscordInvitation[]>) => {
state.readyStatus = "success"
miscAdapter.setAll(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default miscDiscordInvitationSlice.reducer
export const { getRequesting, getSuccess, getFailure } = miscDiscordInvitationSlice.actions
export const fetchMiscDiscordInvitation = elementListFetch(
miscDiscordInvitation,
getRequesting,
getSuccess,
getFailure,
(error: Error) => toastError(`Erreur lors du chargement des données diverses: ${error.message}`)
)
const shouldFetchMiscDiscordInvitation = (state: AppState) =>
state.miscDiscordInvitation.readyStatus !== "success"
export const fetchMiscDiscordInvitationIfNeed = (): AppThunk => (dispatch, getState) => {
const { jwt } = getState().auth
if (shouldFetchMiscDiscordInvitation(getState()))
return dispatch(fetchMiscDiscordInvitation(jwt))
return null
}
export const refreshMiscDiscordInvitation =
(jwt: string): AppThunk =>
(dispatch) =>
dispatch(fetchMiscDiscordInvitation(jwt))
export const selectMiscDiscordInvitationState = (
state: AppState
): EntitiesRequest<MiscDiscordInvitation> => state.miscDiscordInvitation
export const selectMiscDiscordInvitation = createSelector(
selectMiscDiscordInvitationState,
({ ids, entities, readyStatus }) => {
if (readyStatus !== "success") return ""
const id = _.first(ids)
if (id === undefined) return ""
return entities[id]?.discordInvitation || ""
}
)

View File

@ -0,0 +1,64 @@
import { PayloadAction, createSlice, createEntityAdapter, createSelector } from "@reduxjs/toolkit"
import { StateRequest, toastError, elementListFetch } from "./utils"
import { MiscMeetingDate } from "../services/miscs"
import { AppThunk, AppState, EntitiesRequest } from "."
import { miscMeetingDateListGet } from "../services/miscsAccessors"
const miscAdapter = createEntityAdapter<MiscMeetingDate>()
export const initialState = miscAdapter.getInitialState({
readyStatus: "idle",
} as StateRequest)
const miscMeetingDateList = createSlice({
name: "miscMeetingDateList",
initialState,
reducers: {
getRequesting: (state) => {
state.readyStatus = "request"
},
getSuccess: (state, { payload }: PayloadAction<MiscMeetingDate[]>) => {
state.readyStatus = "success"
miscAdapter.setAll(state, payload)
},
getFailure: (state, { payload }: PayloadAction<string>) => {
state.readyStatus = "failure"
state.error = payload
},
},
})
export default miscMeetingDateList.reducer
export const { getRequesting, getSuccess, getFailure } = miscMeetingDateList.actions
export const fetchMiscMeetingDateList = elementListFetch(
miscMeetingDateListGet,
getRequesting,
getSuccess,
getFailure,
(error: Error) => toastError(`Erreur lors du chargement des données diverses: ${error.message}`)
)
const shouldFetchMiscMeetingDateList = (state: AppState) =>
state.miscMeetingDateList.readyStatus !== "success"
export const fetchMiscMeetingDateListIfNeed = (): AppThunk => (dispatch, getState) => {
if (shouldFetchMiscMeetingDateList(getState())) return dispatch(fetchMiscMeetingDateList())
return null
}
export const refreshMiscMeetingDateList = (): AppThunk => (dispatch) =>
dispatch(fetchMiscMeetingDateList())
export const selectMiscMeetingDateListState = (state: AppState): EntitiesRequest<MiscMeetingDate> =>
state.miscMeetingDateList
export const selectMiscMeetingDateList = createSelector(
selectMiscMeetingDateListState,
({ ids, entities, readyStatus }) => {
if (readyStatus !== "success") return []
return ids.map((id) => entities[id]) as MiscMeetingDate[]
}
)

View File

@ -1,9 +1,11 @@
import { History } from "history" import { History } from "history"
import { connectRouter } from "connected-react-router" import { connectRouter } from "connected-react-router"
import announcementList from "./announcementList"
import auth from "./auth" import auth from "./auth"
import gameList from "./gameList" import gameList from "./gameList"
import announcementList from "./announcementList" import miscDiscordInvitation from "./miscDiscordInvitation"
import miscMeetingDateList from "./miscMeetingDateList"
import postulantAdd from "./postulantAdd" import postulantAdd from "./postulantAdd"
import teamList from "./teamList" import teamList from "./teamList"
import ui from "./ui" import ui from "./ui"
@ -24,9 +26,11 @@ import wishList from "./wishList"
// Use inferred return type for making correctly Redux types // Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export default (history: History) => ({ export default (history: History) => ({
announcementList,
auth, auth,
gameList, gameList,
announcementList, miscDiscordInvitation,
miscMeetingDateList,
postulantAdd, postulantAdd,
teamList, teamList,
ui, ui,