From d5eeb44d2f51a883b8e1f5a0fbf5a2f7ed65f227 Mon Sep 17 00:00:00 2001 From: pikiou Date: Thu, 20 Jan 2022 01:09:32 +0100 Subject: [PATCH] Add teamList in store --- src/pages/Teams/Teams.tsx | 63 ++++++++++++++++++++++++++++++ src/pages/Teams/index.tsx | 16 ++++++++ src/pages/Teams/styles.module.scss | 9 +++++ src/routes/index.ts | 6 +++ src/server/gsheets/accessors.ts | 6 ++- src/server/gsheets/teams.ts | 10 +++++ src/server/index.ts | 4 +- src/services/teams.ts | 39 ++++++++++++++++++ src/store/rootReducer.ts | 18 +++++---- src/store/teamList.ts | 48 +++++++++++++++++++++++ 10 files changed, 208 insertions(+), 11 deletions(-) create mode 100644 src/pages/Teams/Teams.tsx create mode 100755 src/pages/Teams/index.tsx create mode 100755 src/pages/Teams/styles.module.scss create mode 100644 src/server/gsheets/teams.ts create mode 100644 src/services/teams.ts create mode 100644 src/store/teamList.ts diff --git a/src/pages/Teams/Teams.tsx b/src/pages/Teams/Teams.tsx new file mode 100644 index 0000000..98d4dd1 --- /dev/null +++ b/src/pages/Teams/Teams.tsx @@ -0,0 +1,63 @@ +import { FC, useEffect, memo } from "react" +import { RouteComponentProps } from "react-router-dom" +import { useDispatch, useSelector, shallowEqual } from "react-redux" +import { Helmet } from "react-helmet" + +import { AppState, AppThunk, EntitiesRequest } from "../../store" +import { Team } from "../../services/teams" +import { fetchTeamListIfNeed } from "../../store/teamList" +import styles from "./styles.module.scss" + +export type Props = RouteComponentProps + +function useList( + stateToProp: (state: AppState) => EntitiesRequest, + fetchDataIfNeed: () => AppThunk +) { + const dispatch = useDispatch() + const { ids, entities, readyStatus } = useSelector(stateToProp, shallowEqual) + + // Fetch client-side data here + useEffect(() => { + dispatch(fetchDataIfNeed()) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dispatch]) + + return () => { + if (!readyStatus || readyStatus === "idle" || readyStatus === "request") + return

Loading...

+ + if (readyStatus === "failure") return

Oops, Failed to load!

+ + return ids.map((id) => { + const team = entities[id] + return team === undefined ? null : ( +
+ {team.name}:{team.description} +
+ Avant: {team.before} +
+ Pendant: {team.during} +
+ Après: {team.after} +
+
+
+ ) + }) + } +} + +const TeamsPage: FC = (): JSX.Element => ( +
+
+ + {useList((state: AppState) => state.teamList, fetchTeamListIfNeed)()} +
+
+) + +// Fetch server-side data here +export const loadData = (): AppThunk[] => [fetchTeamListIfNeed()] + +export default memo(TeamsPage) diff --git a/src/pages/Teams/index.tsx b/src/pages/Teams/index.tsx new file mode 100755 index 0000000..c9f2623 --- /dev/null +++ b/src/pages/Teams/index.tsx @@ -0,0 +1,16 @@ +import loadable from "@loadable/component" + +import { Loading, ErrorBoundary } from "../../components" +import { Props, loadData } from "./Teams" + +const Teams = loadable(() => import("./Teams"), { + fallback: , +}) + +export default (props: Props): JSX.Element => ( + + + +) + +export { loadData } diff --git a/src/pages/Teams/styles.module.scss b/src/pages/Teams/styles.module.scss new file mode 100755 index 0000000..31ba4c2 --- /dev/null +++ b/src/pages/Teams/styles.module.scss @@ -0,0 +1,9 @@ +@import "../../theme/mixins"; + +.teamsPage { + @include page-wrapper-center; +} + +.teamsContent { + @include page-content-wrapper(600px); +} diff --git a/src/routes/index.ts b/src/routes/index.ts index 11082a4..4086fbb 100755 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -3,6 +3,7 @@ import { RouteConfig } from "react-router-config" import App from "../app" import AsyncHome, { loadData as loadHomeData } from "../pages/Home" import AsyncPreRegisterPage, { loadData as loadPreRegisterPage } from "../pages/PreRegister" +import AsyncTeams, { loadData as loadTeamsData } from "../pages/Teams" import AsyncWish, { loadData as loadWishData } from "../pages/Wish" import AsyncVolunteerPage, { loadData as loadVolunteerPageData } from "../pages/VolunteerPage" import Login from "../pages/Login" @@ -37,6 +38,11 @@ export default [ path: "/forgot", component: Forgot, }, + { + path: "/teams", + component: AsyncTeams, + loadData: loadTeamsData, + }, { path: "/wish", component: AsyncWish, diff --git a/src/server/gsheets/accessors.ts b/src/server/gsheets/accessors.ts index 00bc3b8..c66317c 100644 --- a/src/server/gsheets/accessors.ts +++ b/src/server/gsheets/accessors.ts @@ -21,10 +21,12 @@ export type ElementWithId = { id: number } & ElementNoId export class SheetNames { JavGames = "Jeux JAV" - Volunteers = "Membres" - PreVolunteers = "PreMembres" + Teams = "Equipes" + + Volunteers = "Membres" + Wishes = "Envies d'aider" } export const sheetNames = new SheetNames() diff --git a/src/server/gsheets/teams.ts b/src/server/gsheets/teams.ts new file mode 100644 index 0000000..c32ffe7 --- /dev/null +++ b/src/server/gsheets/teams.ts @@ -0,0 +1,10 @@ +import ExpressAccessors from "./expressAccessors" +import { Team, TeamWithoutId, translationTeam } from "../../services/teams" + +const expressAccessor = new ExpressAccessors( + "Teams", + new Team(), + translationTeam +) + +export const teamListGet = expressAccessor.listGet() diff --git a/src/server/index.ts b/src/server/index.ts index 0cdccc0..42469cc 100755 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -18,14 +18,15 @@ import ssr from "./ssr" import certbotRouter from "../routes/certbot" import { secure } from "./secure" import { javGameListGet } from "./gsheets/javGames" -import { wishListGet, wishAdd } from "./gsheets/wishes" import { preVolunteerAdd, preVolunteerCountGet } from "./gsheets/preVolunteers" +import { teamListGet } from "./gsheets/teams" import { volunteerNotifsSet, volunteerSet, volunteerLogin, volunteerForgot, } from "./gsheets/volunteers" +import { wishListGet, wishAdd } from "./gsheets/wishes" import config from "../config" import notificationsSubscribe from "./notificationsSubscribe" import checkAccess from "./checkAccess" @@ -71,6 +72,7 @@ app.post("/VolunteerForgot", volunteerForgot) // Secured APIs app.post("/VolunteerSet", secure as RequestHandler, volunteerSet) +app.get("/TeamListGet", teamListGet) // UNSAFE app.post("/VolunteerGet", secure as RequestHandler, volunteerGet) app.post("/VolunteerNotifsSet", secure as RequestHandler, volunteerNotifsSet) diff --git a/src/services/teams.ts b/src/services/teams.ts new file mode 100644 index 0000000..02acf1d --- /dev/null +++ b/src/services/teams.ts @@ -0,0 +1,39 @@ +import ServiceAccessors from "./accessors" + +export class Team { + id = 0 + + name = "" + + min = 0 + + max = 0 + + description = "" + + before = "" + + during = "" + + after = "" +} + +export const translationTeam: { [k in keyof Team]: string } = { + id: "id", + name: "nom", + min: "min", + max: "max", + description: "description", + before: "avant", + during: "pendant", + after: "après", +} + +const elementName = "Team" + +export type TeamWithoutId = Omit + +const serviceAccessors = new ServiceAccessors(elementName) + +export const teamListGet = serviceAccessors.listGet() +export const teamGet = serviceAccessors.get() diff --git a/src/store/rootReducer.ts b/src/store/rootReducer.ts index 90f0a81..ca3b0bc 100644 --- a/src/store/rootReducer.ts +++ b/src/store/rootReducer.ts @@ -2,9 +2,10 @@ import { History } from "history" import { connectRouter } from "connected-react-router" import auth from "./auth" -import wishAdd from "./wishAdd" -import wishList from "./wishList" import javGameList from "./javGameList" +import preVolunteerAdd from "./preVolunteerAdd" +import preVolunteerCount from "./preVolunteerCount" +import teamList from "./teamList" import volunteer from "./volunteer" import volunteerAdd from "./volunteerAdd" import volunteerList from "./volunteerList" @@ -12,16 +13,17 @@ import volunteerSet from "./volunteerSet" import volunteerLogin from "./volunteerLogin" import volunteerForgot from "./volunteerForgot" import volunteerNotifsSet from "./volunteerNotifsSet" -import preVolunteerAdd from "./preVolunteerAdd" -import preVolunteerCount from "./preVolunteerCount" +import wishAdd from "./wishAdd" +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) => ({ auth, - wishAdd, - wishList, javGameList, + preVolunteerAdd, + preVolunteerCount, + teamList, volunteer, volunteerAdd, volunteerList, @@ -29,8 +31,8 @@ export default (history: History) => ({ volunteerLogin, volunteerForgot, volunteerNotifsSet, - preVolunteerAdd, - preVolunteerCount, + wishAdd, + wishList, router: connectRouter(history) as any, // Register more reducers... }) diff --git a/src/store/teamList.ts b/src/store/teamList.ts new file mode 100644 index 0000000..254e729 --- /dev/null +++ b/src/store/teamList.ts @@ -0,0 +1,48 @@ +import { PayloadAction, createSlice, createEntityAdapter } from "@reduxjs/toolkit" + +import { StateRequest, toastError, elementListFetch } from "./utils" +import { Team, teamListGet } from "../services/teams" +import { AppThunk, AppState } from "." + +const teamAdapter = createEntityAdapter() + +export const initialState = teamAdapter.getInitialState({ + readyStatus: "idle", +} as StateRequest) + +const teamList = createSlice({ + name: "teamList", + initialState, + reducers: { + getRequesting: (state) => { + state.readyStatus = "request" + }, + getSuccess: (state, { payload }: PayloadAction) => { + state.readyStatus = "success" + teamAdapter.setAll(state, payload) + }, + getFailure: (state, { payload }: PayloadAction) => { + state.readyStatus = "failure" + state.error = payload + }, + }, +}) + +export default teamList.reducer +export const { getRequesting, getSuccess, getFailure } = teamList.actions + +export const fetchTeamList = elementListFetch( + teamListGet, + getRequesting, + getSuccess, + getFailure, + (error: Error) => toastError(`Erreur lors du chargement des équipes: ${error.message}`) +) + +const shouldFetchTeamList = (state: AppState) => state.teamList.readyStatus !== "success" + +export const fetchTeamListIfNeed = (): AppThunk => (dispatch, getState) => { + if (shouldFetchTeamList(getState())) return dispatch(fetchTeamList()) + + return null +}