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,
selectVolunteerDiscordId,
} from "../../store/volunteerDiscordId"
import {
fetchMiscDiscordInvitationIfNeed,
selectMiscDiscordInvitation,
} from "../../store/miscDiscordInvitation"
export function AskDiscord(asks: JSX.Element[], id: number): void {
const { dispatch, jwtToken, volunteerAsks } = useAskTools()
const discordId: string | undefined = useSelector(selectVolunteerDiscordId)
const discordInvitation = useSelector(selectMiscDiscordInvitation)
const onSubmit = useCallback((): void => {
dispatch(
@ -38,8 +43,8 @@ export function AskDiscord(asks: JSX.Element[], id: number): void {
totalement via la gestion des notifications.
<br />
Pour rejoindre le serveur PeL, voici le lien d'invitation à cliquer :{" "}
<a href="https://discord.gg/eXhjKxSBB4" onClick={onSubmit}>
https://discord.gg/eXhjKxSBB4
<a href={discordInvitation} onClick={onSubmit}>
{discordInvitation}
</a>{" "}
!
</p>
@ -64,4 +69,4 @@ export function AskDiscord(asks: JSX.Element[], id: number): void {
}
// 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 { validEmail } from "../../utils/standardization"
import { toastError } from "../../store/utils"
import {
fetchMiscMeetingDateListIfNeed,
selectMiscMeetingDateList,
} from "../../store/miscMeetingDateList"
interface Props {
dispatch: AppDispatch
@ -43,6 +47,8 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
const [sending, setSending] = useState(false)
const [changingBackground, setChangingBackground] = useState(0)
const meetingDateList = useSelector(selectMiscMeetingDateList)
useEffect(() => {
const timer = setInterval(() => {
setChangingBackground((changingBackground + 1) % animations.length)
@ -456,6 +462,14 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
>
Street Food Market
</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>
</dd>
@ -469,13 +483,13 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
</div>
<div className={styles.rightCol}>
<div className={styles.rightColContainer}>
{[
{ value: "13mai", desc: "Vendredi 13 mai" },
{ value: "24mai", desc: "Mardi 24 mai" },
{ value: "1juin", desc: "Mercredi 1er juin" },
{ value: "9juin", desc: "Jeudi 9 juin" },
{ value: "", desc: "Aucune date possible" },
].map((option) => (
{_.concat(
meetingDateList.map((meetingDetails) => ({
value: meetingDetails.meetingId,
desc: meetingDetails.meetingTitle,
})),
{ value: "", desc: "Aucune date possible" }
).map((option) => (
<label className={styles.longAnswerLabel} key={option.value}>
<input
type="radio"
@ -793,3 +807,6 @@ const RegisterForm = ({ dispatch }: Props): JSX.Element => {
}
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 { useSelector } from "react-redux"
import classnames from "classnames"
import get from "lodash/get"
import set from "lodash/set"
@ -12,6 +13,10 @@ import {
import FormButton from "../../Form/FormButton/FormButton"
import { fetchVolunteerDayWishesSetIfNeed } from "../../../store/volunteerDayWishesSet"
import IgnoreButton from "../../Form/IgnoreButton/IgnoreButton"
import {
fetchMiscDiscordInvitationIfNeed,
selectMiscDiscordInvitation,
} from "../../../store/miscDiscordInvitation"
type Props = {
children?: ReactNode | undefined
@ -23,6 +28,7 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
const [selection, setSelection] = useState(daysChoiceSelectionDefaultState)
const commentRef = useRef<HTMLTextAreaElement | null>(null)
const [userWishes, saveWishes] = useUserDayWishes()
const discordInvitation = useSelector(selectMiscDiscordInvitation)
const onParticipationChange = (e: React.ChangeEvent<HTMLInputElement>) =>
setParticipation(e.target.value)
@ -107,11 +113,7 @@ const DayWishesForm: FC<Props> = ({ children, afterSubmit }): JSX.Element => {
<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"
>
<a href={discordInvitation} target="_blank" rel="noreferrer">
clique ici{" "}
</a>
.
@ -179,4 +181,4 @@ DayWishesForm.defaultProps = {
export default memo(DayWishesForm)
// 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,
} from "./VolunteerBoard/ParticipationDetailsForm/ParticipationDetailsForm"
import TeamAssignment, { fetchFor as fetchForTeamAssignment } from "./TeamAssignment/TeamAssignment"
import RegisterForm from "./RegisterForm"
import RegisterForm, { fetchFor as fetchForRegisterForm } from "./RegisterForm"
import TeamWishesForm, {
fetchFor as fetchForTeamWishesForm,
} from "./VolunteerBoard/TeamWishesForm/TeamWishesForm"
@ -38,6 +38,7 @@ export {
TeamAssignment,
fetchForTeamAssignment,
RegisterForm,
fetchForRegisterForm,
TeamWishesForm,
fetchForTeamWishesForm,
VolunteerInfo,

View File

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

View File

@ -14,6 +14,8 @@ export class SheetNames {
Games = "Jeux"
Miscs = "Divers"
Postulants = "Postulants"
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 {
toDatabase: newVolunteer,
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 { hasGSheetsAccess } from "./gsheets/accessors"
import { addStatus, showStatusAt } from "./status"
import { miscMeetingDateListGet, miscDiscordInvitation } from "./gsheets/miscs"
checkAccess()
@ -84,6 +85,7 @@ app.get(
*/
// Google Sheets API
app.get("/GameListGet", gameListGet)
app.get("/MiscMeetingDateListGet", miscMeetingDateListGet)
app.get("/WishListGet", wishListGet)
app.post("/WishAdd", wishAdd)
app.post("/PostulantAdd", postulantAdd)
@ -94,6 +96,7 @@ app.get("/VolunteerListGet", secure as RequestHandler, volunteerListGet)
// Secured APIs
app.get("/AnnouncementListGet", secure as RequestHandler, announcementListGet)
app.get("/MiscDiscordInvitationGet", secure as RequestHandler, miscDiscordInvitation)
app.post("/VolunteerSet", secure as RequestHandler, volunteerSet)
app.get("/TeamListGet", teamListGet)
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>>(
apiName: string
): (...params: InputElements) => Promise<{
@ -192,7 +218,7 @@ export default class ServiceAccessors<
}
}
securedCustomGet<InputElements extends Array<any>>(
securedCustomGet<InputElements extends Array<any>, OutputType>(
apiName: string
): (
jwt: string,
@ -216,7 +242,7 @@ export default class ServiceAccessors<
if (data.error) {
throw Error(data.error)
}
return { data }
return { data } as { data: OutputType }
} catch (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,
VolunteerTeamAssign,
VolunteerWithoutId,
VolunteerDiscordId,
} from "./volunteers"
const serviceAccessors = new ServiceAccessors<VolunteerWithoutId, Volunteer>(elementName)
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 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 { connectRouter } from "connected-react-router"
import announcementList from "./announcementList"
import auth from "./auth"
import gameList from "./gameList"
import announcementList from "./announcementList"
import miscDiscordInvitation from "./miscDiscordInvitation"
import miscMeetingDateList from "./miscMeetingDateList"
import postulantAdd from "./postulantAdd"
import teamList from "./teamList"
import ui from "./ui"
@ -24,9 +26,11 @@ import wishList from "./wishList"
// Use inferred return type for making correctly Redux types
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export default (history: History) => ({
announcementList,
auth,
gameList,
announcementList,
miscDiscordInvitation,
miscMeetingDateList,
postulantAdd,
teamList,
ui,